反应挂钩,提供上下文,但不要在状态更改时重新呈现子级

时间:2019-08-19 22:40:54

标签: reactjs react-hooks

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

这是包装纸:

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);

我的问题是,为什么孩子每更新几秒钟就会重新渲染一次,我该如何防止呢?我是否需要拆分上下文,以便秒数一个,而定时器一个呢?

1 个答案:

答案 0 :(得分:1)

上下文值是每个渲染的新对象,因此每次第二次更新

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

您希望使用秒的组件更新为seconds的值,而只使用控件的组件永远不会重新渲染。

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

类似这样的东西:(可能不起作用)

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);