網站搜尋

javascript - 我如何在 Typescript 中製作一個像真正的 if 一樣運作的 React “If” 元件?


我使用 React 製作了一個簡單的 <If /> 函數元件:

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 /> 的元件,它具有以下 Props:

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>
);

正如我所評論的,雖然事實上沒有定義 animal,但我無法告訴 Typescript 我已經檢查過它。使用 animal! 斷言可以起作用,但那並不是我想要的。

有什麼想法嗎?

答案#1

這看起來不可能。

原因:如果我們將 If 組件的 內容 從

if (props.condition) {
  ...
}

到它的相反方向

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

你會發現這一次你想要的類型差異是以相反的方式處理的。

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

這意味著它不是獨立的,因此得出結論,在這種情況下,不可能在不接觸任一組件的情況下進行這種類型的差異化。

我不確定最好的方法是什麼,但這是我的一個想法。

你可以使用 TypeScript 的分配條件型別 NonNullable 來定義 Animal component 的屬性 animal。

文件

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

使用

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

這不是由 If 元件的條件生成的,但由於你只在該條件內使用 child component,在此情況下將 child component 的 props 設計為 非空 是有一定道理的

型別 Animal 確實包含 undefined。

答案#2

透過使用渲染回呼函式,我能夠輸入返回參數不可為空。

我無法修改你的原始 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 的類型相同。

藉此,我們將得到一個渲染回調,該回調將傳入一個經過空值檢查的變數。

  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