简体   繁体   English

使用计算变量与 useState/useEffect 的后果

[英]Consequences of using computed variables vs useState/useEffect

If I have a variable whose value can be fully derived based on the value of another property, is there any consequence/pitfall to initializing a computed variable vs using a combination of useState / useEffect to track the variable?如果我有一个变量的值可以完全基于另一个属性的值导出,那么初始化计算变量与使用useState / useEffect的组合来跟踪变量是否有任何后果/陷阱? Let me illustrate with a contrived example:让我用一个人为的例子来说明:

/**
 * 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>
  )
}

or more simply或更简单地说

/**
 * ex paymentAmounts: [100, 300, 400]
 */
const Option2 = ({paymentAmounts}) => {
  const average = paymentAmounts.reduce((acc, curr) => curr + acc, 0) / paymentAmounts.length

  return (
    <div>
      Average: {average}
    </div>
  )
}

Am I giving up any control and/or React benefits by using Option2?我是否通过使用 Option2 放弃了任何控制和/或 React 好处?

Vue.js seems to have this option viacomputed properties . Vue.js 似乎通过计算属性有这个选项。

The only reason to use hooks or other state tracking functionality is if you plan on changing the state within the component itself.使用钩子或其他状态跟踪功能的唯一原因是如果您计划更改组件本身的状态。 From the example you gave, that is not the case.从你给出的例子来看,情况并非如此。 If the prop paymentAmounts is updated the component will get updated by React and so will your computed constant, average .如果 prop paymentAmounts更新,组件将被 React 更新,你的计算常量average也会更新。

So, you get no benefit from using useState & useEffect here.因此,在这里使用 useState 和 useEffect 没有任何好处。 Keep it simple with your computed constant!用你的计算常数保持简单!

You use state (as in useState that is) when you want to track something in your component (or share it between its children) between rerenders .当您想在 rerenders之间跟踪组件的某些内容(或在其子组件之间共享)时,您可以使用 state(如useState中的)。 In your case, you're getting this "state" from your parent component (the parent is the actual state holder).在您的情况下,您从父组件(父组件是实际的状态持有者)获取此“状态”。 Every change in the parent state ( paymentAmounts in your situation) will reflect in your child component automatically.父状态的每次更改(您的情况下的paymentAmounts )都会自动反映在您的子组件中。

As a general "rule", don't use state for data that can be calculated.作为一般“规则”,不要将状态用于可计算的数据。 Keep in mind that every change of a state tracked variable will force the component to re-render.请记住,状态跟踪变量的每次更改都会强制组件重新渲染。 Another bad usage example is this:另一个不好的用法示例是:

const Example = ({variable1}) => {
  const [variable, setVariable] = useState(variable1);

  ...
}

A couple of additional notes:一些附加说明:

  1. In your first solution you introduce an additional overhead by using useEffect .在您的第一个解决方案中,您使用useEffect引入了额外的开销。 As pointed out already, your child component will always re-render and recalculate the average when your parent's state change.正如已经指出的,当您的父母状态发生变化时,您的子组件将始终重新渲染并重新计算average
  2. You're using setAverage wrong in your useEffect hook.您在setAverage挂钩中使用了错误的useEffect The setter takes either a new value or a function that accepts the current one and returns the new one. setter 接受一个新值或一个接受当前值并返回新值的函数。
  3. Not sure what you expect from the paymentAmounts / paymentAmounts.length calculation.不确定您对paymentAmounts / paymentAmounts.length计算的期望。 I suppose it is just a dummy code, but if not, look into it.我想这只是一个虚拟代码,但如果不是,请查看它。 You're dividing the array itself (not the sum of its values) by its length.您将数组本身(而不是其值的总和)除以其长度。

So, in short - Yes, you should be using a simple variable to calculate the average and ditch the useState / useEffect .因此,简而言之 - 是的,您应该使用一个简单的变量来计算平均值并useState / useEffect Not only you're NOT giving up any benefits, you're actually making your code more performant, maintainable, readable and error-free.您不仅没有放弃任何好处,而且实际上使您的代码更具性能、可维护性、可读性和无错误。

Let's analyze the Option1 component step-by-step:让我们一步一步分析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>
  )
}
  1. Here you are initializing the average state variable from the paymentAmounts prop.在这里,您正在从paymentAmounts初始化average状态变量。 During the initial render, the initial value of the average state variable would be calculated from it.在初始渲染期间,将根据它计算average状态变量的初始值。 So far so good.到目前为止,一切都很好。
  2. In the useEffect you are adding a dependency on the paymentAmounts array from the prop.useEffect中,您正在从 prop 添加对paymentAmounts数组的依赖项。 The effect callback will run when a new array instance is passed as a prop and also on the initial render.当一个新的数组实例作为道具传递时,效果回调将运行,并且在初始渲染时也会运行。 So here is the catch, the effect callback won't run when you push new numbers in the existing array reference, it will only run when you pass a new array instance in the prop having the updated numbers.所以这里有一个问题,当您在现有数组引用中推送新数字时,效果回调不会运行,它只会在您在具有更新数字的道具中传递新数组实例时运行。 So you may see no change in the average if you simply do paymentAmounts.push(400) in Option1 's parent component.因此,如果您只是在Option1的父组件中执行paymentAmounts.push(400) ,您可能会看到average没有变化。

So for all use cases Option2 is the best way to achieve what you wanted to do.因此,对于所有用例, Option2是实现您想要做的事情的最佳方式。 There is no need to introduce a state in this functional component.无需在此功能组件中引入状态。 The value in the UI can be derived from the props itself. UI 中的值可以来源于props本身。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM