Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
243 views
in Technique[技术] by (71.8m points)

javascript - React - Changing the state without using setState: Must avoid it?

My code works, but I have a best practice question: I have an array of objects in the state, and a user interaction will change a value of one object at a time. As far as I know, I'm not supposed to change the state directly, i should always use setState instead. If I want to avoid that with any price, I will deep clone the array by iteration, and change the clone. Then set the state to the clone. In my opinion avoiding to change the state that I will change later anyway is just decreasing my performance.

Detailed version: this.state.data is an array of objects. It represents a list of topics in a forum, and a Favorite button will toggle, calling clickCollect(). Since I have an array in the state, when I change the is_collected property of one item, I need to create a copy of the array to work with, and after changing to the new value, I can set it to the state.

var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});

var data = this.state.data : This would copy the pointer to the array and push(), shift(), etc would alter the state directly. Both data and this.state.data will be affected.

var data = this.state.data.slice(0) : This makes a shallow clone, push and shift doesn't change the state but in my clone I still have pointers to the elements of the state's array. So if I change data[0].is_collected, this.state.data[0].is_collected gets changed as well. This happens before I call setState().

Normally I should do:

var data = []; 
for (var i in this.state.data) {
    data.push(this.state.data[i]); 
}

Then I change the value at index, setting it to true when it's false or false when it's true:

data[index].is_collected = !data[index].is_collected;

And change state:

this.setState({data: data});

Consider my array is relatively big or enormously big, I guess this iteration will reduce the performance of my APP. I would pay that cost if I knew that it is the right way for any reason. However, in this function (clickCollect) I always set the new value to the state, I'm not waiting for a false API response that would say to stop making the change. In all cases, the new value will get into the state. Practically I call setState only for the UI to render again. So the questions are:

  1. Do I have to create the deep clone in this case? (for var i in ...)
  2. If not, does it make sense to make a shallow clone (.slice(0)) if my array contains objects? The changes are being made on the objects inside of the array, so the shallow clone still changes my state, just like a copy (data = this.state.data) would do.

My code is simplified and API calls are cut out for simplicity.

This is a beginner's question, so a totally different approach is also welcome. Or links to other Q & A.

import React from 'react';

var ForumList = React.createClass({
  render: function() {
      return <div className="section-inner">
        {this.state.data.map(this.eachBox)}
      </div>
  },
  eachBox: function(box, i) {
    return <div key={i} className="box-door">
        <div className={"favorite " + (box.is_collected ? "on" : "off")} onTouchStart={this.clickCollect.bind(null, i)}>
          {box.id}
        </div>
    </div>
  },
  getInitialState: function() {
    return {data: [
      {
        id: 47,
        is_collected: false
      },
      {
        id: 23,
        is_collected: false
      },
      {
        id: 5,
        is_collected: true
      }
    ]};
  },
  clickCollect: function(index) {
    var data = this.state.data.slice(0);
    data[index].is_collected = !data[index].is_collected;
    this.setState({data: data});
  }
});

module.exports = ForumList;
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

Personally I don't always follow the rule, if you really understand what you are trying to do then I don't think it's a problem.

var data = this.state.data.slice(0);
data[index].is_collected = !data[index].is_collected;
this.setState({data: data});

In this case, mutating state and calling the setState again like this is fine

this.state.data[index].is_collected = !this.state.data[index].is_collected;
this.setState({data: this.state.data});

The reason you should avoid mutating your state is that if you have a reference to this.state.data, and calling setState multiple times, you may lose your data:

const myData = this.state.data
myData[0] = 'foo'
this.setState({ data: myData })
// do something...
// ...
const someNewData = someFunc()
this.setState({ data: someNewData })

myData[1] = 'bar' // myData is still referencing to the old state
this.setState({ data: myData }) // you lose everything of `someNewData`

If you really concerned about this, just go for immutable.js


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...