类型推断
如果没有给声明的变量指定类型,TypeScript 会根据一些规则给变量推断一个类型。如:
let a = 1;
a = '';
上述代码会报错 Type 'string' is not assignable to type 'number'.
实际上上述等价于下面的代码:
let a: nubmer = 1;
a = '';
从右向左
- 变量的类型可以由定义推断
这是一个从右向左流动类型的示例
let a = 1;
可以看到
如果我们给变量a
赋值其他类型会报错// Type 'string' is not assignable to type 'number'.
a = '';
底部流出
返回类型能被 return 语句推断
function sum(a: number, b: number) {
return a + b;
}
const res = sum(1, 2);
尽管 TypeScript 一般情况下能推断函数的返回值,但是它可能并不是你想要的 ```typescript function addOne(a:any) { return a + 1; // 返回 any 类型 } function sum(a: number, b: number) { return a + addOne(b); // 一个数加一个 any 类型,结果也是 any 类型 }
type Ret = ReturnType
<a name="J7LEu"></a>
## 结构化
- 推断规则也适用于结构化的存在(对象字面量)
```typescript
const person = {
name: 'zhufeng'
};
let name = person.name;
// Type 'number' is not assignable to type 'string'.
name = 1;
推断规则也适用于解构
const person = {
name: 'f'
};
let { name } = person;
// Type 'number' is not assignable to type 'string'.
name = 1;
DefaultProps
interface Person {
name?: string,
age?: number
}
const p1: Person = {
name: 'f',
age: 18
};
const p2 = {
...p1,
gender: 1
};
type P = typeof p2;
/*
type P = {
gender: number;
name?: string | undefined;
age?: number | undefined;
}
*/
交叉类型
- 交叉类型(Intersection Types)是将多个类型合并为一个类型
- 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性 ```typescript interface A { name: string; age: number; } interface B { gender: number; }
type C = A & B; // person 需要具有 A 和 B 所有的属性才行 const person: C = { name: ‘f’, age: 18, gender: 1 };
type D = number | string | boolean; type E = number; type F = D & E; // f 只能赋值 number 类型 const f:F = 1;
上面的例子中给 `person` 和 `f` 的赋值是不是和你想的不一样呢?
```typescript
interface A {
name: string;
age: number;
}
interface B {
gender: number;
}
type C = A | B;
// person 只要包含 A 或者 B 中的任意属性即可,全部包含也行
// const person: C = { gender: 1 };
// const person: C = { name: 'f', age: 18 };
// const person: C = { name: 'f', age: 18, gender: 1 };
type D = number | string | boolean;
type E = number;
type F = D | E;
// f 可以赋值为 number、string、boolean 中的任意类型
// const f: F = 1;
// const f: F = '';
// const f: F = true;
看一下下面的代码我们应该怎么给 ab
赋值呢?
interface A {
a: string | number,
b: string
}
interface B {
a: number | boolean,
c: string
}
type AB = A & B;
let ab: AB = ?
答案如下:
let ab: AB = {a: 1, b: '', c: ''};
mixin 混入模式可以让你从两个对象中创建一个新对象,新对象会拥有着两个对象所有的功能
interface Person {
[prop: string]: any
}
function mixin<T extends Person, U extends Person>(one: T, two: U): T & U {
// Type '{}' is not assignable to type 'T & U'.
const res = <T & U>{};
for (const key in one) {
(<T>res)[key] = one[key];
}
for (const key in two) {
(<U>res)[key] = two[key];
}
return res;
}
const person = mixin({ name: 'f' }, { age: 18 });
/*
person: {
name: string;
} & {
age: number;
}
*/
console.log(person.name, person.age);
可能你看到上面代码中的 (T)res[key]
感觉有点陌生,这是干啥的?实际它就是个类型断言:
let a: number = 1;
// Conversion of type 'number' to type 'string' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
// (<string>a) = '';
(<string>(<unknown>a)) = ''
// 和上面的代码等价,都是断言
(a as unknown as string) = ''
这里说句题外话,在 TS 中我们怎么给一个对象赋值呢?
const person = {};
person.name = 'f';
上述代码会报错 Property 'name' does not exist on type '{}'.
那我们应该怎么办呢?我们可以像下面这样:
interface Person {
[prop: string]: any
}
const person: Person = {};
person.name = 'f';
typeof
可以获取一个变量的类型
let person = {
name: 'f',
age: 18
}
type P = typeof person;
/*
type P = {
name: string;
age: number;
}
*/
索引访问操作符
- 可以通过[]获取一个类型的子类型 ```typescript interface Person { name: string, age: number, jobInfo: { name: string, money: number }, // interests 是一个数组,数组里面的每一项是一个包含 name 属性的对象 interests: {name:string}[] }
const jobInfo: Person[‘jobInfo’] = { name: ‘f’, money: 10000 }; const interestsName: Person[‘interests’][0][‘name’] = ‘’;
<a name="EI09d"></a>
# keyof
- 索引类型查询操作符
```typescript
interface Person {
name: string,
age: number
}
type P = keyof Person;
function getName(p: Person, key: P) {
return p.name;
}
getName({name: 'f', age:18}, 'name');
映射类型
- 在定义的时候用in操作符去批量定义类型中的属性 ```typescript interface Person { name: string, age: number, gender: number }
type P = {
[key in keyof Person]: Person[key]
}; / type P = { name: string; age: number; gender: number; } /
<a name="Y5fGv"></a>
## Partial
Partial 将传入的属性变为可选项
```typescript
interface Person {
name: string,
age: number,
gender: number
}
type P = Partial<Person>;
可以看到结果如下:
如果我们不使用 Partial
怎么将属性转为可选项呢?我们可以使用下面的方法:
type P = {
[key in keyof Person]?: Person[key]
}
Partial
的源码
type Partial<T> = {
[key in keyof T]?: T[key]
}
条件类型
- 在定义泛型的时候能够添加进逻辑分支,以后泛型更加灵活
定义条件类型
```typescript interface Man { name: string } interface Woman { age: number } interface Earn {} interface TubeMoney { money: number }
type Condition
<a name="ckpbb"></a>
## 条件类型的分发
```typescript
interface Man {
name: string
}
interface Woman {
age: number
}
interface Earn {
skills: Array<string>
}
interface TubeMoney {
money: number
}
type Condition<T> = T extends Man ? Earn : TubeMoney;
const condition1: Condition<Woman | Man> = {money: 1}; // condition1: Earn | TubeMoney
const condition2: Condition<Woman | Man> = { skills: ['编程', '开车'] }; // condition2: Earn | TubeMoney
const condition3: Condition<Woman> = { money: 1 }; // condition3: TubeMoney
const condition4: Condition<Man> = {skills: []} // condition3
type Filter<T, U> = T extends U ? T : never;
type R = Filter<string|number|boolean, boolean> // R = boolean
条件类型有一个特性,就是「分布式有条件类型」,但是分布式有条件类型是有前提的,条件类型里待检查的类型必须是naked type parameter。我们把上面的例子做个修改:
// T 必须是裸类型,不能有任何包含或者其他
type Condition<T> = T[] extends Man[] ? Earn : TubeMoney;
let condition: Condition<Woman[] | Man[]>; // condition: TubeMoney
下面这样写是没有问题的:
type Condition<T> = T extends Man[] ? Earn : TubeMoney;
let condition: Condition<Woman[] | Man[]>; // condition: Earn | TubeMoney
内置条件类型
TS 在内置了一些常用的条件类型,可以在 lib.es5.d.ts 中查看:
utility-types
Exclude
从 T 可分配给的类型中排除 U
type Exclude<T, U> = T extends U ? never : T;
type R = Exclude<string | number | boolean, boolean>;
/*
type R = string | number
*/
Extract
从T中抽出可分配给U的属性构成新的类型
type Extract<T, U> = T extends U ? T : never;
type R = Extract<string | number | boolean, boolean>;
/*
type R = boolean
*/
NonNullable
从 T 中排除 null 和 undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type R = NonNullable<string | null | undefined>;
/*
type R = string
*/
ReturnType
infer最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量
获取函数类型的返回类型type Exclude<T, U> = T extends U ? never : T;
type R = Exclude<string | number | boolean, boolean>;
/*
type R = string | number
*/
Parameters
可以通过
Parameters
获取一个参数的列表 ```typescript type Parametersany> = T extends (…args: infer P) => any ? P : never;
function sum(a: number, b: number) {
return a + b;
}
type ParamsType = Parameters
<a name="dL25X"></a>
## InstanceType 和 ConstructorParameters
获取构造函数类型的实例类型
```typescript
class Person {
constructor(public name: string, public age: number) {}
}
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
// 获取类的构造函数的参数类型
type constructorParameters = ConstructorParameters<typeof Person>;
/*
constructorParameters 的结果如下:
type constructorParameters = [name: string, age: number]
*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
type Instance = InstanceType<typeof Person>;
/*
Instance 结果如下:
type Instance = Person
*/
let instance: Instance = { name: '', age: 1 };
infer 的应用案例
infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量。
下面看个简单的例子,注意:代码中的 infer P
表示待推断的函数参数。
// 如果 T 能赋值给 (...args: infer P) => any 则返回推断出的类型变量 P,否则返回 T。
type ParamType<T> = T extends (...args: infer P) => any ? P : T;
interface User {
name: string,
age: number
}
type Func = (user: User) => void;
type Param = ParamType<Func>; // Param = [user: User]
type AA = ParamType<string>; //AA = string
上面的例子中我们在函数类型的参数中使用了 infer ,并不是说 infer 只能用于函数中。
type Person<T> = T extends { name: infer A } ? A : never;
type P = Person<{name: string}>; // P = string
tuple 转 union
type ElementOf<T> = T extends Array<infer E> ? E : never;
type Ttuple = [string, number, boolean];
type TupleToUnion = ElementOf<Ttuple>; // TupleToUnion = string | number | boolean
交叉类型
type T1 = { name: string };
type T2 = { age: number };
type Intersection<T> = T extends { a: (x: infer U) => void, b: (x: infer U) => void } ? U : never;
type T3 = Intersection<{ a: (x:T1) => void, b:(x:T2) => void }>; // T3 = T1 & T2
const person: T3 = { name: '', age: 18 };
内置工具类型
TS 中内置了一些工具类型来帮助我们更好地使用类型系统
utility-types)
TypeScript中增加了对映射类型修饰符的控制
具体而言,一个 readonly 或 ? 修饰符在一个映射类型里可以用前缀 + 或-来表示这个修饰符应该被添加或移除
符号 | 含义 |
---|---|
+? | 变为可选 |
-? | 变为必选 |
Partial
Partial 可以将传入的属性由非可选变为可选,具体使用如下
interface Person {
name: string,
age: number
}
type PersonPartial = Partial<Person>;
const p: PersonPartial = {};
Partial 的代码如下:
type Partial<T> = {
[P in keyof T]?: T[P];
};
类型递归
interface Scores {
math: number,
english: number
}
interface Student {
name: string,
age: number,
scores: Scores
}
type StudentPartial = Partial<Student>;
/*
StudentPartial = {
name?: string | undefined;
age?: number | undefined;
scores?: Scores | undefined;
}
*/
const s: StudentPartial = {name:'', age:1};
scores 必传
const s: StudentPartial = { name: '', age: 1, scores: {math: 1, english: 2}};
怎么不传呢,我们可以自定义 DeepPartial
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object
? DeepPartial<T[U]>
: T[U]
};
修改后的代码如下:
interface Scores {
math: number,
english: number
}
interface Student {
name: string,
age: number,
scores: Scores
}
type DeepPartial<T> = {
[U in keyof T]?: T[U] extends object
? DeepPartial<T[U]>
: T[U]
};
type StudentPartial = DeepPartial<Student>;
/*
StudentPartial = {
name?: string | undefined;
age?: number | undefined;
scores?: DeepPartial<Scores> | undefined;
}
*/
const s: StudentPartial = { name: '', age: 1, scores: {}};
Required
Required 可以将传入的属性中的可选项变为必选项,这里用了 -? 修饰符来实现。
interface Person {
name?: string,
age?: number
}
type Required<T> = {
[P in keyof T]-?: T[P];
};
type RequiredPerson = Required<Person>;
/*
RequiredPerson = {
name: string;
age: number;
}
*/
const p: RequiredPerson = { name: '', age: 18 };
Readonly
Readonly 通过为传入的属性每一项都加上 readonly 修饰符来实现
interface Person {
name: string,
age: number
}
type ReadonlyPerson = Readonly<Person>
const p: ReadonlyPerson = {
name: '',
age: 18
};
// Cannot assign to 'name' because it is a read-only property.
// p.name = 'f';
// Cannot assign to 'age' because it is a read-only property.
// p.age = 19;
如果我们只想某个属性不可选,可以像下面这样写:
interface Person {
readonly name: string,
age: number
}
const p: Person = {
name: '',
age: 18
};
// Cannot assign to 'name' because it is a read-only property.
// p.name = 'f';
p.age = 19;
如果我们不能改 Person
接口的话,可以像下面这样写:
interface Person {
name: string,
age: number
}
type ReadonlyPerson = Person & { readonly name: string };
const p: ReadonlyPerson = {
name: '',
age: 18
};
// Cannot assign to 'name' because it is a read-only property.
// p.name = 'f';
p.age = 19;
Pick
Pick 能够帮助我们从传入的属性中摘取某一项返回,在一个声明好的对象中,挑选一部分出来组成一个新的声明对象
interface Person {
name: string,
age: number
}
type PickPerson = Pick<Person, 'name'>; // PickPerson = { name: string; }
Pick 的源代码如下:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Pick 和 Extract 的区别
它们两个的功能不同 Pick
是在一个声明好的对象中,挑选一部分出来组成一个新的声明对象,Extract
是从 T 中抽出可分配给 U 的属性构成新的类型。
Pick 的源码:
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
Extract 的源码:
type Extract<T, U> = T extends U ? T : never;
Record
构造一个类型,该类型具有一组属性 K ,每个属性的类型为 T 。可用于将一个类型的属性映射为另一个类型。Record 后面的泛型就是对象键和值的类型。他会将一个类型的所有属性值都映射到另一个类型上并创造一个新的类型
简单理解:K 对应对应的key,T 对应对象的 value,返回的就是一个声明好的对象
Record 源码如下:
type Record<K extends keyof any, T> = {
[P in K]: T;
};
keyof any
的值为 string | number | symbol
:
type Key = keyof any; // Key = string | number | symbol
注意: keyof 后面跟的是类型,不是变量。
Recodr 的源码可以看成下面这样:
type Record<K extends string | number | symbol, T> = {
[P in K]: T;
};
也就是 P 的类型可以是 string | number | symbol
。
上面我们对 Record 进行了简单介绍,下面我们来举个例子看看怎么用它:
type PersonType = 'name' | 'age' | 'gender';
type Person = Record<PersonType, string>;
/*
Person = {
name: string;
age: string;
gender: string;
}
*/
案例2:
function mapObject<K extends string | number, T, U>(obj: Record<K, T>, map: (x: T) => U): Record<K, U> {
let result: any = {};
for (const key in obj) {
result[key] = map(obj[key]);
}
return result;
}
let names = { 0: 'hello', 1: 'world' };
let lengths = mapObject<string | number, string, number>(names, (s: string) => s.length);
console.log(lengths);//{ '0': 5, '1': 5 }
自定义高级类型
Proxy
interface Props {
name: string;
age: number;
}
const obj: Props = {
name: 'f',
age: 18
};
type Proxy<T> = {
set(value: T): void;
get(): T;
};
type Proxify<T> = { [P in keyof T]: Proxy<T[P]> };
function proxify<T>(obj: T): Proxify<T> {
const res = <Proxify<T>>{};
for (let k in obj) {
type KeyType = typeof k;
res[k] = {
set(value: T[KeyType]) {
obj[k] = value;
},
get() {
return obj[k];
}
};
}
return res;
}
const proxyProps = proxify<Props>(obj);
console.log(proxyProps);
function unProxify<T>(obj: Proxify<T>): T {
const res = <T>{};
for (const k in obj) {
res[k] = obj[k].get();
}
return res;
}
const originProps = unProxify(proxyProps);
console.log(originProps); // {name: 'f', age: 18}
SetDifference
type SetDifference<A, B> = A extends B ? never : A;
type A = string | number;
type B = number | boolean;
type AB = SetDifference<A, B>; // type AB = string
从上面的例子可以看出它和 Exclude
的效果是一样的,下面我们来看下两个的实现源码:
type Exclude<T, U> = T extends U ? never : T;
type SetDifference<A, B> = A extends B ? never : A;
Omit
以一个类型为基础支持剔除某些属性,然后返回一个新类型。
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Person = {
name: string;
age: string;
gender: number;
};
type PersonType = Omit<Person, 'gender'>;
/*
PersonType = {
name: string;
age: string;
}
*/
下面我们来说下它的源码:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
keyof
的作用是获取某个类型的所有键,它会返回一个联合类型:
type A = keyof { name: '', age: 10 } // A = "name" | "age"
type B = keyof any // B = string | number | symbol
Exclude
的作用前面我们已经讲过了,简单来说就是将类型 T 和 类型 U 进行对比,返回 T 中独有的。
type A = Exclude<string|number|boolean, string|number>; // A = boolean
// 源码
type Exclude<T, U> = T extends U ? never : T;
Pick
能够帮助我们从传入的属性中摘取某一项返回,在一个声明好的对象中,挑选一部分出来组成一个新的声明对象。
type A = Pick<{ name: 'f', age: 18 }, 'name'>;
/*
A = {
name: 'f';
}
*/
// 源码
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
通过上面的学习我们理清楚了一些关于 Omit
的知识点,下面我们来推导下开头的例子,以达到更深入的理解:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
type Person = {
name: string;
age: string;
gender: number;
};
type PersonType = Omit<Person, 'gender'>; =>
type PersonType = Pick<Person, Exclude<'name'|'age'|'gender', 'gender'>> =>
type PersonType = Pick<Person, 'name'|'age'> =>
type PersonType = Pick<Person, 'name'|'age'> =>
type PersonType = {
[P in 'name'|'age']: Person[P] =>
}
type PersonType = {
name: sting,
age: number
}
Diff
Diff<T, U>
找出 T 和 U 不同的地方:
type SetDifference<A, B> = A extends B ? never : A;
type Diff<T extends object, U extends object> = Pick<T, SetDifference<keyof T, keyof U>>;
type A = Diff<{ name: string, age: number }, { name: string }> // A = {age: number;}
推导下:
type SetDifference<A, B> = A extends B ? never : A;
type A = Diff<{ name: string, age: number }, { name: string }> =>
type A = Pick<{ name: string, age: number }, SetDifference<'name'|'age', 'name'>> =>
type A = Pick<{ name: string, age: number }, 'name'|'age' extends 'name' ? never : A> =>
type A = Pick<{ name: string, age: number }, 'age'> =>
type A = {age: number}
Intersection
Intersection<T, U>
找出 T 和 U 相同的部分。
type Intersection<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;
type A = { name: string, age: number, gender: number };
type B = { gender: string };
type AB = Intersection<A, B>; // AB = { gender: number; }
type BA = Intersection<B, A>; // BA = { gender: string; }
我们先来复习下 Extract<T, U>
,它的作用主要是从类型 T 中取可兼容类型 U 的类型
type A = Extract<string | number | boolean, boolean>; // A = boolean
// 源码
type Extract<T, U> = T extends U ? T : never;
推导下
type Intersection<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;
type Extract<T, U> = T extends U ? T : never;
type A = { name: string, age: number, gender: number };
type B = { gender: string };
type AB = Intersection<A, B>; =>
type AB = Pick<{ name: string, age: number, gender: number }, 'name'|'age'|'gender' extends 'gender' ? T : never & 'gender' extends 'name'|'age'|'gender' ? U : never>; =>
type AB = Pick<{ name: string, age: number, gender: number }, 'gender' & 'gender'>; =>
type AB = Pick<{ name: string, age: number, gender: number }, 'gender'>; =>
type AB = { gender: number };
简单总结下就是 Intersection<T, U>
求的交集保留 T
里面的。注意,这和 &
是不一样的:
type A = { name: string, age: number, gender: number };
type B = { gender: string };
type AB = A & B;
let a!: never;
const ab: AB = { name: '', age: 1, gender: a };
A
里面的 gender
和 B
里面的 gender
相交后得到的 gender
的值为 never
。
Overwrite
顾名思义,是用U的属性覆盖T的相同属性。
type SetDifference<A, B> = A extends B ? never : A;
type Diff<T extends object, U extends object> = Pick<T, SetDifference<keyof T, keyof U>>;
type Intersection<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;
type Overwrite<T extends object, U extends object, I = Diff<T, U> & Intersection<U, T>> = Pick<I, keyof I>;
type Old = { name: string; age: number; gender: number };
type New = { age: string; other: string };
// 从结果我们可以看出,只是覆盖了同名属性,并没有添加新属性。
type Res = Overwrite<Old, New>; // { name: string; age: string; gender: number; }
下面我们来推导下上面的结果:
type Res = Overwrite<Old, New>;
=>
type Res = Overwrite<Old, New, I = Diff<Old, NEW> & Intersection<NEW, Old>> = Pick<I, keyof I>;
=>
type Res = Overwrite<Old, New, I = Pick<Old, SetDifference<keyof Old, keyof New>> & Pick<New, Extract<keyof New, keyof Old> & Extract<keyof Old, keyof New>>> = Pick<I, keyof I>;
=>
type Res = Overwrite<Old, New, I = Pick<Old, SetDifference<'name'|'age'|'gender', 'age'|'other'>> & Pick<New, Extract<'age'|'other', 'name'|'age'|'gender'> & Extract<keyof 'name'|'age'|'gender', 'age'|'other'>>> = Pick<I, keyof I>;
=>
type Res = Overwrite<Old, New, I = Pick<Old, 'name'|'gender'> & Pick<New, 'age>> = Pick<I, keyof I>;
=>
type Res = Overwrite<Old, New, I = {
name: string;
gender: number;
} & {
age: string;
} = Pick<I, keyof I>;
=>
type Res = Overwrite<Old, New, I = {
name: string;
gender: number;
} & {
age: string;
} = Pick<{name: string, age: string, gender: number}, 'name'|'gender'|'age'>;
=>
type Res = { name: string; age: string; gender: number; }
如果说我们既想实现覆盖,又想实现添加,该怎么办呢?
type SetDifference<A, B> = A extends B ? never : A;
type Diff<T extends object, U extends object> = Pick<T, SetDifference<keyof T, keyof U>>;
type Intersection<T extends object, U extends object> = Pick<T, Extract<keyof T, keyof U> & Extract<keyof U, keyof T>>;
+ type Overwrite<T extends object, U extends object, I = Diff<T, U> & U> = Pick<I, keyof I>;
type Old = { name: string; age: number; gender: number };
type New = { age: string; other: string };
type Res = Overwrite<Old, New>; // {name: string; age: string; gender: number;other: string;}
Merge
Merge的作用是将两个对象的属性合并
type A = {
name: string,
age: number
};
type B = {
name: string,
gender: number
};
type Compute<T extends any> = T extends Function ? T : { [P in keyof T]: T[P] };
type R = Compute<{ a: '', b: '' }>; //R = { a: ''; b: ''; }
type Merge<A extends object, B extends object> = Compute<A & Omit<B, keyof A>>;
type R1 = Merge<A, B>;
/*
R1 = {
name: string;
age: number;
gender: number;
}
*/
Mutable
将 T 的所有属性的 readonly 移除
interface Person{
readonly name: string,
readonly age: number
}
type Mutable<T> = {
-readonly [P in keyof T]: T[P]
};
const person: Mutable<Person> = {
name: '',
age: 18
};
person.name = 'f';
person.age = 1;