Правильная модификация массивов состояний в ReactJS


417

Я хочу добавить элемент в конец stateмассива, это правильный способ сделать это?

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

Я обеспокоен тем, что изменение массива на месте pushможет вызвать проблемы - это безопасно?

Альтернатива сделать копию массива и сделать setStateэто кажется расточительной.


9
Я думаю, что вы можете использовать помощники реагировать неизменности. смотрите это: facebook.github.io/react/docs/update.html#simple-push
nilgun

setState в массиве состояний, проверьте мое решение. <br/> <br> stackoverflow.com/ru/59711447/9762736
Раджникант Лодхи

Ответы:


763

Документы React говорят:

Относитесь к этому состоянию как к неизменному.

Вы pushбудете напрямую изменять состояние, что потенциально может привести к появлению кода, подверженного ошибкам, даже если впоследствии вы снова «сбрасываете» состояние. F.ex, это может привести к тому, что некоторые методы жизненного цикла, например componentDidUpdate, не сработают.

Рекомендуемый подход в более поздних версиях React заключается в использовании функции обновления при изменении состояний для предотвращения состояний гонки:

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]
})

8
Можете ли вы привести пример того, когда может возникнуть состояние гонки?
soundly_typed

3
@Qiming pushвозвращает новую длину массива, чтобы она не работала. Кроме того, setStateisync и React могут ставить в очередь несколько изменений состояния в один проход рендеринга.
Дэвид Хеллсинг

2
@mindeavor говорит, что у вас есть animationFrame, который ищет параметры в this.state, и другой метод, который изменяет некоторые другие параметры при изменении состояния. Могут быть некоторые кадры, в которых состояние изменилось, но не отражено в методе, который прослушивает изменение, потому что setState является асинхронным.
Дэвид Хеллсинг

1
@ChristopherCamps Этот ответ не поощряет вызов setStateдважды, он показывает два похожих примера установки массива состояний без непосредственного изменения его.
Дэвид Хеллсинг

2
В наши дни простой способ сделать массив состояний неизменным: let list = Array.from(this.state.list); list.push('woo'); this.setState({list});измените его в соответствии со своими предпочтениями стиля.
basicdays

133

Проще всего, если вы используете ES6.

initialArray = [1, 2, 3];

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

Новый массив будет [1,2,3,4]

обновить свое состояние в React

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

Узнайте больше о деструктуризации массива


@Muzietto вы можете уточнить?
StateLess

4
Суть этого вопроса - изменение состояния React, а не изменение массивов. Вы сами поняли мою точку зрения и отредактировали свой ответ. Это делает ваш ответ актуальным. Отлично сработано.
Марко Фаустинелли

2
Ваши вопросы не касаются вопроса ОП напрямую
StateLess

1
@ChanceSmith: это необходимо и в ответе StateLess . Не зависит в обновлении состояния от самого состояния. Официальный документ: reactjs.org/docs/...
Arcol

1
@RayCoder войдите и проверьте значение arrayvar, похоже, это не массив.
StateLess

57

Самый простой способ с 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']]
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]);Достигает желаемой цели. но это не правильный путь, я думаю.
Джонси

26

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

Багги на клике добавят только «третий», а исправленный добавит 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])это мутирует предыдущий объект состояния.
Эмиль Бержерон

1
@EmileBergeron Спасибо за вашу настойчивость. В конце концов я оглянулся и увидел, что мой мозг отказывался видеть, и проверил документы, так что я буду редактировать.
NealeU

1
Хорошо! Очень легко ошибиться, поскольку неизменность в JS неочевидна (тем более при работе с API библиотеки).
Эмиль Бержерон,

17

Как упомянуто в комментарии @nilgun, вы можете использовать помощники реагирования на неизменяемость . Я нашел это, чтобы быть супер полезным.

Из документов:

Простое нажатие

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

initialArray по-прежнему [1, 2, 3].


6
Помощники React по неизменяемости описаны в документации как устаревшие. Теперь вместо этого следует использовать github.com/kolodny/immutability-helper .
Периата Breatta

3

Если вы используете функциональный компонент, используйте его, как показано ниже.

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

За добавленный новый элемент в массив, 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);

0

Я пытаюсь выдвинуть значение в состояние массива и установить значение, как это и определить массив состояния и толкать значение с помощью функции карты.

 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

Это сработало для меня, чтобы добавить массив в массиве

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

0

Вот пример Reactjs Hook 2020 года, который, как я думал, может помочь другим. Я использую его для добавления новых строк в таблицу 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(', '))
        }

}

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

2
Добавьте некоторые объяснения, чтобы этот пост не рассматривался как пост низкого качества stackoverflow.
Харша Бияни

2
@ Кобе это называется «оператор распространения» в ES6 и добавление элементов array2 в array1. спасибо за downvote (:
M.Samiei

@ M.Samiei Вы должны отредактировать свой пост, чтобы избежать отрицательных голосов. Это низкое качество, просто фрагмент кода. Кроме того, опасно изменять состояние таким образом, поскольку обновления могут быть пакетными, поэтому предпочтительный способ, например,setState(state => ({arrayvar: [...state.arrayvar, ...newelement]}) );
NealeU

и распространение newelementобъекта в массиве бросает в TypeErrorлюбом случае.
Эмиль Бержерон

-1
//------------------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;
}));

-6

Этот код работает для меня:

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

2
тем не менее, вы
изменяете

2
И не может корректно обновить состояние компонента, так как .pushвозвращает число , а не массив.
Эмиль Бержерон
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.