typescript - Typescript 是否允许破坏类型安全的突变?
问题描述
在下面的代码片段中,是否证明 Typescript 不能以违反其类型约束的方式警告严格类型对象的突变?
type Animal = {
name: 'cat'
noise: 'meow'
} | {
name: 'dog'
noise: 'woof'
};
const f = (animal: Animal) => {
// Why is this allowed? Typescript still considers this to be of type 'Animal'
// after this mutation, even though its value no longer assignable to any variant
// of Animal.
animal.noise = 'woof';
// ❌ - Typescript failed to update animal's type when we mutated it, so now
// it mistakenly continues to believe it is assignable to Animal everywhere!
// Now its incorrectly typed data can spread throughout the application,
// breaking type safety, potentially everywhere...
const anotherAnimal: Animal = animal;
// ✅ - Typescript correctly prevents us from assigning this combination of fields
// to type Animal. But this is exactly the value we mutated to in our first example,
// so why were we allowed to do that when the outcome is the same, invalid result?
const animalMatchingPostMutationValue: Animal = {
name: 'cat',
noise: 'woof'
}
}
const animal: Animal = {
name: 'cat',
noise: 'meow',
}
f(animal)
在此处查看操场链接。
我认为应该防止这种情况是错误的吗?如果没有,除了简单地避免突变(也许readonly
用来强制执行这种做法)之外,还有其他方法吗?
编辑
重做示例以避免强制转换。
解决方案
你不应该as Animal
使用
const animal: Animal = {
name: 'cat',
noise: 'meow',
} as Animal
它没有意义,并使您的代码unsafe
。
请参阅文档
类型断言是告诉编译器“相信我,我知道我在做什么”的一种方式。类型断言类似于其他语言中的类型转换,但它不执行数据的特殊检查或重组。它没有运行时影响,仅由编译器使用。TypeScript 假定您,程序员,已经执行了您需要的任何特殊检查。
一旦你编写as Animal
了 ,TypeScript 假定 anyAnimal
可以分配给适当的属性:
const animal: Animal = {
name: 'cat',
noise: 'meow',
} as Animal
animal.name // "cat" | "dog"
animal.noise // "meow" | "woof"
它不再能够区分它是否是联合的一部分。
type Name = Animal['name'] // "cat" | "dog"
type Noise = Animal['noise'] // "meow" | "woof"
这就是类型断言不安全的原因,只有在没有其他选择时才应考虑。
突变在打字稿中是有点不安全的操作,因为 TS 除了一种情况外不会跟踪它们。
您可以在我的文章中找到有关 typescript 突变的更多信息:
TL;博士
尽量避免打字稿中的突变
与打字稿中的突变相关的答案列表
TypeScript:为什么我不能分配类型为 { a: "a", b: "b" } 的对象的有效字段
为什么 Typescript 说这个变量是“在它自己的初始化程序中直接或间接引用的”?
https://github.com/microsoft/TypeScript/pull/30769
将 Object.entries reduce 转换为泛型类型
总结 主要问题是对象可变属性是协变的。考虑这个从Titian-Cernicova-Dragomir 'stalk偷来的例子:
type Type = {
name: string
}
type SubTypeA = Type & {
salary: string
}
type SubTypeB = Type & {
car: boolean
}
let employee: SubTypeA = {
name: 'John Doe',
salary: '1000$'
}
let human: Type = {
name: 'Morgan Freeman'
}
let student: SubTypeB = {
name: 'Will',
car: true
}
// same direction
type Covariance<T> = {
box: T
}
let employeeInBox: Covariance<SubTypeA> = {
box: employee
}
let humanInBox: Covariance<Type> = {
box: human
}
/**
* MUTATION
*/
let test: Covariance<Type> = employeeInBox
test.box = student // mutation of employeeInBox
const result_0 = employeeInBox.box.salary // while result_0 is undefined, it is infered a a string
console.log({ result_0 })
result_0
是undefined
而 TS 认为它是string
. 为了修复它,只需使用readonly
标志作为COvariance
类型:
type Covariance<T> = {
readonly box: T
}
现在它失败了。
解决方法,没有readonly
type Cat = {
name: 'cat'
noise: 'meow'
}
type Dog = {
name: 'dog'
noise: 'woof'
};
type Animal<Name, Noise> = { name: Name, noise: Noise }
const f = <
CatName extends Cat['name'],
CatNoise extends Cat['noise'],
DogName extends Dog['name'],
DogNoise extends Dog['noise'],
Pet extends Animal<CatName, CatNoise> | Animal<DogName, DogNoise>
>(animal: Pet) => {
animal.noise = 'woof'; // error
const anotherAnimal: Pet = animal;
// error
const animalMatchingPostMutationValue: Animal = {
name: 'cat',
noise: 'woof'
}
}
const animal: Animal = {
name: 'cat',
noise: 'meow',
}
f(animal) // ok
Playground 你可以让它逆变。看起来不太好,但很有效。
您可以使用 eslint 插件来强制执行readonly
标志
推荐阅读
- php - Need to insert in products table from more than one tables with dynamic user id
- selenium - 如何在 xpath 表达式中添加 OR 逻辑?
- c# - Oauth2 使用 ASP .net 核心 MVC 登录到 Google api
- javascript - Show all DOM nodes in html div
- sql - SequoiaDB查询时如何选择索引扫描或表扫描?
- javascript - JavaScript 与 Node.js
- c# - 在VS2017中使用mingW编译的dll
- flutter - Flutter Scoped Model - Passing multiple Models
- css - Background image blurry on small screens (looks fine in Wordpress/Chrome editors, but on actual phone it's blurry
- php - 使用 GRAPHQL 调用 API 的 PHP cURL