React.js中状态数组的正确修改方法

614

我想在 state 数组的末尾添加一个元素,这是正确的做法吗?

this.state.arrayvar.push(newelement);
this.setState({ arrayvar:this.state.arrayvar });

我担心使用 push 直接修改数组可能会有问题 - 这样做安全吗?

复制数组然后使用 setState 似乎很浪费资源。


12
我认为你可以使用React不可变性助手(react immutability helpers)。请参考这个链接:http://facebook.github.io/react/docs/update.html#simple-push - nilgun
1
在状态数组中使用setState,检查我的解决方案。<br/> <br> https://dev59.com/RFgQ5IYBdhLWcg3w6IOg#59711447 - Rajnikant Lodhi
19个回答

980

根据React文档所述:

把 this.state 视为不可变的。

使用 push 直接改变状态可能导致代码容易出错,即使之后进行“重置”状态也是如此。例如,这可能会导致某些生命周期方法(如 componentDidUpdate)无法触发。

在较新的 React 版本中,推荐的方法是使用更新器函数来修改状态以防止竞态条件:

this.setState(prevState => ({
  arrayvar: [...prevState.arrayvar, newelement]
}))

与使用非标准状态修改相比,内存“浪费”并不是一个问题。

早期React版本的替代语法

您可以使用concat来获得简洁的语法,因为它会返回一个新数组:

this.setState({ 
  arrayvar: this.state.arrayvar.concat([newelement])
})

在ES6中,您可以使用 展开运算符
this.setState({
  arrayvar: [...this.state.arrayvar, newelement]
})

10
可以举个例子说明什么情况下会出现竞争条件吗? - soundly_typed
3
@Qiming push方法返回新数组的长度,因此不起作用。另外,setState是异步的,React可以将多个状态更改排队到单个渲染过程中。 - David Hellsing
2
@mindeavor 假设你有一个 animationFrame,它在 this.state 中查找参数,还有另一个方法,在状态改变时更改一些其他参数。由于 setState 是异步的,可能存在一些状态已经改变但尚未反映在监听状态变化的方法中的帧。 - David Hellsing
1
@David,我建议您更新您的答案,包括@NealeU的版本。这是我现在会这样做,以避免突变任何东西并且对于重复调用是安全的:this.setState(state => ({ ...state, arrayvar: state.arrayvar.concat([newelement]) })); - Christopher Camps
3
如今将状态数组视为不可变的简单方法是:let list = Array.from(this.state.list); list.push('woo'); this.setState({list}); 当然,可以根据自己的风格偏好进行修改。 - basicdays
显示剩余11条评论

167

如果你正在使用ES6,那么这是最简单的方法。

initialArray = [1, 2, 3];

newArray = [ ...initialArray, 4 ]; // --> [1,2,3,4]

新的数组将为[1,2,3,4]

React 中更新您的状态

this.setState({
  arrayvar:[...this.state.arrayvar, newelement]
});

了解更多关于数组解构的内容


1
追加或者前置很简单。那么搜索和替换呢?例如对象数组。我需要通过id搜索并更新一个对象。 - Sisir
2
你的问题并没有直接涉及到原帖的问题。 - StateLess
1
@ChanceSmith:这也需要在*无状态(StateLess)*答案中。不要依赖于状态本身来更新状态。官方文档:https://reactjs.org/docs/state-and-lifecycle.html#state-updates-may-be-asynchronous - arcol
2
@Sisir和任何想知道如何在状态中更新数组中的对象的人,请参考此链接:https://dev59.com/BF4c5IYBdhLWcg3wQoc5。 - Emile Bergeron
1
@RayCoder 记录并检查 arrayvar 的值,看起来它不是数组。 - StateLess
显示剩余2条评论

72

使用 ES6 的最简单方法:

this.setState(prevState => ({
    array: [...prevState.array, newElement]
}))

