作者:小贼先生_ronffy前言本文主要讲解typescript的extends、infer和templateliteraltypes等知识点。对于每个知识点,我都会分别用它们来解决日常开发中的一些实际问题。最后利用这些知识点逐步解决使用dva时出现的类型问题。注意:extends和infer是TS2.8中引入的特性。TemplateLiteralTypes是TS4.1版本中引入的功能。本文不是typescript的入门文档,需要有一定的ts基础,比如ts基本类型、接口、泛型等。在正式讲解知识点之前,抛出几个问题,每个问题请仔细思考,接下来的讲解会围绕这些问题慢慢展开。抛出几个问题1.获取函数的参数类型functionfn(a:number,b:string):string{returna+b;}//期望值[a:number,b:string]typeFnArgs=/*待办事项*/2。如何定义get方法classMyC{data={x:1,o:{y:'2',},};get(key){返回this.data[key];}}constc=newMyC();//1.x的类型应推导为numberconstx=c.get('x');//2.y的类型应推导为string;z不在o对象上,这里TSerrorconst{y,z}=c.get('o');//3.c.data上不存在z属性,这里应该TSerrorconstz=c.get('z');3、获取所有dvaActions类型dva是一个基于redux和redux-saga的数据流解决方案,是一个很好的数据流解决方案。这里我们借用dva中的模型来学习如何在实践中更好的应用TS。如果对dva不熟悉,不影响继续学习。//foottypeFooModel={state:{x:number;};reducers:{add(S:FooModel['state'],A:{payload:string;},):FooModel['state'];};};//bartypeBarModel={state:{y:string;};reducers:{reset(S:BarModel['state'],A:{payload:boolean;},):BarModel['state'];};};//模型类型AllModels={foo:FooModel;酒吧:酒吧模型;};问题:从AllModels中推断出Actions类型//预期类型Actions=|{类型:'foo/add';有效负载:字符串;}|{type:'bar/reset';有效载荷:布尔值;};知识点extendsextends主要有3个功能:类型继承、条件类型、泛型约束。类型继承语法:interfaceI{}classC{}interfaceTextendsI,C{}示例:interfaceAction{type:any;}interfacePayloadActionextendsAction{payload:any;[extraProps:string]:any;}//type和payload是必填字段,其他字段是可选的constaction:PayloadAction={type:'add',payload:1}conditional-typesextends是条件表达式中使用的条件类型。句法:TextendsU?X:Y如果T符合U的类型范围,则返回类型X,否则返回类型Y。示例:typeLimitType=Textendsnumber?数字:字符串类型T1=LimitType<字符串>;//字符串类型T2=LimitType;//number如果T符合number的类型范围,则返回number类型,否则返回string类型。泛型约束可以使用extends来约束泛型的范围和形状。示例:目的:对调用dispatch方法时传入的参数进行TS校验:type和payload为必填属性,payload类型为number。//期望:ts错误:缺少属性“payload”dispatch({type:'add',})//期望:ts错误:缺少属性“type”dispatch({payload:1})//期望:ts错误:类型“字符串”不可分配给类型“数字”。dispatch({type:'add',payload:'1'})//期望:正确dispatch({type:'add',payload:1})implementation://增加泛型P,使用PayloadAction时,有类型定义接口有效负载的能力PayloadActionextendsAction{payload:P;[extraProps:string]:any;}//补充:dispatch类型,泛型A要符合ActiontypeDispatch=(action:A)=>A;//备注:这里dispatch的js实现只是一个例子,不是reduxconstdispatch中的真正实现:Dispatch>=(action)=>action;inferconditiontypetypederivation。示例1://推断函数类型的返回类型ReturnType=Textends(...args:any[])=>inferR?R:any;functionfn():number{return0;}typeR=ReturnType;//如果T可分配给类型(...args:any[])=>any,则数字返回R,否则返回类型any。R是在使用ReturnType时,根据传入或推导出的T函数类型推导出函数返回值的类型。例2:取出数组中的类型typeArrayItemType=Textends(inferU)[]?U:T;类型T1=ArrayItemType;//字符串类型T2=ArrayItemType;//数据类型T3=ArrayItemType;//number模板字面量类型(TemplateLiteralTypes)模板字符串由反引号(\`)标识,模板字符串中的联合类型会被扩展然后排列组合。例子:functionrequest(api,options){returnfetch(api,options);}如何用TS限制api为https://abc.com开头的字符串?输入Api=`${'http'|'https'}://abc.com${string}`;//`http://abc.com${string}`|`https://abc.com${string}`作者:小贼先生_ronffy解决问题现在,相信你已经掌握了extends、infer和templateliteral类型。接下来,让我们一一解决文章开头提出的问题。Fix:Q1获取函数的参数类型。上面学习了ReturnType,就知道如何通过extends和infer获取函数的返回值类型了。让我们看看如何获??取函数的参数类型。输入Args=Textends(...args:inferA)=>any?A:never;typeFnArgs=Args;Fix:Q2如何定义get方法classMyC{get(key:T):MyC['data'][T]{返回this.data[key];}}扩展:如果get支持“属性路径”的参数形式,比如consty=c.get('o.y'),那么TS应该怎么写呢?备注:这里只考虑数据和深层结构都是对象的数据格式,不考虑数组等其他数据格式。先实现get的传参类型:思路:根据对象从上到下找出对象的所有路径,返回所有路径的联合类型classMyC{get>(path:P){//...省略js实现代码}}{x:number;o:{y:字符串}}'x'|'o'|'o.y'typeObjectPropName={[KinkeyofT]:Kextendsstring?T[K]扩展Record?对象路径<路径,K>|ObjectPropName>:ObjectPath:Path;}[keyofT];typeObjectPath=`${Preextends''?curr:`${Pre}.`}${Curr}`;重新实现get方法的返回值类型:思路:根据对象和路径,从上到下逐层验证路径是否存在,classMyC{get>(path:P):ObjectPropType{//...省略js实现代码}}typeObjectPropType=PathextendskeyofT?T[Path]:路径扩展`${inferK}.${inferR}`?K扩展了T的键?ObjectPropType:未知:未知;修复:Q3获取dva类型的所有操作类型GenerateActions>={[ModelNameinkeyofModels]:Models[ModelName]['reducers']extendsnever?never:{[ReducerNameinkeyofModels[ModelName]['reducers']]:Models[ModelName]['reducers'][ReducerName]extends(state:any,action:inferA,)=>任何?{类型:`${string&ModelName}/${string&ReducerName}`;payload:Aextends{payload:inferP}?P:从不;}:绝不;}[keyofModels[ModelName]['reducers']];}[keyofModels];typeActions=GenerateActions;使用//TS错误:无法将类型“string”分配给类型“boolean”"foo/add""到类型""bar/reset""(这里的TS是根据payload从boolean推导出的类型)exportconstb:Actions={type:'foo/add',payload:true,};exportconstc:Actions={type:'foo/add',//TSerror:type"{type:"foo/add";payload:string;}"在"payload1"中不存在,是否要写有效载荷?payload1:true,};//TS错误:类型“foo/add1”不可分配给类型“foo/add”|“bar/reset””exportconstd:Actions={type:'foo/add1',payload1:true,};继续一系列问题:3.1ExtractReducer3.2ExtractModel3.3Nopayload?3.4非有效载荷?3.5Reducer可以不传State吗?Fix:Q3.1ExtractReducer//备注:这里只考虑reducer是函数的情况,dva中的reducer也可能是数组,暂时不考虑。typeReducer=(state:S,action:A)=>S;//foointerfaceFooState{x:number;}typeFooModel={state:FooState;reducers:{add:Reducer;};};Fix:Q3.2抽取ModeltypeModel={state:S;reducers:{[reducerName:string]:(state:S,action:A)=>S;};};//foointerfaceFooState{x:number;}interfaceFooModelextendsModel{state:FooState;reducers:{add:Reducer;};}Fix:Q3.3无payload?增加WithoutNever,不为无payload的动作增加payload试验。typeGenerateActions>={[ModelNameinkeyofModels]:Models[ModelName]['reducers']extendsnever?never:{[ReducerNameinkeyofModels[ModelName]['reducers']]:Models[ModelName]['reducers'][ReducerName]extends(state:any,action:inferA,)=>任何?WithoutNever<{类型:`${string&ModelName}/${string&ReducerName}`;payload:Aextends{payload:inferP}?P:从不;}>:从不;}[keyofModels[ModelName]['reducers']];}[keyofModels];typeWithoutNever=Pick;使用interfaceFooModelextendsModel{reducers:{del:Reducer;};}//TS校试通conste:Actions={type:'foo/del',};Fix:Q3.4非payload?typeGenerateActions>={[ModelNameinkeyofModels]:Models[ModelName]['reducers']extendsnever?never:{[ReducerNameinkeyofModels[ModelName]['reducers']]:Models[ModelName]['reducers'][ReducerName]extends(state:any,action:inferA,)=>任何?A扩展Record?{类型:`${string&ModelName}/${string&ReducerName}`;}&{[KinkeyofA]:A[K];}:{类型:`${string&ModelName}/${string&ReducerName}`;}:绝不;}[keyofModels[ModelName]['reducers']];}[keyofModels];使用interfaceFooModelextendsModel{state:FooState;reducers:{add:Reducer;};}//TS检查通过constf:Actions={type:'foo/add',x:'true',};LegacyQ3.5Reducer不能传State吗?答案是肯定的,这个问题有很多种思考方式,其中一种是:state和reducer都在definedmodel上拿到模型后,将state类型注入reducer,这样定义模型的reducer就不需要手动传递state了。这个问题留给大家去思考和实践,这里就不展开了。综上所述,extends、infer、TemplateLiteralTypes等函数非常灵活和强大。希望大家在这篇文章的基础上,多思考一下如何在实践中应用,减少bug,提高效率。参考文章https://www.typescriptlang.or...https://www.typescriptlang.or...https://www.typescriptlang.or...https://dev.to/tipsy_dev/adva。..