假设我有3个输入:rate、sendAmount和receiveAmount。我把这3个输入放到useEffect上。规则如下:
如果sendAmount改变了,我计算receiveAmount = sendAmount * rate
如果receiveAmount改变了,我计算sendAmount = receiveAmount / rate
如果速率发生变化,当sendAmount > 0时,我计算receiveAmount = sendAmount * rate;当receiveAmount > 0时,我计算sendAmount = receiveAmount / rate
下面是代码和框https://codesandbox.io/s/pkl6vn7x6j来演示这个问题。
是否有一种方法来比较oldValues和newValues,如componentDidUpdate,而不是为这种情况下做3处理程序?
谢谢
下面是我使用usePrevious的最终解决方案
https://codesandbox.io/s/30n01w2r06
在本例中,我不能使用多个useEffect,因为每次更改都会导致相同的网络调用。这就是为什么我还使用changeCount来跟踪更改。这个changeCount还有助于仅从本地跟踪更改,因此可以防止由于服务器更改而引起的不必要的网络调用。
我刚刚发布了react-delta,它解决了这种情况。在我看来,useEffect有太多的责任。
责任
它使用Object.is比较其依赖数组中的所有值
它基于#1的结果运行effect/cleanup回调
分解责任
react-delta将useEffect的职责分解为几个更小的钩子。
责任# 1
usePrevious(值)
useLatest(值)
useDelta(价值,选项)
useDeltaArray (valueArray选项)
useDeltaObject (valueObject选项)
一些(deltaArray)
每一个(deltaArray)
责任# 2
布尔useConditionalEffect(回调)
根据我的经验,这种方法比useEffect/useRef解决方案更灵活、干净和简洁。
如果你更喜欢使用useEffect替换方法:
const usePreviousEffect = (fn, inputs = []) => {
const previousInputsRef = useRef([...inputs])
useEffect(() => {
fn(previousInputsRef.current)
previousInputsRef.current = [...inputs]
}, inputs)
}
像这样使用它:
usePreviousEffect(
([prevReceiveAmount, prevSendAmount]) => {
if (prevReceiveAmount !== receiveAmount) // side effect here
if (prevSendAmount !== sendAmount) // side effect here
},
[receiveAmount, sendAmount]
)
注意,第一次执行效果时,先前传递给fn的值将与初始输入值相同。只有当值没有改变时,这才会对您有影响。
您可以编写一个自定义钩子,使用useRef为您提供先前的道具
function usePrevious(value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
然后在useEffect中使用它
const Component = (props) => {
const {receiveAmount, sendAmount } = props
const prevAmount = usePrevious({receiveAmount, sendAmount});
useEffect(() => {
if(prevAmount.receiveAmount !== receiveAmount) {
// process here
}
if(prevAmount.sendAmount !== sendAmount) {
// process here
}
}, [receiveAmount, sendAmount])
}
但是,如果您为每个想要单独处理的变更id分别使用两个useEffect,那么阅读和理解起来会更清晰,可能会更好
下面是我使用的一个自定义钩子,我认为它比使用usePrevious更直观。
import { useRef, useEffect } from 'react'
// useTransition :: Array a => (a -> Void, a) -> Void
// |_______| |
// | |
// callback deps
//
// The useTransition hook is similar to the useEffect hook. It requires
// a callback function and an array of dependencies. Unlike the useEffect
// hook, the callback function is only called when the dependencies change.
// Hence, it's not called when the component mounts because there is no change
// in the dependencies. The callback function is supplied the previous array of
// dependencies which it can use to perform transition-based effects.
const useTransition = (callback, deps) => {
const func = useRef(null)
useEffect(() => {
func.current = callback
}, [callback])
const args = useRef(null)
useEffect(() => {
if (args.current !== null) func.current(...args.current)
args.current = deps
}, deps)
}
您可以像下面那样使用useTransition。
useTransition((prevRate, prevSendAmount, prevReceiveAmount) => {
if (sendAmount !== prevSendAmount || rate !== prevRate && sendAmount > 0) {
const newReceiveAmount = sendAmount * rate
// do something
} else {
const newSendAmount = receiveAmount / rate
// do something
}
}, [rate, sendAmount, receiveAmount])
希望这能有所帮助。
选项1 -当值改变时运行useEffect
const Component = (props) => {
useEffect(() => {
console.log("val1 has changed");
}, [val1]);
return <div>...</div>;
};
Demo
选项2 - useHasChanged钩子
比较当前值和以前值是一种常见的模式,它证明了它自己的自定义钩子隐藏了实现细节。
const Component = (props) => {
const hasVal1Changed = useHasChanged(val1)
useEffect(() => {
if (hasVal1Changed ) {
console.log("val1 has changed");
}
});
return <div>...</div>;
};
const useHasChanged= (val: any) => {
const prevVal = usePrevious(val)
return prevVal !== val
}
const usePrevious = (value) => {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
Demo
我刚刚发布了react-delta,它解决了这种情况。在我看来,useEffect有太多的责任。
责任
它使用Object.is比较其依赖数组中的所有值
它基于#1的结果运行effect/cleanup回调
分解责任
react-delta将useEffect的职责分解为几个更小的钩子。
责任# 1
usePrevious(值)
useLatest(值)
useDelta(价值,选项)
useDeltaArray (valueArray选项)
useDeltaObject (valueObject选项)
一些(deltaArray)
每一个(deltaArray)
责任# 2
布尔useConditionalEffect(回调)
根据我的经验,这种方法比useEffect/useRef解决方案更灵活、干净和简洁。