React Hooks 常见问题及解决方案
常见问题
-
🐤 useState 和 setState 有什么明显的区别?
-
🐤 useState 和 useReducer 的初始值如果是个执行函数返回值,执行函数是否会多次执行?
-
🐤 还原 useReducer 的初始值,为什么还原不回去了?
-
🐤 useEffect 如何模拟 componentDidMount、componentUpdate、componentWillUnmount 生命周期?
-
🐤 如何在 useEffect 中正确的为 DOM 设置事件监听?
-
🐤 useEffect、useCallback、useMemo 中取到的 state、props 中为什么会是旧值?
-
🐤 useEffect 为什么会出现无限执行的问题?
-
🐤 useEffect 中出现竞态如何解决?
-
🐤 如何在函数组件中保存一些属性,跟随组件进行创建和销毁?
-
🐤 当 useCallback 会频繁触发时,应该如何进行优化?
-
🐤 useCallback 和 useMemo 的使用场景有何区别?
一、函数组件渲染过程
先来看一下函数组件的运作方式:
Counter.js
function Counter() {
const [count, setCount] = useState(0);
return <p onClick={
() => setCount(count + 1)}>count: {
count}</p>;
}
每次点击 p
标签,count
都会 + 1,setCount
会触发函数组件的渲染。函数组件的重新渲染其实是当前函数的重新执行。
在函数组件的每一次渲染中,内部的 state
、函数以及传入的 props
都是独立的。
比如:
// 第一次渲染
function Counter() {
// 第一次渲染,count = 0
const [count, setCount] = useState(0);
return <p onClick={
() => setCount(count + 1)}>clicked {
count} times</p>;
}
// 点击 p 标签触发第二次渲染
function Counter() {
// 第二次渲染,count = 1
const [count, setCount] = useState(0);
return <p onClick={
() => setCount(count + 1)}>clicked {
count} times</p>;
}
// 点击 p 标签触发第三次渲染
function Counter() {
// 第三次渲染,count = 2
const [count, setCount] = useState(0);
return <p onClick={
() => setCount(count + 1)}>clicked {
count} times</p>;
}
// ...
在函数组件中声明的方法也是类似。因此,在函数组件渲染的每一帧对应这自己独立的
state
、function
、props
。
二、useState
/ useReducer
useState
VS setState
-
useState
只能作用在函数组件,setState
只能作用在类组件 -
useState
可以在函数组件中声明多个,而类组件中的状态值都必须声明在this
的state
对象中 -
一般的情况下,
state
改变时:-
useState
修改state
时,同一个useState
声明的值会被 覆盖处理,多个useState
声明的值会触发 多次渲染 -
setState
修改state
时,多次setState
的对象会被 合并处理
-
-
useState
修改state
时,设置相同的值,函数组件不会重新渲染,而继承Component
的类组件,即便setState
相同的值,也会触发渲染
useState
VS useReducer
初始值
useState
设置初始值时,如果初始值是个值,可以直接设置,如果是个函数返回值,建议使用回调函数的方式设置
const initCount = c => {
console.log('initCount 执行');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(initCount(0));
return <p onClick={
() => setCount(count + 1)}>clicked {
count} times</p>;
}
会发现即便 Counter
组件重新渲染时没有再给 count
重新赋初始值,但是 initCount
函数却会重复执行
修改成回调函数的方式:
const initCount = c => {
console.log('initCount 执行');
return c * 2;
};
function Counter() {
const [count, setCount] = useState(() => initCount(0));
return <p onClick={
() => setCount(count + 1)}>clicked {
count} times</p>;
}
这个时候,initCount
函数只会在 Counter
组件初始化的时候执行,之后无论组件如何渲染,initCount
函数都不会再执行
useReducer
设置初始值时,初始值只能是个值,不能使用回调函数的方式- 如果是个执行函数返回值,那么在组件重新渲染时,这个执行函数依然会执行
修改状态
useState
修改状态时,同一个useState
声明的状态会被覆盖处理
function Counter() {
const [count, setCount] = useState(0);
return (
<p
onClick={
() => {
setCount(count + 1);
setCount(count + 2);
}}
>
clicked {
count} times
</p>
);
}
当前界面中
count
的step
是 2
useReducer
修改状态时,多次dispatch
会按顺序执行,依次对组件进行渲染
function Counter() {
const [count, dispatch] = useReducer((x, payload) => x + payload, 0);
return (
<p
onClick={
() => {
dispatch(1);
dispatch(2);
}}
>
clicked {
count} times
</p>
);
}
当前界面中
count
的step
是 3
还原 useReducer
的初始值,为什么还原不了
比如下面这个例子:
const initPerson = {
name: '小明' };
const reducer = function (state, action) {
switch (action.type) {
case 'CHANGE':
state.name = action.payload;
return {
...state };
case 'RESET':
return initPerson;
default:
return state;
}
};
function Counter() {
const [person, dispatch] = useReducer(reducer, initPerson);
const [value, setValue