当我更改其克隆时反应 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