在下面的代码片段中,是否证明了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)请参见操场链接here。
我错误地认为应该防止这种情况发生吗?如果不是,除了简单地避免突变(也许使用readonly来强制执行此实践)之外,还有其他方法吗?
编辑
修改了示例,以避免强制转换。
发布于 2021-10-29 10:48:17
不应在中使用as Animal
const animal: Animal = {
name: 'cat',
noise: 'meow',
} as Animal它没有任何意义,会使您代码变得unsafe。
请参阅docs
类型断言是一种告诉编译器“相信我,我知道我在做什么”的方法。类型断言类似于其他语言中的类型转换,但它不执行特殊的数据检查或重新构造。它没有运行时影响,仅供编译器使用。TypeScript假定您(程序员)已经执行了所需的任何特殊检查。
一旦您编写了as Animal,TypeScript就假定任何Animal都可以分配给相应的属性:
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"这就是为什么类型断言是不安全的,只有在没有其他选择的情况下才应该考虑。
突变在typescript中是有点不安全的操作,因为除了一个case之外,TS不会跟踪它们。
你可以在my article中找到更多关于typescript突变的信息
TL;DR
只是试图避免typescript中的突变
与typescript中的突变相关的答案列表
How to selectively assign from one Partial to another in typescript,
Why can I index by string to get a property value but not to set it?
TypeScript: Why can't I assign a valid field of an object with type { a: "a", b: "b" }
Why does Typescript say this variable is "referenced directly or indirectly in its own initializer"?
https://github.com/microsoft/TypeScript/pull/30769
Cannot dynamically set interface object's property values
Convert Object.entries reduce to Generics type
摘要主要问题是对象可变属性是协变的。COnsider此示例是从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。为了解决这个问题,只需对COvariance类型使用readonly标志:
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) // okPlayground,你可以把它变成逆变量。看起来不太好,但很管用。
您可以使用eslint插件来强制执行readonly标志
https://stackoverflow.com/questions/69767493
复制相似问题