相当于使用 React hooks 的 componentDidUpdate
Equivalent to componentDidUpdate using React hooks
tldr; 如何模拟 componentDidUpdate
或以其他方式使用带有数组的 key
道具来强制重置我的组件?
我正在实现一个组件,它显示一个计时器并在它达到零时执行回调。目的是让回调更新对象列表。后一个组件由新的 React hooks useState
和 useEffect
组成。
state
包含对计时器启动时间和剩余时间的引用。 effect
设置一个间隔,每秒调用一次以更新剩余时间,并检查是否应调用回调。
该组件不是为了重新安排计时器,也不是为了在间隔达到零时保持间隔,它应该执行回调和空闲。为了让计时器刷新,我希望将一个数组传递给 key
,这会导致组件的状态被重置,从而计时器会重新启动。不幸的是 key
必须与字符串一起使用,因此无论我的数组引用是否已更改都不会产生任何影响。
我也尝试通过传递我关心的数组来推送对道具的更改,但状态保持不变,因此间隔没有重置。
为了强制仅使用新挂钩更新状态,观察数组中浅层变化的首选方法是什么?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]
.
重新安装组件的一种方法是提供新的 key
属性。它不一定是一个字符串,但它会在内部被强制转换为一个字符串,所以如果 listOfObjects
是一个字符串,则预计 key
在内部与 listOfObjects.toString()
.
进行比较
可以使用任何随机密钥,例如uuid
或 Math.random()
。 listOfObjects
的浅比较可以在父组件中进行,以提供新的键。 useMemo
hook可以在parent state中使用,有条件地更新remount key,listOfObjects
可以作为需要记忆的参数列表。这是一个 example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
作为重新安装密钥的替代方法,子组件可以重置自己的状态并公开回调以触发重置。
在子组件内部对 listOfObjects
进行浅层比较将是一种反模式,因为这需要它了解父组件的实现。
简而言之,您想在数组引用更改时重置计时器,对吗?
如果是这样,您将需要使用一些差异机制,基于纯钩子的解决方案将利用 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
useRef
在功能组件中创建了一个“实例变量”。它作为一个标志来指示它是否处于挂载或更新阶段而不更新状态。
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
// do componentDidMount logic
mounted.current = true;
} else {
// do componentDidUpdate logic
}
});
使用自定义挂钩
export const useComponentDidUpdate = (effect, dependencies) => {
const hasMounted = useRef(false);
useEffect(
() => {
if (!hasMounted.current) {
hasMounted.current = true;
return;
}
effect();
},
dependencies
);
};
初始渲染后不会运行效果。此后,它取决于应观察的值数组。如果它是空的,它会在每次渲染后 运行。否则,它会 运行 当其中一个值发生变化时。
先创建挂钩
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>
);
}
您可以使用 react-use
中的 useUpdateEffect
。
通用 TypeScript 版本:
import { DependencyList, useEffect, useRef } from "react"
type Destructor = () => void
type MountEffectCallback = (firstLoad: boolean) => (void | Destructor)
export const useDidUpdateEffect = (effect: MountEffectCallback, deps: DependencyList) => {
const firstLoad = useRef(true)
useEffect(() => {
effect(firstLoad.current)
if (firstLoad.current) {
firstLoad.current = false
}
}, deps)
}
tldr; 如何模拟 componentDidUpdate
或以其他方式使用带有数组的 key
道具来强制重置我的组件?
我正在实现一个组件,它显示一个计时器并在它达到零时执行回调。目的是让回调更新对象列表。后一个组件由新的 React hooks useState
和 useEffect
组成。
state
包含对计时器启动时间和剩余时间的引用。 effect
设置一个间隔,每秒调用一次以更新剩余时间,并检查是否应调用回调。
该组件不是为了重新安排计时器,也不是为了在间隔达到零时保持间隔,它应该执行回调和空闲。为了让计时器刷新,我希望将一个数组传递给 key
,这会导致组件的状态被重置,从而计时器会重新启动。不幸的是 key
必须与字符串一起使用,因此无论我的数组引用是否已更改都不会产生任何影响。
我也尝试通过传递我关心的数组来推送对道具的更改,但状态保持不变,因此间隔没有重置。
为了强制仅使用新挂钩更新状态,观察数组中浅层变化的首选方法是什么?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]
.
重新安装组件的一种方法是提供新的 key
属性。它不一定是一个字符串,但它会在内部被强制转换为一个字符串,所以如果 listOfObjects
是一个字符串,则预计 key
在内部与 listOfObjects.toString()
.
可以使用任何随机密钥,例如uuid
或 Math.random()
。 listOfObjects
的浅比较可以在父组件中进行,以提供新的键。 useMemo
hook可以在parent state中使用,有条件地更新remount key,listOfObjects
可以作为需要记忆的参数列表。这是一个 example:
const remountKey = useMemo(() => Math.random(), listOfObjects);
return (
<div>
<RefresherTimer delay={3000} callback={() => console.log('refreshed')} key={remountKey} />
</div>
);
作为重新安装密钥的替代方法,子组件可以重置自己的状态并公开回调以触发重置。
在子组件内部对 listOfObjects
进行浅层比较将是一种反模式,因为这需要它了解父组件的实现。
简而言之,您想在数组引用更改时重置计时器,对吗?
如果是这样,您将需要使用一些差异机制,基于纯钩子的解决方案将利用 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
useRef
在功能组件中创建了一个“实例变量”。它作为一个标志来指示它是否处于挂载或更新阶段而不更新状态。
const mounted = useRef();
useEffect(() => {
if (!mounted.current) {
// do componentDidMount logic
mounted.current = true;
} else {
// do componentDidUpdate logic
}
});
使用自定义挂钩
export const useComponentDidUpdate = (effect, dependencies) => {
const hasMounted = useRef(false);
useEffect(
() => {
if (!hasMounted.current) {
hasMounted.current = true;
return;
}
effect();
},
dependencies
);
};
初始渲染后不会运行效果。此后,它取决于应观察的值数组。如果它是空的,它会在每次渲染后 运行。否则,它会 运行 当其中一个值发生变化时。
先创建挂钩
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>
);
}
您可以使用 react-use
中的 useUpdateEffect
。
通用 TypeScript 版本:
import { DependencyList, useEffect, useRef } from "react"
type Destructor = () => void
type MountEffectCallback = (firstLoad: boolean) => (void | Destructor)
export const useDidUpdateEffect = (effect: MountEffectCallback, deps: DependencyList) => {
const firstLoad = useRef(true)
useEffect(() => {
effect(firstLoad.current)
if (firstLoad.current) {
firstLoad.current = false
}
}, deps)
}