使用useState多次渲染問題

uccs發表於2021-12-17

使用 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中,同步程式碼會合並渲染,非同步程式碼不會合並渲染。

下面的程式碼只會渲染一次,它會將setLoadingsetData進行合併。這個其實和類元件是一樣的,在非同步函式中不會合並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
}, []);

參考文章:https://stackoverflow.com/que...

相關文章