【问题标题】:Should I use separate useState for each property我应该为每个属性使用单独的 useState
【发布时间】:2021-02-07 10:39:05
【问题描述】:

我写了以下代码:

function CreateModal({toggleCreatePorjectModal, fetchProjects}) {
    const [formObj, setFormObj] = useState({name: "", link: "", error: ""})


    function validateLinkAndCreateProject() {
        if(formObj.link.trim() === "" || formObj.name.trim() === "") {
            setFormObj(state => ({...state, error: "ALL fields are mandatory, please enter Project Name and Feed Link to create project."}))
            return
        }
        
        rssFeedParser(formObj.link.trim())
            .then((res) => {
                axios.post(`${ServerPath}/projects/create`, {
                    name: formObj.name, 
                    link: formObj.link
                })
                .then(response => {
                    if(response.data.error) {
                        setFormObj(state => ({...state, error: "Something went wrong. Please try again after some time."}))
                    } else {
                        fetchProjects()
                        toggleCreatePorjectModal(false)
                    }  
                })
                .catch((err) => {
                    setFormObj(state => ({...state, error: err.msg}))
                })
            })
            .catch((err) => {
                setFormObj(state => ({...state, error: err.msg})) 
            })
    }
    return  (
        <div >
            {/*Will Render Something based on above code*/}
        </div>
    )
}

我只使用了一种 useState 来在一个对象中存储诸如“名称”、“链接”、“错误”之类的属性。因为我想将 FormObj 和 validateLinkAndCreateProject 的逻辑保持在一起。所有三个属性都只使用一个 useEffect。因此,我认为最好将所有三个属性都保留在 useState 中,而不是创建 3 个不同的 usestate。

但是我的经理和我的技术架构师告诉我要创建 3 个 useState,每个属性一个 useState,即名称、链接和错误的单独 useState。根据他们的说法,永远不要在 useState 中创建对象,例如 useState({name: "", link: "", error: ""})。我应该始终为每个属性创建单独的 useState。

据我了解,在较旧的 react 中,我们曾经只有一种状态,在一个对象中曾经有 30 到 40 个属性。随着 React 钩子的引入,按照“关注点分离”,我们假设将相关逻辑与其余代码分开。因此,我们最终会得到 5 到 6 个状态,其中包含具有 4 到 5 个属性的对象。

注意:这里的“关注点分离”意味着以依赖状态 + useEffect + useRef 等保持在一起的方式破坏类代码。下面是来自 React 文档的引用。

Hooks 让您可以根据内容将一个组件拆分为更小的函数 件是相关的(例如设置订阅或获取 数据),而不是根据生命周期方法强制拆分。

我想我错过了什么吗??,可以创建 useState({name: "", link: "", error: ""})。要么 我应该为它们中的每一个创建单独的状态吗?

【问题讨论】:

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


【解决方案1】:

我意识到这是一个老问题,但如果其他人试图解决这个问题,我想说我已经以这种方式构建了一个完整的生产应用程序。每个页面(它是一个 next.js 应用程序)都使用一个 useState,并且其中是一个具有许多属性的对象。错误、表单数据、加载等

如果您阅读他们推荐的 react 文档,因为每次都重新评估对象可能会出现性能问题。即使这样,该应用程序也可以快速运行。我认为我们过于复杂化事情的方式太频繁了。当然,减速器会更好。但是,通过保持简单,您可以在不涉及抽象复杂性的情况下开发速度,其中的错误和性能问题比人们想象的要少得多。请记住,React 团队很聪明。他们不会让你失败。如果它真的极大地阻碍了性能,并且它是像 useState 这样的基本和基本的东西,他们会竭尽全力阻止你做这些事情。

顺便说一句,这个生产应用程序每周为数百名活跃用户提供点餐服务,我们没有收到任何投诉,只听到了好消息。

