簡體   English   中英

如何使用 React.FC<props> 當子節點可以是 React 節點或函數時鍵入

[英]How to use React.FC<props> type when the children can either be a React node or a function

我有這個示例組件

import React, { FC, ReactNode, useMemo } from "react";
import PropTypes from "prop-types";

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props) {
  const val = useMemo(() => {
    return 1;
  }, []);

  return (
    <div>
      {typeof props.children === "function"
        ? props.children(val)
        : props.children}
    </div>
  );
};

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

export default Comp;

我的意圖是組件的children屬性可以是

  • 一個node ,它被描述為

    任何可以渲染的東西:數字、字符串、元素或包含這些類型的數組(或片段)。

  • 一個function (或“渲染道具”),它只是從組件內部獲取一個值並返回另一個node

這里的重點是明確的, children可以是一個( node ,它幾乎是一切)或另一個(它只是一個function

問題

但是,我在類型檢查方面面臨以下問題。

  • 如果我保留此處顯示的代碼,我會在線上收到以下錯誤消息? props.children(val) ? props.children(val)

    此表達式不可調用。 並非所有類型為“功能 | ”的成分 ((x: number) => ReactNode) | (字符串 & {}) | (數字和{})| (false & {}) | (真 & {}) | ({} & 字符串) | ({} & 數字) | ({} & 假) | ({} & 真) | (((x: number) => ReactNode) & 字符串)

我不明白這個錯誤。

  • 如果我將Props類型更改為
type Props = {
  children: (x: number) => ReactNode;
};

並依賴於 React 自己的type PropsWithChildren<P> = P & { children?: ReactNode }; 處理children不是函數的情況,然后我得到錯誤

(property) children?: PropTypes.Validator<(x: number) => React.ReactNode> Type 'Validator' 不能分配給 type 'Validator<(x: number) => ReactNode>'。 類型 'ReactNodeLike' 不可分配給類型 '(x: number) => ReactNode'。 類型 'string' 不可分配給類型 '(x: number) => ReactNode'.ts(2322) Comp.tsx(5, 3): 預期類型來自屬性 'children',它在類型上聲明

在線children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired

唯一的解決方案是將Props類型保留為

type Props = {
  children: (x: number) => ReactNode;
};

並將Comp.propTypes更改為children: PropTypes.func.isRequired ,這不是我想要的,因為我想明確表示。

問題

我怎樣才能使代碼保持明確,如本問題開頭所示,並且類型檢查不會對我拋出錯誤?

代碼沙盒鏈接

域名:

React.FC類型是導致上述錯誤的原因:

  1. 它已經包含類型為ReactNode默認children ReactNode ,它們與包含在Props您自己的children類型合並( & )。
  2. ReactNode是一種相當寬的類型,限制了編譯器將children聯合類型縮小到可調用函數的能力,並結合第 1 點。

一個解決方案是省略FC並使用比ReactNode更窄的類型來提高類型安全性:

type Renderable = number | string | ReactElement | Renderable[]
type Props = {
  children: ((x: number) => Renderable) | Renderable;
};

更多細節

首先,這里是內置的 React 類型:

 type ReactText = string | number; type ReactChild = ReactElement | ReactText; interface ReactNodeArray extends Array<ReactNode> {} type ReactFragment = {} | ReactNodeArray; type ReactNode = ReactChild | ReactFragment | ReactPortal | boolean | null | undefined; interface FunctionComponent<P = {}> { (props: PropsWithChildren<P>, context?: any): ReactElement | null; propTypes?: WeakValidationMap<P>; contextTypes?: ValidationMap<any>; defaultProps?: Partial<P>; displayName?: string; } type PropsWithChildren<P> = P & { children?: ReactNode };

1.) 您使用FC<Props>鍵入Comp FC內部已經包含了一個類型為ReactNodechildren聲明,它與來自Props children定義合並:

