在前端开发内容学习中,React 从类组件到 Hooks:为什么 Hooks 改变了 React?是常见主题。很多人在阅读时会遇到概念分散、步骤不清和注意点难以归纳的问题。本文按照基础概念、操作流程和关键细节,对相关内容进行整理。
学习 React 时,经常会看到两种写法:

class App extends React.Component {}
和
function App() {}
很多新手会疑惑:
为什么 React 有两种组件?
为什么现在项目几乎都在使用函数组件?
Hooks 又是什么?
想搞懂这些问题,就需要了解 React 的发展历史。
在 React 16.8 之前:
函数组件
=
UI展示
类组件
=
真正开发业务
原因很简单:
只有类组件拥有状态和生命周期
所以绝大部分业务代码都写在类组件中。
类组件本质上是一个 JavaScript Class。
例如:
import React from 'react'
class App extends React.Component {
render() {
return (
<h1>Hello React</h1>
)
}
}
export default App
React 会创建类的实例:
new App()
再渲染页面。
React 中最重要的概念之一:
State(状态)
状态可以理解为:
数据发生变化
组件会重新渲染
例如:
计数器数字
用户信息
购物车数量
主题颜色
这些都属于状态。
类组件使用:
this.state
保存状态。
例如:
class App extends React.Component {
state = {
count: 0
}
render() {
return (
<h1>{this.state.count}</h1>
)
}
}
错误写法:
this.state.count++
React 不会更新页面。
因为:
React无法感知变化
React 提供:
this.setState()
修改状态。
例如:
this.setState({
count: 1
})
执行过程:
修改状态
↓
触发重新渲染
↓
更新页面
所以:
setState
=
修改状态
+
触发更新
类组件最大的特点之一:
生命周期(Lifecycle)
组件从出生到销毁:
创建
↓
运行
↓
更新
↓
销毁
React 在这些阶段提供对应钩子函数。
组件挂载完成后执行。
componentDidMount() {
}
触发时机:
页面首次渲染完成
常见用途:
发送请求
获取数据
订阅事件
开启定时器
例如:
componentDidMount(){
fetch('/api/user')
}
组件更新后执行。
componentDidUpdate() {
}
触发时机:
state变化
props变化
之后执行。
例如:
数据更新
↓
组件重新渲染
↓
componentDidUpdate
组件销毁前执行。
componentWillUnmount() {
}
常见用途:
清除定时器
解绑事件
取消订阅
例如:
componentWillUnmount(){
clearInterval(timer)
}
随着项目越来越大:
生命周期逻辑分散
this容易丢失
代码复用困难
逻辑组织混乱
例如:
获取数据
↓
componentDidMount
处理更新
↓
componentDidUpdate
清理资源
↓
componentWillUnmount
同一个功能被拆散到多个地方。
维护成本越来越高。
2019年 React 发布:
React Hooks
这是 React 历史上最重要的更新之一。
目标:
让函数组件拥有类组件的能力
从此:
函数组件
=
状态
+
生命周期
+
逻辑复用
现代 React 项目几乎全部采用:
function App() {
}
写法。
例如:
function App() {
return (
<h1>Hello React</h1>
)
}
官方定义:
Hook 是特殊函数
允许函数组件使用 React 特性
最常用的两个 Hook:
useState
useEffect
作用:
给函数组件定义状态
例如:
import { useState } from 'react'
function App() {
const [count, setCount] =
useState(0)
return (
<button
onClick={() =>
setCount(count + 1)
}
>
{count}
</button>
)
}
const [state, setState] =
useState(initialValue)
例如:
const [count, setCount] =
useState(0)
对应:
count
↓
当前状态
setCount
↓
修改状态
作用:
处理副作用
例如:
发送请求
操作DOM
监听事件
定时器
这些都属于副作用。
useEffect(() => {
})
触发:
首次渲染
+
每次重新渲染
useEffect(() => {
}, [])
触发:
仅组件首次加载
等价于:
componentDidMount
useEffect(() => {
}, [count])
触发:
首次渲染
+
count变化时
例如:
count更新
↓
useEffect执行
useEffect(() => {
return () => {
}
}, [])
返回函数称为:
Cleanup Function
清理函数
执行时机:
组件卸载前
例如:
useEffect(() => {
const timer =
setInterval(() => {},1000)
return () => {
clearInterval(timer)
}
}, [])
作用:
释放资源
避免内存泄漏
类组件:
componentDidMount
componentDidUpdate
componentWillUnmount
函数组件:
useEffect(() => {
// mount
return () => {
// unmount
}
}, [])
再配合依赖数组:
useEffect(() => {
// update
}, [state])
基本覆盖生命周期需求。
类组件:
经常出现this指向问题
函数组件:
没有this
逻辑更简单。
多个组件共享逻辑:
自定义Hook
就能完成。
类组件:
一个功能
分散在多个生命周期
Hooks:
一个功能
集中在一个useEffect
维护成本更低。
状态
数据变化会导致组件重新渲染
this.state
this.setState()
为函数组件定义状态
首次渲染
+
每次重新渲染
仅首次加载
首次加载
+
x变化时
组件卸载前执行
用于资源清理
React
│
├── React16.8之前
│
│ └── 类组件
│ │
│ ├── state
│ ├── setState
│ └── 生命周期
│
└── React16.8之后
│
└── Hooks
│
├── useState
└── useEffect
生命周期
componentDidMount
↓
componentDidUpdate
↓
componentWillUnmount
对应
useEffect
React16.8之前:
类组件
state
setState
生命周期
----------------
React16.8之后:
函数组件
Hooks
----------------
useState:
定义状态
setState:
修改状态
----------------
useEffect():
首次加载
+
每次更新
useEffect([],):
仅首次加载
useEffect([x]):
首次加载
+
x变化
----------------
return函数:
组件卸载前执行
用于清理资源