React Hooks
- State Hook
- Effect Hook
- 其他 Hook
- 自定义 Hook
- 组件逻辑复用
- 规范和注意事项
一、 React Hooks的背景
1. 函数组件的特点
- 没有组件实例
- 没有生命周期
- 没有 state 和 setState,只能接收 props
2. class组件的问题
- 大型组件很难拆分和重构,很难测试(即class不易拆分)
- 相同业务逻辑,分散到各个方法中,逻辑混乱
- 复用逻辑变得复杂,如 Mixins、HOC、Render Props
3. React组件更容易用函数表达
- React提倡函数式编程
- 函数更灵活,更容易拆分和测试
- 函数组件太简单,需要增强能力---Hooks
二、State Hooks
让函数组件实现 state 和 setState
- 默认函数组件没有state
- 函数组件是一个纯函数,执行完即销毁,无法存储 state
- 需要 State Hook,即把 state 功能“钩”到纯函数中
// 写函数组件时,要引入 React,否则就是一个普通函数
import React, { useState } from 'react'
function ClickCounter() {
// useState是一个 Hook,需要一个初始值,返回一个数组 [ state, setState ]
const [count, setCount] = useState(0)
return <div>
<p>你点击了 { count }</p>
<button onClick={() => setCount(count + 1)} ></button>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13
1. useState使用总结
useState(initialVal)
传入初始值,初始值可以是基本类型,也可以是对象,返回[ state, setState ]
- 通过
state
获取值 - 通过
setState
修改值,遵循不可变值规范
2. Hooks 命名规范
- 规定所有的 Hooks 都用
use
开头,如useXxx
- 自定义 Hook 也要 以
use
开头 - 非 Hooks 的地方,尽量不要使用
useXxx
写法
三、Effect Hooks
让函数组件模拟生命周期
- 默认函数组件没有生命周期
- 函数组件是一个纯函数,执行完即销毁,自己无法实现生命周期
- 使用
Effect Hook
把生命周期“钩”到纯函数中
// 写函数组件时,要引入 React,否则就是一个普通函数
import React, { useState, useEffect } from 'react'
function ClickCounter() {
// useState是一个 Hook,需要一个初始值,返回一个数组 [ state, setState ]
const [count, setCount] = useState(0)
// 1. 模拟 componentDitMount 和 componentDitUpdate
useEffect(() => {
// TODO
})
// 2. 模拟 componentDitMount
useEffect(() => {
// TODO
}, [ ] )
// 3. 模拟 componentDitUpdate
useEffect(() => {
// TODO
}, [ count ] )
// 4. 模拟 componentWillUnMount
useEffect(() => {
// TODO
return () => {
// TODO
}
}, [ count ] )
return <div>
<p>你点击了 { count }</p>
<button onClick={() => setCount(count + 1)} ></button>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
1. useEffect使用总结
- 模拟
componentDitMount
-useEffect
依赖[ ]
- 模拟
componentDitUpdate
-useEffect
无依赖,或依赖[ a, b ]
- 模拟
componentWillUnMount
-useEffect
中返回一个函数
2. useEffect让纯函数有了副作用
- 默认情况下,执行纯函数,输入参数,返回结果,无副作用
- 所谓副作用,就是对函数之外造成影响,如设置全局定时任务
- 而组件需要副作用,所以需要
useEffect
钩,如纯函数
3. useEffect中返回的函数 fn
useEffect
依赖[ ]
,组件销毁时执行fn
,等价于componentWillUnMount
useEffect
无依赖或依赖[ a, b ]
,组件更新时执行fn
,即下一次执行useEffect
之前,就会执行fn
,无论更新或卸载
四、其他 Hooks
- useRef
- useContext
- useReducer
- useMemo
- useCallback
1. useRef
import React, { useRef, useEffect } from 'react'
function UseRef() {
const btnRef = useRef(null)
useEffect(() => {
console.log(btnRef.current)
}, [ ] )
return <div>
<button ref={btnRef}></button>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2. useContext
import React, { useContext } from 'react'
function ThemeButton() {
const theme = useContexts(ThemeContext)
return <button style={background: them.background}></button>
}
1
2
3
4
5
6
7
2
3
4
5
6
7
3. useReducer
import React, { useReducer } from 'react'
function reducer(state = 0, action) {
switch(action) {
case 'increment':
return {count: state.count + 1}
case 'decrement':
return {count: state.count - 1}
default:
return state
}
}
function ReducerDemo() {
const [state, dispatch] = useReducer(reducer, initialState)
return <div>
<p>{state.count}</p>
<button onClick={() => dispatch('increment')}>加1</button>
<button onClick={() => dispatch('decrement')}>减1</button>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
useReducer和redux的区别
- useReducer是useState的代替方案,用于state复杂变化
- useReducer是单个组件状态管理,组件通讯还需要props
- redux是全局状态管理,多组件共享数据
4. useMemo做性能优化
import React, {memo, useMemo} from 'react'
// 子组件
const Child = memo( ( { userInfo } ) => {
return <div>
姓名:{userInfo.name}-年龄:{userInfo.age}
</div>
})
// 父组件
function Parent() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
// const userInfo = {name, age: 20}
// 用 useMemo 缓存数据,有依赖
const userInfo = useMemo(() => {
return {
name,
age: 21
}
}, [name])
return <div>
<p>
count is {count}
<button onClick={() => setCount(count + 1)}>click</button>
<Child userInfo={userInfo}></Child>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
使用总结
- React中,父组件更新时,默认情况下会更新所有子组件
- class组件使用 SCU 和 PureComponent 做优化
- Hooks 中使用 useMemo,但优化的原理是相同的
5. useCallback做性能优化
import React, {memo, useState, useMemo} from 'react'
// 子组件
const Child = memo( ( { userInfo, onChange } ) => {
return <div>
姓名:{userInfo.name}-年龄:{userInfo.age}
<input onChange={onChange}></input>
</div>
})
// 父组件
function Parent() {
const [count, setCount] = useState(0)
const [name, setName] = useState('张三')
// const userInfo = {name, age: 20}
// 用 useMemo 缓存数据,有依赖
const userInfo = useMemo(() => {
return {
name,
age: 21
}
}, [name])
// function onChange(e) {
// console.log(e.target.value)
// }
const onChnage = useCallback(e => {
console.log(e.target.value)
})
return <div>
<p>
count is {count}
<button onClick={() => setCount(count + 1)}>click</button>
<Child userInfo={userInfo} onChange={onChnage}></Child>
</div>
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
使用总结
- useMemo缓存数据
- useCallback缓存函数
- 两者是 React Hooks 的常见优化策略
五、自定义 Hook
- 封装通用的功能
- 开发和使用第三方 Hooks
- 自定义 Hook 带来了无限的扩展性,解耦代码
import React, { useState, useEffect } from 'react'
import axios from 'axios'
// 必须使用 useXxx 格式
function useAxios(url) {
const [loading, setLoading] = useState(false)
const [data, setData] = useState()
const [error, setError] = useState()
useEffect(() => {
setLoading(true)
axios.get(url)
.then(res => setData(res))
.catch(error => setError(error))
.finally(() => setLoading(false))
}, [])
return [loading, data, error]
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
七、Hooks 使用规范
- 只能用于React函数组件和自定义Hook中,其他地方不可以
- 只能用于顶层代码,不能再循环、判断中使用Hooks,且不能被打断,如没执行到hook就return
- eslint插件 eslint-plugin-react-hooks可以检测
八、Hooks 逻辑复用
1. Mixins、HOC和Render Props逻辑复用的弊端
(1)Mixins
- 变量来源不清
- 变量冲突
- 调用顺序复杂混乱
(2)HOC
- 组件层级嵌套过多,不易渲染,不易调试
- HOC 会劫持 props,必须严格遵守规范,容易出现疏漏
(3)Render Props
- 学习成本高,不易理解
- 只能传递纯函数,而默认情况下纯函数功能有限
2. Hooks 做组件逻辑复用的好处
- 完全符合 Hooks 原有规范,没有其他要求,易理解记忆
- 变量来源和作用域明确
- 不会产生组件嵌套
九、React Hooks 注意事项
- useState初始化值,只有第一次有效(即re-render时,如果初始值变化,不通过setState修改,初始值不变。)
- useEffect内部不能修改 state(依赖为
[ ]
时,re-render时不会重新执行effect函数,此时修改state无效。如果有对应依赖时,re-render时会执行effect函数,修改state有效) - useEffect可能出现死循环(依赖为对象或数组时,原因是底层使用
Object.is
比较新旧数据时候有变化,比较数组或对象不相等,值类型和引用类型)