【问题标题】:Switching between two different prop types in one React component在一个 React 组件中切换两种不同的 props 类型
【发布时间】:2021-11-02 07:39:06
【问题描述】:

TL;DR:Here's the TS playground.

我在使用 React 组件道具类型定义时遇到了一些问题。我在 npm 上发布了一个组件,该组件目前采用一组简单的 props,主要是布尔值和字符串,其中一个 props 类型为 RG 和一些基于 RG 类型的回调函数。

我想要提供的是相同的组件,但带有一个可选的boolean 属性,当true 时,“翻转”组件以使用新的RGIC 类型而不是RG。假设这是当前的Props 接口:

interface Props {
  x: string;
  y: string;
  rg?: RG;
  func(rg: RG): void;
}

我想用这个接口提供一个组件的重载:

interface PropsIC {
  x: string;
  y: string;
  rg?: RGIC;
  func(rg: RGIC): void;
}

保持向后兼容性很重要,因此新道具在外部 API 中必须是可选的。

这些是我想出的类型:

type RGAny = RG | RGIC;

interface CommonProps {
  x: string;
  y: string;
  flag: boolean; // <-- this is the new prop
}
interface PropsStandard extends CommonProps {
  flag: false;
  query?: RG;
  func(q: RG): void;
}
interface PropsIC extends CommonProps {
  flag: true;
  query?: RGIC;
  func(q: RGIC): void;
}

type Props = (Omit<PropsStandard, 'flag'> & { flag?: boolean }) | (Omit<PropsIC, 'flag'> & { flag?: boolean });

type PropsImpl = PropsStandard | PropsIC;

这就是我使用它们的方式:

注意:我确信上面的类型可以使用一些改进,所以请随意评论这些,但下面的代码块是我的问题所在。

// This is the exported component, the API surface
export function Component(props: Props) {
  if (props.flag) {
    return ComponentImpl({ ...props, flag: true } as PropsIC)
  }
  return ComponentImpl({ ...props, flag: false } as PropsStandard);
}

// This is the component's internal implementation with two overloads,
// one for RG props (flag is undefined or false) and one for RGIC props (flag === true).
function ComponentImpl(props: PropsStandard): JSX.Element;
function ComponentImpl(props: PropsIC): JSX.Element;
function ComponentImpl({ query, func, flag, x, y }: PropsImpl) {
  // On this next line, I want to declare RG or RGIC instead of RGAny,
  // based on the value of the flag prop (or whether props is PropsStandard or PropsIC).
  const [root, setRoot] = useState<RGAny>(query ?? { combinator: 'and', rules: [] });

  useEffect(() => {
    func(root) // <-- TS error here because RGAny is not assignable to RG
  }, [root])

  return (<div>{flag ? 'True' : 'False'}</div>);
}

这是用户实现组件的方式:

// The user's application component
const App = () => {
  const [query, setQuery] = useState<RG>({ combinator: 'and', rules: [] })
  const [queryIC, setQueryIC] = useState<RGIC>({ rules: [] })
  const [flag, setFlag] = useState(false);

  return flag
    ? <Component x="test" y="test" query={queryIC} func={(q: RGIC) => console.log(q)} flag />
    : <Component x="test" y="test" query={query} func={(q: RG) => console.log(q)} />;
}

ReactDOM.render(<App />, document.body);

我了解如何使用自定义类型保护和 in 运算符(如 herehere),但我不确定如何将它们应用于整个组件函数体而不复制整个实施。

更新:this TS playground 已根据 @chazsolo 的评论更新了类型。

【问题讨论】:

  • 请提供最小可重现示例
  • TS 游乐场的例子还不够吗?
  • 没关系,但如果你把它最小化一点会好很多
  • 您是否考虑过键入组件而不是重载?像export function Component&lt;T extends RG | RGIC = RG&gt;(props: Props&lt;T&gt;) { 这样的东西然后使用通用的T 类型作为useState?
  • 是的,@chazsolo,我认为做到了!我已经通过指向更新的 TS 游乐场的链接更新了问题。想要提供您的评论作为答案,以便我将其标记为解决方案?

标签: reactjs typescript


【解决方案1】:

考虑添加类似于(不完整代码)的generic type,而不是向您的组件添加可选标志:

interface Props<T> & CommonProps {
  func(q: T): void;
  query: T;
}

function Component<T extends RG | RGIC = RG>(props: Props<T>) {
   // ...
}

T extends RG | RGIC = RG 允许您设置可以使用的有效类型(RGRGIC)并默认为RG,以防用户不传入。然后将推断出正确的类型基于赋予组件的内容(因此,func 中的参数之类的东西在使用时不需要重新输入)。

用法如下:

return flag
  ? <Component<RGIC> query={queryIC} func={(q) => console.log(q)} />
  : <Component<RG> query={query} func={(q) => console.log(q)} />;

【讨论】:

    猜你喜欢
    • 2020-11-06
    • 2018-12-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-06-22
    • 2018-12-01
    相关资源
    最近更新 更多