【问题标题】:React functional components, setState and immutabilityReact 函数式组件,setState 和不变性
【发布时间】:2022-01-11 10:09:52
【问题描述】:

感觉有点笨,我已经使用 React 2 年了,一直认为你必须使用 setState 和对象的新副本以避免改变状态。但是,此示例更改了状态并使用 setState 和相同的对象引用,没有任何问题。

为什么会这样?

Working Code Pen

const { useState } = React;

const Counter = () => {
  const [countObject, setCountObject] = useState({count: 0});

  const onClick = () => {
    countObject.count = countObject.count + 1;
    setCountObject(countObject); // mutated object, same reference
    
  }
  
  return (
    <div>
      <p>You clicked {countObject.count} times</p>
      <button onClick={onClick}>
        Click me
      </button>
    </div>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))

【问题讨论】:

  • 有趣的是,我无法在 Stack sn-ps(堆栈溢出的 sn-p 编辑器)中重现此行为。我猜 codepen 出了点问题?
  • 实际上,这是您的反应版本的问题:检查您在 codepen 中使用的 umd 链接,它们是 alpha 版本(16.7.0-alpha.2),可能是一些错误在那里面。尝试使用17.0.2 而不是16.7.0-alpha.2
  • 谢谢@NickParsons,这就是问题所在! :) forked codepen,没注意到原作者用的是alpha版本。
  • @gfels 太好了 :) 很高兴现在解决了

标签: javascript reactjs react-hooks


【解决方案1】:

您的 codepen 使用的是 React 16.7 的 alpha 版本。 Hooks 是在 16.8 中发布的,因此要可靠地使用 Hooks,您应该使用 16.8 或更高版本(在 React 的非 alpha 版本中):

您可以通过单击 JavaScript 窗格上代码笔中的齿轮图标来更新上述两个链接的版本号。

请注意,以下 sn-p 使用 16.7.0-alpha.2 并遇到与您相同的问题:

const { useState } = React;

const Counter = () => {
  const [countObject, setCountObject] = useState({count: 0});

  const onClick = () => {
    countObject.count = countObject.count + 1;
    setCountObject(countObject);
  }
  
  return (
    <div>
      <p>You clicked {countObject.count} times</p>
      <button onClick={onClick}>
        Click me
      </button>
    </div>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.7.0-alpha.2/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.7.0-alpha.2/umd/react-dom.production.min.js"></script>
<div id="app"></div>

但是,将您的版本更改为 16.8 或更高版本可以解决问题(在撰写本文时,我们已更新至 v17.0.2):

const { useState } = React;

const Counter = () => {
  const [countObject, setCountObject] = useState({count: 0});

  const onClick = () => {
    countObject.count = countObject.count + 1;
    setCountObject(countObject);
  }
  
  return (
    <div>
      <p>You clicked {countObject.count} times</p>
      <button onClick={onClick}>
        Click me
      </button>
    </div>
  )
}

ReactDOM.render(<Counter />, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.0/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.0/umd/react-dom.production.min.js"></script>
<div id="app"></div>

【讨论】:

  • 顺便说一句,纯粹是为了回答为什么这在当前版本的 React 中不再有效的隐含问题:在新钩子实现中的 react reconciler 中,在第 947 行的 rerenderReducer 中,我们可以看到收到了一些注册更新的代码,用于检查最新值是否与先前记忆的值匹配。这在内部使用Object.is(如果可用)。 if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate();}
  • 来源 1 - React 代码库第 947 行:github.com/facebook/react/blob/…
  • @JamieDixon 哦,很有趣,感谢您对此的进一步了解。很高兴看到 react 内部如何“在幕后”处理这个问题。
【解决方案2】:

不变性当然很重要,你的代码可以工作,只是因为修改原始对象不会影响功能。通过共享初始值更改代码,然后单击任意按钮,您会发现错误。

const { useState } = React;

const Counter = () => {
  const value = { count: 0 }
  const [countObject, setCountObject] = useState(value);
  const [countObject2, setCountObject2] = useState(value);

  const onClick = () => {
    countObject.count = countObject.count + 1;
    setCountObject(countObject);
  }
  
  const onClick2 = () => {
    countObject2.count = countObject2.count + 1;
    setCountObject(countObject2);
  }
  
  return (
    <div>
      <p>You clicked {countObject.count} times</p>
      <button onClick={onClick}>
        Click me
      </button>

      <p>You2 clicked {countObject2.count} times</p>
      <button onClick={onClick2}>
        Click me2
      </button>
    </div>
  )
}

ReactDOM.render(<div><Counter /></div>, document.getElementById('app'))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-02-06
    • 1970-01-01
    • 1970-01-01
    • 2017-12-18
    相关资源
    最近更新 更多