ts由浅入深
一、先建立整体心智模型TypeScript 做三件事描述数据结构 — 对象、数组、函数长什么样interface、type约束代码行为 — 不该传的别传、不该调的别调类型检查类型推导 — 你写一部分TS 帮你推另一部分泛型推断、typeof、infer例如exporttypeBusinessParamsValuestypeofinitialValues;这行的意思是从运行时对象 initialValues 反推类型以后改字段只改一处类型自动跟着变。这是非常好的实践。二、基础类型从 string 到「精确描述」2.1 字面量类型 vs 宽泛类型// 宽泛任何字符串都行typeStatusstring;// 精确只能是这三个之一typeStatusloading|success|error;2.2 联合类型 A | B表示「可能是 A也可能是 B」typeIdstring|number;typeResult{ok:true;data:User}|{ok:false;error:string};2.3 类型收窄Type Narrowing常见手段写法场景typeof x ‘string’原始类型x instanceof Date类实例‘key’ in obj对象是否有某字段x null / x null空值检查2.4 type vs interfaceinterfacetype对象形状✅✅联合类型 A | B❌✅交叉类型 A B可以 extends✅ 更自然声明合并同名自动合并✅❌映射/条件类型❌✅经验法则前端项目组件 Props、API 响应对象 → interface 或 type 都行联合类型、工具类型、从值推导 → 用 type2.5 可选 ?、只读 readonly、索引签名typeFormField{label:string;name:string;// 必填min?:number;// 可选类型是 number | undefinedreadonlyid:string;// 不可重新赋值};// 索引签名键是 string值是 unknowntypeJsonObjectRecordstring,unknown;// 等价于 { [key: string]: unknown }2.6 as const — 让 TS 推断最窄类型// 普通推断string[]constarr[a,b];// as constreadonly [a, b]constarr[a,b]asconst;// 对象 as constconstconfig{mode:strict,retries:3,}asconst;// mode 类型是 strict不是 string2.7 typeof — 从值推类型exportconstinitialValues{cameraCount:64,bitrate:4,// ...};// 推导出// { cameraCount: number; bitrate: number; ... }exporttypeBusinessParamsValuestypeofinitialValues;还可以取属性类型typeCameraCountBusinessParamsValues[cameraCount];// numbertypeKeyskeyofBusinessParamsValues;// cameraCount | bitrate | videoRetentionDays | ...keyof T 对象所有键组成的联合类型。你项目里已经用了三、泛型类型的「函数参数」3.1 为什么需要泛型没有泛型时要么写死类型要么用 any// 泛型灵活 安全functionwrapT(value:T):{data:T}{return{data:value};}constawrap(123);// { data: number }constbwrap(hello);// { data: string }你项目里的 API 层api.d.ts Lines 1-5 interface ApiResponseT unknown { code: number; message: string; data: T; }T 是占位符调用时填入具体类型interfacePageResultT{list:T[];total:number;}typeUserListResponseApiResponsePageResultUser;asyncfunctionfetchUsers(){constresawaitrequest.getApiResponsePageResultUser(/users);res.data.data.list;// User[]}3.2 泛型约束 T extends U表示「T 必须是 U 的子类型」functiongetPropertyT,KextendskeyofT(obj:T,key:K):T[K]{returnobj[key];}constvaluesinitialValues;getProperty(values,cameraCount);// ✅ numbergetProperty(values,foo);// ❌ 报错3.3 泛型默认值interfaceApiResponseTunknown{...}// 不传 T 时T unknown四、函数类型你 debounce 的核心4.1 函数类型写法// 写法 1箭头typeHandler(values:BusinessParamsValues)void;// 写法 2call signaturetypeHandler{(values:BusinessParamsValues):void;};// 写法 3带额外属性debounce 的返回类型typeDebouncedHandlerHandler{cancel:()void;flush:()void;};定义的函数返回值类型 T { cancel; flush }意思是保留原函数类型 T再附加两个方法。4.2 Parameters — 取函数参数元组typeFn(name:string,age:number)void;typeArgsParametersFn;// [name: string, age: number]4.3 ReturnType — 取函数返回值typeFn()PromiseUser[];typeResultReturnTypeFn;// PromiseUser[]debounce 里还用了lettimer:ReturnTypetypeofsetTimeout|nullnull;传给 ReturnType 的必须是「类型」先用 typeof 得到函数类型再取返回值在浏览器/Node 里是 Timeout 或 number。4.4 函数重载overload对外多个签名实现只有一个functionformat(value:string):string;functionformat(value:number):string;functionformat(value:string|number):string{returnString(value);}适合「参数不同返回/行为不同」的 API。一句话总结重载不是语法炫技而是告诉 TypeScript“虽然我只有一个实现但调用时请根据你传了什么给我更精确的类型。”如果输入输出关系很简单string | number → string一个函数签名就够只有调用时类型推断要更细时重载才值得写。五、内置工具类型抄作业清单这些 TS 内置不用自己写直接 import 思路interfaceUser{id:string;name:string;age:number;email?:string;}工具类型作用例子结果Partial所有属性变可选{ id?: string; name?: string; … }Required所有属性变必填email 也必填PickT, K取部分字段PickUser, ‘id’ | ‘name’OmitT, K去掉部分字段OmitUser, ‘email’RecordK, V构造字典Record‘a’ | ‘b’, numberExcludeT, U从联合中排除Exclude‘a’|‘b’|‘c’, ‘a’ → ‘b’|‘c’ExtractT, U从联合中提取Extract‘a’|‘b’, ‘a’|‘c’ → ‘a’NonNullable去掉 null/undefined你项目里用了ReturnType函数返回值见上Parameters函数参数见上Awaited解包 PromiseAwaitedPromise → User例子1request.ts Lines 25-25 export type RequestParams NonNullableAxiosRequestConfig[params];AxiosRequestConfig[“params”] 可能是 undefinedNonNullable 把它去掉。例子2// 表单提交时只要部分字段typeBusinessParamsSubmitPickBusinessParamsValues,cameraCount|bitrate;// 编辑草稿全部可选六、自定义工具类型进阶但实用6.1 索引访问类型 T[K]typeValuesBusinessParamsValues[cameraCount];// numbertypeAllValuesBusinessParamsValues[keyofBusinessParamsValues];// number因为所有字段都是 number6.2 映射类型 { [K in keyof T]: … }// 把所有字段变成 readonlytypeReadonlyT{readonly[KinkeyofT]:T[K];};// 把所有字段变成可选Partial 就是这样实现的typeMyPartialT{[KinkeyofT]?:T[K];};6.3 条件类型 T extends U ? X : YtypeIsStringTTextendsstring?true:false;typeAIsStringhello;// truetypeBIsString123;// false6.4 infer — 在条件类型里「提取」类型这是 Parameters、ReturnType 的底层原理// 手写 ParameterstypeMyParametersTTextends(...args:inferP)unknown?P:never;// 手写 ReturnTypetypeMyReturnTypeTTextends(...args:never[])inferR?R:never;// 解包 PromisetypeUnwrapPromiseTTextendsPromiseinferU?U:T;读法infer P 「如果 T 能匹配这个函数形状就把参数列表命名为 P」。6.5 交叉类型 A B合并多个类型typeWithTimestampBusinessParamsValues{updatedAt:string};debounce 返回类型T { cancel: () void; flush: () void } 原函数 两个新方法。七、any vs unknown vs 类型断言7.1 优先级unknown最安全 具体类型 any最不推荐anyunknown赋值给任何类型✅❌ 需先收窄调用方法/访问属性✅❌ 需先收窄适合场景legacy 代码过渡外部不可信数据比如(_:unknown,allValues:BusinessParamsValues){debouncedSaveCache(allValues);第一个参数用 unknown 而不是 any——表示「我不关心这个参数」且不会污染类型环境。这是好写法。7.2 类型断言 as T告诉 TS「我比你知道得更清楚」。八、React TypeScript 实战模式8.1 组件 Props// ✅ 推荐直接定义 props 类型interfaceBusinessParamsFormProps{form:FormInstanceBusinessParamsValues;}functionBusinessParamsForm({form}:BusinessParamsFormProps){...}// ❌ 不必再用 React.FC旧写法constComp:React.FCProps(props)...;8.2 泛型 Form// antd Form 支持泛型字段名和值类型会联动const[form]Form.useFormBusinessParamsValues();form.getFieldValue(cameraCount);// numberform.getFieldValue(xxx);// ❌ 报错8.3 Hook 泛型functionuseLocalStorageT(key:string,initial:T){const[value,setValue]useStateT((){constrawlocalStorage.getItem(key);returnraw?(JSON.parse(raw)asT):initial;});return[value,setValue]asconst;}const[params,setParams]useLocalStorageBusinessParamsValues(key,initialValues,);九、模块增强你项目里已有当第三方库类型不够用时可以「扩展」它declaremoduleaxios{interfaceAxiosRequestConfig{ignoreError?:boolean;}}意思是给 axios 的 AxiosRequestConfig 追加一个可选字段 ignoreError全局生效。