使用 useState 多次渲染问题
使用hooks
时经常会写出下面的代码,然后就会发现页面渲染了两遍,有时候会更头疼。
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(async () => {
const res = await axios.get("xxx");
setLoading(false);
setData(res);
}, []);
在React
中,同步代码会合并渲染,异步代码不会合并渲染。
下面的代码只会渲染一次,它会将setLoading
和setData
进行合并。这个其实和类组件是一样的,在异步函数中不会合并setState
。
const [loading, setLoading] = useState(true);
const [data, setData] = useState(null);
useEffect(() => {
setLoading(false);
setData({ a: 1 });
}, []);
类组件中解决多次渲染问题比较好弄,但是在hooks
就比较麻烦。
方法一:将多个状态合并到一个状态中
将所有的依赖状态都放到一个对象中,在setState
一起设置,就能解决多次渲染的问题了,如下代码
const [request, setRequest] = useState({ loading: true, data: null });
useEffect(async () => {
const res = await axios.get("xxx");
setRequest({ loading: false, data: res });
}, []);
但是这样有个问题,如果只想setState
一个依赖项时,需要将别的依赖项也要传进去,否则这个值会丢失。React
内部并不会帮你做去合并。
setRequest({ data: res }); // loading 值丢失了。
解决方法是使用扩展运算符
setRequest({ ...request, data: res });
// 或者
setRequest((prevState) => ({ ...prevState, data: res }));
方法二:写一个自定义合并依赖项的hook
每次setState
都要使用扩展运算符合并依赖项太麻烦了。
使用React
自定义hook
功能,写一个合并依赖项的useMergeState
钩子。
自定义hook
需要使用use
开头。
const useMergeState = (initialState) => {
const [state, setState] = useState(initialState);
const setMergeState = (newState) =>
setState((prevState) => ({ ...prevState, newState }));
return [state, setMergeState];
};
/* 使用 */
const [request, setRequest] = useMegeState({ loading: false, data: null });
useEffect(async () => {
const res = await axios.get("xxx");
setRequest({ loading: true, data: res });
// ...
setRequest({ data: { a: 1 } }); // loading 状态不会丢失,还是 true
}, []);
方案三:使用useReducer
React
提供了useReducer
来管理各个依赖项,而不是使用useState
。
const [request, setRequest] = useReducer(
(prevState, newState) => ({ ...prevState, newState }),
{ loading: false, data: null }
);
useEffect(async () => {
const res = await axios.get("xxx");
setRequest({ loading: true, data: res });
// ...
setRequest({ data: { a: 1 } }); // loading 状态不会丢失,还是 true
}, []);
如果想要获取上一个状态,需要对上面的代码进行改造。
const [request, setRequest] = useReducer(
(prevState, newState) => {
const newWithPrevState = typeof newState === "function" ? newState(prevState) : newState;
return{ ...prevState, newWithPrevState })
},
{ loading: false, data: null }
);
useEffect(async () => {
const res = await axios.get("xxx");
setRequest((prevState) => {
return { loading: true, data: res }
});
// ...
setRequest({ data: { a: 1 } }); // loading 状态不会丢失,还是 true
}, []);
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。