使用 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
}, []);