使用计算变量与 useState/useEffect 的结果
Consequences of using computed variables vs useState/useEffect
如果我有一个变量,其值可以根据另一个 属性 的值完全导出,是否有任何 consequence/pitfall 来初始化计算变量与使用 [=13= 的组合]/useEffect
来跟踪变量?让我用一个人为的例子来说明:
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option1 = ({paymentAmounts}) => {
const [average, setAverage] = useState(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
useEffect(() => {
setAverage(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
}, [paymentAmounts])
return (
<div>
Average: {average}
</div>
)
}
或更简单地说
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option2 = ({paymentAmounts}) => {
const average = paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length
return (
<div>
Average: {average}
</div>
)
}
我是否放弃了任何控制权 and/or React 使用 Option2 的好处?
Vue.js 似乎通过 computed properties 有这个选项。
使用挂钩或其他状态跟踪功能的唯一原因是您打算更改组件本身的状态。从你举的例子来看,情况并非如此。如果 prop paymentAmounts
被更新,组件将被 React 更新,你的计算常量 average
.
也会更新。
因此,在这里使用 useState 和 useEffect 对您没有任何好处。使用计算常量保持简单!
当你想在你的组件中跟踪某些东西(或者在它的children之间共享它时,你可以使用状态(就像在useState
中一样) ) 在重新渲染之间。在你的例子中,你从你的 parent 组件中得到这个“状态”(parent 是实际的状态持有者)。 parent 状态(paymentAmounts
在您的情况下)的每个更改都会自动反映在您的 child 组件中。
作为一般“规则”,不要对可以计算的数据使用状态。请记住,状态跟踪变量的每次更改都会强制组件 re-render。另一个错误的用法示例是:
const Example = ({variable1}) => {
const [variable, setVariable] = useState(variable1);
...
}
一些补充说明:
- 在您的第一个解决方案中,您通过使用引入了额外的开销
useEffect
。正如已经指出的那样,您的 child 组件将
始终 re-render 并在 parent 时重新计算 average
状态改变。
- 您在
useEffect
挂钩中使用 setAverage
错误。 setter
采用新值或接受当前值的函数
和 returns 新的。
- 不确定您对
paymentAmounts / paymentAmounts.length
计算的期望。我想这只是一个假人
代码,但如果没有,请查看它。您将数组本身(而不是其值的总和)除以其长度。
所以,简而言之 - 是的,您应该使用一个简单的变量来计算平均值并放弃 useState
/useEffect
。您不仅 NOT 放弃了任何好处,实际上还使您的代码更高效、更易于维护、更易读并且 error-free.
让我们逐步分析 Option1
组件:
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option1 = ({paymentAmounts}) => {
//1
const [average, setAverage] = useState(paymentAmounts / paymentAmounts.length)
//2
useEffect(() => {
setAverage(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
}, [ paymentAmounts])
return (
<div>
Average: {average}
</div>
)
}
- 在这里,您正在从
paymentAmounts
属性中初始化 average
状态变量。在初始渲染期间,将从中计算 average
状态变量的初始值。到目前为止一切顺利。
- 在
useEffect
中,您要从 prop 添加对 paymentAmounts
数组的依赖。当一个新的数组实例作为道具传递时以及在初始渲染时,效果回调将 运行 。所以这里有一个问题,当你在现有数组引用中推送新数字时,效果回调不会 运行 ,当你在具有更新数字的道具中传递新数组实例时,它只会 运行 .
因此,如果您只是在 Option1
的父组件中执行 paymentAmounts.push(400)
,您可能会看到 average
没有变化。
因此,对于所有用例,Option2
是实现您想做的事情的最佳方式。无需在该功能组件中引入状态。 UI 中的值可以从 props
本身派生。
如果我有一个变量,其值可以根据另一个 属性 的值完全导出,是否有任何 consequence/pitfall 来初始化计算变量与使用 [=13= 的组合]/useEffect
来跟踪变量?让我用一个人为的例子来说明:
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option1 = ({paymentAmounts}) => {
const [average, setAverage] = useState(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
useEffect(() => {
setAverage(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
}, [paymentAmounts])
return (
<div>
Average: {average}
</div>
)
}
或更简单地说
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option2 = ({paymentAmounts}) => {
const average = paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length
return (
<div>
Average: {average}
</div>
)
}
我是否放弃了任何控制权 and/or React 使用 Option2 的好处?
Vue.js 似乎通过 computed properties 有这个选项。
使用挂钩或其他状态跟踪功能的唯一原因是您打算更改组件本身的状态。从你举的例子来看,情况并非如此。如果 prop paymentAmounts
被更新,组件将被 React 更新,你的计算常量 average
.
因此,在这里使用 useState 和 useEffect 对您没有任何好处。使用计算常量保持简单!
当你想在你的组件中跟踪某些东西(或者在它的children之间共享它时,你可以使用状态(就像在useState
中一样) ) 在重新渲染之间。在你的例子中,你从你的 parent 组件中得到这个“状态”(parent 是实际的状态持有者)。 parent 状态(paymentAmounts
在您的情况下)的每个更改都会自动反映在您的 child 组件中。
作为一般“规则”,不要对可以计算的数据使用状态。请记住,状态跟踪变量的每次更改都会强制组件 re-render。另一个错误的用法示例是:
const Example = ({variable1}) => {
const [variable, setVariable] = useState(variable1);
...
}
一些补充说明:
- 在您的第一个解决方案中,您通过使用引入了额外的开销
useEffect
。正如已经指出的那样,您的 child 组件将 始终 re-render 并在 parent 时重新计算average
状态改变。 - 您在
useEffect
挂钩中使用setAverage
错误。 setter 采用新值或接受当前值的函数 和 returns 新的。 - 不确定您对
paymentAmounts / paymentAmounts.length
计算的期望。我想这只是一个假人 代码,但如果没有,请查看它。您将数组本身(而不是其值的总和)除以其长度。
所以,简而言之 - 是的,您应该使用一个简单的变量来计算平均值并放弃 useState
/useEffect
。您不仅 NOT 放弃了任何好处,实际上还使您的代码更高效、更易于维护、更易读并且 error-free.
让我们逐步分析 Option1
组件:
/**
* ex paymentAmounts: [100, 300, 400]
*/
const Option1 = ({paymentAmounts}) => {
//1
const [average, setAverage] = useState(paymentAmounts / paymentAmounts.length)
//2
useEffect(() => {
setAverage(paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length)
}, [ paymentAmounts])
return (
<div>
Average: {average}
</div>
)
}
- 在这里,您正在从
paymentAmounts
属性中初始化average
状态变量。在初始渲染期间,将从中计算average
状态变量的初始值。到目前为止一切顺利。 - 在
useEffect
中,您要从 prop 添加对paymentAmounts
数组的依赖。当一个新的数组实例作为道具传递时以及在初始渲染时,效果回调将 运行 。所以这里有一个问题,当你在现有数组引用中推送新数字时,效果回调不会 运行 ,当你在具有更新数字的道具中传递新数组实例时,它只会 运行 . 因此,如果您只是在Option1
的父组件中执行paymentAmounts.push(400)
,您可能会看到average
没有变化。
因此,对于所有用例,Option2
是实现您想做的事情的最佳方式。无需在该功能组件中引入状态。 UI 中的值可以从 props
本身派生。