[英]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 可能無法確定您狀態的哪一部分實際發生了變化,並且可能在下一次渲染時構造一個與預期不同的樹。
根據文檔:
永遠不要直接改變
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
改變了現有數組的內容,而不是在刪除特定項目后返回一個新數組。 因此,您應該使用擴展運算符復制您的數組。
// 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; 具有不可變值的代碼更容易推理。 長答案在這里。
為什么在反應中如此普遍地使用擴展運算符?
在this.state
,強烈建議避免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)
});
};
如果您需要使用其中一個變異函數,您可以選擇通過以下方式將它們與 spread 一起使用,以避免變異狀態並可能導致應用程序中的錯誤:
// 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"]
});
這家伙的代碼應用的擴展運算符導致過濾器函數返回的數組再次被復制。
由於 [.filter 正在返回一個新數組][ https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter] ,您已經避免直接在 state 中改變數組. 似乎擴展運算符可能是多余的。
我還想指出的一件事是,雖然復制數組中的值可能與舊數組中的值相同( 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
希望這可以幫助!
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.