【问题标题】:React hooks, provide context but don't rerender children on state change反应钩子,提供上下文但不重新渲染状态变化的孩子
【发布时间】:2019-08-19 22:40:54
【问题描述】:

我的目标是能够有一个儿童可以访问的计时器。问题是只有少数组件需要秒值,但许多组件需要能够操作计时器(暂停、更改值、重置等)。我的解决方案是将所有子项都包含在其中,但访问它的子项仍会在每次第二次更新时呈现 - 即使我没有在上下文中解压缩秒数。

这是包装器:

import * as React from 'react';
import Timer, {ITimerSettings} from '../../utilities/Timer';

export const TimerContext = React.createContext({
   seconds: null,
   setTimer: null,
   timer: null
});
export const TimerProvider = TimerContext.Provider;
export const TimerConsumer = TimerContext.Consumer;

interface ITimerWrapperProps {
   isPaused: boolean;
   isReady: boolean;
   children: any;
}

const TimerWrapper: React.FC<ITimerWrapperProps> = ({isReady, isPaused, children}) => {

   const timer = React.useRef<Timer>(new Timer());
   const [seconds, setSeconds] = React.useState<number>(null);

   React.useEffect(() => {
      if (isReady && timer.current && timer.current.duration) {
         isPaused ? timer.current.stop() : timer.current.start();
      }
   }, [isReady, isPaused]);

   const setTimer = React.useCallback((settings: Partial<ITimerSettings>): void => {
      if (timer.current) {
         timer.current.reset({
            callback: i => setSeconds(i),
            ...settings
         });
      }
   }, []);

   return (
      <TimerProvider value={{seconds, timer, setTimer}}>
         {children}
      </TimerProvider>
   );
};

export default React.memo(TimerWrapper);

这是孩子访问它的方式:

   const {timer, setTimer} = React.useContext(TimerContext);

我的问题是,为什么每次更新秒数时孩子都会重新渲染,我该如何防止它?我是否需要拆分上下文,以便有一个用于秒数和一个用于计时器?

【问题讨论】:

  • 尝试直接传递计时器引用,而不是timer.current,因为timer.current 每次更新计时器都有一个新值(如果我理解正确的话)并尝试在引用中存储秒数。您可能不想在每个计时器滴答声中更新任何道具
  • 好主意 - 我尝试只传递 ref 并使用 timer.current 访问它,但这似乎不起作用。

标签: reactjs react-hooks


【解决方案1】:

上下文值是每次渲染的新对象,所以每次第二次更新

<TimerProvider value={ {seconds, timer, setTimer} }>

您希望使用秒数的组件使用seconds 值更新,并且只使用控件的组件从不重新呈现。

我想我会将其拆分为TimerValueContext 和TimerControlsContext。控件的值始终具有相同的实例。然后消费者可以选择一个或两个。

类似这样的:(可能无法正常工作的代码)

const TimerControlsContext = React.createContext({
   setTimer: null,
   timer: null
});

const TimerValueContext = React.createContext(0);

export const useTimerValue = () => {
    const context = useContext(TimerValueContext);
    if (context) return context;
    throw new Error('Outside of provider!');
};

export const useTimerControls = () => {
    const context = useContext(TimerControlsContext);
    if (context) return context;
    throw new Error('Outside of provider!');
};

interface ITimerWrapperProps {
   isPaused: boolean;
   isReady: boolean;
   children: any;
}

const TimerWrapper: React.FC<ITimerWrapperProps> = ({isReady, isPaused, children}) => {

   const timer = React.useRef<Timer>(new Timer());
   const [seconds, setSeconds] = React.useState<number>(null);

   React.useEffect(() => {
      if (isReady && timer.current && timer.current.duration) {
         isPaused ? timer.current.stop() : timer.current.start();
      }
   }, [isReady, isPaused]);

   const setTimer = React.useCallback((settings: Partial<ITimerSettings>): void => {
      if (timer.current) {
         timer.current.reset({
            callback: i => setSeconds(i),
            ...settings
         });
      }
   }, []);


   const [controlsInstance, _] = React.useState({timer, setTimer});

   return (
      <TimerControlsContext.Provider value={controlsInstance}>
            <TimerValueContext.Provider value={seconds}>
                 {children}
           <TimerValueContext.Provider>
      </TimerControlsContext>
   );
};

export default React.memo(TimerWrapper);

【讨论】:

  • 这似乎有效 - 将 getter 和 setter 分成两个不同的上下文!我唯一感到困惑的是,为什么需要将控件置于自己的状态?我认为通过为计时器使用 ref 并为 setTimer 函数使用 useCallback 就没有必要了?
  • 我认为&lt;TimerProvider value={ {seconds, timer, setTimer} }&gt; 是关键。每次都有一个新对象的值,即使消费者只解构未更改的timer 实例,当seconds 值导致它更新时,包含它的匿名对象也发生了变化。
  • 我询问状态的原因是我先用单独的提供程序尝试了它,但没有使用控件实例部分(我只是直接传入了计时器和 setTimer),它没有工作。不过,当我添加 useState 时它起作用了。
猜你喜欢
  • 2020-08-19
  • 2016-07-07
  • 1970-01-01
  • 2019-11-23
  • 1970-01-01
  • 2016-12-18
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
相关资源
最近更新 更多