我该如何在Typescript中制作一个类似于“ if”的React“ If”组件?


11

<If />使用React 创建了一个简单的功能组件:

import React, { ReactElement } from "react";

interface Props {
    condition: boolean;
    comment?: any;
}

export function If(props: React.PropsWithChildren<Props>): ReactElement | null {
    if (props.condition) {
        return <>{props.children}</>;
    }

    return null;
}

它使我可以编写更简洁的代码,例如:

render() {

    ...
    <If condition={truthy}>
       presnet if truthy
    </If>
    ...

在大多数情况下,它工作良好,但是当我想检查例如是否未定义给定变量然后将其作为属性传递时,这便成为问题。我举一个例子:

假设我有一个名为的组件<Animal />,它具有以下属性:

interface AnimalProps {
  animal: Animal;
}

现在我有另一个组件,它呈现以下DOM:

const animal: Animal | undefined = ...;

return (
  <If condition={animal !== undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>
);

正如我所评论的,尽管实际上没有定义动物,但我无法告诉Typescript我已经检查过它。断言animal!将起作用,但这不是我想要的。

有任何想法吗?


1
不知道打字稿中是否有可能告诉他您已经检查了null安全性。可能{animal !== undefined && <Anibal animal={animal} />}会工作
OlivierBoissé

1
投放有效吗?<Animal animal={animal as Animal} />
paul

@OlivierBoissé我被限制使用该语法
Eliya Cohen

@paul是的,但是放在“!”上不是吗?到底?
Eliya Cohen

Answers:


3

似乎不可能。

原因:如果我们从以下位置更改If组件的内容

if (props.condition) {
  ...
}

相反

if (!props.condition) {
  ...
}

您会发现这一次您希望以相反的方式处理diff类型。

  <If condition={animal === undefined} comment="if animal is defined, then present it">
    <Animal animal={animal} /> // <-- Error! expected Animal, but got Animal | undefined
  </If>

这意味着它不是独立的,从而得出的结论是,在这种情况下,如果不触摸两个组件中的任何一个,就不可能进行这种类型的区分。


我不确定什么是最好的方法,但这是我的想法之一。

您可以使用打字稿的Distributive条件类型NonNullable来定义Animal componentprops 。animal

文献

type T34 = NonNullable<string | number | undefined>;  // string | number

用法

interface AnimalProps {
  // Before
  animal: Animal;
  // After
  animal: NonNullable<Animal>;
}

它不是由生成If组件的条件,但因为你只使用child component该条件里面,这让一些感觉来设计child component的道具作为none nullable,但条件是下

类型Animal包含undefined


我不确定它如何解决我的问题。Animal组件与If组件无关。它不应该适应If组件。另外,我不确定NonNullable与我的问题有什么关系
Eliya Cohen

@EliyaCohen更新,此答案需要附加任何内容吗?
keikai

我赞成您的回答,尽管我不能接受,因为这不是解决方案。当TS和React使这种事情成为可能时,将来可能会得到解决。
Eliya Cohen

1

简短的答案?

你不能

由于您已将定义animalAnimal | undefined,因此唯一的删除undefined方法是创建防护或将其重铸animal为其他东西。您已经将类型防护隐藏在condition属性中,并且TypeScript无法知道那里发生了什么,因此它无法知道您在Animal和之间进行选择undefined。您需要强制转换或使用!

请考虑一下:虽然这可能听起来更干净,但是它创建了一段需要理解和维护的代码,也许其他人可能需要理解和维护。本质上,您正在创建一种由React组件组成的新语言,除了TypeScript之外,其他人还需要学习。

有条件输出JSX的另一种方法是定义render包含条件内容的变量,例如

render() {
  const conditionalComponent = condition ? <Component/> : null;

  return (
    <Zoo>
      { conditionalComponent }
    </Zoo>
  );
}

这是其他开发人员可以立即识别并且无需查找的标准方法。


0

通过使用渲染回调,我可以键入return参数不可为空。

我无法修改您的原始If组件,因为我不知道您的condition断言以及它断言的变量,即condition={animal !== undefined || true}

因此,不幸的是,我制作了一个新组件IsDefined来处理这种情况:

interface IsDefinedProps<T> {
  check: T;
  children: (defined: NonNullable<T>) => JSX.Element;
}

function nonNullable<T>(value: T): value is NonNullable<T> {
  return value !== undefined || value !== null;
}

function IsDefined({ children, check }: IsDefinedProps<T>) {
  return nonNullable(check) ? children(check) : null;
}

表示children将是一个回调,并将传递T类型的NonNullable,该类型与相同check

这样,我们将获得一个渲染回调,该回调将传递一个经过null检查的变量。

  const aDefined: string | undefined = mapping.a;
  const bUndefined: string | undefined = mapping.b;

  return (
    <div className="App">
      <IsDefined check={aDefined}>
        {aDefined => <DoSomething message={aDefined} />} // is defined and renders
      </IsDefined>
      <IsDefined check={bUndefined}>
        {bUndefined => <DoSomething message={bUndefined} />} // is undefined and doesn't render
      </IsDefined>
    </div>
  );

我在这里有一个工作示例https://codesandbox.io/s/blazing-pine-2t4sm


这是一个不错的方法,但不幸的是,它违反了使用If组件的全部目的(因此看起来很干净)
Eliya Cohen
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.