自动反应状态变量更新,而无需调用setState

时间:2020-06-02 16:10:46

标签: javascript reactjs immutability

我正面临以下问题,无法解决。

在状态下,我有两个变量称为userDetails和userDetailsCopy。在componentDidMount中,我要进行API调用,并将数据保存在userDetails和userDetailsCopy中。

出于比较目的,我正在维护另一个名为userDetailsCopy的副本。

我只更新setState内的userDetails,但即使userDetailsCopy也正在更新,而不是具有旧的API数据。

下面是代码:

'value: ' + String(v)

2 个答案:

答案 0 :(得分:2)

问题在于您认为您正在通过这样做来复制状态下的对象

     let userDetail = this.state.userDetails
     userDetail.name = text

但是,在Javascript中,对象不是像这样复制的,而是通过引用传递的。因此,此时的userDetail包含在您的状态下对userDetails的引用,并且当您对userDetail进行突变时,它就会对状态中的那个进行突变。

ref:https://we-are.bookmyshow.com/understanding-deep-and-shallow-copy-in-javascript-13438bad941c

要正确地将对象从状态克隆到本地变量,您需要执行以下操作:

let userDetail = {...this.state.userDetails}

OR

let userDetail = Object.assign({}, this.state.userDetails)

永远记住,对象是通过引用传递的,而不是值。

编辑:我没有正确阅读问题,但以上答案仍然有效。 userDetailCopy之所以也要更新,是因为resp.data是通过引用传递给它们两个的,并且编辑其中任何一个都会编辑另一个。

答案 1 :(得分:1)

反应状态及其数据应视为不可变的。

来自React documentation

请勿直接更改this.state,因为之后可能会调用setState() 替换您所做的突变。像对待this.state一样 不变的。

有五种方法将状态视为不变:

方法1:Object.assign和Array.concat

updateValue = (text, index) => {
  const { userDetails } = this.state;

  const userDetail = Object.assign({}, userDetails[index]);

  userDetail.name = text;

  const newUserDetails = []
    .concat(userDetails.slice(0, index))
    .concat(userDetail)
    .concat(userDetails.slice(index + 1));

  this.setState({
    userDetails: newUserDetails
  });
}

方法2:对象和数组扩展

updateValue = (text, index) => {
  const { userDetails } = this.state;

  const userDetail = { ...userDetails[index], name: text };

  this.setState({
    userDetails: [
      ...userDetails.slice(0, index),
      userDetail,
      ...userDetails.slice(index + 1)
    ]
  });
}

方法3:不变性助手

import update from 'immutability-helper';

updateValue = (text, index) => {
  const userDetails = update(this.state.userDetails, {
    [index]: {
      $merge: {
        name: text
      }
    }
  });

  this.setState({ userDetails });
};

方法4:Immutable.js

import { Map, List } from 'immutable';

updateValue = (text, index) => {
  const userDetails = this.state.userDetails.setIn([index, 'name'], text);

  this.setState({ userDetails });
};

方法5:即时

import produce from "immer";

updateValue = (text, index) => {
  this.setState(
    produce(draft => {
        draft.userDetails[index].name = text;
    })
  );
};

注意: 选项#1和#2仅执行浅表克隆。因此,如果您的对象包含嵌套对象,则这些嵌套对象将通过引用而不是值进行复制。因此,如果您更改嵌套对象,则将对原始对象进行变异。

要保持userDetailsCopy不变,您需要保持state(当然还有state.userDetails)的不变性。

function getUserDerails() {
  return new Promise(resolve => setTimeout(
    () => resolve([
      { id: 1, name: 'Tom', age : 40 },
      { id: 2, name: 'Jerry', age : 35 }
    ]),
    300
  ));
}

class App extends React.Component {
  state = {
    userDetails: [],
    userDetailsCopy: []
  };

  componentDidMount() {
    getUserDerails().then(users => this.setState({
      userDetails: users,
      userDetailsCopy: users
    }));
  }
  
  createChangeHandler = userDetailId => ({ target: { value } }) => {
    const { userDetails } = this.state;
    
    const index = userDetails.findIndex(({ id }) => id === userDetailId);
    const userDetail = { ...userDetails[index], name: value };

    this.setState({
      userDetails: [
        ...userDetails.slice(0, index),
        userDetail,
        ...userDetails.slice(index + 1)
      ]
    });
  };
  
  render() {
    const { userDetails, userDetailsCopy } = this.state;
    
    return (
      <React.Fragment>
        {userDetails.map(userDetail => (
          <input
            key={userDetail.id}
            onChange={this.createChangeHandler(userDetail.id)}
            value={userDetail.name}
          />
        ))}
        
        <pre>userDetails: {JSON.stringify(userDetails)}</pre>
        <pre>userDetailsCopy: {JSON.stringify(userDetailsCopy)}</pre>
      </React.Fragment>
    );
  }
}

ReactDOM.render(
  <App />,
  document.getElementById("root")
);
<div id="root"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>