【问题标题】:Why is my globalState state object being updated before I update it myself?为什么我的 globalState 状态对象在我自己更新之前被更新?
【发布时间】:2021-06-04 11:16:57
【问题描述】:

我有一个多选项卡视图,我通过全局状态控制数据,并通过 useContext(连同 setState 更新程序函数)传递。

结构类似

globalState: {
    company: {
        list: [
            [{name: ..., ...}, {field1: ..., ... }],
            [{name: ..., ...}, {field1: ..., ... }],
            ...
        ]
    },
    ...
}

我在第一个选项卡中有一个表格,其中每一行显示每个内部列表数组 (globalState.company.list[X][0]) 的第一个对象中的详细信息,并且有几个复选框可以切换每个内部列表中第二个对象中的字段数组(globalState.company.list[X][1])。

我遇到的问题是,当我选中特定字段的复选框时,所有公司都将该字段设置为该值之前我在来自复选框本身。

这里是创建复选框和处理程序的所有相关代码:

<td><Checkbox
    disabled={tpr.disabled} // true or false
    checked={tpr.checked} // true or false
    onChange={checkboxOnChange} // function handler
    targetId={company.id} // number
    field={"field1"} />
</td>

复选框定义

const Checkbox = React.memo(({ disabled, checked, onChange, targetId, field }) => {
    return (
        <input
            type="checkbox"
            style={ /* omitted */ }
            defaultChecked={checked}
            disabled={disabled}
            onChange={(e) => onChange(e, targetId, field)}
        />
    );
});

onChange 处理程序回调

const checkboxOnChange = (e, id, field) => {
    const checked = e.target.checked;

    console.log("GLOBAL STATE PRE", globalState.companies.list);

    let foundCompany = globalState.companies.list.find(company => company[0].id === id);
    foundCompany[1][field].checked = checked;
    console.log("foundCompany", foundCompany);

    console.log("GLOBAL STATE POST", globalState.companies.list);

    setGlobalState(prevState => ({
        ...prevState,
        companies: {
            ...prevState.companies,
            list: prevState.companies.list.map(company => {
                console.log("company PRE ANYTHING", company);
                if (company[0].id === foundCompany[0].id) {
                    console.log("Inside here");
                    return foundCompany;
                }
                console.log("company", company);
                return company;
            })
        }
    }));
};

我从 GLOBAL STATE PRE 日志中看到,如果我要为 field1 选中一个框,那么所有公司都会在我修改其他任何内容之前检查 field1。我可以确认,在选中该框之前,globalState 与我期望的一样,在加载时正确设置了所有数据和字段。

在下图中,我选中了第二家公司数组中的 TPR 框,在发生其他任何事情之前,第二家和第三家公司已经将 TPR 设置为 true。

任何帮助将不胜感激,我将分享我能够分享的更多细节。谢谢。

【问题讨论】:

  • 因为你在记录它之前就对其进行了变异:foundCompany[1][field].checked = checked
  • 该行发生在我执行全局预日志之后,无论如何,由于它来自 find(),它应该只是单个受影响的公司插槽,对吗?我可以理解这条线会影响原始数组,但是更新和记录的顺序没有意义。
  • @jonrsharpe 当我删除整个 foundCompany 位,并将该映射内的 if 语句更改为 if (company[0].id == id) { company[1][field].checked = checked; } 时,会发生同样的事情,确认您提到的行无关紧要。
  • “不要改变状态”。在操作状态之前进行浅拷贝或深拷贝。
  • if (company[0].id == id) { company[1][field].checked = checked; } 这仍在改变状态。

标签: javascript reactjs react-hooks use-state use-context


【解决方案1】:

只是不要直接改变状态对象:

const checkboxOnChange = (e, id, field) => {
    const checked = e.target.checked;
    setGlobalState(prevState => ({
        ...prevState,
        companies: {
            ...prevState.companies,
            list: prevState.companies.list.map(company => {
                if (company[0].id === id) {
                    return {
                        ...company,
                        checked
                    };
                }
                return {
                    ...company
                };
            })
        }
    }));
};  

【讨论】:

  • 谢谢,我在问题帖子的 cmets 中掩盖了这个事实,但没有考虑清楚。感谢您在此处和其他回复中的回答。
【解决方案2】:

在您调用 setGlobalState 之前,globalState 对象正在更新,因为您正在改变当前状态(例如 foundCompany[1][field].checked = checked;

解决此问题的一种方法是复制状态对象,使其不引用当前状态。例如

var cloneDeep = require('lodash.clonedeep');

...

let clonedGlobalState = cloneDeep(globalState);
let foundCompany = clonedGlobalState.companies.list.find(company => company[0].id === id);
foundCompany[1][field].checked = checked;

我建议使用像 Lodash's cloneDeep 这样的深度克隆函数,因为使用扩展运算符在您的实例中创建副本会在列表数组中创建对象的浅表副本。

一旦你克隆了状态,你就可以安全地将它更新到你想要的新状态(即不用担心改变现有的 globalState 对象),然后在调用 setGlobalState 时引用它。

【讨论】:

  • 这不是一个好的解决方案。 OP 已经在使用深度对象解构来创建新实例,现在您正在引入一个新库来实现这一点。解决方案是更改 setState 回调中的checked 标志。
  • @RobertoZvjerković OP 正在改变他们对foundCompany[1][field].checked = checked; 的调用中的原始状态。他们需要在执行此操作之前创建不引用原始全局状态对象的数据克隆,如我的回答中所述。此外,当用于在 setGlobalState 中创建对象的副本时,“vanilla”javascript 扩展运算符在处理对象(这是公司数组中的内容)时执行浅拷贝而不是深拷贝。因此再次需要使用深层复制功能,例如 cloneDeep。
  • “OP 在调用 foundCompany[1][field].checked = checked; 时改变了原始状态。”我知道,这就是为什么我说“解决方案是更改 setState 回调中的已检查标志”,而不是之前。 “当用于在 setGlobalState 中创建对象的副本时,“vanilla”javascript 扩展运算符也会进行浅拷贝”。除非您也对内部对象进行破坏,OP 已经在这样做了。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-01-05
  • 2023-01-28
  • 2017-01-25
  • 2011-04-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多