Functional Component — 是一种使用普通JavaScript函数创建React组件的方式,在React 16.8发布和Hook出现后成为标准。函数式组件接收props并返回React元素,无需继承React.Component。根据React, 2024的数据,React生态系统中97%的新组件都是作为函数编写的。
要点
Functional Component(函数式组件)——是定义为函数的React组件,接收props对象并返回React元素。在React 16.8之前,这类组件被称为stateless(无状态),因为它们无法管理内部状态。
随着React 16.8(2019年2月)中Hook的出现,函数式组件获得了对状态(useState)、副作用(useEffect)、上下文(useContext)和引用(useRef)的访问。术语“stateless”已经过时——现代函数式组件完全管理状态。
React官方推荐函数式组件作为创建组件的主要方式。react.dev上的所有文档、示例、教程和新API(Server Components、Actions)都基于函数式方法构建。类组件没有被移除,但React的新功能只能通过Hook使用。
最简单的函数式组件——一个返回JSX的函数。它接收props作为参数,不需要构造函数、super调用或this绑定。Props直接通过函数参数可用——没有this.props。
// Functional component without state
function Greeting({ name }) {
return <h1>Hello, {name}!</h1>;
}
// Arrow function (common variant)
const Greeting = ({ name }) => (
<h1>Hello, {name}!</h1>
);
要添加状态,请使用useState。Hook返回一个包含两个值的元组:当前状态和setter函数。与类的this.setState不同,useState完全替换值,而不与之前的值合并。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Increment
</button>
</div>
);
}
Hook是现代React的基础。每个Hook解决特定的任务:useState — 状态,useEffect — 副作用,useContext — 访问上下文,useRef — 对DOM元素的引用,useCallback和useMemo — 记忆化。
useEffect同时替代了componentDidMount、componentDidUpdate和componentWillUnmount。第一个参数——带有effect的回调,第二个——依赖数组。如果数组为空,effect在挂载后执行一次。如果指定了依赖——在任何一个依赖发生变化时执行。
import React, { useState, useEffect } from 'react';
function UserData({ userId }) {
const [user, setUser] = useState(null);
useEffect(() => {
let cancelled = false;
fetch(`/api/users/${userId}`)
.then(res => res.json())
.then(data => {
if (!cancelled) setUser(data);
});
// Cleanup - analog of componentWillUnmount
return () => { cancelled = true; };
}, [userId]); // Dependency
return <div>{user?.name}</div>;
}
函数式组件的主要优势——通过自定义Hook重用逻辑。如果在类组件中逻辑在componentDidMount和componentDidUpdate之间重复,在函数式组件中它被提取到以use为前缀的单独函数中。自定义Hook是HOC和render props的主要替代方案。
React推荐组合而非继承来实现组件间代码复用。函数式组件非常适合组合:一个组件可以包含多个其他组件,向它们传递props和children。
children模式——当组件通过children prop接收子元素时——自然实现:function Layout({ children })。来自Vue的slots对应物通过命名的props-component实现:function Page({ header, sidebar, content })。
继承在React中几乎不使用。即使是技术上支持extends的类组件,也很少形成超过一层深度的层次结构。使用组合的函数式组件完全满足了无需继承的代码复用需求。
函数式组件具有内置的优化机制。React.memo — 类似于PureComponent的函数版本:如果props没有变化则阻止重新渲染(浅比较)。useMemo在渲染之间缓存计算结果。
import React, { useMemo } from 'react';
// React.memo - memoize component
const ExpensiveList = React.memo(({ items }) => {
return <ul>
{items.map(item =>
<li key={item.id}>{item.name}</li>
)}
</ul>;
});
// useMemo - caching result
function Dashboard({ transactions }) {
const summary = useMemo(() => {
return transactions.reduce((acc, t) => acc + t.amount, 0);
}, [transactions]);
return <p>Total: {summary}</p>;
}
useCallback记忆化回调函数,使它们不会在每次渲染时重新创建。这在向包裹在React.memo中的子组件传递函数时至关重要:没有useCallback,子组件每次都会重新渲染,因为函数的引用发生了变化。
两种方法之间的区别超出了语法。函数式组件——是一个闭包:状态和效果绑定到特定的渲染。类组件——是this:状态总是从this.state获取,而在异步回调执行时this.state可能已经过时。
| 方面 | 函数式 | 类 |
|---|---|---|
| 状态 | useState(隔离的) | this.state + this.setState |
| 效果 | useEffect带依赖 | componentDidMount + componentDidUpdate |
| 上下文 | useContext | Context.Consumer或static contextType |
| 优化 | React.memo + useMemo | PureComponent + shouldComponentUpdate |
| 逻辑重用 | 自定义Hook | HOC / Render props |
| this | 无 | 构造函数中bind |
函数式组件提供线性执行流程。在类组件中,相关逻辑常常分散在多个生命周期方法中。例如,WebSocket订阅在componentDidMount中设置并在componentWillUnmount中移除。在函数式组件中,两个操作——都在一个带有清理函数的useEffect中。
TypeScript与函数式组件比与类协作更自然。Props类型化 — 函数参数的接口。返回值的类型 — JSX.Element或ReactNode。useState自动从初始值推断类型。
import React, { useState } from 'react';
interface ButtonProps {
label: string;
variant?: 'primary' | 'secondary';
onClick: () => void;
}
const Button: React.FC<ButtonProps> =
({ label, variant = 'primary', onClick }) => (
<button
className={`btn btn-${variant}`}
onClick={onClick}
>
{label}
</button>
);
现代TypeScript允许使用区分联合类型对useReducer进行类型化,在自定义Hook中使用泛型,以及通过React.PropsWithChildren严格检查children。函数式组件与TypeScript在无需额外抽象的情况下提供完整的类型安全性。
常见问题
函数式组件更简单:没有构造函数、bind、this和分散的生命周期方法。Hook允许将相关逻辑分组。React将资源投入在Hook的开发上,而非类。Server Components和新功能仅在函数中可用。
可以,通过useEffect内部设置定时器并带清理函数来清除clearInterval。请注意闭包:如果你要递增计数器,请使用setState的函数形式:setCount(c => c + 1),以避免依赖闭包中的旧值。
使用useRef存储之前的值。在每次渲染后在useEffect中更新ref。对于视觉比较,自定义Hook usePrevious可以提供帮助:const prev = usePrevious(value);
React.memo — 用于记忆化整个组件的HOC:如果props没有变化则阻止重新渲染。useMemo — 用于记忆化组件内特定值的Hook:基于依赖数组在渲染之间缓存函数的结果。
最简单的方法 — useState从props初始化 + useEffect依赖props进行同步。对于罕见情况,React建议:在输入数据变化时使用key强制重新创建组件。
总结
我们将开发一款交钥匙移动应用程序
IT Sectr自2017年以来为初创企业和企业打造iOS和Android应用程序。我们将为您提供咨询并提出最佳解决方案。