在 React.js 中正确修改状态数组

Correct modification of state arrays in React.js

提问人:fadedbee 提问时间:10/8/2014 最后编辑:Or Assayagfadedbee 更新时间:2/16/2023 访问量:908137

问:

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

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

我担心就地修改数组可能会导致麻烦 - 安全吗?push

制作数组副本的替代方案,这似乎是浪费。setState

JavaScript 反应 JS

评论

12赞 nilgun 10/8/2014
我认为您可以使用 react 不可变性助手。看这个: facebook.github.io/react/docs/update.html#simple-push
1赞 Rajnikant Lodhi 1/13/2020
状态数组中的 setState 检查我的解决方案.<br/> <br> stackoverflow.com/a/59711447/9762736

答:

979赞 David Hellsing 10/8/2014 #1

React 文档说:

将 this.state 视为不可变的。

您将直接更改状态,这可能会导致容易出错的代码,即使您之后再次“重置”状态。例如,这可能会导致某些生命周期方法(如不会触发)。pushcomponentDidUpdate

在更高版本的 React 中,推荐的方法是在修改状态时使用 updater 函数来防止争用条件:

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

与使用非标准状态修改可能遇到的错误相比,内存“浪费”不是问题。

早期 React 版本的替代语法

您可以使用它来获取干净的语法,因为它返回一个新数组:concat

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

在 ES6 中,您可以使用 Spread Operator

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

评论

10赞 soundly_typed 2/11/2015
您能否举例说明何时会发生争用条件?
3赞 David Hellsing 7/1/2015
@Qiming 返回新的数组长度,因此该长度不起作用。此外,是异步的,React 可以将多个状态更改排队到单个渲染通道中。pushsetState
2赞 David Hellsing 12/21/2016
@mindeavor假设您有一个 animationFrame 在 this.state 中查找参数,以及另一个在状态更改时更改其他一些参数的方法。可能在某些帧中,状态已更改,但未反映在侦听更改的方法中,因为 setState 是异步的。
1赞 David Hellsing 2/8/2017
@ChristopherCamps 这个答案不鼓励调用两次,它显示了两个类似的设置状态数组而不直接改变它的例子。setState
3赞 basicdays 3/4/2017
如今,将状态数组视为不可变的一种简单方法是:当然,根据您的样式偏好进行修改。let list = Array.from(this.state.list); list.push('woo'); this.setState({list});
21赞 Clarkie 8/28/2015 #2

正如评论中提到的@nilgun,您可以使用 react 不可变性帮助程序。我发现这非常有用。

从文档中:

简单推送

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

initialArray 仍为 [1, 2, 3]。

评论

8赞 Periata Breatta 1/20/2017
React 不可变性帮助程序在文档中被描述为已弃用。现在应该改用 github.com/kolodny/immutability-helper
0赞 Priya Ranjan Singh 2/24/2022
这个答案和这个评论是真实的,我很感激花时间以如此简洁的方式写下它——你们俩。 表达了这种担忧,并在这个方向上有很多想法和代码。immutability-helper
167赞 StateLess 5/3/2016 #3

最简单,如果您使用 .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赞 Sisir 7/7/2017
追加或预置非常简单。搜索和替换呢?例如,对象数组。我需要更新一个按 id 搜索的对象吗?
2赞 StateLess 7/10/2017
您的问题与OP问题没有直接关系
1赞 arcol 12/26/2018
@ChanceSmith:StateLess 答案中也需要它。不要依赖于状态本身的状态更新。官方文档:reactjs.org/docs/...
2赞 Emile Bergeron 8/7/2019
@Sisir和任何想知道如何更新状态数组中的对象的人。
1赞 StateLess 9/11/2019
@RayCoder日志并检查 arrayvar 的值,看起来它不是数组。
33赞 NealeU 1/3/2017 #4

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)。

buggy onClick 只会添加“Third”,但固定的 Bug 会按预期添加 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'));

评论

0赞 Albizia 7/25/2019
真的有发生这种情况吗?如果我们只是这样做this.setState( update(this.state, {array: {$push: ["First", "Second", "Third"]}}) )
1赞 NealeU 8/4/2019
@Albizia我认为你应该找一个同事和他们讨论一下。如果只进行一个 setState 调用,则不会出现批处理问题。重点是要显示 React 批处理更新,所以是的......确实有一个案例,这就是您可以在上面代码的 JSBin 版本中找到的。这个线程中几乎所有的答案都无法解决这个问题,所以会有很多代码有时会出错
0赞 Emile Bergeron 8/7/2019
state.array = state.array.concat([4])这将改变以前的状态对象。
1赞 NealeU 11/1/2019
@EmileBergeron 感谢您的坚持。我最终回头看了看,看到了我的大脑拒绝看到的东西,并检查了文档,所以我会编辑。
1赞 Emile Bergeron 11/1/2019
好!这真的很容易出错,因为 JS 中的不变性并不明显(在处理库的 API 时更是如此)。
72赞 Ridd 6/29/2017 #5

最简单的方法:ES6

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

评论

0赞 Johncy 9/21/2018
对不起,就我而言,我想将数组推入数组。 推送我的新数组后.如何推动这个@David和@RiddtableData = [['test','test']]tableData = [['test','test'],['new','new']]
0赞 Ridd 9/24/2018
@Johncy 如果您想尝试:[['test','test'],['new','new']]this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
0赞 Johncy 9/24/2018
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]);
-10赞 Hamid Hosseinpour 9/24/2018 #6

这段代码对我有用:

