英文:
Why Typescript doesn't complain about this?
问题
我很困惑,当我在useState
hook中将超集类型分配给其子集类型时,为什么 TypeScript 不会抱怨类型不匹配?如果这是有意的,应该如何为useState
hook 添加类型以使其正确报错?
interface Animal {
name: string;
food: string;
legs: number;
}
interface Dog {
name: string;
food: string;
}
const animal: Animal = {
name: 'animal',
food: 'animal food',
legs: 4,
};
function App() {
const [data, setData] = useState<Dog>(animal);
...
}
英文:
I'm so confused, when I assign a superset type to their subset type in the useState
hook, why would Typescript not complain about mismatched type? And if it's intended, how should I type the useState
hook to make it complain properly?
`
interface Animal {
name: string;
food: string;
legs: number;
}
interface Dog {
name: string;
food: string;
}
const animal: Animal = {
name: 'animal',
food: 'animal food',
legs: 4,
};
function App() {
const [data, setData] = useState<Dog>(animal);
...
}
答案1
得分: 3
静态类型检查器可以在比较类型时使用类型的名称(名义类型)或结构(结构类型),例如在检查一个类型是否是另一个类型的子类型时。
TypeScript 使用结构类型系统。
详见类型兼容性。
TypeScript中的类型兼容性基于结构子类型。结构类型是一种仅基于它们的成员关系来关联类型的方式。这与名义类型形成对比。
TypeScript结构类型系统的基本规则是:如果
y
至少具有与x
相同的成员,则x
与y
兼容。
对比名义类型。
名义类型意味着仅当它们的声明命名相同类型时,两个变量才是类型兼容的。例如,在C语言中,同一翻译单元中具有不同名称的两个结构类型永远不会被视为兼容,即使它们具有相同的字段声明。
像C++、Java和Swift等语言主要采用名义类型系统。
// 伪代码:名义系统
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // 错误!
像Go、TypeScript和Elm等语言主要采用结构类型系统。
// 伪代码:结构系统
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // 成功!
一种在TypeScript中声明名义类型的方式:
interface Animal {
name: string;
food: string;
legs: number;
}
interface Dog {
name: string;
food: string;
}
type Brand<T, K> = T & { __brand: K }
type AnimalNominal = Brand<Animal, 'animal'>;
type DogNominal = Brand<Dog, 'dog'>;
const animal: AnimalNominal = {
name: 'animal',
food: 'animal food',
legs: 4,
__brand: 'animal'
};
const dog: DogNominal = {
name: 'hot dog',
food: 'dog food',
__brand: 'dog'
}
const s1: AnimalNominal = animal // 可行
const s2: AnimalNominal = dog; // 错误
const s3: DogNominal = animal; // 错误
const s4: DogNominal = dog; // 可行
名义类型提案:https://github.com/Microsoft/Typescript/issues/202
utility-types
包中的Brand<T, U>
类型。
基于类型T定义U的名义类型
英文:
A static type checker can use either the name (nominal typing) or the structure (structural typing) of types when comparing them against other types (like when checking if one is a subtype
of another).
TypeScript uses a structure typing system.
See Type Compatibility.
> Type compatibility in TypeScript is based on structural subtyping. Structural typing is a way of relating types based solely on their members. This is in contrast with nominal typing.
> The basic rule for TypeScript’s structural type system is that x
is compatible with y
if y
has at least the same members as x
V.S. Nominal typing
> Nominal typing means that two variables are type-compatible if and only if their declarations name the same type. For example, in C, two struct types with different names in the same translation unit are never considered compatible, even if they have identical field declarations.
Languages like C++, Java, and Swift have primarily nominal type systems.
// Pseudo code: nominal system
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // Error!
Languages like Go, TypeScript and Elm have primarily structural type systems
// Pseudo code: structural system
class Foo { method(input: string) { /* ... */ } }
class Bar { method(input: string) { /* ... */ } }
let foo: Foo = new Bar(); // Works!
One way to declare nominal typing in TypeScript:
interface Animal {
name: string;
food: string;
legs: number;
}
interface Dog {
name: string;
food: string;
}
type Brand<T, K> = T & { __brand: K }
type AnimalNominal = Brand<Animal, 'animal'>;
type DogNominal = Brand<Dog, 'dog'>
const animal: AnimalNominal = {
name: 'animal',
food: 'animal food',
legs: 4,
__brand: 'animal'
};
const dog: DogNominal = {
name: 'hot dog',
food: 'dog food',
__brand: 'dog'
}
const s1: AnimalNominal = animal // ok
const s2: AnimalNominal = dog; // error
const s3: DogNominal = animal; // error
const s4: DogNominal = dog; // ok
Nominal typing proposal: https://github.com/Microsoft/Typescript/issues/202
The Brand<T, U>
type of utility-types
package.
> Define nominal type of U based on type of T
答案2
得分: 0
要使赋值有效,必须符合类型兼容性规则。
TypeScript允许将超集类型分配给子集类型,因为超集类型包含子集类型所需的所有属性和值。
如果您希望TypeScript正确地抱怨不匹配的类型,您可以显式地为useState()
钩子指定所需的类型。在这种情况下,您会希望将Animal指定为useState()
钩子的类型,因为它与您正在初始化的animal对象匹配。
const [data, setData] = useState<Animal>(animal);
请记住,TypeScript类型检查在编译时静态执行。
英文:
the assignment to be valid due to type compatibility rules.
TypeScript allows assigning a superset type to a subset type because the superset type contains all the properties and values required by the subset type.
If you want TypeScript to properly complain about the mismatched type, you can explicitly type the useState()
hook with the desired type. In this case, you would want to specify Animal as the type for the useState()
hook, as it matches the animal object you are initializing it with.
const [data, setData] = useState<Animal>(animal);
Remember that TypeScript type checks are performed statically at compile time,
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论