Правильна модифікація державних масивів у ReactJS


416

Я хочу додати елемент до кінця 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/a/59711447/9762736
Rajnikant Lodhi

Відповіді:


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 :

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

8
Чи можете ви навести приклад, коли настане умова перегонів?
soundly_typed

3
@Qiming pushповертає нову довжину масиву, щоб він не працював. Крім того, setStateє асинхронізація і React може встановити чергу кількох змін стану в один пропуск візуалізації.
Девід Гелсінг

2
@mindeavor стверджує, що у вас є анімаціяFrame, яка шукає параметри в this.state, та інший метод, який змінює деякі інші параметри при зміні стану. Можуть бути деякі кадри, де стан змінився, але не відображено в методі, який слухає зміни, тому що setState є асинхронним.
Девід Гелсінг

1
@ChristopherCamps Ця відповідь не заохочує дзвонити setStateдвічі, вона показує два подібних приклади встановлення масиву стану, не змінюючи його безпосередньо.
Девід Гелсінг

2
Найпростішим способом трактувати масив стану як непорушного в наші дні є: let list = Array.from(this.state.list); list.push('woo'); this.setState({list}); Зміна звичайно своїх стильових уподобань.
основні дні

133

Найпростіше, якщо ви використовуєте ES6.

initialArray = [1, 2, 3];

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

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

оновити свій стан в Реакт

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

Дізнайтеся більше про знищення масиву


@Muzietto ви можете розробити?
StateLess

4
Суть цього питання полягає в зміні стану React, не змінюючи масивів. Ви самі бачили мою думку і відредагували свою відповідь. Це робить вашу відповідь актуальною. Молодці.
Марко Фаустінеллі

2
Ваші запитання не стосуються питання ОП безпосередньо
StateLess

1
@ChanceSmith: він потрібен і у відповіді StateLess . Не залежать в оновленнях стану від самої держави. Офіційний doc: reactjs.org/docs/…
arcol

1
@RayCoder журнал і перевірити значення масиву, схоже, це не масив.
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 ).

Помилка 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"]}}) )
АЛБІЗ

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]

початковийArray як і раніше [1, 2, 3].


6
Помічники незмінності React описані як застарілі в документації. Тепер замість цього слід використовувати github.com/kolodny/immutability-helper .
Periata 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

Я намагаюся натиснути значення в стані масиву і встановити таке значення, як це, і визначити масив стану і значення push за функцією карти.

 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
@Kobe цей виклик "оператора розповсюдження" в ES6 та додавання елементів array2 до масиву1. дякую за 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;
}));

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.