【问题标题】:Mutating nested object/arrays as states in React [duplicate]在 React [重复] 中将嵌套对象/数组变异为状态
【发布时间】:2019-03-19 06:50:06
【问题描述】:

我正在使用看起来像这样的 React 组件(我使用的组件的简化版本,如下所示)。

我的问题是:如何制作相同但使用 this.setState? 下面的代码有效,但我直接改变状态,我收到以下警告:

不要直接改变状态。使用 setState()

class App extends Component {
  constructor(props) {
    super(props);
    this.state = {
      playerState: [
        {
          name: 'Jack',
          hp: 30
        },{
          name: 'Alan',
          hp: 28
        }
      ],
    };
  }
  lowerPlayerHealth = (index) => () => {
    this.state.playerState[index].hp = this.state.playerState[index].hp - 1
    this.forceUpdate();
  }
  render() {
    return (
      <div className="App">
        <p>Player 1: {this.state.playerState[0].name}</p>
        <p>Health: {this.state.playerState[0].hp}</p>
        <button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
        <p>Player 2: {this.state.playerState[1].name}</p>
        <p>Health: {this.state.playerState[1].hp}</p>
        <button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
      </div>
    );
  }
}

渲染后是这样的:

【问题讨论】:

  • 简单的阅读setState() docs 会让你自己解决这个小问题
  • @charlietfl 我会读一遍
  • 如果 this.state.playerState[index] 作为 prop 传递给子组件,并且这个子组件是 extends PureComponent,它永远不会在更改时重新渲染。这只是第一个用例,当突变状态可以完全破坏您的应用程序中的某些内容时。有一系列原因遵循文档并且从不改变状态。

标签: javascript reactjs stateful


【解决方案1】:

您已经回答了自己的问题:不要改变状态。此外,最佳实践建议使用 setState 的函数版本。

由于playerState 是一个数组,所以使用Array.map to create a new array containing the same objects,只替换您要更改的那个:

lowerPlayerHealth = (indexToUpdate) => () => {
  this.setState(state => ({
    ...state,
    playerState: state.playerState.map(
      (item, index) => index === indexToUpdate
        ? {
          ...item,
          hp: item.hp - 1
        }
        : oldItem
    )
  }));
}

如果您将playerState 设为对象 而不是数组,则可以使其更整洁:

lowerPlayerHealth = (indexToUpdate) => () => {
  this.setState(state => ({
    ...state,
    playerState: {
      ...state.playerState,
      [indexToUpdate]: {
        ...state.playerState[indexToUpdate],
        hp: state.playerState[idToindexToUpdatepdate].hp - 1
      }
    }
  }));
}

【讨论】:

  • this.state.playerState 应该是允许[index]: 方法的对象而不是数组。对于数组,它应该是另一个使用的基于.map() 的模式。你能更新你的答案吗?
  • @skyboyer 类数组索引适用于对象和数组。我让你an example
  • 但您正在将数组转换为对象。如果您将playerState 保留为数组,您将收到unexpected token 错误
  • @skyboyer 你是对的。您将如何处理它,将其保留为单个 this.setState 调用但保留数组? (请随时编辑我的答案)
  • 完成,检查更新版本
【解决方案2】:

如果要修改状态中的现有值,千万不要直接从状态中获取值并更新状态对象,而是使用setState 中的更新函数,这样可以保证状态值是更新状态时需要的那些。这就是 React 的状态工作原理,这是一个非常常见的 React 错误。

来自official docs

setState() 并不总是立即更新组件。有可能 批处理或推迟更新。这使得阅读 this.state 就在调用 setState() 之后,这是一个潜在的陷阱。相反,使用 componentDidUpdate 或 setState 回调(setState(updater, 回调)),其中任何一个都保证在更新后触发 已应用。如果需要根据前面的设置状态 状态,请阅读下面的更新程序参数。

setState() 总是会导致重新渲染,除非 shouldComponentUpdate() 返回 false。如果可变对象正在 使用和条件渲染逻辑无法实现 shouldComponentUpdate(),只有在新状态时才调用 setState() 与之前的状态不同将避免不必要的重新渲染。

第一个参数是一个带有签名的更新函数:

(state, props) =&gt; stateChange

state 是对更改时组件状态的引用 被应用。它不应该直接突变。相反,改变 应该通过基于来自的输入构建一个新对象来表示 状态和道具。

updater 函数接收到的 state 和 props 都是有保证的 是最新的。更新器的输出与 状态。

因此,当您想使用 updater 函数的第一个参数更新 setState 函数内的组件时,您必须准确获取该值。

lowerPlayerHealth = (index) => () => {
    // use setState rather than mutating the state directly
    this.setState((state, props) => {
    // state here is the current state. Use it to update the current value found in state and ensure that it will be set correctly
      return (state); // update state ensuring the correct values
    });
  }

解决方案

降低在状态中找到的值:

class App extends React.Component {
  constructor(props){
    super(props);
    this.state = {
      playerState: [
        {
          name: 'Jack',
          hp: 30
        }, {
          name: 'Alan',
          hp: 28
        }
      ],
      };
  }

  lowerPlayerHealth = (index) => () => {
    this.setState((state, props) => {
      state.playerState[index].hp -=1; //update the current value found in state
      return (state); // update state ensuring the correct values
    });
  }

  render() {
    return (
      <div className="App">
        <p>Player 1: {this.state.playerState[0].name}</p>
        <p>Health: {this.state.playerState[0].hp}</p>
        <button onClick={this.lowerPlayerHealth(0)}>Hit player 1</button>
        <p>Player 2: {this.state.playerState[1].name}</p>
        <p>Health: {this.state.playerState[1].hp}</p>
        <button onClick={this.lowerPlayerHealth(1)}>Hit player 2</button>
      </div>
      );
  }
}

【讨论】:

  • 使用 React 我们不应该改变状态。你的代码做到了。
  • @skyboyer “我们永远不应该改变状态”是什么意思,你的意思是我们永远不应该使用 setState?我迷路了……
  • 我的意思是我们的代码setState 应该在每次需要更改深层嵌套的单个属性时返回新对象时的工作方式。否则PureComponent 将永远不会重新渲染。一些细节:daveceddia.com/why-not-modify-react-state-directly
  • @skyboyer 实际上你不应该将 PureComponent 与具有深层嵌套属性的对象一起使用......或者我错了。 about PureComponent and state 声明“仅当您希望拥有简单的道具和状态时才扩展 PureComponent”。您能否扩展您的想法,以便我可以对此事进行适当的阅读并了解这一点?谢谢!
  • @skyboyer 我认为我们在这里偏离了轨道......建议 PureComponent 不应该使用复杂的对象,但如果你做得好当然它会起作用。主要问题是关于直接改变状态,使用 setState 并在 setState 中改变对象是解决这个问题的方法,如答案文档中所述。如果我错了,请纠正我,如果您觉得它做的不正确,请随时更新答案。学习新知识并纠正任何错误总是好的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-12-23
  • 1970-01-01
  • 1970-01-01
  • 2018-11-08
  • 2021-06-16
  • 2020-04-01
  • 2020-11-13
相关资源
最近更新 更多