React 函数式组件、setState 和不变性

gfe*_*els 5 javascript reactjs react-hooks

感觉有点愚蠢,我已经使用 React 两年了,并且一直认为你必须使用setState对象的新副本来避免状态发生变化。然而,这个例子改变了状态并使用setState相同的对象引用,没有任何问题。

为什么这有效?

工作代码笔

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'))
Run Code Online (Sandbox Code Playgroud)

Nic*_*ons 5

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

在此输入图像描述

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

请注意,下面的代码片段使用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'))
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

但是,将版本更改为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'))
Run Code Online (Sandbox Code Playgroud)
<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>
Run Code Online (Sandbox Code Playgroud)

  • 顺便说一句,纯粹是为了回答为什么这在当前版本的 React 中不再起作用的隐含问题:在新 hooks 实现的 React reconciler 中,在第 947 行的 rerenderReducer 中,我们可以看到有一些代码用于注册更新收到后检查最新值是否与先前存储的值匹配。在内部,这会使用“Object.is”(如果可用)。`if (!is(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate();}` (2认同)