React Hooks 是 React 16.8 的新增特性,它可以让我们在不编写 类组件(class component) 的情况下使用 state 以及其他的 React 特性(例如生命周期),所以在 React Hooks 之前,主要是编写类组件。
类组件存在的问题
- 逻辑复杂的组件难以开发和维护,组件的生命周期函数中可能包含各种互不想关的逻辑,或者同样的逻辑需要在不同的生命周期函数中实现,例如:在
componentDidMount
中请求数据,在componentDidUpdate
中判断状态变化请求数据。 - 组件状态复用,在 React Hooks 之前,逻辑复用基本是用高阶组件来实现,这种方式会有嵌套地狱 的问题
1 | export default withA( |
this
指向问题,在类组件中要搞清楚this
的指向问题,增加学习成本
React Hooks 基本能解决上面的问题,而且同一个业务用函数组件的代码量比类组件要少很多,有一些特性用类组件很难实现,所以现在 React 基本都是编写函数组件。
注意:React Hooks 只能在函数组件中使用,不能在类组件、函数组件之外的地方使用。
useState
useState
可以在组件内添加一个状态变量
1 | const [state, setState] = useState(initialState); |
参数
initialState
:初始化值,不设置为undefined
,**只在第一次渲染会用到!**
返回值
state
:当前状态的值(第一次渲染为初始化值)setState
:设置状态值的函数,调用之后组件重新渲染,并且根据新的值返回DOM结构
用useState
写一个计数器的例子:如果初始化值需要写一些业务代码的话:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import { memo, useState } from "react";
function Counter(props) {
const [counter, setCounter] = useState(0)
return (
<div>
<h2>当前计数: {counter}</h2>
<button onClick={e => setCounter(counter+1)}>+1</button>
<button onClick={e => setCounter(counter-1)}>-1</button>
</div>
)
}1
2
3const [counter, setCounter] = useState(() => {
return 0
})
useEffect
useEffect
可以让你完成一些类似类组件生命周期的功能,通常用来执行一些副作用的代码。
副作用是指一个 function
做了和本身运算返回值(渲染)无关的事,类似于网络请求、修改DOM、事件监听等
1 | useEffect(setup, dependencies?) |
参数
setup
:是一个回调函数,当组件被添加到 DOM 中会被执行,在这里执行副作用的代码,这个回调函数可以返回另一个回调函数cleanup
(清除函数),当组件在 DOM 中被移除会执行cleanup
dependencies
:可选,是一个数组,表示该useEffect
受哪些state
的影响,如果有state
被修改的话,会执行cleanup
,然后再执行setup
。如果不传那么每一次重新渲染就会执行Effect
,如果传空数组[]
表示不依赖任何state
,适合只执行一次的操作
可以写多个 useEffect
把不同的逻辑分离,调用顺序是每一个 effect
的顺序
1 |
|
根据 useEffect
的机制如果监听某一个属性被改变也可以用
useContext
在 React Hooks 之前如果组件中需要多个 Context
数据时,会有大量的嵌套问题,推荐用 useContext
1 | import React, { memo, useContext } from 'react' |
useCallback
先了解一下 memo
memo
先简单介绍一下 memo
的机制,如果希望子组件避免没必要的重新渲染,可以用这个高阶组件,只要 props
没有发生变化,那么就不会重新渲染
1 | import { memo } from 'react'; |
useCallback 运行机制
useCallback
可以在组件渲染之间保存函数定义
1 | const cachedFn = useCallback(fn, dependencies) |
参数
fn
:希望被缓存的函数,第一次渲染的时候会返回这个函数(不会调用),下次渲染的时候如果依赖没有改变会返回上一次渲染的函数,如果改变会返回这次传入的函数dependencies
:依赖值,一般是fn
中用到的参数
useCallback
可以做性能优化,让子组件避免没必要的重新渲染
1 |
|
当按钮添加的时候执行 setCount
导致 App 组件重新渲染,会重新定义一个新的 increment
函数,那么 ChildComponent 的 memo
不会生效,从而使 ChildComponent 组件渲染,但 ChildComponent 重新渲染是没必要的,如果它还有大量子组件的话会导致性能严重。
使用 useCallback
优化:
1 |
|
useMemo
useMemo
可以在组件渲染之间缓存函数的返回值
1 | const cachedValue = useMemo(calculateValue, dependencies) |
参数
calculateValue
:回调函数,这个函数的返回值就是想要缓存的值dependencies
:依赖返回值
cachedValue
:在第一次渲染时候会调用calculateValue
,然后返回calculateValue
的返回值,下一次渲染的时候,如果dependencies
没有改变,那么会返回上一次渲染的值,不需要重新计算。
1 |
|
和 useCallback 的区别
以下写法是等价的:
1 | const increment = useCallback(fn, []) |
useRef
useRef
返回一个对象,这个对象在组件的整个生命周期保持不变。常见的使用常见
持有 DOM
保存一个数据
持有 DOM 的案例:
1 |
|
useImperativeHandle
useImperativeHandle
在使用 ref
时可以自定义暴露给父组件的函数
1 |
|
参数
ref
:父组件传过来的ref
createHandle
:回调函数,这个函数返回一个对象,包含想要暴露给ref
使用的函数dependencies
:可选参数,依赖,如果依赖的值改变了createHandle
会重新调用
使用场景:
- 限制某一个DOM的操作权限
- 暴露方法给父组件
1 |
|
App 组件的 inputRef
并没有直接绑定到 A 组件的 input
元素,所以只能调用 focus
函数
useLayoutEffect
和 useEffect
类似,不过会在组件渲染之前执行回调函数,**会损耗性能!**
使用场景:
- 如果渲染的内容不是想要的,可以在这里修改再进行渲染
1 |
|
useTransition
useTransition
可以让优先级较低或渲染比较耗时的组件稍后渲染
1 | const [isPending, startTransition] = useTransition() |
返回值
- isPending
:是否在过渡状态
startTransition
:函数,让优先级低的状态更新在这里处理有点类似防抖(debounce)和节流(throttle),但是对比这两个有以下优势: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
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import React, { memo, useState, useTransition } from 'react'
import namesArray from './namesArray'
const App = memo(() => {
const [ names, setNames ] = useState(namesArray)
const [ isPending, startTransition ] = useTransition()
function valueChangeHandle(event) {
startTransition(() => {
const keyword = event.target.value
const filterNames = namesArray.filter(item => item.includes(keyword))
setNames(filterNames)
})
}
return (
<div>
<input type="text" onInput={valueChangeHandle}/>
<h2>列表: {isPending && <span>loading</span>} </h2>
<ul>
{
names.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
})
export default App- 更新协调过程是**可中断的**,渲染引擎不会长时间被阻塞,用户可以及时得到响应
- 不需要开发人员去做额外的考虑, 整个优化过程交给
react
和浏览器
就行
useDeferredValue
useDeferredValue
可以让 UI 延迟更新
1 | const deferredValue = useDeferredValue(value) |
参数
value
:想要延迟更新的值
返回值
deferredValue
:第一次渲染的时候返回value
,在下一次渲染的时候会延迟更新(返回旧值),最终拿到最新值重新渲染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
41
42
43
44
45
46
47
48
49
50
51
52
import React, { memo, useState, useTransition } from 'react'
import namesArray from './namesArray'
const App = memo(() => {
const [ names, setNames ] = useState(namesArray)
const deferredNames = useDeferredValue(names)
function valueChangeHandle(event) {
startTransition(() => {
const keyword = event.target.value
const filterNames = namesArray.filter(item => item.includes(keyword))
setNames(filterNames)
})
}
return (
<div>
<input type="text" onInput={valueChangeHandle}/>
<h2>列表: {isPending && <span>loading</span>} </h2>
<ul>
{
deferredNames.map((item, index) => {
return <li key={index}>{item}</li>
})
}
</ul>
</div>
)
})
export default App