簡體   English   中英

當 state 在 useEffect() 中更新時,我應該如何觸發重新渲染

[英]How should I trigger a re-render when the state is updated inside useEffect()

我試圖弄清楚我應該如何使用 React 鈎子來構造我的代碼,這樣:

  1. 我有一個 state 變量,它表示我的應用程序是否正在“加載”結果
  2. loading state 在useEffect()運行以獲取要顯示的數據時設置為true
  3. useEffect()結束時,我將loading設置為false
  4. 在子組件中,這個loading state作為prop傳遞來改變各種組件

我的代碼的簡化 model 如下:

const App = () => {
   const [state, stateDispatch] = useReducer(stateReducer,{loading: false})
   useEffect(() => {
      stateDispatch({type:'loadingResults'});
      loadResults();
      stateDispatch({type:'finishedLoadingResults'});
   });
   return (
      <ExampleComponent loading={state.loading} />
   );
}

const stateReducer = (prevState,action) => {
    switch(action.type) {
      case 'loadingResults':
        return {...prevState, loading:true};
      case 'finishedLoadingResults':
        return {...prevState, loading: false};
      default:
        throw new Error();
    }

}

示例“子”組件如下:

const SampleComponent = (props) => {
    const [buttonText, setButtonText] = useState('Load More');

    useEffect(() => {
        if (props.loading) {
            setButtonText('Loading...')
        } else {
            setButtonText('Load More');
        }
    },[props.loading]);

    return (
        <div onClick={(event) => props.getBooks(event,false)} className="moreResults">{buttonText}</div>
    )
}

使用當前的設置,我永遠不會看到重新渲染子組件 - 即使看起來if (props.loading)確實被正確評估了。 例如,如果我在 if check 和 else check 中放置一個console.log ,我會在loading state 切換時看到兩者都打印出來。

或者,如果我在App組件中定義state.loadinguseEffect() function 有依賴關系,我會得到無限的重新渲染,因為我正在切換loading Z9ED39E2EA931586B6E985Ef 內部useEffect()

知道我在這里缺少什么嗎?

您的子組件中不需要額外useEffectuseState 父項中的道具正在發生變化,因此足以重新渲染您的組件。

const SampleComponent = (props) => (
  <div onClick={(event) => props.getBooks(event,false)}
   className="moreResults">{props.loading ? 'Loading...' : 'Load More'}
  </div>
)

組件僅在其任何 prop 或 state 更改時才決定重新渲染。

Javascript 由於其單線程特性而同步運行所有內容。 因此,react diffing function 以識別組件是否需要重新渲染將以同步順序發生,即在這種情況下 useEffect 鈎子執行完成之后。

useEffect(() => {
    stateDispatch({type:'loadingResults'}); 
    // -> state update happened
    // -> but re-render won't happen, since useEffect metho execution is not complete yet
    loadResults();
    stateDispatch({type:'finishedLoadingResults'});
 });

但是到那時你已經完成加載並重置 state。 由於沒有變化,組件不會重新渲染。

此外,您不能在鈎子之外設置 state,因為任何 setState 觸發器都會觸發重新渲染,這將再次設置 state,這將創建一個無限循環。

解決方案:調用 setState 以在 useEffect 塊之外的 if 條件內加載。類似這樣的東西。

const App = () => {
   const [state, stateDispatch] = useReducer(stateReducer,{loading: false})
   useEffect(() => {
      if(!state.loading) {
          loadResults();
          stateDispatch({type:'finishedLoadingResults'});
      }
   },[state.loading]);
   if(notResults) {
      stateDispatch({type:'loadingResults'});
   }
   return (
      <ExampleComponent loading={state.loading} />
   );
}
  • 首先,你的 App 組件中的 useEffect 沒有依賴列表,所以它在每次渲染時運行。 由於它設置了 state,這會導致新的重新渲染,之后您的 useEffect 會再次被調用,依此類推。所以您會得到一個無限循環。

    您必須將依賴項列表添加到 useEffect。

  • Secondly, If your loadData function is an asynchronous function (which it should be if you're fetching data), it will return a promise, so you can dispatch "finishedLoadingResults" when the promise resolves.

    這為您的 useEffect 提供了以下內容:

    useEffect(() => {
        stateDispatch({ type: "loadingResults" });
        loadResults()
        .then(() => stateDispatch({ type: "finishedLoadingResults" }));
    }, []);
  • 第三,正如 jmargolisvt 已經指出的那樣,您的子組件中不需要額外的 useEffect 。 當您的 App 組件設置“加載”state 時,這將導致 App 組件與子組件一起重新渲染,因此無需使用 buttonText state 變量。

您可以在此 Sandbox中看到工作代碼。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM