Menu

React 类组件:state、生命周期与改写为函数组件

类组件是一个继承 React.Component、从 render 方法返回 JSX 的 JavaScript 类。学习 this.props、this.state 和 setState,绑定事件处理函数,生命周期方法,PureComponent,以及如何把类组件改写成函数组件。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

类组件是一个继承 React.Component、并从 render() 方法返回 JSX 的 JavaScript 类。它从 this.props 读取 props,把 state 存放在 this.state 中,并用 this.setState() 更新它。在新代码中,带 hooks 的函数组件已经取代了类组件,但类组件仍然被完整支持,在许多老代码库中随处可见,而且是编写错误边界的唯一方式。

每个计数器都保存着自己的 state。再加一个 <Counter label="Plums" />,它同样从 0 开始。

类组件的结构

import { Component } from 'react';

class Welcome extends Component {
    static defaultProps = { name: 'stranger' };

    constructor(props) {
        super(props); // required before using this
        this.state = { visits: 1 };
    }

    render() {
        return <h1>Hello, {this.props.name}. Visit #{this.state.visits}</h1>;
    }
}
  • 这个类继承 Component(如果写了 import React from 'react',也可以继承 React.Component)。
  • render() 是必需的。它必须是纯函数,就像函数组件的函数体一样:读取 this.props 和 this.state,返回 JSX。
  • this.props 存放 props。它们是只读的。
  • state 可以在 constructor 中设置(先调用 super(props)),如今更常见的写法是作为类字段:state = { visits: 1 };。
  • 在 React 19 中,static defaultProps 对类组件仍然有效(只有函数组件中的它被移除了)。

this.setState 会合并,还可以接收更新函数

setState 有两点与 useState 的 setter 不同。它会把你传入的对象合并到当前 state 中,所以你只需更新你写到的那些键。另外,和所有 React state 更新一样,它不会立即生效,所以当下一个值依赖于上一个值时,要使用更新函数的写法。

“+3 with objects”只加了一,因为三次调用读取的都是同一个 this.state.likes。“+3 with updaters”加了三。“Move to Paris”保留了 name 和 likes(这就是合并),却丢失了 zip,因为合并只有一层深。写成 address: { ...this.state.address, city: 'Paris' } 就能保留它。setState 可选的第二个参数会在更新之后运行,控制台会在那里显示新的地址。

绑定事件处理函数

以 onClick={this.handleClick} 形式传递的方法,之后会作为普通函数被调用,脱离了它所属的对象,所以它内部的 this 是 undefined。

第一个按钮会在控制台记录一个 TypeError,提示无法读取 undefined 的 setState。第二个按钮可以正常工作,因为类字段中的箭头函数会在实例创建时捕获 this。较老的修复方法是在构造函数中绑定,这在遗留代码中仍然很常见:

constructor(props) {
    super(props);
    this.state = { on: false };
    this.handleClick = this.handleClick.bind(this);
}

内联箭头函数 onClick={() => this.handleClick()} 也可以。

生命周期方法

类组件通过有固定名称的方法来响应挂载、更新和卸载。最常见的几个是:

  • componentDidMount():首次渲染的结果进入 DOM 之后。在这里启动定时器、订阅和请求。
  • componentDidUpdate(prevProps, prevState):每次更新之后。做事之前先和之前的值比较,否则会陷入循环。
  • componentWillUnmount():移除之前。撤销 componentDidMount 启动的一切。
  • shouldComponentUpdate(nextProps, nextState):返回 false 可以跳过一次渲染。

组件生命周期一页给出了完整列表、它们的执行顺序,以及一个会记录每个方法调用的组件。

把类组件改写成函数组件

下面这个类用到了三个常见的生命周期方法。它每秒计时一次,在 label prop 改变时记录日志,并在被移除时停止定时器。

下面是同一个组件的函数版本。每个生命周期方法都变成了某个 effect 的一部分,相关的设置和清理代码挨在一起,而不是分散在两个方法中。

两个计时器计数方式相同,每次 label 改变时都会记录日志。函数版本在挂载时还会多记录一次,因为 effect 在首次渲染之后也会运行。任何改写都可以按以下步骤进行:

  1. this.props.x 变成解构出来的 prop { x }。
  2. this.state 的每个键都变成单独的 useState。记住 setter 是替换而不是合并。
  3. 方法变成组件内部的普通函数。不需要绑定。
  4. componentDidMount、componentDidUpdate 和 componentWillUnmount 变成 useEffect 调用,按它们做的事情分组,而不是按它们运行的时机分组。
  5. createRef 变成 useRef,static contextType 加 this.context 变成 useContext 或 use。

错误边界保留为类组件;它们没有对应的函数写法。

PureComponent

普通的类组件在父组件每次渲染时都会渲染。PureComponent 会先把新的 props 和 state 与之前的进行一层深的比较,如果没有变化就跳过渲染。它是 React.memo 的类组件对应物。

点击按钮:控制台只记录了 PlainLabel rendered,因为 PureLabel 看到的 text 没有变。给两者都传入 text={'hello ' + clicks},它们每次点击都会渲染。

这种比较是浅比较,所以如果某个 prop 在父组件每次渲染时都是新的对象或函数(style={{ color: 'red' }}、onClick={() => ...}),它就会失效。这和 React.memo 的陷阱是一样的。

React 19 从类组件中移除的东西

如果你在维护老的类组件代码,有几种写法在 React 19 中已经不能用了:

  • 字符串 ref(ref="input" 配合 this.refs.input)。改用 createRef() 或 ref 回调。
  • 旧版 context(contextTypes、childContextTypes、getChildContext)。改用 createContext 配合 static contextType。
  • propTypes 会被忽略。改用 TypeScript 或你自己的检查。
  • findDOMNode 已被移除。改为在 DOM 元素上放一个 ref。

老的 componentWillMount、componentWillReceiveProps 和 componentWillUpdate 方法也属于旧版 API。无论用旧名字还是 UNSAFE_ 名字(UNSAFE_componentWillMount 等),它们仍然会运行,但已被弃用,StrictMode 也会对它们发出警告;把它们的代码移到 componentDidMount、componentDidUpdate 或 getDerivedStateFromProps 中。

常见问题

React 中的类组件被弃用了吗?

没有。React 19 仍然支持类组件,它们也会继续正常工作,但 React 文档建议新代码使用带 hooks 的函数组件。错误边界仍然必须用类来写。

类组件和函数组件有什么区别?

类组件把 state 存放在 this.state 中,用 this.setState 更新它,并通过有固定名称的方法响应生命周期。函数组件用 hooks(useState、useEffect)完成同样的工作,不需要 this,代码也更短。

setState 会立即更新 state 吗?

不会。this.setState 只是安排一次更新,React 稍后才会渲染,所以调用之后 this.state 仍然是旧值。当新值依赖于旧值时,传入一个函数 this.setState((state) => ...);要读取更新后的结果,可以在第二个参数的回调或 componentDidUpdate 中读取。

为什么我的事件处理函数中 this 是 undefined?

以 onClick={this.handleClick} 形式传递的类方法,被调用时脱离了它所属的对象,所以 this 是 undefined。把处理函数定义成类字段箭头函数(handleClick = () => { ... }),或者在构造函数中绑定它。

PureComponent 是什么?

一种在 props 和 state 与上一次浅比较相等时跳过渲染的 Component。它是 React.memo 的类组件版本。

Coddy 编程语言插图

和 Coddy 一起学编程

开始