我们不能只改变状态..?副作用是什么。。?

Can't we just mutate state ..? What is the side-effect..?

根据https://reactjs.org/docs/hooks-reference.html#functional-updates的建议,我们建议通过以下方法进行功能更新:

function Counter({initialCount}) {
  const [count, setCount] = useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => prevCount - 1)}>-</button>
      <button onClick={() => setCount(prevCount => prevCount + 1)}>+</button>
    </>
  );
}

那是一个非常低级的例子,所以大多数情况下我们都面临着我们应该将复杂状态传递给组件的情况,在这种情况下,下面的方法非常有效:

export default function App() {
  const [state, setState] = useState({ number: 10 });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={() => {
          setState((state) => {
            state.number++; //+ I am concerned about this mutation though.
            return { ...state };
          });
        }}
      >
        {state.number}
      </button>
    </div>
  );
}

可操作性证明:Codesanbdox link 用于我的代码 here

但这通常不会被人们看到用 React 更新他们的 OSS 中的状态,为什么会这样..?

如果使用这种形式的更新会导致任何不良副作用,有人可以帮助我吗?如果可以,请提供这样的 codesandbox 实例来复制该行为..?

谢谢。

以下是您的代码无法运行的示例,导致意外结果。对于复杂的状态,确保对其进行深度克隆。 这里是codesandboxlink

import "./styles.css";
import { useState } from "react";
export default function App() {
  const [state, setState] = useState({ number: 10, arr: [] });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={() => {
          setState((state) => {
            state.number++;
            state.arr.push(1);
            return { ...state };

            /* 
            My Solution : Deep clone state
            const clone = JSON.parse(JSON.stringify(state));
            clone.number++;
            clone.arr.push(1);
            console.log(clone);
            return { ...clone }; 
            */
          });
        }}
      >
        {state.number}
      </button>
    </div>
  );
}

你可以试试:

import React from 'react';
import ReactDOM from 'react-dom';

function App({initialCount}) {
  const [count, setCount] = React.useState(initialCount);
  return (
    <>
      Count: {count}
      <button onClick={() => setCount(initialCount)}>Reset</button>
      <button onClick={() => setCount(prevCount => ({ prevCount: prevCount - 1}))}>-</button>
      <button onClick={() => setCount(prevCount => ({ prevCount: prevCount + 1}))}>+</button>
    </>
  );
}

export default App;


ReactDOM.render(
  <React.StrictMode>
    <App initialCount={{ number: 10 }}/>
  </React.StrictMode>,
  document.getElementById('root')
);

虽然您的示例有效,但它并不是完美且无错误最多的编写方式。

你的代码工作的原因是因为你浅克隆状态并返回它导致 Object.is 签入反应失败并重新渲染组件。 另请注意,传播语法仅对对象进行一级克隆

当您像上面那样更新嵌套变量,然后将其作为 props 传递给其他组件,然后扩展 PureComponent 或使用 React.meemo 优化重新渲染时,就会出现问题

例如:

const SomeComp = memo((props) => {
  return <span>{props.nestedKey.id}</span>;
});
export default function App() {
  const [state, setState] = useState({ number: 10, nested: { id: 1 } });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={() => {
          setState((state) => {
            state.number++;
            state.nested.id++;
            return { ...state };
          });
        }}
      >
        {state.number}
        <SomeComp nestedKey={state.nested} />
      </button>
    </div>
  );
}

如果你看上面的代码,即使更新了 id 值,SomeComp 也不会重新渲染来更新它的值

DEMO sandbox

更新状态的正确方法如下

export default function App() {
  const [state, setState] = useState({ number: 10 });
  return (
    <div className="App">
      <h1>Hello CodeSandbox</h1>
      <button
        onClick={() => {
          setState((state) => {
            return { ...state, number: state.number + 1 };
          });
        }}
      >
        {state.number}
      </button>
    </div>
  );
}