为什么函数重载可以解决类型缩小问题?
Why does function overload fix type-narrowing issue?
我遇到了一个问题,它基本上涉及缩小从一个对象中编辑的类型 return,通过它的键访问。假设我有一个对象,其值可以是 number
或 string
:
type Value = number | string;
enum DatumKey {
numberDatum = 'numberDatum',
stringDatum = 'stringDatum',
}
class DemoClass {
private data = {
[DatumKey.numberDatum]: 1337,
[DatumKey.stringDatum]: 'foobar',
}
public getValue(key: DatumKey): Value {
return this.data[key];
}
public doSomething(): void {
const num: number = this.getValue(DatumKey.numberDatum); // TS expects `number | string`, I expect `number`
const str: string = this.getValue(DatumKey.stringDatum); // TS expects `number | string`, I expect `string`
console.log({ num, str });
}
}
const dc = new DemoClass();
dc.doSomething();
See Typescript Playground example.
方法失败
所以,为了缩小类型,我尝试使用通用类型,然后我可以告诉 getValue<...>()
我在 return 中期望什么样的类型:
public getValue<T extends Value>(key: DatumKey): T {
// Error here: Type 'Value' not assignable to type 'T'
return this.data[key];
}
public doSomething(): void {
const num: number = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
const str: string = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}
但是,通过这样做,我从 TypeScript 中得到一个错误:
Type 'Value' is not assignable to type 'T'.
'Value' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
Type 'string' is not assignable to type 'T'.
'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
See Typescript Playground example.
工作方式
然后我在这里遇到了一个类似的问题,。确实如此。但是,我不确定为什么函数重载可以解决问题:
public getValue<T extends Value>(key: DatumKey): T
public getValue(key: DatumKey): Value {
return this.data[key];
}
public doSomething(): void {
const num = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
const str = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}
您的工作解决方案并不真正有效。变量 num
和 str
的类型分别为 Value
而不是 number
和 string
.
更强大的解决方案:
enum DatumKey {
numberDatum = 'numberDatum',
stringDatum = 'stringDatum',
}
const data = {
[DatumKey.numberDatum]: 1337,
[DatumKey.stringDatum]: 'foobar',
};
function getValue<T extends keyof typeof data>(key: T): (typeof data)[T] {
return data[key];
}
// You don't have to explicitly type num and str. They are automatically inferred
const num = getValue(DatumKey.numberDatum);
const str = getValue(DatumKey.stringDatum);
我遇到了一个问题,它基本上涉及缩小从一个对象中编辑的类型 return,通过它的键访问。假设我有一个对象,其值可以是 number
或 string
:
type Value = number | string;
enum DatumKey {
numberDatum = 'numberDatum',
stringDatum = 'stringDatum',
}
class DemoClass {
private data = {
[DatumKey.numberDatum]: 1337,
[DatumKey.stringDatum]: 'foobar',
}
public getValue(key: DatumKey): Value {
return this.data[key];
}
public doSomething(): void {
const num: number = this.getValue(DatumKey.numberDatum); // TS expects `number | string`, I expect `number`
const str: string = this.getValue(DatumKey.stringDatum); // TS expects `number | string`, I expect `string`
console.log({ num, str });
}
}
const dc = new DemoClass();
dc.doSomething();
See Typescript Playground example.
方法失败
所以,为了缩小类型,我尝试使用通用类型,然后我可以告诉 getValue<...>()
我在 return 中期望什么样的类型:
public getValue<T extends Value>(key: DatumKey): T {
// Error here: Type 'Value' not assignable to type 'T'
return this.data[key];
}
public doSomething(): void {
const num: number = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
const str: string = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}
但是,通过这样做,我从 TypeScript 中得到一个错误:
Type 'Value' is not assignable to type 'T'.
'Value' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
Type 'string' is not assignable to type 'T'.
'string' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint '{}'.
See Typescript Playground example.
工作方式
然后我在这里遇到了一个类似的问题,
public getValue<T extends Value>(key: DatumKey): T
public getValue(key: DatumKey): Value {
return this.data[key];
}
public doSomething(): void {
const num = this.getValue<number>(DatumKey.numberDatum); // TS expects `number`, it's good!
const str = this.getValue<string>(DatumKey.stringDatum); // TS expects `string`, it's good!
}
您的工作解决方案并不真正有效。变量 num
和 str
的类型分别为 Value
而不是 number
和 string
.
更强大的解决方案:
enum DatumKey {
numberDatum = 'numberDatum',
stringDatum = 'stringDatum',
}
const data = {
[DatumKey.numberDatum]: 1337,
[DatumKey.stringDatum]: 'foobar',
};
function getValue<T extends keyof typeof data>(key: T): (typeof data)[T] {
return data[key];
}
// You don't have to explicitly type num and str. They are automatically inferred
const num = getValue(DatumKey.numberDatum);
const str = getValue(DatumKey.stringDatum);