当我更改其克隆时反应 prevState 发生变化,使用 newArray = [...prevState.oldArray] 克隆
React prevState changing when I change its clone, cloned with newArray = [...prevState.oldArray]
如果这是一个愚蠢的问题,我很困惑并提前道歉,我做了我的研究但找不到答案。
我按照官方教程here安装了react。
然后我按照教程进行到最后没有问题。一切正常。我在 Ubuntu 20.04.02 中使用 Visual Studio 代码。
Visual Studios 代码报告:
版本:1.59.0
提交:379476f0e13988d90fab105c5c19e7abc8b1dea8
日期:2021-08-04T23:13:20.182Z
电子:13.1.7
Chrome: 91.0.4472.124
Node.js:14.16.0
V8:9.1.269.36-电子.0
OS:Linux x64 5.11.0-25-通用快照
然后我开始学习以下课程:scrimba
面对一个练习,我采用了以下解决方案:
handleChange(id) {
this.setState(prevState => {
let newTodos = [...prevState.todos];
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
console.log('newTodos= ')
console.log(newTodos)
console.log('prevState.todos= ')
console.log(prevState.todos)
return {
todos: newTodos
}
})
}
我从控制台得到的输出显示我改变 newTodos 改变了 preState 是这样的:
newTodos=
App.js:38 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:39 prevState.todos=
App.js:40 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
但是现在,如果我使用课程作者的第二个解决方案(他的第一个解决方案也更改了 preState,但这是出于可预见的原因),则不会再发生这种情况。这是他的解决方案和复选框中相同更改的控制台输出(我选中了 todos[1].id = 2
对应的复选框)
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
控制台输出现在是正确的,preState 没有改变
(5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: false}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:59 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
为什么我的解决方案中的 prevState 发生了变化?我错过了什么?
感谢您的帮助,我担心对 javascript 或反应产生重大误解。
我将包含复制此文件所需的所有文件。 index.js 和 index.css 进入 react 安装的 src 子目录,其他文件进入 src/components 子目录。
Index.js 文件:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
App.js 文件的解决方案,另一个注释掉:
import React from "react"
import TodoItem from "./TodoItem"
import todosData from "./todosData"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
// My solution, which has the problem of changing the preState, no idea why
// this.setState(prevState => {
// let newTodos = [...prevState.todos];
// const item = newTodos.find(item => item.id==id)
// if (newTodos == prevState.todos){
// console.log("Alarm, this should be true");
// }
// item.completed = !item.completed
// console.log('newTodos= ')
// console.log(newTodos)
// console.log('prevState.todos= ')
// console.log(prevState.todos)
// return {
// todos: newTodos
// }
// })
/***************** HIS SOLUTION***************** */
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem handleChange= {this.handleChange} key={item.id} item={item} />)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
子组件TodoItem.js:
function TodoItem(props) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem
和用于运行的数据:
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery shopping",
completed: false
},
{
id: 3,
text: "Clean gecko tank",
completed: false
},
{
id: 4,
text: "Mow lawn",
completed: true
},
{
id: 5,
text: "Catch up on Arrested Development",
completed: false
}
]
export default todosData
item.completed = !item.completed
上一行导致 prevState
发生变化。
当你有
let a = [{test:1}]
你也是
let b = [...a];
b[0].test=312;
现在你也改了a[0]
(其实a[0]
和b[0]
指的是同一个对象)
这是因为...
做了一个浅拷贝。详细了解深拷贝与浅拷贝。
我想您可能已经发现了两个示例之间的区别。
[...prevState.todos]
问题是 todos
的项目是这样的对象。
{
id: 1,
text: "Take out the trash",
completed: true
},
Javascript 通过引用传递对象(类似于指针)。
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
由于 item
是对此处对象的引用,更改 item.completed
会导致更改 prevState。
我们来看第二个例子。
如您所见,他们在 map
函数
中创建了 todo
的新副本
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
这里的...todo, completed: !todo.completed
表示使用todo
的字段:value对,设置completed
字段值为!todo.completed
如果这是一个愚蠢的问题,我很困惑并提前道歉,我做了我的研究但找不到答案。 我按照官方教程here安装了react。 然后我按照教程进行到最后没有问题。一切正常。我在 Ubuntu 20.04.02 中使用 Visual Studio 代码。 Visual Studios 代码报告: 版本:1.59.0 提交:379476f0e13988d90fab105c5c19e7abc8b1dea8 日期:2021-08-04T23:13:20.182Z 电子:13.1.7 Chrome: 91.0.4472.124 Node.js:14.16.0 V8:9.1.269.36-电子.0 OS:Linux x64 5.11.0-25-通用快照
然后我开始学习以下课程:scrimba 面对一个练习,我采用了以下解决方案:
handleChange(id) {
this.setState(prevState => {
let newTodos = [...prevState.todos];
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
console.log('newTodos= ')
console.log(newTodos)
console.log('prevState.todos= ')
console.log(prevState.todos)
return {
todos: newTodos
}
})
}
我从控制台得到的输出显示我改变 newTodos 改变了 preState 是这样的:
newTodos=
App.js:38 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:39 prevState.todos=
App.js:40 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
但是现在,如果我使用课程作者的第二个解决方案(他的第一个解决方案也更改了 preState,但这是出于可预见的原因),则不会再发生这种情况。这是他的解决方案和复选框中相同更改的控制台输出(我选中了 todos[1].id = 2
对应的复选框)
handleChange(id) {
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
控制台输出现在是正确的,preState 没有改变
(5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: false}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
App.js:59 (5) [{…}, {…}, {…}, {…}, {…}]0: {id: 1, text: "Take out the trash", completed: true}1: {id: 2, text: "Grocery shopping", completed: true}2: {id: 3, text: "Clean gecko tank", completed: false}3: {id: 4, text: "Mow lawn", completed: true}4: {id: 5, text: "Catch up on Arrested Development", completed: false}length: 5[[Prototype]]: Array(0)
为什么我的解决方案中的 prevState 发生了变化?我错过了什么?
感谢您的帮助,我担心对 javascript 或反应产生重大误解。
我将包含复制此文件所需的所有文件。 index.js 和 index.css 进入 react 安装的 src 子目录,其他文件进入 src/components 子目录。
Index.js 文件:
import React from 'react'
import ReactDOM from 'react-dom'
import App from './components/App'
import './index.css'
ReactDOM.render(<App />, document.getElementById('root'))
App.js 文件的解决方案,另一个注释掉:
import React from "react"
import TodoItem from "./TodoItem"
import todosData from "./todosData"
class App extends React.Component {
constructor() {
super()
this.state = {
todos: todosData
}
this.handleChange = this.handleChange.bind(this)
}
handleChange(id) {
// My solution, which has the problem of changing the preState, no idea why
// this.setState(prevState => {
// let newTodos = [...prevState.todos];
// const item = newTodos.find(item => item.id==id)
// if (newTodos == prevState.todos){
// console.log("Alarm, this should be true");
// }
// item.completed = !item.completed
// console.log('newTodos= ')
// console.log(newTodos)
// console.log('prevState.todos= ')
// console.log(prevState.todos)
// return {
// todos: newTodos
// }
// })
/***************** HIS SOLUTION***************** */
this.setState(prevState => {
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
console.log(prevState.todos)
console.log(updatedTodos)
return {
todos: updatedTodos
}
})
}
render() {
const todoItems = this.state.todos.map(item => <TodoItem handleChange= {this.handleChange} key={item.id} item={item} />)
return (
<div className="todo-list">
{todoItems}
</div>
)
}
}
export default App
子组件TodoItem.js:
function TodoItem(props) {
return (
<div className="todo-item">
<input
type="checkbox"
checked={props.item.completed}
onChange={() => props.handleChange(props.item.id)}
/>
<p>{props.item.text}</p>
</div>
)
}
export default TodoItem
和用于运行的数据:
const todosData = [
{
id: 1,
text: "Take out the trash",
completed: true
},
{
id: 2,
text: "Grocery shopping",
completed: false
},
{
id: 3,
text: "Clean gecko tank",
completed: false
},
{
id: 4,
text: "Mow lawn",
completed: true
},
{
id: 5,
text: "Catch up on Arrested Development",
completed: false
}
]
export default todosData
item.completed = !item.completed
上一行导致 prevState
发生变化。
当你有
let a = [{test:1}]
你也是
let b = [...a];
b[0].test=312;
现在你也改了a[0]
(其实a[0]
和b[0]
指的是同一个对象)
这是因为...
做了一个浅拷贝。详细了解深拷贝与浅拷贝。
我想您可能已经发现了两个示例之间的区别。
[...prevState.todos]
问题是 todos
的项目是这样的对象。
{
id: 1,
text: "Take out the trash",
completed: true
},
Javascript 通过引用传递对象(类似于指针)。
const item = newTodos.find(item => item.id==id)
if (newTodos == prevState.todos){
console.log("Alarm, this should be true");
}
item.completed = !item.completed
由于 item
是对此处对象的引用,更改 item.completed
会导致更改 prevState。
我们来看第二个例子。
如您所见,他们在 map
函数
todo
的新副本
const updatedTodos = prevState.todos.map(todo => {
if (todo.id === id) {
return {
...todo,
completed: !todo.completed
}
}
return todo
})
这里的...todo, completed: !todo.completed
表示使用todo
的字段:value对,设置completed
字段值为!todo.completed