receiveAmount = sendAmount * rate
sendAmount = receiveAmount / rate
receiveAmount = sendAmount * rate
时计算sendAmount > 0
或在sendAmount = receiveAmount / rate
时计算receiveAmount > 0
这是代码框https://codesandbox.io/s/pkl6vn7x6j来说明问题。
有没有办法像oldValues
那样比较newValues
和componentDidUpdate
,而不是为此情况制作3个处理程序?
谢谢
这是我使用usePrevious
的最终解决方案
https://codesandbox.io/s/30n01w2r06
在这种情况下,我不能使用多个useEffect
,因为每次更改都会导致相同的网络呼叫。这就是为什么我也使用changeCount
来跟踪更改的原因。 changeCount
还可帮助仅跟踪来自本地的更改,因此我可以防止由于服务器的更改而导致不必要的网络呼叫。
答案 0 :(得分:32)
您可以编写一个自定义钩子来为您提供previous props using 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
,则要更清晰地阅读和理解,您想分别处理它们
答案 1 :(得分:20)
如果有人正在寻找使用的TypeScript版本上一个:
import { useEffect, useRef } from "react";
const usePrevious = <T extends {}>(value: T) => {
const ref = useRef<T>();
useEffect(() => {
ref.current = value;
});
return ref.current;
};
答案 2 :(得分:6)
摆脱已接受的答案,这是不需要自定义钩子的替代解决方案:
const Component = ({ receiveAmount, sendAmount }) => {
const prevAmount = useRef({ receiveAmount, sendAmount }).current;
useEffect(() => {
if (prevAmount.receiveAmount !== receiveAmount) {
// process here
}
if (prevAmount.sendAmount !== sendAmount) {
// process here
}
return () => {
prevAmount.receiveAmount = receiveAmount;
prevAmount.sendAmount = sendAmount;
};
}, [receiveAmount, sendAmount]);
};
这假定您实际上需要引用“此处处理”位中任何内容的先前值。否则,除非您的条件不直接进行!==
比较,否则这里最简单的解决方案是:
const Component = ({ receiveAmount, sendAmount }) => {
useEffect(() => {
// process here
}, [receiveAmount]);
useEffect(() => {
// process here
}, [sendAmount]);
};
答案 3 :(得分:2)
将当前值与上一个值进行比较是一种常见的模式,它证明了自定义钩子本身是可以隐藏实现细节的。
const useCompare = (val: any) => {
const prevVal = usePrevious(val)
return prevVal !== val
}
const usePrevious = (value) {
const ref = useRef();
useEffect(() => {
ref.current = value;
});
return ref.current;
}
const Component = (props) => {
...
const hasVal1Changed = useCompare(val1)
const hasVal2Changed = useCompare(val2);
useEffect(() => {
if (hasVal1Changed ) {
console.log("val1 has changed");
}
if (hasVal2Changed ) {
console.log("val2 has changed");
}
});
return <div>...</div>;
};
const Component = (props) => {
...
useEffect(() => {
console.log("val1 has changed");
}, [val1]);
useEffect(() => {
console.log("val2 has changed");
}, [val2]);
return <div>...</div>;
};
答案 4 :(得分:2)
对于真正简单的道具比较,您可以使用useEffect和useState轻松检查道具是否已更新。
const myComponent = ({ prop }) => {
const [propHasChanged, setPropHasChanged] = useState(false)
useEffect(() => {
setHasPropChanged(true)
}, [prop])
if(propHasChanged){
~ do stuff here ~
}
}
useEffect检查该道具是否已更改,更新状态以说它已经拥有,并允许您的条件代码运行。
答案 5 :(得分:2)
使用Ref会在应用中引入一种新的错误。
让我们看看使用$invoice_TaxTotal_SubTotal_Category = new \Bulut\eFaturaUBL\TaxCategory();
$invoice_TaxTotal_SubTotal_Category->TaxExemptionReasonCode = "301";
$invoice_TaxTotal_SubTotal_Category->TaxExemptionReason = "description";
$invoice_TaxTotal_SubTotal_Category_Schema = new \Bulut\eFaturaUBL\TaxScheme();
$invoice_TaxTotal_SubTotal_Category_Schema->Name = "TAX";
$invoice_TaxTotal_SubTotal_Category_Schema->TaxTypeCode = "0015";
$invoice_TaxTotal_SubTotal_Category->TaxScheme = $invoice_TaxTotal_SubTotal_Category_Schema;
$invoice_TaxTotal_SubTotal->TaxCategory = $invoice_TaxTotal_SubTotal_Category;
$invoice_TaxTotal->TaxSubtotal = $invoice_TaxTotal_SubTotal;
$invoice->TaxTotal = $invoice_TaxTotal;
的这个案例,该案例之前有人评论过:
正如我们在这里看到的那样,我们没有更新内部usePrevious
,因为我们正在使用ref
答案 6 :(得分:2)
如果您喜欢使用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
的先前值将与初始输入值相同。这仅对您很重要,如果您想在不变不变的情况下执行某些操作。
答案 7 :(得分:1)
由于状态没有与功能组件中的组件实例紧密耦合,因此无法在useEffect
中访问先前状态,而无需先保存它,例如,使用useRef
。这也意味着状态更新可能在错误的位置被错误地实现了,因为先前的状态在setState
更新程序函数中可用。
这是useReducer
的好用例,它提供了类似Redux的存储并允许实现各自的模式。状态更新是显式执行的,因此无需弄清楚哪个状态属性被更新;从派遣行动中已经很清楚了。
这是一个example,看起来可能像这样:
function reducer({ sendAmount, receiveAmount, rate }, action) {
switch (action.type) {
case "sendAmount":
sendAmount = action.payload;
return {
sendAmount,
receiveAmount: sendAmount * rate,
rate
};
case "receiveAmount":
receiveAmount = action.payload;
return {
sendAmount: receiveAmount / rate,
receiveAmount,
rate
};
case "rate":
rate = action.payload;
return {
sendAmount: receiveAmount ? receiveAmount / rate : sendAmount,
receiveAmount: sendAmount ? sendAmount * rate : receiveAmount,
rate
};
default:
throw new Error();
}
}
function handleChange(e) {
const { name, value } = e.target;
dispatch({
type: name,
payload: value
});
}
...
const [state, dispatch] = useReducer(reducer, {
rate: 2,
sendAmount: 0,
receiveAmount: 0
});
...
答案 8 :(得分:1)
这是我使用的自定义钩子,我相信它比使用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])
希望有帮助。
答案 9 :(得分:0)
我刚刚发布了react-delta,它解决了这种情况。我认为useEffect
的职责太多。
Object.is
react-delta
将useEffect
的职责分解为几个较小的钩子。
职责1
usePrevious(value)
useLatest(value)
useDelta(value, options)
useDeltaArray(valueArray, options)
useDeltaObject(valueObject, options)
some(deltaArray)
every(deltaArray)
责任2
根据我的经验,这种方法比useEffect
/ useRef
解决方案更加灵活,简洁和简洁。
答案 10 :(得分:0)
您可以使用useImmer代替useState并访问该状态。 示例:https://css-tricks.com/build-a-chat-app-using-react-hooks-in-100-lines-of-code/
答案 11 :(得分:0)
我不喜欢上面的任何答案,我希望能够传递一组布尔值,如果其中一个为真,则重新渲染
/**
* effect fires if one of the conditions in the dependency array is true
*/
export const useEffectCompare = (callback: () => void, conditions: boolean[], effect = useEffect) => {
const shouldUpdate = useRef(false);
if (conditions.some((cond) => cond)) shouldUpdate.current = !shouldUpdate.current;
effect(callback, [shouldUpdate.current]);
};
//usage - will fire because one of the dependencies is true.
useEffectCompare(() => {
console.log('test!');
}, [false, true]);
答案 12 :(得分:-1)
在你的情况下(简单对象):
useEffect(()=>{
// your logic
}, [rate, sendAmount, receiveAmount])
其他情况(复杂对象)
const {cityInfo} = props;
useEffect(()=>{
// some logic
}, [cityInfo.cityId])