Зачем использовать оператор распространения при вызове setState () в React? - PullRequest
2 голосов
/ 05 марта 2020

Я просто начинаю поднимать реагировать. js, поэтому я прошел много уроков и наткнулся на этот бит, который в основном означал удаление элемента из состояния.

вот как парень представил мне функцию удаления

  delTodo = id => {
    this.setState({
      todos: [...this.state.todos.filter(todo => todo.id !== id)]
    });
  };

Поскольку я не очень знаком с javascript, мне было трудно понять, что делает оператор ... и почему именно он использует это в данном сценарии. Поэтому, чтобы лучше понять, как это работает, я немного поиграл в консоль и понял, что array = [...array]. Но так ли это? Делает ли этот бит ту же самую вещь, что и выше?

  delTodo = id => {
    this.setState({
      todos: this.state.todos.filter(todo => todo.id !== id)
    });
  };

Может ли кто-то более опытный объяснить мне, почему он решил использовать этот подход вместо того, который я придумал?

Ответы [ 4 ]

2 голосов
/ 05 марта 2020

Зачем вообще использовать оператор распространения?

Оператор распространения ... часто используется для создания мелких копий массивов или объектов. Это особенно полезно, когда вы стремитесь избежать изменения значений, что поощряется по разным причинам. TLDR; Код с неизменяемыми значениями гораздо проще рассуждать. Длинный ответ здесь .

Почему оператор спреда так часто используется в реакции?

В реакции это Настоятельно рекомендуется , чтобы избежать мутации this.state и вместо этого вызвать this.setState(newState). Непосредственное изменение состояния не вызовет повторную визуализацию и может привести к ухудшению UX, неожиданному поведению или даже ошибкам. Это потому, что это может привести к тому, что внутреннее состояние будет отличаться от состояния, которое отображается.

Чтобы избежать манипулирования значениями, стало обычной практикой использование оператора распространения для создания производных объектов (или массивов), без изменения оригинала:

// current state
let initialState = {
    user: "Bastian",
    activeTodo: "do nothing",
    todos: ["do nothing"]
}


function addNewTodo(newTodo) {
    // - first spread state, to copy over the current state and avoid mutation
    // - then set the fields you wish to modify
    this.setState({
        ...this.state,
        activeTodo: newTodo,
        todos: [...this.state.todos, newTodo]
    })
}

// updating state like this...
addNewTodo("go for a run")
// results in the initial state to be replaced by this:
let updatedState = {
    user: "Bastian",
    activeTodo: "go for a run",
    todos: ["do nothing", "go for a run"]
}

Почему в этом примере используется оператор распространения?

Вероятно, чтобы избежать случайного изменения состояния. Хотя Array.filter() не изменяет исходный массив и безопасен для использования в состоянии реакции, есть несколько других методов, которые изменяют исходный массив и не должны использоваться в состоянии. Например: .push(), .pop(), .splice(). Распространяя массив перед вызовом над ним операции, вы гарантируете, что не изменяете состояние. При этом, я считаю, что автор сделал опечатку и вместо этого собирался сделать следующее:

 delTodo = id => {
    this.setState({
      todos: [...this.state.todos].filter(todo => todo.id !== id)
    });
  };

Если вам нужно использовать одну из мутирующих функций, вы можете использовать их с распространением в Чтобы избежать изменения состояния и возможных ошибок в вашем приложении, выполните следующие действия:

// here we mutate the copied array, before we set it as the new state
// note that we spread BEFORE using an array method
this.setState({
      todos: [...this.state.todos].push("new todo")
});

// in this case, you can also avoid mutation alltogether:
this.setState({
      todos: [...this.state.todos, "new todo"]
});
2 голосов
/ 05 марта 2020

Согласно документации:

Никогда не изменяйте this.state напрямую, поскольку последующий вызов setState() может заменить сделанную вами мутацию. Обрабатывайте this.state, как если бы это было immutable.


Итак, в примере из упомянутого вами учебника вам не нужно будет делать копию массива для обновления вашего состояния.

// GOOD
delTodo = id => {
  this.setState({
    todos: this.state.todos.filter(...)
  })
}

Array.filter метод создает новый массив и не изменяет исходный массив, поэтому он не будет напрямую изменять ваше состояние. То же самое относится и к таким методам, как Array.map или Array.concat.

Если ваше состояние является массивом и вы применяете методы, которые могут изменяться, вам следует скопировать ваш массив.

Узнайте больше, какие методы Array являются изменяемыми:

Однако, если вы сделать что-то вроде следующего:

// BAD
delTodo = id => {
  const todos = this.state.todos
  todos.splice(id, 1)
  this.setState({ todos: todos })
}

Тогда вы будете изменять свое состояние напрямую, потому что Array.splice изменяет содержимое существующего массива, а не возвращает новый массив после удаления указанного c вещь. Следовательно, вы должны скопировать ваш массив с помощью оператора распространения.

// GOOD
delTodo = id => {
  const todos = [...this.state.todos]
  todos.splice(id, 1)
  this.setState({ todos: todos })
}

Аналогично objects, вы должны применить ту же технику.

// BAD
updateFoo = () => {
  const foo = this.state.foo // `foo` is an object {}
  foo.bar = "HelloWorld"
  this.setState({ foo: foo })
}

Вышеприведенное непосредственно мутирует ваше государство, поэтому вы должны сделать копию, а затем обновить свое состояние.

// GOOD
updateFoo = () => {
  const foo = {...this.state.foo} // `foo` is an object {}
  foo.bar = "HelloWorld"
  this.setState({ foo: foo })
}

Надеюсь, это поможет.

2 голосов
/ 05 марта 2020

Поскольку .filter дает вам новый массив (чем мутирует исходный массив), он является приемлемым и приводит к тому же поведению, что делает распространение избыточным здесь.

Что неприемлемо:

const delIndex = this.state.todos.findIndex(todo => todo.id !== id);
this.state.todos.splice(delIndex, 1); // state mutation

this.setState({
  todos: this.state.todos
});

slice хорошо, хотя:

const delIndex = this.state.todos.findIndex(todo => todo.id !== id);

this.setState({
  todos: [
    ...this.state.todos.slice(0, delIndex),
    ...this.state.todos.slice(delIndex + 1)
  ]
});

Если вы изменяете состояние (с сохранением ref), React не сможет определить, какая часть вашего состояния фактически изменилась, и, вероятно, построить дерево на следующем рендере, который отличается от ожидаемого.

1 голос
/ 05 марта 2020

Оператор распространения, который применяет код парня, вызывает повторное копирование массива, возвращенного функцией фильтра.

Поскольку [.filter возвращает новый массив] [https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter] , вы уже избежите непосредственного изменения массива в состоянии. Кажется, что оператор распространения может быть избыточным.

Я также хотел бы отметить одну вещь: значения копируемого массива могут совпадать со значениями в старом массиве (array = [...array]). изменения экземпляров, поэтому вы не сможете использовать «===» или «==» для проверки на строгую эквивалентность.

const a = ['a', 'b']
const b = [...a]

console.log(a === b) // false
console.log(a == b) // false
console.log(a[0] === b[0]) // true
console.log(a[1] === b[1]) // true

Надеюсь, это поможет!

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...