TypeScript 定义 React 函数组件的 4 种方式及注意事项

作者:纸飞机

时间:2026-10-25

浏览:620

本文详细介绍在 React 项目中使用 TypeScript 定义函数组件的四种主要方式,包括 React.FC、JSX.Element、直接接口定义及 PropsWithChildren。同时针对返回值限制、数组填充及泛型使用等常见开发陷阱提供解决方案与最佳实践。

文章目录

前言

在基于 React 进行项目开发时,组件是最核心的构建模块。组件主要分为函数组件和类组件两种形式,我们可以参考如下定义方式:

函数组件的定义示例:

function Welcome(props) {
  return <h1>Hello, {props.name}</h1>;
}

类组件的定义示例:

class Welcome extends React.Component {
  render() {
    return <h1>Hello, {this.props.name}</h1>;
  }
}

本文将重点讲解利用 TypeScript 定义函数式组件的四种具体方法,并梳理开发过程中需要关注的几个关键问题。

如何使用 TypeScript 定义函数式组件

标准的函数式组件通常接收一个 props 参数,并返回一个 JSX 元素或者 null。

在使用 TypeScript 定义函数组件时,存在四种可选方案。每种方案各有优劣,开发者应根据具体场景进行选择。

1. 使用 React.FC

由于 React 本身并非由 TypeScript 编写,其类型定义依赖于社区维护的 @type/react 包。该包提供了一个通用类型 FC,可用于为函数组件添加类型约束。

type FCProps = { text: string };
// React.FunctionComponent 的简写
const FCComponent: React.FC<FCProps> = ({ text = "" }) => <div>{text}</div>;

上述代码中的 React.FC 实际上是 React.FunctionComponent 的简写形式。

当组件内部包含子元素时,TypeScript 编译器通常会发出警告:

type FCProps = { text: string };
const FCComponent: React.FC<FCProps> = ({ text = "" }) => <div>{text}</div>;

function App() {
  return (
    <div className="App">
        <FCComponent text="Hello Chris1993.">
            <span>children</span>
        </FCComponent>
    </div>
  );
}

具体的警告信息如下:

Type '{ children: string; text: string; }' is not assignable to type 'IntrinsicAttributes & FCProps'.
  Property 'children' does not exist on type 'IntrinsicAttributes & FCProps'.

目前官方已不推荐继续使用此方式,相关讨论可参考以下链接:

  • Remove React.FC from Typescript template #8177
  • 《TypeScript + React: Why I don't use React.FC》

2. 使用 JSX.Element

通过将 JSX.Element 类型指定为函数组件的返回值类型,若组件返回非 JSX.Element 类型的值,TypeScript 将报错提示。

type FCProps = { text: string };
const ElementComponent = ({ text }: FCProps): JSX.Element => <div>{text}</div>;
function App() {
  return (
    <div className="App">
        <ElementComponent text="Hello Chris1993."></ElementComponent>
    </div>
  );
}

3. 直接定义完整类型

由于 React 组件在包含子元素时,会隐式传递一个 children 属性,这可能导致参数类型定义出现偏差。因此,我们可以直接定义一个包含 children 属性类型的完整参数接口:

type FCProps = { text: string; children?: any };
const FCComponent: React.FC<FCProps> = ({ text = "" }) => <div>{text}</div>;

function App() {
  return (
    <div className="App">
        <FCComponent text="Hello Chris1993.">
            <span>children</span>
        </FCComponent>
    </div>
  );
}

4. 使用 React.PropsWithChildren

第三种方法要求每次手动编写 children 属性类型,较为繁琐。此时可以使用 React.PropsWithChildren 类型,该类型内部已封装了 children 的类型声明:

// react/index.d.ts
type PropsWithChildren<P> = P & { children?: ReactNode };

因此,采用 React.PropsWithChildren 类型定义函数组件时,无需再单独处理 children 的类型问题:

type IProps = React.PropsWithChildren<{ text: string }>;
const PropsComponent = ({ text }: IProps) => <div>{text}</div>;
function App() {
  return (
    <div className="App">
        <PropsComponent text="Hello Chris1993.">
            <span>children</span>
        </PropsComponent>
    </div>
  );
}

使用过程需要注意的点

1. 函数式组件返回值不能是布尔值

在函数组件内部使用条件语句时,如果返回值是非 JSX 元素且非 null 的值,React 会抛出错误:

const ConditionComponent = ({ useRender = false }) =>
  useRender ? <span>Render ConditionComponent</span> : false;// ❌

function App() {
  return (
    <div className="App">
        <ConditionComponent useRender></ConditionComponent>
        {/* 'ConditionComponent' cannot be used as a JSX component.
            Its return type 'false | Element' is not a valid JSX element.
            Type 'boolean' is not assignable to type 'ReactElement<any, any>'.
        */}
    </div>
  );
}

正确的处理逻辑是确保函数组件返回有效的 JSX 元素或 null:

const ConditionComponent = ({ useRender = false }) =>
  useRender ? <span>Render ConditionComponent</span> : <span>error</span>;// ✅

// or

const ConditionComponent = ({ useRender = false }) =>
  useRender ? <span>Render ConditionComponent</span> : null;// ✅

此外,当属性 useRender 的值为 true 时,若处理不当同样会引发错误:

const ConditionComponent = ({ useRender = false }) =>
  useRender && <span>Render ConditionComponent</span>;// ❌

2. 无法为组件使用 Array.fill() 填充

当组件直接返回 Array.fill() 的结果时,TypeScript 会提示类型错误。

const ArrayComponent = () => Array(3).fill(<span>Chris1993</span>); // ❌

function App() {
  return (
    <div className="App">
      <ArrayComponent></ArrayComponent>
    </div>
  );
}

具体的错误提示如下:

'ArrayComponent' cannot be used as a JSX component.
  Its return type 'any[]' is not a valid JSX element.
    Type 'any[]' is missing the following properties from type 'ReactElement<any, any>': type, props, key

解决该问题的方法是明确指定函数的返回值类型:

const ArrayComponent = () =>
  Array(3).fill(<span>Chris1993</span>) as any as JSX.Element; // ✅

3. 支持使用泛型来创建组件

在基于 TypeScript 开发 React 函数式组件时,可以利用泛型进行约束,从而声明一个泛型组件(Generic Components),使组件具备更高的灵活性。

具体使用方式如下:

interface GenericProps<T> {
  content: T;
}
const GenericComponent = <T extends unknown>(props: GenericProps<T>) => {
  const { content } = props;
  const component = <>{content}</>;
  return <div>{component}</div>;
};
function App() {
  return (
    <div className="App">
      { /* Success ✅ */}
      <GenericComponent<number> content={10} />
      { /* Error ❌ Type 'string' is not assignable to type 'number'. */}
      <GenericComponent<number> content={"10"} />
    </div>
  );
}

关于 Generic Components 章节中介绍的更高级用法:

interface Props<T> {
  items: T[];
  renderItem: (item: T) => React.ReactNode;
}

const List = <T extends unknown>(props: Props<T>) => {
  const { items, renderItem } = props;
  const [state, setState] = React.useState<T[]>([]); // You can use type T in List function scope.
  return (
    <div>
      {items.map(renderItem)}
      <button onClick={() => setState(items)}>Clone</button>
      {JSON.stringify(state, null, 2)}
    </div>
  );
};
function App() {
  return (
    <div className="App">
        <List<number>
          items={[1, 2]} // type of 'string' inferred
          renderItem={(item) => (
            <li key={item}>
              {/* Error: Property 'toPrecision' does not exist on type 'string'. */}
              {item.toPrecision(3)}
            </li>
          )}
        />
    </div>
  );
}

至此,关于使用 TypeScript 开发 React 函数组件的介绍已完成。如需了解更多 TypeScript 与 React 相关的内容,建议查阅本站往期文章或继续阅读后续系列文章,感谢大家的支持。

实用工具推荐

电子包浆

绿化、压缩、噪点和虚拟水印,自由调节做旧程度。

使用 ›
图片压缩

选择画质和输出格式,预览压缩前后的大小。

使用 ›
相关文章