为什么在 React 中调用 'setState()' 时要使用扩展运算符?

Why use the spread operator when calling 'setState()' in React?

我刚开始学习 react.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)
    });
  };

更有经验的人可以向我解释为什么他选择使用这种方法而不是我想出的方法吗?

由于 .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 可能无法确定你的状态的哪一部分实际发生了变化,并且可能在下一次渲染时构建一棵与预期不同的树。

这家伙的代码应用的扩展运算符导致从过滤函数返回的数组被再次复制。

由于[.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

希望对您有所帮助!

根据文档:

Never mutate this.state directly, as calling setState() afterwards may replace the mutation you made. Treat this.state as if it were immutable.


因此,在您提到的教程示例中,您不需要复制数组来更新您的状态。

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

Array.filter 方法创建一个新数组并且不会改变原始数组,因此它不会直接改变你的状态。同样的事情适用于 Array.mapArray.concat.

等方法

如果你的状态是一个数组并且你正在应用可变的方法,你应该复制你的数组。

查看更多内容以了解哪些 Array 方法是可变的:

但是,如果您要执行以下操作:

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

然后你会直接改变你的状态,因为 Array.splice 会更改现有数组的内容,而不是在删除特定项目后返回一个新数组。因此,您应该使用扩展运算符复制您的数组。

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

希望这对您有所帮助。

为什么要使用展开运算符?

展开运算符...通常用于创建数组或对象的浅表副本。当您旨在避免改变值时,这尤其有用,出于不同的原因,我们鼓励这样做。 TLDR;具有不可变值的代码更容易推理。长答案 .

为什么spread运算符在react中这么常用?

在 React 中,强烈建议 避免 this.state 的突变,而是调用 this.setState(newState)。直接改变状态不会触发重新渲染,并可能导致糟糕的用户体验、意外行为,甚至错误。这是因为它可能导致内部状态与正在呈现的状态不同。

为避免操纵值,使用展开运算符创建对象(或数组)的导数而不改变原始值已成为一种常见的做法:

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