繁体   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