在以下示例中,getValue function
无法获得正确的类型。
有什么方法可以通过可配置的设置来推断类型吗?
Example Play
例如
class User {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Dog {
name: string;
constructor(name: string) {
this.name = name;
}
}
class Cat {
name: string;
constructor(name: string) {
this.name = name;
}
}
const settings = [
{ key: 'a', value: 'string' },
{ key: 'b', value: 123 },
{ key: 'c', value: true },
{ key: 'dog', value: Dog },
{ key: 'cat', value: Cat },
{ key: Dog, value: Cat },
{ key: Cat, value: Dog },
{ key: User, value: User },
]
function getValue(key: any) {
const item = settings.find(obj => obj.key === key);
if (item) {
const { value } = item
if (typeof value === 'function') {
return new value('test value');
} else {
return value;
}
} else {
throw new Error('not found');
}
}
当然,我可以使用as Dog
强制转换类型。
const dog = getValue('dog') as Dog;
但是我觉得有点多余。
有没有更好的方法来解决这个问题?
-编辑2020-10-20 ---
实际上,settings
不是预先定义的。
可以在运行时中对其进行修改。
最后,我想实现一个像角度依赖注入函数之类的函数。
settings's
结构如下:
[
{provide: User, useValue: new User('Tom')},
{provide: Cat, useClass: Cat},
{provide: Dog, useClass: Dog},
{provide: AnotherDog, useExiting: Dog},
{provide: AnotherCat, useFactory: function(user) { return user.cat; }, deps: [User]},
]
settings
是未预定义的。可以在运行时中对其进行修改。
例如
let settings = [
{ key: Cat, value: Cat },
{ key: Dog, value: Dog },
]
const cat1 = getValue(Cat); // cat1 is Cat
const dog1 = getValue(Dog); // dog1 is Dog
settings = [
{ key: Cat, value: Dog },
{ key: Dog, value: Cat },
]
const cat2 = getValue(Cat); // cat2 is Dog
const dog2 = getValue(Dog); // dog2 is Cat
这就是我的意思,可以在运行时中修改设置。
例如2
let settings = [
{ key: Cat, value: Cat },
{ key: Dog, value: Dog },
]
// cat1 is Cat, getValue return type default as Parameter Cat
const cat1 = getValue(Cat);
// dog1 is Dog, getValue return type default as Parameter Dog
const dog1 = getValue(Dog);
settings = [
{ key: Cat, value: Dog },
{ key: Dog, value: Cat },
]
// cat2 is Dog, getValue return type is set by generic type Dog
const cat2 = getValue<Dog>(Cat);
// dog2 is Cat, getValue return type is set by generic type Cat
const dog2 = getValue<Cat>(Dog);
可以通过可选的泛型类型像这样实现getValue函数吗?
答案 0 :(得分:1)
在您的示例中,User
,Cat
和Dog
是structurally identical,因此TypeScript将它们视为同一类型。这可能是不可取的,所以我将给他们一些不同的结构:
class User {
name: string;
password: string = "hunter2";
constructor(name: string) {
this.name = name;
}
}
class Dog {
name: string;
bark() {
}
constructor(name: string) {
this.name = name;
}
}
class Cat {
name: string;
meow() {
}
constructor(name: string) {
this.name = name;
}
}
现在,编译器可以区分它们。
我在这里的方法是使getValue()
成为一个generic函数,其返回值为conditional type:
type Settings = typeof settings[number];
type SettingsValue<K extends Settings["key"]> = Extract<Settings, { key: K }>["value"];
type InstanceIfCtor<T> = T extends new (...args: any) => infer R ? R : T;
type SettingsOutputValue<K extends Settings["key"]> = InstanceIfCtor<SettingsValue<K>>;
declare function getValue<K extends Settings["key"]>(key: K): SettingsOutputValue<K>;
Settings
类型只是{key: K, value: V}
中所有settings
元素类型的并集。然后,SettingsValue<K>
取K
的{{1}}类型之一的key
,并返回对应的Settings
类型。
类型value
采用类型InstanceIfCtor<T>
,如果T
是构造函数类型,则返回其对应的实例类型;否则返回T
。
然后T
首先获得值类型SettingsOutputValue<K>
,然后对其求值SettingsValue<K>
以找到InstanceIfCtor<>
应该返回的类型。如果getValue()
不是K
的构造函数之一,则返回类型仅为SettingsValue<K>
。否则,如果SettingsValue<K>
是构造函数,则SettingsValue<K>
是该构造函数的实例类型。
实际上无法以编译器验证为安全的方式实施SettingsOutputValue<K>
,因为它无法遵循将getValue()
转换为K
所需的高阶推理。未指定的SettingsOutputValue<K>
。通常,我只是作为单个调用签名overload来执行此操作,以放松类型检查,请记住,我需要注意我的实现确实在执行调用签名所说的操作:
K
好的,让我们对其进行测试:
function getValue<K extends Settings["key"]>(key: K): SettingsOutputValue<K>;
function getValue(key: Settings["key"]) {
const item = settings.find(obj => obj.key === key);
if (item) {
const { value } = item
if (typeof value === 'function') {
return new value('test value');
} else {
return value;
}
} else {
throw new Error('not found');
}
}
看起来不错。分别推断const dog = getValue('dog'); // Dog
dog.bark();
const cat = getValue('cat'); // Cat
cat.meow();
const user = getValue(User); // User
user.password.toUpperCase();
,dog
和cat
的类型为user
,Dog
和Cat
。
答案 1 :(得分:0)
const settings = [
{ key: 'a', value: 'string' },
{ key: 'b', value: 123 },
{ key: 'c', value: true },
{ key: 'dog', value: Dog },
{ key: 'cat', value: Cat },
{ key: Dog, value: Cat },
{ key: Cat, value: Dog },
{ key: User, value: User },
] as const;
type SettingsKey = typeof settings[number]['key'];
function getValue(key: SettingsKey) {
const item = settings.find(obj => obj.key === key);
if (item) {
const { value } = item
if (typeof value === 'function') {
// const value: new (name: string) => User | Dog | Cat
return new value('test value');
} else {
return value;
}
} else {
throw new Error('not found');
}
}