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
