在React中将嵌套对象/数组作为状态进行突变

时间:2018-10-14 20:05:45

标签: javascript reactjs stateful

我正在使用看起来像这样的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>
    );
  }
}

渲染后,它看起来像这样:

enter image description here

2 个答案:

答案 0 :(得分:2)

如果要修改状态中的现有值,则永远不要直接从状态中获取值并更新状态对象,而应使用setState中的updater函数,以便可以保证状态值是您在更新状态时需要的。这就是React状态的工作方式,这是一个非常常见的React错误。

official docs

  

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

     

setState()将始终导致重新渲染,除非   shouldComponentUpdate()返回false。如果存在易变的物体   使用和条件渲染逻辑无法在   shouldComponentUpdate(),仅在新状态时调用setState()   与以前的状态有所不同,将避免不必要的重新渲染。

     

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

     

(state, props) => stateChange

     

state是对更改时组件状态的引用   正在应用。它不应该直接突变。相反,变化   应该通过根据来自   状态和道具。

     

由updater函数接收的状态和道具均得到保证   是最新的。更新程序的输出与   状态。

因此,当您要使用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>
      );
  }
}

答案 1 :(得分: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设为 object 而不是数组,则可以使其更整洁:

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