繁体   English   中英

带有设置的打字稿类型推断

[英]typescript type infer with settings

在以下示例中, getValue function无法获取正确的类型。
有没有办法通过可配置的设置来推断类型?
示例播放

例如

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 函数可以通过可选的泛型类型来实现吗?

在您的示例中, UserCatDog结构上是相同的,因此 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()成为一个通用函数,其返回值是条件类型

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 ,它是Settingskey类型之一,并返回相应的value类型。

类型InstanceIfCtor<T>接受类型T ,如果T是构造函数类型,则返回其对应的实例类型; 否则返回T

并且SettingsOutputValue<K>首先获取值类型SettingsValue<K> ,然后评估它的InstanceIfCtor<>以找到getValue()应返回的类型。 如果KSettingsValue<K>不是构造函数的类型之一,则返回类型只是SettingsValue<K> 否则,如果SettingsValue<K>构造函数,则SettingsOutputValue<K>是该构造函数的实例类型。


实现getValue()不能真正以编译器验证为安全的方式完成,因为它无法遵循将K转换为未指定K SettingsOutputValue<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();

看起来挺好的。 dogcatuser变量被推断为分别属于DogCatUser类型。


Playground 链接到代码

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');
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM