TypeScript中的React.FC<Props>混淆

213

我正在学习TypeScript,但有些内容让我感到困惑。其中一个部分如下:

interface Props {
  name: string;
}

const PrintName: React.FC<Props> = (props) => {
  return (
    <div>
      <p style={{ fontWeight: props.priority ? "bold" : "normal" }}>
        {props.name}
      </p>
    </div>
  )
}

const PrintName2 = (props: Props) => {
  return (
    <div>
      <p style={{ fontWeight: props.priority ? "bold" : "normal" }}>
        {props.name}
      </p>
    </div>
  )
}

对于上述两个功能组件,我看到TypeScript生成了相同的JS代码。在可读性方面,PrintName2组件对我来说似乎更加简洁明了。我想知道这两种定义之间有什么区别,是否有人在使用第二种类型的React组件?


4
这里回答了一个关于React无状态组件返回类型的TypeScript问题,更多是关于在React.FC中可以找到的属性。 - apincik
https://www.harrymt.com/blog/2020/05/20/react-typescript-react-fc.html - zloctb
5个回答

182
感谢所有答案。它们是正确的,但我正在寻找更详细的版本。我进行了更多的研究,并在GitHub上找到了React+TypeScript Cheatsheets中的这个内容。 函数组件 可以编写为普通函数,接受一个props参数并返回JSX元素。
type AppProps = { message: string }; /* could also use interface */

const App = ({ message }: AppProps) => <div>{message}</div>;

React.FC/React.FunctionComponent怎么样? 你也可以使用React.FunctionComponent(或简写的React.FC)编写组件:

const App: React.FC<{ message: string }> = ({ message }) => (
  <div>{message}</div>
);

与“普通函数”版本的一些区别:

它为静态属性(如displayNamepropTypesdefaultProps)提供类型检查和自动完成 - 然而,目前已知使用React.FunctionComponent时存在defaultProps问题。有关详细信息,请参见此issue - 滚动到我们的defaultProps部分以获取那里的键入建议。

它提供了对子元素的隐式定义(请参见下文) - 但是,隐式子元素类型存在一些问题(例如DefinitelyTyped#33006),并且明确表达消耗子元素的组件可能被认为是更好的风格。

const Title: React.FunctionComponent<{ title: string }> = ({
  children,
  title
}) => <div title={title}>{children}</div>;

在未来,它可能会自动将props标记为只读,但如果在参数列表中解构props对象,则这一点就无关紧要了。
React.FunctionComponent明确了返回类型,而普通函数版本是隐式的(否则需要额外的注释)。
在大多数情况下,使用哪种语法几乎没有什么区别,但React.FC语法略微冗长,没有提供明显的优势,因此优先考虑“普通函数”语法。

嗨Kuldeep,首先感谢您提供详细的答案。也许缺少“有关详细信息,请参见此问题”的链接。如果我错了,请忽略 :) - Rafael Lebre
1
嘿,详细解释的链接是我在第一段提到的 Github 链接。 :) 谢谢 - Kuldeep Bora
自React 18起,隐式的children属性已被移除。 请参见:https://github.com/DefinitelyTyped/DefinitelyTyped/pull/56210 - Michał Styś
从现在开始,您如何访问子元素?我是否应该在类型定义中手动添加一个 children: ReactNode 属性? - babaliaris

86

React.FC 不是定义 React 组件的首选方式,这里有一个链接

我个人使用以下类型:

const Component1 = ({ prop1, prop2 }): JSX.Element => { /*...*/ }

React.FC 的缺点:

  1. 即使您的组件不需要子元素,它也会提供子元素的隐式定义。这可能会导致错误。
  2. 不支持泛型。
  3. defaultProps 不兼容。

3
如何对prop1、prop2等进行类型检查? - Arafat Zahan
25
如果您不使用“children”属性,请使用React.VFC - KJ Sudarshan
1
@KJSudarshan 很好的笔记,应该有更多的赞或者回答。 - Joe Lloyd
1
  1. 每个React元素都有“children”,查看“React.createElement”。如果您不想要它们,请不要渲染它们。或使用“React.VFC”。
  2. 它是通用的设计xD。但是无论如何,这不是“React.FC”的问题,而是“.tsx”语法的问题。如果您想使用通用属性创建lambda函数,则会失败。
  3. “defaultProps”是运行时部分。从某些外部魔术源设置道具是brrrr..我永远不会理解这一点,因为我可以在组件中设置默认值。
- Profesor08
3
回应此答案中的一些评论:截至React 18,React.VFC已被弃用。https://github.com/DefinitelyTyped/DefinitelyTyped/pull/59882 - Ben.12
显示剩余3条评论

38

最佳解决方案:

第二种方法 + 返回类型

const PrintName2 = ({ prop1, prop2 }: Props): JSX.Element => { /** */}

这样可以完全控制Props,并且更加明确(没有隐藏的魔术)。

链接至GitHub PR以移除React.FC

Kent C Dodds有一些关于此的想法

次优解决方案:

第一个版本(React.FC)会为您添加一些类型 - 它们来自React Typings

interface FunctionComponent<P = {}> {
  (props: PropsWithChildren<P>, context?: any): ReactElement | null;
  propTypes?: WeakValidationMap<P>;
  contextTypes?: ValidationMap<any>;
  defaultProps?: Partial<P>;
  displayName?: string;
}

这种方法有一些好处,但显而易见的一点是,如果你的组件有子组件,那么你就不需要手动添加一个children属性。它的缺点是默认属性存在问题,并且许多组件没有子组件。


1
这已经不再是事实了,React.FC 不再包含 children 属性! :D - cdimitroulas
@cdimitroulas,你现在会推荐使用React.FC吗? - Jonathan Irwin
1
我已经在各个地方使用它了。我认为它非常好用,可以提供良好的类型安全性,确保您始终从所有代码路径返回有效值。只有一个问题,就是由于某种原因,它不允许您从组件中返回纯字符串,这应该是有效的。 - cdimitroulas

14

由于你正在使用React和TypeScript,你应该始终使用第一种模式,因为它将确保你的组件更加严格类型化,因为它意味着PrintName将是React函数组件类型,并且它接受Props类型的属性。

const PrintName: React.FC<Props>

您可以在React TypeScript typings存储库的此处阅读有关功能组件的完整接口定义。

您提供的第二个示例没有提供任何类型定义,除了它是一个接受类型为Props的一组参数的函数,并且通常可以返回任何内容。

因此,编写

const PrintName2 = (props:Props)

类似于

const PrintName2: JSX.Element = (props:Props) 

由于TypeScript无法自动推断它是功能组件,因此需要进行手动类型注释。


OK。但第二种情况的返回类型应该被推断出来。将鼠标悬停在其上方会显示以下内容:const PrintName2: (props: Props) => JSX.Element - Kuldeep Bora
2
虽然它会被推断,但有时推断并不完全正确,但是如果您在添加返回值之前先添加类型信息,则会出现错误,直到满足返回类型规范为止。 - crashmstr
1
它可能会将其推断为JSX元素,但不会自动将其推断为函数组件。 - wentjun
2
最佳答案,直截了当 - dman

4

不再使用React.FC。人们过去使用React.FC的原因是它可以将更多类型如children添加到您的props中。

在您的代码示例中,您不需要children,所以只需像这样编写即可

const PrintName: (props: {name:string; priority: "bold" | "normal"}) => {
  return (
    <div>
      <p style={{ fontWeight: props.priority ? "bold" : "normal" }}>
        {props.name}
      </p>
    </div>
  )
}

但你最终会遇到的问题是,你想将子组件传递给你的组件,而这样做的首选方法是像这样:
interface Props extends React.PropsWithChildren {
   name: string
   priority: "bold" | "normal"
}

const PrintName: (props: Props) => {
  return (
    <div>
      <h1>{props.name}</h1>
      <p style={{ fontWeight: props.priority ? "bold" : "normal" }}> 
        {props.children}
      </p>
    </div>
  )
}

使用React.PropsWithChildren进行扩展与直接键入相同。
interface Props {
   name:string
   priority: "bold" | "normal"
   children?: ReactNode
}

React.PropsWithChildren也可以传递一个泛型,但默认值是unknown。如果你想这样设置children,但除非你确定只想要特定类型,否则最好使用默认值。
React.PropsWithChildren<Button> 

并传入您在组件中想要的子元素类型。

您不必传递JSX.Element的返回类型,因为它是可以推断的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接