抱歉,在我的情况下,我想将一个数组推入到另一个数组中。tableData = [['test','test']] 推入我的新数组之后,tableData = [['test','test'],['new','new']]。 请问如何实现这个功能 @David 和 @Ridd? - Johncy
@Johncy 如果您想要获得 [['test','test'],['new','new']] 请尝试:this.setState({       tableData: [...this.state.tableData, ['new', 'new']] - Ridd
这是翻译后的文本: this.setState({ tableData: [...this.state.tableData ,[item.student_name,item.homework_status_name,item.comments===null?'-':item.comments] ] }); 它将新数组插入两次 this.state.tableData.push([item.student_name,item.homework_status_name,item.comments===null?'-':item.comments]); 它实现了我想要的结果,但我认为这不是正确的方法。 - Johncy

43

目前有很多人遇到更新useState钩子状态的问题。我使用这种方法来安全地更新它,并希望在这里分享。

这是我的状态

const [state, setState] = useState([])
假设我有一个名为obj1的对象,我想将它追加到我的状态中。我建议这样做:

假设我有一个名为obj1的对象,我想将其附加到我的状态中。我建议像这样做:

setState(prevState => [...prevState, obj1])

这将安全地在末尾插入对象,并保持状态的一致性。


3
这是更新状态的正确方式,当其新值取决于先前的值时。文档:如果下一个状态取决于当前状态,则建议使用更新程序函数形式: setState((state) => ...,请参见https://en.reactjs.org/docs/react-component.html#setstate - 状态更新不会立即发生,因此在使用原始状态变量时,某些更新可能会被新的更新覆盖。 - Marcin Wojnarski
除了在应该同时添加内容时无法正常工作之外,@MarcinWojnarski。 - Boiethios

33

React 可能会批量更新,因此正确的做法是提供一个执行更新的函数给 setState。

对于 React 更新插件,以下代码可以可靠地工作:

this.setState( state => update(state, {array: {$push: [4]}}) );

或者对于concat()函数:
this.setState( state => ({
    array: state.array.concat([4])
}));

以下是错误示例https://jsbin.com/mofekakuqi/7/edit?js,output,它展示了如果犯错会发生什么情况。
setTimeout()方法正确添加了三个项目,因为React不会在setTimeout回调中批处理更新(请参阅https://groups.google.com/d/msg/reactjs/G6pljvpTGX0/0ihYw2zK9dEJ)。
有错误的onClick函数只会添加“Third”,但修复后的函数将按预期添加F、S和T。
class List extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      array: []
    }

    setTimeout(this.addSome, 500);
  }

  addSome = () => {
      this.setState(
        update(this.state, {array: {$push: ["First"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Second"]}}));
      this.setState(
        update(this.state, {array: {$push: ["Third"]}}));
    };

  addSomeFixed = () => {
      this.setState( state => 
        update(state, {array: {$push: ["F"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["S"]}}));
      this.setState( state => 
        update(state, {array: {$push: ["T"]}}));
    };



  render() {

    const list = this.state.array.map((item, i) => {
      return <li key={i}>{item}</li>
    });
       console.log(this.state);

    return (
      <div className='list'>
        <button onClick={this.addSome}>add three</button>
        <button onClick={this.addSomeFixed}>add three (fixed)</button>
        <ul>
        {list}
        </ul>
      </div>
    );
  }
};


ReactDOM.render(<List />, document.getElementById('app'));

真的有这种情况发生吗? 如果我们只是简单地执行this.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) ) - Albizia
1
@Albizia 我认为你应该找一个同事并与他们讨论。如果您只进行一次setState调用,则没有批处理问题。重点是要显示React批处理更新,因此是的...确实存在一个情况,这就是您可以在上述代码的JSBin版本中找到的内容。 几乎所有此线程中的答案都未能解决此问题,因此可能会有很多代码出现故障的情况。 - NealeU
state.array = state.array.concat([4]) 这会改变先前的状态对象。 - Emile Bergeron
1
@EmileBergeron 感谢您的坚持不懈。最终我回过头来看,发现了我的大脑一直拒绝看到的问题,并检查了文档,所以我会进行编辑。 - NealeU
1
很好!在JS中,不可变性并不明显(尤其是在处理库的API时),所以很容易出错。 - Emile Bergeron
显示剩余2条评论

31

如果您在React中使用函数式组件

const [cars, setCars] = useState([{
  name: 'Audi',
  type: 'sedan'
}, {
  name: 'BMW',
  type: 'sedan'
}])

...

const newCar = {
  name: 'Benz',
  type: 'sedan'
}

const updatedCarsArray = [...cars, newCar];

setCars(updatedCarsArray);

21

正如@nilgun在评论中提到的那样,您可以使用React immutability helpers。 我发现这个非常有用。

从文档中可以看到:

简单推入

var initialArray = [1, 2, 3];
var newArray = update(initialArray, {$push: [4]}); // => [1, 2, 3, 4]

initialArray 仍然是 [1, 2, 3]。


8
React 不可变性辅助工具在文档中被描述为已弃用。现在应该使用 https://github.com/kolodny/immutability-helper 代替。 - Periata Breatta
这个回答和评论是真实的,我很感激你们花时间以如此简洁的方式写下它们。immutability-helper 表达了这个问题,并在这个方向上有很多想法和代码。 - Priya Ranjan Singh

8
如果您正在使用功能组件,请按以下方式使用此内容。
const [chatHistory, setChatHistory] = useState([]); // define the state

const chatHistoryList = [...chatHistory, {'from':'me', 'message':e.target.value}]; // new array need to update
setChatHistory(chatHistoryList); // update the state

1
伙计,我挣扎了12个小时,毫无头绪,不知道发生了什么事情,你救了我的命。 - Hiệp Nguyễn
@HiệpNguyễn 很好。 - Harshan Morawaka

1
this.setState(preState=>({arrayvar:[...prevState.arrayvar,newelement]}))

这将有效地解决这个问题。

我的问题不完全相同,但你的方括号是一样的。感谢你让我的一天变得更好 :) - AnumR

0
这是一个2020年的Reactjs Hook示例,我认为它可以帮助别人。我正在使用它来向Reactjs表格添加新行。如果有需要改进的地方,请告诉我。
向函数式状态组件添加新元素:
定义状态数据:
    const [data, setData] = useState([
        { id: 1, name: 'John', age: 16 },
        { id: 2, name: 'Jane', age: 22 },
        { id: 3, name: 'Josh', age: 21 }
    ]);

让按钮触发一个函数以添加新元素。
<Button
    // pass the current state data to the handleAdd function so we can append to it.
    onClick={() => handleAdd(data)}>
    Add a row
</Button>

function handleAdd(currentData) {

        // return last data array element
        let lastDataObject = currentTableData[currentTableData.length - 1]

        // assign last elements ID to a variable.
        let lastID = Object.values(lastDataObject)[0] 

        // build a new element with a new ID based off the last element in the array
        let newDataElement = {
            id: lastID + 1,
            name: 'Jill',
            age: 55,
        }

        // build a new state object 
        const newStateData = [...currentData, newDataElement ]

        // update the state
        setData(newStateData);

        // print newly updated state
        for (const element of newStateData) {
            console.log('New Data: ' + Object.values(element).join(', '))
        }

}

如果我想从数组中移除一个元素,该怎么做? - Ken
@Ken,你正在使用什么类型的数组?你的数组对象应该内置一个删除函数。你需要触发删除操作,然后更新状态。 - Ian Smith

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接