fetch('http://localhost:8080')
  .then(response => response.json())
  .then(json => {
    this.setState({mystate: this.state.mystate.push.apply(this.state.mystate, json)})
  })

评论

2赞 Asbar Ali 1/30/2019
不过,你直接改变状态
2赞 Emile Bergeron 8/23/2019
并且无法正确更新组件的状态,因为 .push 返回一个数字,而不是一个数组。
-1赞 Rajnikant Lodhi 1/13/2020 #7

我正在尝试在数组状态中推送值并像这样设置值,并通过映射函数定义状态数组和推送值。

 this.state = {
        createJob: [],
        totalAmount:Number=0
    }


 your_API_JSON_Array.map((_) => {
                this.setState({totalAmount:this.state.totalAmount += _.your_API_JSON.price})
                this.state.createJob.push({ id: _._id, price: _.your_API_JSON.price })
                return this.setState({createJob: this.state.createJob})
            })
0赞 Jagger 2/18/2020 #8

对于将新元素添加到数组中,应该是答案。push()

对于删除元素和更新数组的状态,下面的代码对我有用。 不能工作。splice(index, 1)

const [arrayState, setArrayState] = React.useState<any[]>([]);
...

// index is the index for the element you want to remove
const newArrayState = arrayState.filter((value, theIndex) => {return index !== theIndex});
setArrayState(newArrayState);

评论

2赞 Emile Bergeron 8/27/2020
push改变数组,这是 React 中的反模式。
8赞 Harshan Morawaka 3/6/2020 #9

如果您使用的是功能组件,请按如下方式使用。

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赞 Hiệp Nguyễn 1/11/2022
伙计,我挣扎了 12 个小时,不知道发生了什么,你救了我的命
0赞 Harshan Morawaka 2/10/2022
@HiệpNguyễn不错
-3赞 ANUBHAV RAWAT 3/9/2020 #10
//------------------code is return in typescript 

const updateMyData1 = (rowIndex:any, columnId:any, value:any) => {

    setItems(old => old.map((row, index) => {
        if (index === rowIndex) {
        return Object.assign(Object.assign({}, old[rowIndex]), { [columnId]: value });
    }
    return row;
}));
-2赞 MadKad 3/17/2020 #11

这对我在数组中添加数组有用

this.setState(prevState => ({
    component: prevState.component.concat(new Array(['new', 'new']))
}));
0赞 Ian Smith 6/1/2020 #12

这是一个 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(', '))
        }

}

评论

0赞 Ken 2/7/2021
如果我想从数组中删除一个元素,而不是添加元素怎么办?
0赞 Ian Smith 2/7/2021
@Ken您正在使用哪种阵列?数组对象应内置一个内置的删除函数。您将触发删除,然后更新状态。
-1赞 L Andy 11/24/2020 #13

当我想修改数组状态时,我遇到了类似的问题 同时保留元素在数组中的位置

这是一个在喜欢和不喜欢之间切换的功能:

    const liker = (index) =>
        setData((prevState) => {
            prevState[index].like = !prevState[index].like;
            return [...prevState];
        });

正如我们所说,该函数在数组状态中获取元素的索引,然后我们继续修改旧状态并重建状态树

评论

0赞 Emile Bergeron 12/9/2020
这是对先前状态的变异,这是 React 中的反模式。
0赞 L Andy 12/9/2020
@EmileBergeron我们如何在不违反 react 规则的情况下修改状态中的元素,同时保留位置呢?
0赞 Emile Bergeron 12/9/2020
您需要在嵌套的每个步骤制作一个浅拷贝。
31赞 codrp 1/6/2021 #14

如果你在 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);
43赞 moshfiqrony 8/21/2021 #15

目前很多人都面临着更新useState钩子状态的问题。我使用这种方法来安全地更新它,并想在这里分享它。

这是我的状态

const [state, setState] = useState([])

假设我有一个对象名称,我希望它附加到我的状态中。我会建议这样做obj1

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

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

评论

3赞 Marcin Wojnarski 12/3/2021
当状态的新值依赖于前一个状态时,这是更新状态的正确方法。文档:如果下一个状态取决于当前状态,我们建议使用 updater 函数形式:,请参阅 en.reactjs.org/docs/react-component.html#setstate -- 状态更新不会立即发生,因此在使用原始状态变量时,某些更新可能会被新更新覆盖。setState((state) => ...
0赞 Boiethios 12/25/2022
@MarcinWojnarski 除了当东西应该同时附加时它不起作用......
1赞 raja saad 10/13/2021 #16
this.setState(preState=>({arrayvar:[...prevState.arrayvar,newelement]}))

这将解决这个问题。

评论

0赞 AnumR 9/24/2022
我的问题完全不一样,但你的方括号和我一样,谢谢:)👍
-2赞 Fernando Alcantara 11/3/2021 #17
//get the value you want to add
const valor1 = event.target.elements.valor1.value;

//add in object 
        const todo = {
            valor1,
        }

//now you just push the new value into the state

//prevlista is the value of the old array before updating, it takes the old array value makes a copy and adds a new value

setValor(prevLista =>{
return prevLista.concat(todo) })
     
0赞 meiser 4/4/2022 #18

我所做的是更新状态之外的值并执行 forceupdate(),react 管理的东西越少越好,因为您可以更好地控制更新的内容。 此外,如果更新速度很快,则为每次更新创建新阵列可能成本太高

评论

0赞 Besworks 4/6/2022
您可以通过包含一个最小的、可重复的示例来改进您的答案。