【问题标题】:Typescript pick up one given interface打字稿选择一个给定的界面
【发布时间】:2021-12-24 12:17:24
【问题描述】:

我想创建一个类似<alert id="component" /> 的 React 警报组件,还有一个我想从"info", "success", "warning", "error" 中选择一个道具,以便显示不同的消息颜色,该组件类似于<alert id="component" info/> 或@ 987654324@

使用 Typescript,如何使界面在给定的四个选项中只有一个值?

我现在做的是:

type MessageStringType = "info" | "success" | "warning" | "error";

type MessageTypes = {
  [K in MessageStringType]: boolean;
};

type oneMessageTypes = Partial<MessageTypes>;

interface Props extends oneMessageTypes {
  id: string;
}

但是这个允许来自"info", "success", "warning", "error" 的空值,我怎样才能使类型检查至少需要一个值? 就像:

<Component id="abc" info />           //correct
<Component id="abc" success />        //correct
<Component id="abc" />                //wrong
<Component id="abc" info success />   //wrong

【问题讨论】:

  • 为什么不能直接说interface Props { id: MessageStringType; }
  • 不...组件设计不像{ messageType: one of messageType value},他们更喜欢直接使用infosuccess作为道具而不是messageType
  • 虽然,我明白你想要什么,它只是看起来不错(老实说,道具类型 def 会令人费解)。除了装饰目的之外,这对用户来说是困难的。更好的设计,IMO,应该是&lt;Component variant={"info" | "success" | ...} /&gt;,因为用户可以这样做:&lt;Component variant={isSuccess ? "success" : "error"} .../&gt;

标签: reactjs typescript


【解决方案1】:

这里有两块。

首先,如果您对其进行硬编码,您需要找出适用于此的类型。 “必须只有一个”的另一种说法是“一个必须有一个值,所有其他的都必须缺失或未定义”。

type Test =
  | { a: string, b?: void }
  | { a?: void, b: string }

const a: Test = { a: 'asd' }
const b: Test = { b: 'asd' }
const c: Test = {} // error
const d: Test = { a: 'asd', b: 'asd' } // error

这将创建一个联合,其中所有键都为联合所知,但联合的每个成员只允许其中一个键具有值。

这将是您的道具类型的基础。

Playground


后半部分是从字符串的联合中生成该类型。这更棘手。

您需要将该联合分配到一个新类型中。您可以为源联合的每个成员获得一个新成员。并且有一个奇怪的技巧,那就是使用条件类型。 (见this answer)。

使用它我们可以创建一个这样的类型:

type UnionToBooleanProps<T extends string, U = T> =
    T extends string ?
        { [otherKeys in Exclude<U, T> & string]?: void } &
        { [key in T]: boolean }
    : never ;

让我们来看看这个。

type UnionToBooleanProps<T extends string, U = T> =

此类型接受一个泛型类型参数T,用于为每个成员命名所需属性的字符串联合。然后它将U 泛型参数分配给相同的类型。

T extends string ?

这会启动一个条件类型,以确保我们最终得到一个联合,而不是具有联合值的对象类型。

{ [otherKeys in Exclude<U, T> & string]?: void } &

这声明了所有不能有值的键。它映射每个键,并且对于与此联合成员的键不匹配的每个键(这是Exclude 所做的),我们声明它不能有值。

{ [key in T]: boolean }

然后与 this 相交,它添加回上一行未排除的一个键,并声明它的值必须为 boolean

: never

最后一行是永远不会使用的条件类型的假分支。我们只需要它来使条件类型在语法上有效。

有了它,一切正常!

import React from 'react'

type UnionToBooleanProps<T extends string, U = T> =
    T extends string ?
        { [otherKeys in Exclude<U, T> & string]?: void } &
        { [key in T]: boolean }
    : never ;


type MessageStringType = "info" | "success" | "warning" | "error";

type OneMessageTypes = UnionToBooleanProps<MessageStringType>

type Props = OneMessageTypes & {
  id: string;
}

function Component(props: Props) {
    return <></>
}

const a = <Component id="abc" info />           //correct
const b = <Component id="abc" success />        //correct
const c = <Component id="abc" />                //wrong
const d = <Component id="abc" info success />   //wrong

Playground


话虽如此,@Nishant 的评论可能是正确的。对于这个组件的使用者来说,仅仅拥有一个{ type: MessageStringType } 可能是一个更好的 API。

【讨论】:

  • 我 100% 同意使用 { type: MessageStringType },这也是 MUI 所做的。但是,这个最初的设计不是我的……我只是满足他们的要求……
  • 我可以用另一个泛型替换Exclude 中的MessageStringType 吗?
  • 当然,这行告诉它要使用哪个联合字符串:type OneMessageTypes = UnionToBooleanProps&lt;MessageStringType&gt;MessageStringType 替换为您想要的任何字符串联合。
  • 我可以使用UnionToBooleanProps&lt;MessageStringType, MessageStringType2&gt;,然后使用type UnionToBooleanProps&lt;T extends string, K extends string&gt;[otherKeys in Exclude&lt;K, T&gt;]?: void,这样Exclude 中的K 是另一个泛型。这行得通吗?
  • 哦,我误会了,是的,我的答案并不是真正通用的,因为它对联合类型进行了硬编码。不过这应该可行:tsplay.dev/WoJJlm 我会更新我的答案
猜你喜欢
  • 2018-02-04
  • 1970-01-01
  • 2018-08-05
  • 1970-01-01
  • 2016-06-21
  • 2021-09-26
  • 2016-10-07
  • 1970-01-01
  • 2021-01-29
相关资源
最近更新 更多