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