【讨论】:

  • 很高兴阅读个人经验以及良好的文档,而不是一次对 Stackoverflow 的硬引用和意见。 - 干杯
  • 我们还构建了一个生产 Next.js 应用程序,我们的经验是一样的。如果我们有一个具有超过 3 个按逻辑分组的状态属性的组件,它们就会被放入一个对象中。大规模管理具有 300 个键的对象,我们没有看到显着的性能影响。
【解决方案2】:

这是一个判断调用,但在大多数情况下,是的,对这三个状态信息中的每一个都使用单独的 useState 调用(或改用 useReducer)。这样,设置它们就简单明了。例如:

const [name, setName] = useState("");
const onNameChange = e => setName(e.target.value);
<input
    type="text"
    onChange={onNameChange}
>

基于类的组件获得一个setState 函数,该函数接受部分 状态更新并将它们合并到组件的状态中,但这不是useState 的setter 的工作方式;它完全替换了状态项。¹这意味着,如果您在问题中使用复合状态项,则必须在每次调用 setter 时都包含其所有部分 - 这会使您意外使用旧的副本您不是有意更新的部分:

const [formObj, setFormObj] = useState({name: "", link: "", error: ""});
const onNameChange = ({target: {value}}) => setFormObj({...formObj, name: value}); // <== USUALLY WRONG

问题在于formObj 中的数据可能已过期。您必须改用回调表单:

const [formObj, setFormObj] = useState({name: "", link: "", error: ""});
const onNameChange = ({target: {value}}) => setFormObj(formObj => ({...formObj, name: value}));

另一个问题是,如果您在 useEffect/useMemo/useCallback/etc. 中使用任何这些状态作为依赖项,也很容易出错。

所以是的,您的经理和技术架构师可能是正确的,使用单独的 useState 调用(或 useReducer)。但是,这又是一个判断电话。你可以一直使用useFormObj的回调形式。


另一种选择:

您可以为表单对象创建一个钩子,其中包含接受字符串或事件的名称、链接和错误的单独设置器,如下所示:

// A reusable utility function: if the argument is an object with a `target` property,
// return `x.target.value`; otherwise, just return `x`.
function unwrapValue(x) {
    if (typeof x === "object" && x.target) {
        x = x.target.value;
    }
    return x;
}

// Off-the-cuff, untested, just a sketch
function useFormObj(initialFormObj = {name: "", link: "", error: ""}) {
    const [formObj, setFormObj] = useState(initialFormObj);
    const setters = useRef(null);
    if (!setters.current) {
        // Only true on first call
        setters.current = {
            setName(name) {
                name = unwrapValue(name);
                setFormObj(formObj => ({...formObj, name}));
            },
            setLink(link) {
                link = unwrapValue(link);
                setFormObj(formObj => ({...formObj, link}));
            },
            setError(error) {
                error = unwrapValue(error);
                setFormObj(formObj => ({...formObj, error}));
            },
        };
    }
    return [formObj, setters.current];
}

然后:

const [formObj, {setName, setLink, setError}] = useFormObj();
<input
    type="text"
    onChange={setName}
>

当您需要在多个组件中使用表单对象时,这很方便,或者您只想拥有更小更易于测试的部分。


¹来自the documentation(您必须稍微向下滚动到注意:):

注意

与类组件中的setState 方法不同,useState 不会自动合并更新对象。您可以通过将函数更新程序形式与对象传播语法相结合来复制此行为:

setState(prevState => {
  // Object.assign would also work
  return {...prevState, ...updatedValues};
});

另一个选项是useReducer,它更适合管理包含多个子值的状态对象。

【讨论】:

  • 谢谢。这对我的理解有很大帮助。
【解决方案3】:

useState 确实完全取代了状态,但是您可以创建自己的自定义钩子,您可以尝试使用扩展运算符更新状态,例如 {...obj, ...values} 所以这种方式你不必更新整个对象,传播运算符会小心的。

我认为您可以使用自定义挂钩并进行管理。

【讨论】:

  • 谢谢。这对我的理解有很大帮助。
猜你喜欢
  • 2012-08-30
  • 1970-01-01
  • 2022-08-11
  • 2015-07-20
  • 2018-03-20
  • 2015-12-10
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多