React-Hooks 自 React 16.8 以来才真正被推而广之,它的背后涉及对 类组件 和 函数组件 两种组件形式的思考和侧重。
# 类组件
class Demo extends React.Component {
state = {
text: ""
}
componentDidMount(){}
render(){
return (
<div className='demo'>
{this.state.text}
</div>
)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 函数组件(无状态组件)
function DemoFunction(props){
const { text } = props
return (
<div className='demo'>
{text}
</div>
)
}
2
3
4
5
6
7
8
# 对比
类组件和函数组件的区别大致有以下几点:
- 类组件需要继承 class,函数组件不需要
- 类组件可以访问生命周期方法,函数组件不能
- 类组件中可以获得实例化后的 this,并基于 this 做很多事情,函数组件不能
- 类组件可以定义并维护 state,函数组件不能
在 Reack-Hooks 出现之前,类组件的能力明显强于函数组件,类组件固然强大,但它绝非万能。而 函数组件会捕获 render 内部的状态,这是两类组件最大的不同。
React 组件本身的定位是函数:一个吃数据,吐 UI 的函数。开发者编写声明式代码,而 React 把声明式代码转换为命令式的 DOM 操作,把数据层面的描述映射到用户可见的 UI 变化中,也就是说 React 的数据应该总是和渲染绑定在一起,而类组件做不到这一点,函数组件真正地把数据和渲染绑定到了一起。
为什么类组件不可以?
因为虽然
props
本身是不可变的,但是this
是可变的,this
上的数据是可以被修改的,this.props
每次都会获取最新的props
。
React-Hooks 给函数组件提供了丰富的零件,从而让函数组件发挥到极致。
# useState
useState
为函数组件引入状态,同样逻辑的函数组件相比类组件而言,复杂度要低得多。
import React, { useState } from 'react'
export default function Demo(){
const [text, setText] = useState('初始文本')
function changeText(){
return setText('修改后的文本')
}
return (
<div className='demo'>
<p>{text}</p>
<button onClick={changeText}>点击修改</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
useState
返回的是一个数组,第一个元素对应 state
变量,第二个元素对应 能够修改这个变量的 API。
在调用 React.useState
的时候,实际上是给这个组件关联了 一个 状态。
# useEffect
useEffect
允许函数组件执行副作用操作,相当于它是一个为函数组件引入副作用的钩子,在一定程度上弥补了生命周期的缺席。
import React, { useState, useEffect } from 'react'
function Increase(){
const [count, setCount] = useState(0)
useEffect(() => {
const list = document.getElementById('list')
const item = document.createElement('li')
item.innerHTML = '哈哈'
list.append(item)
})
return (
<div>
<p>count为:{count}</p>
<ul id='list'></ul>
<button onClick={() => setCount(count + 1)}>增加</button>
</div>
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 调用规则
每一次渲染后都执行的副作用:传入回调函数,不传依赖数组
useEffect(callback)
1仅在挂载阶段执行一次副作用:传入回调函数,且该函数的返回值不是一个函数,同时传入一个空数组
useEffect(() => { // 业务逻辑 }, [])
1
2
3仅在挂载阶段和卸载阶段执行的副作用:传入回调函数,且这个函数的返回值是一个函数,同时传入一个空数组
// 将在挂载阶段执行A,卸载阶段执行B useEffect(() => { // 业务逻辑A // 返回一个函数记为B return () => { } }, []) // 此处的B被叫做 “清除函数”,类似于 componentWillUnmount 的逻辑 // 只要在 useEffect 回调中返回了一个函数,它就会被作为清除函数来处理
1
2
3
4
5
6
7
8
9
10
11
12每次渲染都触发,且卸载阶段也会被触发的副作用:传入回调函数,且这个函数的返回值是一个函数,同时不传第二个参数
useEffect(() => { // 业务逻辑A // 返回一个函数记为B return (() => { }) })
1
2
3
4
5
6
7
8根据一定的依赖条件来触发副作用:传入回调函数,同时传入一个非空的数组
useEffect(() => { // 业务逻辑 // 若 xxx 是一个函数,则 xxx 会在组件卸载时被触发 return xxx }, [n1, n2]) // n1, n2 一般来源于组件本身的数据,若数组不为空,那么 react 会在一次 // 新的渲染中对比前后两次数组内的数据是否有变化,并且在有更新的前提下, // 去触发 useEffect 里的副作用逻辑
1
2
3
4
5
6
7
8
9
10
# 为什么需要 React-Hooks?
告别难以理解的 Class
Class 有两大痛点:this 和 生命周期。
- this 使用不当会有很大的问题(于是用 bind、箭头函数等解决)
- 生命周期增加了学习成本,同时也存在不合理的逻辑规划方式:逻辑曾经一度与生命周期耦合在一起。
解决业务逻辑难以拆分的问题
使状态逻辑复用变得简单可行
函数组件从设计思想上来看,更加契合 React 的理念:UI = render(data)
React-Hooks 的好处:
- 更好的逻辑拆分,按照逻辑上的关联拆分进不同的函数组件里,从而帮助实现业务逻辑的聚合,避免复杂的组价和冗余的代码。
- 状态复用,之前靠的是 HOC(高阶组件)和 Render Props 这些组件设计模式,不过这些设计模式同时也会破坏组件的结构,最常见的就是 “嵌套地域”。而 Hooks 可以看作是 React 未解决状态逻辑复用这个问题所提供的一个原生途径,现在可以通过自定义 Hook 来达到既不破坏组件结构、又能够实现逻辑复用的效果。
# 使用原则
- 只在 React 函数中调用 Hook
- 不要在循环、条件或嵌套函数中调用 Hook
- 要确保 Hooks 在每次渲染时都保持同样的执行顺序
# 原理
Hooks 的正常运作,在底层依赖于顺序链表。
# 调用链路
以 useState
为例,下图是首次渲染:

下图为更新渲染:

以上两个链路,区别就在于一个是 mountState
,一个是 updateState
:
mountState
(首次渲染)构建链表并渲染updateState
依次遍历链表并渲染
hooks 的渲染是通过 “依次遍历” 来定位每个 hooks 内容的。如果前后两次读到的链表在顺序上出现差异,那么渲染的结果是不可控的(比如你想改 name 字段,但是它却改成了 age 字段)。
hooks 的本质是链表