Javascript 相当于使用 React 钩子的 componentDidUpdate

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/53255951/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-23 05:05:34  来源:igfitidea点击:

Equivalent to componentDidUpdate using React hooks

javascriptreactjsreact-hooks

提问by FMCorz

tldr;How do I simulate componentDidUpdateor otherwise use the keyprop with an array to force my component to be reset?

tldr; 我如何模拟componentDidUpdate或以其他方式使用key带有数组的道具来强制重置我的组件?

I'm implementing a component which displays a timer and executes a callback when it reaches zero. The intent is for the callback to update a list of objects. The latter component is made of the new React hooksuseStateand useEffect.

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

The statecontains a reference to the time at which the timer was started, and the time remaining. The effectsets an interval called every second to update the time remaining, and to check whether the callback should be called.

state包含在该定时器启动时的基准,而剩余的时间。该effect套间隔称为每秒钟更新的剩余时间,并检查是否回调应该叫。

The component is not meant to reschedule a timer, or keep the interval going when it reaches zero, it's supposed to execute the callback and idle. In order for the timer to refresh, I was hoping to pass an array to keywhich would cause the component's state to be reset, and thus the timer would restart. Unfortunately keymust be used with a string, and therefore whether or not my array's reference has changed produces no effect.

该组件不是为了重新安排计时器,或者在它达到零时保持间隔,它应该执行回调和空闲。为了让计时器刷新,我希望传递一个数组,key该数组会导致组件的状态被重置,因此计时器将重新启动。不幸的是key必须与字符串一起使用,因此我的数组引用是否已更改不会产生任何影响。

I also tried to push changes to the props by passing the array that I was concerned about, but the state was maintained and thus the interval was not reset.

我还尝试通过传递我所关心的数组来推动对 props 的更改,但是状态被保持,因此间隔没有被重置。

What would be the preferred method to observe shallow changes in an array in order to force a state to be updated solely using the new hooks API?

观察数组中的浅层变化以强制仅使用新的钩子 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;

Attempted to use with key:

尝试使用key

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

Attempted to use with a props change:

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

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

listOfObjectsrefers to an array of objects, where the objects themselves won't necessarily change, so the array should be compared with !==. Typically, the value will be coming from Redux, where the action updateListOfObjectscauses the array to be reinitialised like so: newListOfObjects = [...listOfObjects].

listOfObjects指的是一个对象数组,其中对象本身不一定会改变,因此应该将数组与!==. 通常,该值将来自Redux,该操作updateListOfObjects会导致数组像这样重新初始化:newListOfObjects = [...listOfObjects]

回答by Morgan Cheng

The useRefcreates an "instance variable" in functional component. It acts as a flag to indicate whether it is in mount or update phase without updating state.

所述useRef创建了功能成分的“实例变量”。它作为一个标志来指示它是处于挂载还是更新阶段,而不是更新状态。

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

回答by Bear-Foot

In short, you want to reset your timer when the reference of the array changes, right ? If so, you will need to use some diffing mechanism, a pure hooks based solution would take advantage of the second parameter of useEffect, like so:

简而言之,当数组的引用发生变化时,您想重置计时器,对吗?如果是这样,您将需要使用一些差异机制,基于纯钩子的解决方案将利用 的第二个参数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
  })
}

More information about this behaviour here https://reactjs.org/docs/hooks-effect.html#tip-optimizing-performance-by-skipping-effects

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

回答by Ben Carp

use a custom hook

使用自定义钩子

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

Effect will not run after the initial render. Thereafter, it depends on the array of values that should be observed. If it's empty, it will run after every render. Otherwise, it will run when one of it's values has changed.

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

回答by Estus Flask

A way to remount a component is to provide new keyproperty. It's not necessarily a string but it will be coerced to a string internally, so if listOfObjectsis a string, it's expected that keyis compared internally with listOfObjects.toString().

重新挂载组件的一种方法是提供新key属性。它不一定是一个字符串,但它会在内部被强制转换为一个字符串,所以如果listOfObjects是一个字符串,它应该在key内部与listOfObjects.toString().

Any random key can be used, e.g. uuidor Math.random(). Shallow comparison of listOfObjectscan be performed in parent component to provide new key. useMemohook can be used in parent state to conditionally update remount key, and listOfObjectscan be used as a list of parameters that need to be memoized. Here's an example:

可以使用任何随机密钥,例如uuidMath.random()listOfObjects可以在父组件中进行浅比较以提供新的密钥。useMemo钩子可以在父状态中用于有条件地更新remount key,并且listOfObjects可以用作需要记忆的参数列表。这是一个例子

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

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

As an alternative to remount key, child component could be able to reset own state and expose a callback to trigger a reset.

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

Doing shallow comparison of listOfObjectsinside child component would be an antipattern because this requires it to be aware of parent component implementation.

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