我应该为每个 属性 使用单独的 useState
Should I use separate useState for each property
我写了下面的代码:
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 来存储一个对象中的属性,如“name”、“link”、“error”。因为我想将 FormObj 和 validateLinkAndCreateProject 的逻辑保持在一起。所有三个属性只使用一个 useEffect。因此,我认为最好将所有三个属性保留在 useState 中,而不是创建 3 个不同的 useState。
但是我的经理和我的技术架构师告诉我创建 3 个 useState,每个 属性 一个 useState,也就是说,将名称、link 和错误的 useState 分开。根据他们的说法,永远不要像 useState({name: "", link: "", error: ""}) 这样在 useState 中创建对象。我应该始终为每个 属性.
创建单独的 useState
据我了解,在较早的 React 中,我们曾经只有一个状态,在一个对象中通常有 30 到 40 个属性。通过引入 React hooks,根据“关注点分离”,我们应该将相关逻辑与其余代码分开。因此,我们最终将有 5 到 6 个状态具有具有 4 到 5 个属性的对象。
注意:这里的“关注点分离”意味着以依赖状态 + useEffect + useRef 等保持在一起的方式打破 class 代码。以下引用自 React 文档。
Hooks let you split one component into smaller functions based on what
pieces are related (such as setting up a subscription or fetching
data), rather than forcing a split based on lifecycle methods.
我想我是否遗漏了什么??,可以创建 useState({name: "", link: "", error: ""})。要么
我应该为它们中的每一个创建单独的状态吗?
这是一个判断调用,但在大多数情况下,是的,对这三个状态信息中的每一个使用单独的 useState
调用(或者改用 useReducer
)。这样,设置它们就简单明了。例如:
const [name, setName] = useState("");
const onNameChange = e => setName(e.target.value);
<input
type="text"
onChange={onNameChange}
>
Class-based 组件获得一个 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
/等中使用这些状态中的任何一个作为依赖项,则很容易获得那也错了。
所以是的,您的经理和技术架构师可能是正确的,使用单独的 useState
调用(或 useReducer
)。但是,这又是一个判断电话;您可以始终使用 useFormObj
.
的回调形式
另一个选项:
您可以为表单对象创建一个挂钩,其中包含单独的 setter 名称,link 和接受字符串或事件的错误,如下所示:
// 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}
>
当您需要在多个组件中使用表单对象,或者您只想拥有更小的 easily-testable 个组件时,这会很方便。
¹ 来自 the documentation(您必须稍微向下滚动到 注意:):
Note
Unlike the setState
method found in class components, useState
does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:
setState(prevState => {
// Object.assign would also work
return {...prevState, ...updatedValues};
});
Another option is useReducer
, which is more suited for managing state objects that contain multiple sub-values.
useState 确实完全取代了状态,但您可以创建自己的自定义挂钩,您可以尝试使用扩展运算符更新状态,例如 {...obj, ...values}
所以这样你就不必更新整个对象,传播运算符会处理。
我认为您可以使用自定义挂钩并进行管理。
我意识到这是一个老问题,但如果有人试图解决这个问题,我想说我已经用这种方式构建了一个完整的生产应用程序。每个页面(它是一个 next.js 应用程序)都使用一个 useState 并且在其中是一个具有许多属性的对象。错误、表单数据、加载等
如果您阅读了反应文档,他们会建议您不要这样做,因为由于每次都会重新评估对象,因此可能会出现性能问题。即便如此,该应用程序仍能快速运行。我认为我们经常把事情复杂化。当然,减速器会更好。但是,通过保持简单而不陷入抽象的复杂性,您可以开发的速度比人们似乎认为的要少得多的错误和性能问题。请记住,React 团队很聪明。他们不会让你失败。如果它真的以一种很好的方式阻碍了性能,并且它是像 useState 这样的基本和基本的东西,他们会竭尽全力阻止你做这样的事情。
顺便说一句,这个生产应用程序每周为数百名活跃用户提供订餐服务,我们没有收到任何投诉,只听到好消息。
我写了下面的代码:
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 来存储一个对象中的属性,如“name”、“link”、“error”。因为我想将 FormObj 和 validateLinkAndCreateProject 的逻辑保持在一起。所有三个属性只使用一个 useEffect。因此,我认为最好将所有三个属性保留在 useState 中,而不是创建 3 个不同的 useState。
但是我的经理和我的技术架构师告诉我创建 3 个 useState,每个 属性 一个 useState,也就是说,将名称、link 和错误的 useState 分开。根据他们的说法,永远不要像 useState({name: "", link: "", error: ""}) 这样在 useState 中创建对象。我应该始终为每个 属性.
创建单独的 useState据我了解,在较早的 React 中,我们曾经只有一个状态,在一个对象中通常有 30 到 40 个属性。通过引入 React hooks,根据“关注点分离”,我们应该将相关逻辑与其余代码分开。因此,我们最终将有 5 到 6 个状态具有具有 4 到 5 个属性的对象。
注意:这里的“关注点分离”意味着以依赖状态 + useEffect + useRef 等保持在一起的方式打破 class 代码。以下引用自 React 文档。
Hooks let you split one component into smaller functions based on what pieces are related (such as setting up a subscription or fetching data), rather than forcing a split based on lifecycle methods.
我想我是否遗漏了什么??,可以创建 useState({name: "", link: "", error: ""})。要么 我应该为它们中的每一个创建单独的状态吗?
这是一个判断调用,但在大多数情况下,是的,对这三个状态信息中的每一个使用单独的 useState
调用(或者改用 useReducer
)。这样,设置它们就简单明了。例如:
const [name, setName] = useState("");
const onNameChange = e => setName(e.target.value);
<input
type="text"
onChange={onNameChange}
>
Class-based 组件获得一个 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
/等中使用这些状态中的任何一个作为依赖项,则很容易获得那也错了。
所以是的,您的经理和技术架构师可能是正确的,使用单独的 useState
调用(或 useReducer
)。但是,这又是一个判断电话;您可以始终使用 useFormObj
.
另一个选项:
您可以为表单对象创建一个挂钩,其中包含单独的 setter 名称,link 和接受字符串或事件的错误,如下所示:
// 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}
>
当您需要在多个组件中使用表单对象,或者您只想拥有更小的 easily-testable 个组件时,这会很方便。
¹ 来自 the documentation(您必须稍微向下滚动到 注意:):
Note
Unlike the
setState
method found in class components,useState
does not automatically merge update objects. You can replicate this behavior by combining the function updater form with object spread syntax:setState(prevState => { // Object.assign would also work return {...prevState, ...updatedValues}; });
Another option is
useReducer
, which is more suited for managing state objects that contain multiple sub-values.
useState 确实完全取代了状态,但您可以创建自己的自定义挂钩,您可以尝试使用扩展运算符更新状态,例如 {...obj, ...values} 所以这样你就不必更新整个对象,传播运算符会处理。
我认为您可以使用自定义挂钩并进行管理。
我意识到这是一个老问题,但如果有人试图解决这个问题,我想说我已经用这种方式构建了一个完整的生产应用程序。每个页面(它是一个 next.js 应用程序)都使用一个 useState 并且在其中是一个具有许多属性的对象。错误、表单数据、加载等
如果您阅读了反应文档,他们会建议您不要这样做,因为由于每次都会重新评估对象,因此可能会出现性能问题。即便如此,该应用程序仍能快速运行。我认为我们经常把事情复杂化。当然,减速器会更好。但是,通过保持简单而不陷入抽象的复杂性,您可以开发的速度比人们似乎认为的要少得多的错误和性能问题。请记住,React 团队很聪明。他们不会让你失败。如果它真的以一种很好的方式阻碍了性能,并且它是像 useState 这样的基本和基本的东西,他们会竭尽全力阻止你做这样的事情。
顺便说一句,这个生产应用程序每周为数百名活跃用户提供订餐服务,我们没有收到任何投诉,只听到好消息。