progress_activity
interface 和 types
共同点
二者都可用于描述对象。
区别
扩展方式:
interface:用 extends 进行扩展
type:主要用 & 进行扩展
1// interface
2interface User { name: string };
3interface Admin extends User { role:
联合类型
interface 声明方式基本是固定的,只能用来描述 “对象” 的结构
type 是 类型别名,有多种声明方式用于不同的类型,更全能
1// 联合类型 Union
2type Id = string
联合声明
interface 可以不同文件/位置重复声明,这些重复声明会合并成联合声明。多用于给全局对象或第三方库做扩展时使用。
type 不允许重复声明
1interface Global { app?: {version: string }}
any/unknown/never
any
含义:指定这个值可以使任意类型,给他无论给他传递什么类型的值都不会有问题。相当于告诉 TypeScript 不要对这个变量进行检查。
带来的问题:
- TypeScript 编译通过不代表运行时没有问题,使用 any 会将编译时就能暴露出来的问题带到运行时环境。
- any 会污染类型推断,及一旦一个指定类型的值被 any 类型影响,后续关于这个值的类型推断都不一定可信. 例如我声明一个 obj 为 any 类型 ——
const obj: any = { a: 1 }; 那么之后在编辑器中, 在输入 obj. 后不会有属性或方法提示, 即使此时 obj 是包含 a 这个属性的。同理, 在输入 obj.a. 的时候,也不会有任何方法提示,即使此时 a 是 number 类型,其本身至少是包含 .toString()/ .valueOf() 等方法的。
应用场景
any 一般用于接入第三方无类型库或需要快速产出原型等场景下。不过还是建议尽快调整成 unknow 或具体类型。
<aside>
💡
在严格的业务场景下均不建议使用 any,而且通常会将此规则其加入 tslint 或者 eslint。
</aside>
unknow
含义:用于声明值的类型未知,不能直接使用,必须先做类型守卫或断言。
1const a: unknown = JSON.parse("{foo:1}");
2
使用场景
- 外部输入:接口返回,localStorage,postMessage 等
JSON.parse
catch(e: unknown) catch 中的错误对象现在在 TypeScript 中一般为 unkonwn
never
含义:这个值永远不会存在 / 某个函数永不返回(和 返回 void 有本质的区别,详见下文)/ 永远无法访问的代码
1// 函数只可能 throw, 不 return
2function fn(): never {
使用场景:
主要用于在 TypeScript 下,在编译期间对一些期望永远无法访问的代码部分做兜里处理。
上面的函数中,default 分支中的写法,如果我希望能在编译时让 TypeScript 对 switch 分支做 ”穷尽检查”,当走到 default 分支时, prop 必须是 never ,即前面的分支已经覆盖了所有的可能。
所有如果后来 type A 增加了新的类型 ”c”,但没有在函数中添加上对 case "c" 分支的处理时,在编译阶段 TypeScript 就会报错,因为此时 default 分支里面的 prop 是 "c" 而不是 never 了。
相当于在这里做了一个声明:这里永远不可达,除非前面代码出现了漏洞。
函数返回 never 和 void 的区别
void 表示有返回,但没有返回值,或者返回值不重要。函数是 “正常” 运行到 return 结束的,但不需要关心 return 的值(通常为 undefined)。
never 表示函数永不结束,如死循环或不会 “正常” 结束,如 throw new Error("error") 。函数不会走到 return 这一步。
Parameters<T>/ReturnType<T>
Parameters<T>
含义:如果 T 是函数类型,就把它的参数列表推断成一个 tuple P 并返回。
1function fn(a: string, b: number, c?: boolean)
ReturnType<T>
含义:如果 T 是函数类型,就把它的参数列表推断成一个 tuple P 并返回。
1function fn(a: number, b: string) {
2
需要注意的点
- 对有重载的函数,
Parameters<typeof fn> / ReturnType<typeof fn> 通常会取到最后一个签名(实现签名)或某种合并结果,不一定是你期望的“所有重载”。这种场景建议为每个重载单独定义类型或用接口建模。
1// 重载声明
2function parse(input: string)
ReturnType 在异步函数中直接拿到的是 Promise,如果要拿到实际返回值,需要添加 Awaited
1async function fn() {
2 return "123";
Parameters 只适用于函数类型
Parameters 传非函数会报约束错误,或得到 never/any.
1const a = "123";
2
3type A = Parameters
使用场景
- 高阶函数/装饰器
1// 希望实现一个高阶函数 wrap(fn),让它的入参类型和出参类型与 fn 一致。
2function withLog<T extends (...
- 把现有函数的签名复用到别处(避免重复写类型)
1// 有 service.fetchUser, 期望在 mock、测试、或 adapter 里保持同签名。
2typeFetchUser =typeof fetchUser;
3typeFetchUserArgs =Parameters<
- 事件/回调类型对齐(React props / SDK 回调
1// 表单组件 onSubmit 的参数类型复用到校验器、提交器
2typeOnSubmit =(values: { name:string
类型安全的“转发调用”(proxy / RPC / client)
1typeApi = {
2getUser:(id:string) =>
"admin"
}
;
4// type
5type TUser = { name: string };
6type TAdmin = TUser & { role: "admin" };
|
number
;
3const idStr: Id = "a"; // √
4const idNum: Id = 123; // √
5const idBoolean:Id = false; // × —— 类型必须是 string 或者 number
6
7// 元组类型 tuple
8type StringNumberPair = [string, number];
9const strNum: StringNumberPair = ["abc", 123]; // √
10const numStr: StringNumberPair = [123,"abc"]; // × —— 数组中第一项元素类型必须为 string, 且第二项元素类型必须为 number
11
12// 函数 function
13type Fn = (x:number) => string;
14const foo:Fn = (a:number) => { return "123" }; // √
15const bar:Fn = (a:string) => { return 123 }; // × —— 参数类型必须是 number, 且返回值类型必须是 string
2
// 再次声明 Global
3interface Global { logger?: (msg: string) => void }
4const a: Global = {};
5const myappVersion = a.app?.version; // √
6a.logger?.("success"); // √
7
8type TypeA = string;
9type TypeA = number; // × —— 标识 TypeA 重复
(
)
;
// √ —— TypeScript 编译通过. 运行时: ×, x 上没有 foo 方法
3x(); // √ —— TypeScript 编译通过. 运行时: ×, x is not a function
4x.bar = 100; // √ —— TypeScript 编译通过. 运行时: ×, 不会有报错提示, 但获取 x.bar 永远都是 undefine
5x = ["hello"]; // √ —— TypeScript 编译通过. 运行时不会报错, 但在大部分业务场景下, 类型不同会引发非预期的问题。
6const n: string = x; // √ —— TypeScript 编译通过.
a
.
foo
// × —— a 的类型为“未知”
3if (a && typeof a === "object" && "foo" in a) {
4 // 添加类型判断后, 才可使用
5 console.log( a.foo ); // √ 打印 1
6}
3 throw "err";
4}
5// 函数内部无限循环,永不结束
6function fn(): never {
7 while (true) {}
8}
9// 永远无法访问的代码
10function fn(x: "number" | "string") {
11 if (typeof x === "number") {
12 return 123;
13 } else if (typeof x === "string") {
14 return "123";
15 } else {
16 const n: never = x; // n 的类型就是 never, 因为正常情况下, 这里永远无法访问到。
17 }
18}
function
assertNever
(
x
:
never
)
:
never
{
4 throw new Error("Unexpected: " + x);
5}
6
7function fn(prop: a) {
8 switch (prop) {
9 case "a":
10 return 1;
11 case "b":
12 return 2;
13 default:
14 return assertNever(prop);
15 }
16}
{
2 return c ? a : b;
3}
4
5type P = Parameters<typeof fn>;
6// 相当于 type P = [a: string, b: number, c?: boolean | undefined]
return
{
code
:
a
,
msg
:
b
}
;
3}
4
5type R = ReturnType<typeof fn>;
6// 相当于 type R = { code: number; msg: string; }
:
number
;
3function parse(input: number): string;
4
5// 重载函数实现
6function parse(input: string | number) {
7 return typeof input === "string" ? input.length : String(input);
8}
9
10type P = Parameters<typeof parse>;
11type R = ReturnType<typeof parse>;
12
13// 期望是:
14// P 是 [string] | [number]
15// R 是 number | string(并且能跟参数关联)
16//
17// 实际上通常会变成:
18// P = [input: string | number]
19// R = string | number
3}
4type R = ReturnType<typeof fn>;
5// type R = Promise<string> // Promise
6
7type RealR = Awaited<ReturnType<typeof fn>>;
8// type RealR = string // string
<
t
4
5ypeof a>;
6// × —— 类型 string 不满足约束“(...args: any) => any”。
args
:
Parameters
<
T
>
)
=>
ReturnType
<
T
>>
(
fn
:
T
)
{
3 return (...args: Parameters<T>): ReturnType<T> => {
4 console.log("call with", args);
5 return fn(...args);
6 };
7}
8
9const a = (num: number) => num + 1;
10const logA = withLog(a);
11type TypeA = typeof a; // (num: number) => number
12type TypeLogA = typeof logA; // (num: number) => number 与 TypeA 一致
FetchUser
>
;
4typeFetchUserRes =ReturnType<FetchUser>;
}
)
=>
Promise
<
void
>
;
3
4typeOnSubmitArgs =Parameters<OnSubmit>[0];// { name: string }
5typeOnSubmitReturn =ReturnType<OnSubmit>;// Promise<void>
Promise
<
{
id
:
string
}
>
;
3listUsers:(q: { page:number }) =>Promise<{items:any[] }>;
4};
5
6function call<Kextends keyofApi>(
7key: K,
8 ...args:Parameters<Api[K]>
9):ReturnType<Api[K]> {
10// transport.call(key, args)...
11thrownewError("not implemented");
12}