使用 TypeScript 在 React 中输入默认的直通道具

Typing defaulted passthrough props in React with TypeScript

React 组件通常会接受一些 props 并将其传递给 children。如果 child 上的一个或多个属性由于由 child 的 defaultProps 指定而可选,如何定义 typeinterface 为 parent 正确接受它自己的和它的 child 的道具?

考虑以下示例:

interface ParagraphProps {
  body: string,
  imgSrc: string,
}

interface SectionProps extends ParagraphProps {
  title: string,
}

class Paragraph extends React.Component<ParagraphProps> {
  static defaultProps = {
    imgSrc: '../images/section-break.jpg',
  };

  render() {
    const { body, imgSrc } = this.props;

    return (
      <p>{body}</p>
      {!!imgSrc && <img src={imgSrc}>}
    );
  }
}

class Section extends React.Component<SectionProps> {
  render() {
    const { title, ...rest } = this.props;

    return (
      <section>
        <h1>{title}</h1>
        <Paragraph {...rest}>
      </section>
    );
  }
}

现在,声明 <Section title='T' body='B'> 将导致错误:

Property 'imgSrc' is missing in type [...]

如果相反,我们为 Section 定义道具,如下所示:

interface SectionProps {
  title: string,
}

type FullSectionProps = Partial<SectionProps & PartialProps>;

然后我们发现现在titlebody是可选的,这不是我们想要的

在保持DRY的同时,如何指定Section的props来规定titlebody是必须的,imgSrc是可选的?

由于 ParagraphProps 接口对应于 Paragraph 组件,因此保留那些 "aligned" 可能是有意义的。你知道 imgSrc 有一个默认值,所以在界面中将其标记为可选是有意义的,因为 no-one 使用 Paragraph 组件(不仅Section 组件)需要传入 imgSrc.

interface ParagraphProps {
  body: string,
  imgSrc?: string,
}

如果不是 Paragraph 组件的所有用户都需要传入 imgSrcSection 组件除外),那么它可能更有意义将该默认值移动到 imgSrcSection 组件。

最后,如果你想让它更动态,你可以做类似下面的事情,尽管在这个例子中它可能比必要的更复杂。

// Only needed if not 3.5+ (https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-5.html#the-omit-helper-type)
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;

interface ParagraphProps {
  body: string;
  imgSrc: string;
}

type PropsWithDefaults = keyof typeof Paragraph.defaultProps;
type TransformedProps = Partial<Pick<ParagraphProps, PropsWithDefaults>> &
  Omit<ParagraphProps, PropsWithDefaults>;

interface SectionProps extends TransformedProps {
  title: string;
}

class Section extends React.Component<SectionProps> {
  render() {
    const { title, ...rest } = this.props;

    return <Paragraph {...rest} />;
  }
}

class Paragraph extends React.Component<ParagraphProps> {
  static defaultProps = {
    imgSrc: "../images/section-break.jpg"
  };

  render() {
    return null;
  }
}

<Section body="foo" title="baz" />
<Section body="foo" title="baz" imgSrc="override" />

此处,TransformedProps 类型包含来自 ParagraphProps 的所有道具,其中 Paragraph.defaultProps 中的道具通过使用 Partial 变为可选。有关 PickOmit 如何形成此结构的更多详细信息,请参阅 Advanced Types documentation