type Props = { children: ((x: number) => ReactNode) | ReactNode } & 
  { children?: ReactNode }
// this is how the actual/effective props rather look like

2.) 查看ReactNode類型,您會發現類型變得相當復雜。 ReactNode包括類型{}經由ReactFragment ,這是不同之處一切的超型nullundefined 我不知道這種類型形狀背后的確切決定, microsoft/TypeScript#21699暗示了歷史和向后兼容的原因。

因此, children類型比預期更廣泛。 這會導致您的原始錯誤: type guard typeof props.children === "function"無法正確縮小類型 " typeof props.children === "function" " 以使其不再function

解決方案

省略React.FC

最后, React.FC只是一個函數類型,具有額外的屬性,如propTypesdisplayName等,帶有自以為是的、廣泛的children類型。 此處省略FC將為編譯器和 IDE 顯示提供更安全、更易於理解的類型。 如果我接受你的定義Anything that can be renderedchildren Anything that can be rendered ,那可能是:

import React, { ReactChild } from "react";
// You could keep `ReactNode`, though we can do better with more narrow types
type Renderable = ReactChild | Renderable[]

type Props = {
  children: ((x: number) => Renderable) | Renderable;
};

const Comp = (props: Props) => {...} // leave out `FC` type

沒有children自定義FC類型

你可以定義你自己的FC版本,它包含React.FC中的所有內容,除了那些寬children類型:

type FC_NoChildren<P = {}> = { [K in keyof FC<P>]: FC<P>[K] } & // propTypes etc.
{ (props: P, context?: any): ReactElement | null } // changed call signature

const Comp: FC_NoChildren<Props> = props => ...

游樂場示例

我認為全球聯盟可能會有所幫助:

type Props = {
  children: ((x: number) => ReactNode);
} | {
  children: ReactNode;
};

另一個有效的解決方案,並且不需要以任何不同的方式編寫Props聲明或以不同的方式重寫任何其他內容,是在組件定義期間嚴格定義props參數的類型,如下所示

type Props = {
  children: ((x: number) => ReactNode) | ReactNode;
};

const Comp: FC<Props> = function Comp(props: Props) { // we strictly define the props type here
  ...
}

Comp.propTypes = {
  children: PropTypes.oneOfType([PropTypes.node, PropTypes.func]).isRequired
};

我不是 100% 確定為什么這會有所不同,我的直覺是我們將我們自己的Props定義“強制”到類型檢查器,因此我們限制了可能的范圍。

更新

自從我問了最初的問題后,我最終決定采用以下解決方案來解決我的問題:我定義了自己的函數組件類型

//global.d.ts

declare module 'react' {
  // Do not arbitrarily pass children down to props.
  // Do not type check actual propTypes because they cannot always map 1:1 with TS types,
  // forcing you to go with PropTypes.any very often, in order for the TS compiler
  // to shut up

  type CFC<P = {}> = CustomFunctionComponent<P>;

  interface CustomFunctionComponent<P = {}> {
    (props: P, context?: any): ReactElement | null;
    propTypes?: { [key: string]: any };
    contextTypes?: ValidationMap<any>;
    defaultProps?: Partial<P>;
    displayName?: string;
  }
}

這個解決方案

  • 允許我嚴格定義什么是函數組件
  • 不強迫任何任意children道具進入我的定義
  • 不交叉引用任何實際的Component.propTypes與 TS type Props = {...} 很多時候他們不會准確地映射到 1:1,我被迫使用PropTypes.any這不是我想要的。

我將Component.propTypes與 TS 類型一起保留的原因是,雖然 TS 在開發過程中非常好,但PropTypes實際上會在運行時出現錯誤類型值的情況下發出警告,這是有用的行為,例如, API 響應中的字段應該是一個數字,現在是一個字符串。 這樣的事情可能會發生,這不是 TS 可以提供幫助的。

進一步閱讀

https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237 https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34237#issuecomment-486374424

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM