【问题标题】:Typescript, automatically infer type from object keys打字稿,自动从对象键推断类型
【发布时间】:2021-08-29 00:51:59
【问题描述】:

我想为 Select 组件声明一个接口,该接口可以是多项选择或单项选择。

interface MySelect<T extends boolean> {
    multi: T, // Is this a multiple items select
    onChange: (item: T extends true ? string[]: string) => void // the onChange signature differs according to T
}

它有效,但我必须明确设置泛型类型 T:

const mySelect: MySelect<true> = { // Here
    multi: true, // And here
    onChange: (items) => {}
}

我想知道是否可以让 TS 自动从“multi”值推断 T:

const mySelect: MySelect = {
    multi: true, // multi is true so T is true
    onChange: (items) => {}
}

重要更新:我希望“multiple”是可选的(如果 key 丢失或未定义,它将默认为 false)

【问题讨论】:

标签: typescript typescript-generics


【解决方案1】:

您已更新您的问题,说 multi 应该是可选的(默认为 false)。这排除了有区别的联合(下面水平线下的先前答案)。

我想我会使用你联合在一起的两个接口,以及(如有必要)它们共同拥有的东西的基本接口。当您需要知道选择的类型时,您可能需要类型保护函数。

// Things all MySelects have in common (if you have anything other than `onChange`)
interface MySelectBase {
    name: string;
}
// A single-select version of MySelect
interface MySingleSelect extends MySelectBase {
    multi?: false;
    onChange: (item: string) => void;
}
// A multi-select version of MySelect
interface MyMultiSelect extends MySelectBase {
    multi: true;
    onChange: (items: string[]) => void;
}
// The unified type
type MySelect = MySingleSelect | MyMultiSelect;

// Type guard function to see whether it's a single select
const isSingleSelect = (select: MySelect): select is MySingleSelect => {
    return !select.multi; // !undefined and !false are both true
};

// Type guard function to see whether it's a multi select
const isMultiSelect = (select: MySelect): select is MyMultiSelect => {
    return !!select.multi; // !!undefined and !!true are both true
};

创建示例:

const single: MySingleSelect = {
    name: "some-single-select-field",
    onChange : (item) => { console.log(item); }
};

const multi: MyMultiSelect = {
    multi: true,
    name: "some-multi-select-field",
    onChange : (items) => { console.log(items); }
};

MySelect(组合接口)使用示例:

const useMySelect = (select: MySelect) => {
    // No need for a guard on anything but `onChange`
    console.log(select.name);
    // `onChange` will be a union type until/unless you use a type guard
    const onChange = select.onChange;
    //    ^^^^^^^^−−−−−−−−−− type is `((item: string) => void) | ((items: string[]) => void)`
    if (isSingleSelect(select)) {
        // It's a MySingleSelect
        const onChange = select.onChange;
        //    ^^^^^^^^−−−−−−−−−− type is `(item: string) => void`
    } else {
        // It's a MyMultiSelect
        const onChange = select.onChange;
        //    ^^^^^^^^−−−−−−−−−− type is `(items: string[]) => void`
    }
};

Playground link


对于那些不需要将multi 设为可选的人来说,这是原始答案:

您可以通过将 MySelect 声明为类型的联合来做到这一点,其中一个与 multi: true,另一个与 multi: false

type MySelect =
    {
        multi: true;
        onChange: (items: string[]) => void;
    }
    |
    {
        multi: false;
        onChange: (item: string) => void;
    };

然后你得到:

const mySelect: MySelect = {
    multi: true,
    onChange: (items) => {}
//  ^^^^^^^^−−−−−−−−−−− correctly inferred as (items: string[]) => void
};

Playground link

这称为discriminated union:由一个(或多个)字段的类型区分(区分)的类型的联合。

如果您有大量没有变化的其他属性,您可以使用交集将它们添加到可区分联合:

type MySelect =
    (
        {
            multi: true;
            onChange: (items: string[]) => void;
        }
        |
        {
            multi: false;
            onChange: (item: string) => void;
        }
    )
    &
    {
        the:        number;
        other:      string;
        properties: string;
    };

【讨论】:

  • 在这里,在我的博客catchts.com/callbacks#callback_in_union%202 中,您可以找到如何以更通用的方式制作分布式联合类型
  • 抱歉忘了说 multiple 是一个可选参数(默认为 false)。在这种情况下,受歧视的工会似乎达不到要求link
  • @htulipe - 这同时是一件很容易忘记提及的事情,也是一件非常重要的事情不要忘记提及。 ? 我已经更新了答案。
【解决方案2】:

您可以使用通用标识函数来做到这一点:

function helper<T extends boolean>(obj: MySelect<T>): MySelect<T> {
    return obj;
}

// MySelect<true> inferred
const mySelect = helper({
    multi: true,
    onChange: (items) => {}
});

Playground Link

也就是说,当您的类型参数没有一组有限的不同选项时,这种技术会更有用;在您的情况下,T extends boolean 可能更好地实现为 T.J. 中的标记联合类型。克劳德的回答,没有泛型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-06-08
    • 2020-04-02
    • 2018-09-19
    • 1970-01-01
    • 2019-10-27
    • 2018-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多