typescript的fresh literal type的坑
Opened this issue · 0 comments
今天公司小伙伴分享Typescript实践时,提到的一个小坑就是对象字面量的赋值问题。本文结合规范谈谈关于fresh object literal
type的小坑。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return {
color: 'sss',
area: 10,
};
}
const obj: SquareConfig = { colour: 'red', width: 100 }; // 1.报错
let mySquare = createSquare({ colour: 'red', width: 100 }); // 2.报错
const obj2 = { colour: 'red', width: 100 }
createSquare(obj2); // 3.不报错
createSquare({ colour: 'red', width: 100 } as SquareConfig) // 4.不报错
上面这个case就是Ts中使用对象字面量常常碰到的一个坑。同一个对象为什么通过第一种和第二种的方式调用会报错,通过第三种和第四种的方式调用却不会报错呢。
其实上面的报错是Typescript进行assignment compatibility检测出来的。
Typescript实际存在着两种兼容性,子类型兼容性(subtype compatibility)和赋值兼容性(assignment
compatibility)。上例子中的兼容性正对应了赋值兼容性。
对于赋值语句和参数调用时都会触发赋值兼容性检测。
考虑赋值操作 T = S, S可以赋值给T的一个条件是 S相对于T并没有excess properties。
这里引入了excess properties的概念,Ts引入excess
properties的目的就在于对于对象字面量的赋值,比相对于普通的右值进行更加严格的检查,以防止用户的拼写或者添加了多余属性错误。
excess properties概念定义如下:
S相对于T存在 excess properties,当且仅当:
- S是一个fresh object literal type,
- S 有一个或多个T中不expect的属性
规范毕竟是规范,概念层出不穷,这里又引入了两个额外的概念
fresh object literal type 和expect
首先解释下expect,我们说一个属性P被类型T expect 当且仅当满足如下之一条件:
- T 不是对象(object),联合(union),或者交叉(Intersection)类型
- T 是对象类型且
1. T 存在和P同名的属性
2. T 存在string或者index signature 如 { [key:string]:string}
3. T 不存在属性
4. T 是全局的 Object - T是一个 union或者 intersection 类型且 P 是T组成type的expect 属性 (这里已经递归了,expect的判定是个递归算法)
对象字面量如 { colour: 'red', width: 100 }
的类型为 fresh object literal type
,我们可以通过widen或者assertion的方式将 fresh object literal type
的freshness擦除。看看这里又引入了widen和assertion的概念
widen的概念是Ts为了帮助用户做自动类型推断而引入的。
如
var name = "Steve"; // 自动推断name 为string
实际上Ts的类型推倒稍显复杂,你能猜到下面的类型推倒结果吗?
var name1 = 'steve'; // string
const name2 = 'hello'; // 'hello'
const a = null; // any
var b = null; // any
const c = undefined; // any
var d = undefined; // any
const obj = { a: 'hello', b: 2} // { a: string, b: number, c: any}
widen type的一条规则是所有undefined和null的地方都被推倒为any。具体的全部规则我也不知道啊。
讲到这里我们终于可以解决开始的题目了。
interface SquareConfig {
color?: string;
width?: number;
}
function createSquare(config: SquareConfig): { color: string; area: number } {
return {
color: 'sss',
area: 10,
};
}
const obj: SquareConfig = { colour: 'red', width: 100 }; // 1.fresh object type
let mySquare = createSquare({ colour: 'red', width: 100 }); // 2. fresh object type
const obj2 = { colour: 'red', width: 100 }; //3. widen form :{ colour: string, width: number}
const obj4 = 'string';
createSquare(obj2);
createSquare({ colour: 'red', width: 100 } as SquareConfig); // 4. assertion
上例中1和2都是属于S属于fresh object type且含有excess property即 colour
,3属于通过widen消除freshness,4属于通过assertion消除freshness 。
那么我们还有其他方法来解决1和2中的报错吗?
当然可以只要破坏 S相对于T存在excess property的两个必要条件之一即可
- S是一个fresh object literal type,
- S 有一个或多个T中不expect的属性
方法3和方法4都是破坏了条件1,我们也可以通过破坏条件2解决报错问题。即S中的属性都被T expect即可,回顾属性P被类型T
expect条件。我们任意满足四个条件之一即可。
const obj3: { [key: string]: any; color?: string; width?: number } = {
colour: 'red',
width: 100,
}; // index signature
const obj4: {} = {
colour: 'red',
width: 100,
}; // has no properties
const obj5: Object = {
colour: 'red',
width: 100,
}; // global Object