本节摘要:React 组件有函数组件与类组件两种写法,现代工程以函数组件为准。本节对照两者的结构、状态管理与生命周期风格,说明为什么推荐函数组件,并让你读得懂仍然存在的类组件代码。
本节阅读目标
阅读完本节,你应当能够:
同样是"显示一个列表并支持点击"的组件,用两种写法呈现一次。
函数组件:
function Todos({ items }) { const [checkedId, setCheckedId] = useState(null); return ( <ul> {items.map((it) => ( <li key={it.id} onClick={() => setCheckedId(it.id)}>{it.text}</li> ))} </ul> ); }
类组件:
class Todos extends React.Component { constructor(props) { super(props); this.state = { checkedId: null }; } handleClick = (id) => this.setState({ checkedId: id }); render() { return ( <ul> {this.props.items.map((it) => ( <li key={it.id} onClick={() => this.handleClick(it.id)}>{it.text}</li> ))} </ul> ); } }
肉眼可见的差异:类组件多了构造函数、this 前缀、render 方法;函数组件则直接返回 JSX,状态交给 Hook。
三点理由叠加,让函数组件成为默认选择。

当团队评审"新组件用哪种写法"时,结论几乎一边倒:函数组件 + Hooks。类组件只在兜底或兼容历史依赖时出现。但也别一看到类组件就皱眉——能看懂、能维护它在新老交替期仍然必要。这正符合本教程一贯的态度:先弄懂两者,再明确选择。
把"一个输入框 + 一个实时显示"的最小例再对照一次,方便你直观比对两种写法。
用了函数组件加一个 state,代码是:
function Editor({ initialText = '' }) { const [text, setText] = useState(initialText); return <input value={text} onChange={(e) => setText(e.target.value)} />; }
换成类组件则是同一个需求的另一副面孔:
class Editor extends React.Component { state = { text: this.props.initialText || '' }; render() { return ( <input value={this.state.text} onChange={(e) => this.setState({ text: e.target.value })} /> ); } }
请对比两段里"改 text"的入口:函数组件是一个名为 setText 的普通函数,类组件是被包在 this.setState 里的方法。前者哪一步调用、更新后会发生什么都不需要你操心 this 指向;后者只要忘了绑定事件回调的 this,就会在运行时给你惊喜。这份"少一个心智负担"的差距,正是函数组件在团队评审里得分最高的那条理由。
写法这件事的一大分歧点是:单凭"函数组件更好"就能让所有人无缝写代码吗?答案是不能——还得把这条路定成团队共识。评审时最常见的来回是两类语气:一类是"我这就一个输入框,类组件写起来挺直白",另一类是"新功能一律函数组件,README 定死了"。前者是个人习惯驱动的,后者是规则驱动的。比稿现场需要的恰恰是后者:把"新组件用函数组件、旧代码可读可维护即可"写进文档,新入职的同学与久经沙场的老手才不至于每次开会都在写法上重新吵一轮。
这份共识还该顺手约定两件副产物:给"什么时候可以容忍类组件"画一条清晰的例外(比如某个第三方组件必须继承类基类),以及给"函数组件拆 Hooks 的粒度"打个底(一个 Hook 拆到多少人能一眼读懂)——让"循规"不变成"僵化"。把默认规则、允许例外、和边界都钉清楚,函数组件推荐才有落点,而不是一句空口号。
类组件里出现过的生命周期方法,在函数组件里被 useEffect 等 Hook 重新表达——这正是下一节的主角。先讲数据流:State 与 Props。