typescript深入理解系列之类型推论:是否应该偷懒不写类型
写在最前:笔者从去年就开始接触学习ts,但是看了几次文档都朦朦的,所以本系列旨在分享学习ts路上一些较为深入的理解(结合一个实际例子去讲解)
类型推论,通俗点理解就是ts推断出了变量的类型。理解类型推论,能更清楚地知道,啥时候应该声明类型,啥时候不应该。
TypeScript是怎么推论的
普通变量
当我们赋值变量时,变量就会被推论出一个类型。
const x = 1; // x被推论为 number类型 const fn = () => { const x = 1;} // fn 被推论为 () => void;复制代码
数组类型
Typescript在推论数组类型时,如果数组成员类型是不一致的,在推论时会考虑每个成员的类型并尝试找出他们相同的类型后进行组合。并且值得注意的是,推论时会做最宽松的推论,即成员类型联合后的数组类型。 这样,数组后续的操作就是很宽松的。可以看些例子
const x = ['1', '2', 3]; // x被推论为 (string | number)[] const xx = ['1', 2, false]; // x被推论为 (string | number | boolean)[] xx.push(true); // 通过 x.push(true); // 不通过 Argument of type 'boolean' is not assignable to parameter of type 'string | number'.ts(2345)复制代码
当成员类型有一些共同点的时候,Typescipt推论时会自动推论出共同点。 不过值得注意的是,并不是一定都能推论出对的共同点。
interface Basic { a: string; } interface BasicMore extends Basic { b: string; } interface BasicMore2 extends Basic { c: string; } const x: Basic = { a: "1", }; const xx: BasicMore = { a: "1", b: "1", }; const xxx: BasicMore2 = { a: "1", c: "1", }; const aa = [x, xx, xxx]; // aa 被推论出了 Basic[]复制代码
上下文推断
有时候,TypeScript也会根据上下文去进行一些类型推断
interface Fn { (value: number): number; x?: string; } export const fn: Fn = (value) => { // (parameter) value: number return Number(value); }复制代码
在这个例子中可以看到value是自动的被推论为number类型。
总的来说,在很多场景下,对于基本的变量,我们是不需要手动声明类型的,对于函数,我们需要声明出入参或者声明适配的interface或者type。但是还有很多情况下,我们需要摒弃推论出来的类型,手动赋予类型。
不要过分依赖类型推论
推论不总是对的
虽然TypeScript可以推论出函数的返回类型,但不一定是你所预期的。
接下来我们看个例子
const fs = () => [1, '11']; // 期望返回类型是 [number, string] const [a, b] = fs(); // 期望a是number,b是string const result = a + 3; // 结果报错 Operator '+' cannot be applied to types 'string | number' and 'number'.ts(2365) // 从这个例子中,我们期待ts自动推论出了[number, string]的元组类型,并且从而推论出a为number类型,但实际上 // 函数fs被推论为 const fs: () => (string | number)[] // 所以在这种时候,主动声明fs的返回类型为 [number, string] 就是有必要的。 const fs = (): [number, string] => [1, '11']; const [a] = fs(); const result = a + 3; // 正确复制代码
应该主动定义类型
其实在大部分情况下,你都应该主动定义类型,毕竟你才是知道这个变量类型的人。而且有时候,主动声明类型,可以带来一些类型规范上的好处。
// 首先声明所有的学科枚举 enum Subject { "Chinese", "Math", "English", "Physics", "Chemical", "Biology", "Politics", "History", "Geography" } // 定义通用学科 type BasicSubject = Subject.Chinese | Subject.Math | Subject.English; // 定义理科 type ScienceSubject = Subject.Physics | Subject.Chemical | Subject.Biology | BasicSubject; // 定义文科 type LiberalSubject = Subject.Politics | Subject.History | Subject.Geography | BasicSubject; // 随便写个理科分数模型变量 const record: Record<ScienceSubject, number> = { [Subject.Chinese]: 100, [Subject.Math]: 100, [Subject.English]: 100, [Subject.Physics]: 100, [Subject.Chemical]: 100, } // 如果此时我们不给record主动声明这个类型,这里是不会报错的 // 由于我们主动声明了类型,record会报错:Property '5' is missing in type '{ 0: number; 1: number; 2: number; 3: number; 4: number; }' but required in type 'Record<ScienceSubject, number>'. // 为啥呢,因为我们强约束record是理科类型的分数模型,当我们少写了一个生物分数时,就会报错。这是其中一个好处,提醒我们写错了或者写多了。 // 第二个好处是,如果我们的理科多了一门功课,例如说 天文学, 我们只需要在Subject的enum里先添加这个类型,再定义好
作者:啊K
链接:https://juejin.cn/post/7026562828043550756