React中使用this.setState时如何避免修改原始JSON数据顺序
解决React中排序修改原始JSON数据的问题
这个问题的核心原因是JavaScript数组是引用类型,而且sort()方法是原地排序——它会直接修改调用它的原数组。你把原始的test JSON数组直接赋值给了state,之后对this.state.test调用sort()时,实际上是在修改原始的JSON数组(因为它们指向同一个内存地址)。
下面是具体的修复方案:
1. 初始化state时拷贝原始数组
首先,在组件的constructor里,不要直接将原始test数组赋值给state,而是创建一个浅拷贝,这样state里的数组和原始JSON数组就会是两个独立的引用:
constructor() { super(); // 用扩展运算符创建数组拷贝,也可以用 test.slice() const testCopy = [...test]; this.state = { index: 0, result: 0, testLength: test.length, test: testCopy }; }
2. 排序时始终操作拷贝后的数组
在getTest方法里,不要直接对this.state.test调用sort()(这会修改state里的数组本身,虽然不会影响原始JSON,但不符合React state不可直接修改的原则),而是先创建数组的拷贝,再对拷贝排序。同时,对于每个问题的answer数组,也要做同样的处理——因为数组里的对象是引用类型,直接排序item.answer会修改state里对应对象的answer数组:
getTest() { // 先拷贝state里的test数组,再排序 const sortedTest = [...this.state.test].sort((a, b) => 0.5 - Math.random()); return sortedTest.map((item, i) => { if (i == 0) { // 拷贝answer数组再排序 const sortedAnswers = [...item.answer].sort((a, b) => 0.5 - Math.random()); return ( <section key={i} className="d-flex main-sec"> <h3>{item.question}</h3> <form className="list-group form"> {sortedAnswers.map((ansItem, ansIndex) => { return ( <li key={ansIndex} className="list-group-item test-list list-group-item-action"> <input type="radio" name="answer" value={ansItem.isTrue} onClick={this.onChange.bind(this)} /> <p>{ansItem.ans}</p> </li> ); })} </form> <button className="pre-button btn btn-lg btn-danger" onClick={this.prevEl.bind(this)}> <i className="fa fa-long-arrow-left" aria-hidden="true"/> Prev </button> <button className="next-button btn btn-lg btn-success" onClick={this.nextEl.bind(this)}> Next <i className="fa fa-long-arrow-right" aria-hidden="true" /> </button> </section> ); } else if(i == this.state.testLength - 1) { const sortedAnswers = [...item.answer].sort((a, b) => 0.5 - Math.random()); return ( <section key={i} className="d-none"> <h3>{item.question}</h3> <form className="list-group form"> {sortedAnswers.map((ansItem, ansIndex) => { return ( <li key={ansIndex} className="list-group-item test-list list-group-item-action"> <input type="radio" name="answer" value={ansItem.isTrue} onClick={this.onChange.bind(this)} /> <p>{ansItem.ans}</p> </li> ); })} </form> <button className="pre-button btn btn-lg btn-danger" onClick={this.prevEl.bind(this)}> <i className="fa fa-long-arrow-left" aria-hidden="true"/> Prev </button> <div id="check-result"> <button className="pre-button btn btn-lg btn-success" onClick={this.prevEl.bind(this)}> Check Your Result </button> </div> </section> ) } else { // 这里如果需要排序answer,同样要先拷贝;不需要排序的话,拷贝也能避免意外修改 const sortedAnswers = [...item.answer]; return ( <section key={i} className="d-none"> <h3>{item.question}</h3> <form className="list-group form"> {sortedAnswers.map((ansItem, ansIndex) => { return ( <li key={ansIndex} className="list-group-item test-list list-group-item-action"> <input type="radio" name="answer" value={ansItem.isTrue} onClick={this.onChange.bind(this)} /> <p>{ansItem.ans}</p> </li> ); })} </form> <button className="pre-button btn btn-lg btn-danger" onClick={this.prevEl.bind(this)}> <i className="fa fa-long-arrow-left" aria-hidden="true" /> Prev </button> <button className="pre-button btn btn-lg btn-success" onClick={this.nextEl.bind(this)}> Next <i className="fa fa-long-arrow-right" aria-hidden="true" /> </button> </section> ); } }); }
为什么这样有效?
- 扩展运算符
[...arr]会创建一个新的数组实例,新数组和原数组共享里面的对象引用,但数组本身是独立的——排序操作只会影响新数组,不会修改原数组。 - 这样既保证了你的随机排序需求,又不会触碰原始的JSON数据,同时也遵循了React中“不要直接修改state”的最佳实践。
内容的提问来源于stack exchange,提问作者Alisher Musurmonv
相关产品推荐
相关产品推荐

