【问题标题】:Correct modification of state arrays in React.js正确修改 React.js 中的状态数组
【发布时间】:2014-12-02 21:41:38
【问题描述】:

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

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

我担心使用 push 就地修改数组可能会导致问题 - 安全吗?

制作数组副本的替代方法,setStateing 这似乎很浪费。

【问题讨论】:

标签: javascript reactjs


【解决方案1】:

React docs 说:

将 this.state 视为不可变。

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

在以后的 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]
})

【讨论】:

  • 您能否举例说明何时会出现竞争条件?
  • @Qiming push 返回新的数组长度,所以这不起作用。此外,setState 是异步的,React 可以将多个状态更改排队到单个渲染通道中。
  • @mindeavor 说您有一个在 this.state 中查找参数的 animationFrame,以及另一个在状态更改时更改其他一些参数的方法。可能有一些帧的状态发生了变化,但没有反映在监听变化的方法中,因为 setState 是异步的。
  • @ChristopherCamps 这个答案不鼓励调用setState 两次,它显示了两个类似的设置状态数组而不直接改变它的例子。
  • 如今将状态数组视为不可变的一种简单方法是:let list = Array.from(this.state.list); list.push('woo'); this.setState({list}); 当然可以修改为您的样式偏好。
【解决方案2】:

正如评论中提到的@nilgun,您可以使用反应immutability helpers。我发现这非常有用。

来自文档:

简单推送

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

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

【讨论】:

【解决方案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]
});

Learn more about array destructuring

【讨论】:

  • @Muzietto 你能详细说明一下吗?
  • 这个问题的症结在于改变 React 状态,而不是修改数组。您自己看到了我的观点并编辑了您的答案。这使您的答案具有相关性。干得好。
  • 您的问题与OP问题没有直接关系
  • @ChanceSmith:StateLess 答案中也需要它。不要依赖于状态本身的状态更新。官方文档:reactjs.org/docs/…
  • @RayCoder 记录并检查arrayvar的值,看起来它不是数组。
【解决方案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)。

有bug的onClick只会加“Third”,而固定的onClick会按预期加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 我认为你应该找一个同事和他们讨论一下。如果您只进行一次 setState 调用,则不存在批处理问题。重点是为了说明 React 是批量更新的,所以是的……确实有一个案例,就是上面代码的 JSBin 版本中可以找到的。这个线程中的几乎所有答案都无法解决这个问题,所以会有很多代码有时会出错
  • state.array = state.array.concat([4]) 这会改变之前的状态对象。
  • @EmileBergeron 感谢您的坚持。我最终回头看到了我的大脑拒绝看到的内容,并检查了文档,所以我将进行编辑。
  • 好!很容易出错,因为 JS 中的不变性并不明显(在处理库的 API 时更是如此)。
【解决方案5】:

ES6最简单的方法:

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

【讨论】:

  • 对不起,在我的情况下,我想将一个数组推入数组。 tableData = [['test','test']] 推送我的新数组tableData = [['test','test'],['new','new']] 后。如何推送这个@David 和@Ridd
  • @Johncy 如果您想获得[['test','test'],['new','new']],请尝试:this.setState({ tableData: [...this.state.tableData, ['new', 'new']]
  • 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]); 它实现了我想要的东西。但我认为这不是正确的方式。
【解决方案6】:

这段代码对我有用:

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

【讨论】:

  • 仍然,你直接改变状态
  • 并且由于.push returns a number而不是数组而无法正确更新组件的状态。
【解决方案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})
            })

【讨论】:

    【解决方案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);
    

    【讨论】:

    【解决方案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
    

    【讨论】:

      【解决方案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;
      }));
      

      【讨论】:

        【解决方案11】:

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

        this.setState(prevState => ({
            component: prevState.component.concat(new Array(['new', 'new']))
        }));
        

        【讨论】:

          【解决方案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(', '))
                  }
          
          }
          

          【讨论】:

          • 如果我想从数组中删除一个元素而不是添加,该怎么办?
          • @Ken 您使用的是哪种阵列?你的数组对象应该有一个内置的删除函数。你会触发删除,然后更新状态。
          【解决方案13】:

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

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

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

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

          【讨论】:

          • 这是在改变之前的状态,这是 React 中的一种反模式。
          • @EmileBergeron 我们如何在不破坏反应规则的情况下修改状态中的元素同时保留位置?
          【解决方案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);
          

          【讨论】:

            【解决方案15】:

            由于不允许直接改变状态,因此不能简单地将项目推送到数组。

            state = {
                  value: '',
                  list: ['a', 'b', 'c'],
                };
            
            this.setState({
              list: [...this.state.list, newelement]
            })
            
            
            //or
            
              onAddItem = () => {
                // not allowed AND not working
                this.setState(state => {
                  const list = state.list.push(state.value);
             
                  return {
                    list,
                    value: '',
                  };
                });
              };
            

            know more

            【讨论】:

            • 在第二个语句中,您将丢失“值”键。你应该做的只是更新保持上一个状态的列表: setState({...state, list: [...state.list, newelement]})
            【解决方案16】:

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

            这是我的状态

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

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

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

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

            【讨论】:

            • 当新值依赖于前一个状态时,这是更新状态的正确方法。文档:如果下一个状态依赖于当前状态,我们推荐使用更新函数形式: setState((state) =&gt; ...,见en.reactjs.org/docs/react-component.html#setstate -- 状态更新不会立即发生,所以在使用原始状态时变量,某些更新可能会被新更新覆盖。
            【解决方案17】:
            this.setState(preState=>({arrayvar:[...prevState.arrayvar,newelement]}))
            

            这将解决这个问题。

            【讨论】:

              【解决方案18】:
              //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) })
                   
              

              【讨论】:

                【解决方案19】:

                状态是一个对象。您的用例状态可能是这样的:

                this.state = {
                    items: [],
                }
                

                新状态的引用必须不同于旧状态。

                所以试试这个:

                this.setState({
                    items: [
                        ...this.state.items,
                        newElement,
                    ],
                })
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2022-01-10
                  • 1970-01-01
                  • 1970-01-01
                  • 2017-02-26
                  相关资源
                  最近更新 更多