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 中被移除会执行cleanupdependencies:可选,是一个数组,表示该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:父组件传过来的refcreateHandle:回调函数,这个函数返回一个对象,包含想要暴露给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