等效于使用React钩子的componentDidUpdate

时间:2018-11-12 04:24:45

标签: javascript reactjs react-hooks

tldr; 如何模拟componentDidUpdate或将key与数组一起使用以强制重置组件?

我正在实现一个显示计时器的组件,并在该组件达到零时执行回调。目的是让回调更新对象列表。后一部分由新的React hooks useStateuseEffect组成。

state包含对计时器启动时间和剩余时间的引用。 effect设置每秒调用一次的间隔,以更新剩余时间,并检查是否应调用回调。

该组件无意于重新安排计时器的时间,也不打算在间隔达到零时保持间隔,而是应该执行回调和空闲。为了刷新计时器,我希望将数组传递给key,这将导致组件状态被重置,因此计时器将重新启动。不幸的是,key必须与字符串一起使用,因此数组的引用是否已更改都没有效果。

我还尝试通过传递我关注的数组来对道具进行更改,但是状态得以保持,因此间隔没有重置。

观察数组中的浅层变化以强制仅使用新的hooks API更新状态的首选方法是什么?

import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';

function getTimeRemaining(startedAt, delay) {
    const now = new Date();
    const end = new Date(startedAt.getTime() + delay);
    return Math.max(0, end.getTime() - now.getTime());
}

function RefresherTimer(props) {
    const [startedAt, setStartedAt] = useState(new Date());
    const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

    useEffect(() => {

        if (timeRemaining <= 0) {
            // The component is set to idle, we do not set the interval.
            return;
        }

        // Set the interval to refresh the component every second.
        const i = setInterval(() => {
            const nowRemaining = getTimeRemaining(startedAt, props.delay);
            setTimeRemaining(nowRemaining);

            if (nowRemaining <= 0) {
                props.callback();
                clearInterval(i);
            }
        }, 1000);

        return () => {
            clearInterval(i);
        };
    });

    let message = `Refreshing in ${Math.ceil(timeRemaining / 1000)}s.`;
    if (timeRemaining <= 0) {
        message = 'Refreshing now...';
    }

    return <div>{message}</div>;
}

RefresherTimer.propTypes = {
    callback: PropTypes.func.isRequired,
    delay: PropTypes.number
};

RefresherTimer.defaultProps = {
    delay: 2000
};

export default RefresherTimer;

尝试与key一起使用:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} key={listOfObjects} />

尝试与道具更改一起使用:

<RefresherTimer delay={20000} callback={props.updateListOfObjects} somethingThatChanges={listOfObjects} />

listOfObjects是指对象数组,其中对象本身不一定会更改,因此应将该数组与!==进行比较。通常,值将来自Redux,其中操作updateListOfObjects导致数组重新初始化,如下所示:newListOfObjects = [...listOfObjects]

6 个答案:

答案 0 :(得分:2)

简而言之,您想在数组的引用更改时重置计时器,对吧? 如果是这样,您将需要使用一些差异机制,基于纯钩子的解决方案将利用useEffect的第二个参数,如下所示:

function RefresherTimer(props) {
    const [startedAt, setStartedAt] = useState(new Date());
    const [timeRemaining, setTimeRemaining] = useState(getTimeRemaining(startedAt, props.delay));

    //reset part, lets just set startedAt to now
    useEffect(() => setStartedAt(new Date()),
      //important part
      [props.listOfObjects] // <= means: run this effect only if any variable
      // in that array is different from the last run
    )

    useEffect(() => {
    // everything with intervals, and the render
}

有关此行为的更多信息,请点击https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

答案 1 :(得分:2)

useRef在功能组件中创建一个“实例变量”。它充当标志以指示它是处于挂载阶段还是处于更新阶段而不更新状态。

const mounted = useRef();
useEffect(() => {
  if (!mounted.current) {
    mounted.current = true;
  } else {
    // do componentDidUpate logic
  }
});

答案 2 :(得分:1)

先创建钩子

function usePrevious(value) {
  const ref = useRef();
  useEffect(() => {
    ref.current = value;
  }, [value]);
  return ref.current;
}

现在在主函数中

import React, {useEffect, useState} from 'react';
import {Text, View} from 'react-native';
import usePrevious from './usePrevious';

export default function Example() {
  const [count, setCount] = useState(0);
  const prevCount = usePrevious(count);
  

  useEffect(() => {
    // this one is your didupdate method for count variable
    if (count != prevCount) {
      alert('count updated')
    }
  }, [count]);



  return (
    <View>
      <Text>
        You clicked {count} times {prevCount}{' '}
      </Text>
      
      <Text onPress={() => setCount(count + 1)}>Increment</Text>

      <Text onPress={() => setCount(count - 1)}>Decrement</Text>
    </View>
  );
}

答案 3 :(得分:0)

一种重新安装组件的方法是提供新的key属性。它不一定是字符串,但会在内部强制转换为字符串,因此,如果listOfObjects是字符串,则可以将keylistOfObjects.toString()在内部进行比较。

可以使用任何随机密钥,例如uuidMath.random()。可以在父组件中执行listOfObjects的浅表比较,以提供新的密钥。可以在父状态下使用useMemo挂钩来有条件地更新重装密钥,并且listOfObjects可以用作需要记忆的参数列表。这是example

  const remountKey = useMemo(() => Math.random(), listOfObjects);

  return (
    <div>
      <RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
    </div>
  );

作为重新安装密钥的替代方法,子组件可以重置自己的状态并公开回调以触发重置。

在子组件内部进行listOfObjects的浅表比较将是一种反模式,因为这需要它了解父组件的实现。

答案 4 :(得分:0)

使用自定义钩子

export const useComponentDidUpdate = (effect: () => unknown ,observe: any[]) => {
    const hasMounted = useRef(false)
    useEffect(()=>{
        if(!hasMounted.current){
            hasMounted.current=true 
            return 
        }
        effect()
    }, observe)
}

效果在初始渲染后将不会运行。此后,它取决于应观察的值数组。如果为空,它将在每次渲染后运行。否则,它将在其中一个值更改时运行。

答案 5 :(得分:0)

您可以使用 react-use 中的 useUpdateEffect