yum*_*ins 13 javascript state setstate reactjs react-hooks
我正在 React 中使用深度嵌套的状态对象。我的代码库要求我们尝试坚持使用函数组件,因此每次我想更新嵌套对象内的键/值对时,我都必须使用钩子来设置状态。不过,我似乎无法了解更深层次的嵌套项目。我有一个带有 onChange 处理程序的下拉菜单。. .in 的 onChange 处理程序是一个内联函数,可以直接设置正在更改的任何键/值对的值。
但是,我在每个内联函数中的展开运算符之后使用的语法是错误的。
作为一种解决方法,我已经将内联函数分解为它自己的函数,每次状态更改时都会重写整个状态对象,但这是非常耗时和丑陋的。我宁愿像下面这样内联:
const [stateObject, setStateObject] = useState({
top_level_prop: [
{
nestedProp1: "nestVal1",
nestedProp2: "nestVal2"
nestedProp3: "nestVal3",
nestedProp4: [
{
deepNestProp1: "deepNestedVal1",
deepNestProp2: "deepNestedVal2"
}
]
}
]
});
<h3>Top Level Prop</h3>
<span>NestedProp1:</span>
<select
id="nested-prop1-selector"
value={stateObject.top_level_prop[0].nestedProp1}
onChange={e => setStateObject({...stateObject,
top_level_prop[0].nestedProp1: e.target.value})}
>
<option value="nestVal1">nestVal1</option>
<option value="nestVal2">nestVal2</option>
<option value="nestVal3">nestVal3</option>
</select>
<h3>Nested Prop 4</h3>
<span>Deep Nest Prop 1:</span>
<select
id="deep-nested-prop-1-selector"
value={stateObject.top_level_prop[0].nestprop4[0].deepNestProp1}
onChange={e => setStateObject({...stateObject,
top_level_prop[0].nestedProp4[0].deepNestProp1: e.target.value})}
>
<option value="deepNestVal1">deepNestVal1</option>
<option value="deepNestVal2">deepNestVal2</option>
<option value="deepNestVal3">deepNestVal3</option>
</select>
Run Code Online (Sandbox Code Playgroud)
上面代码的结果给了我一个“nestProp1”和“deepNestProp1”是未定义的,大概是因为它们永远不会被每个选择器到达/改变它们的状态。我的预期输出将是与选择器当前 val 的值匹配的选定选项(状态更改后)。任何帮助将不胜感激。
cbd*_*per 13
我认为您应该使用 的功能形式setState,以便您可以访问当前状态并更新它。
喜欢:
setState((prevState) =>
//DO WHATEVER WITH THE CURRENT STATE AND RETURN A NEW ONE
return newState;
);
Run Code Online (Sandbox Code Playgroud)
看看是否有帮助:
setState((prevState) =>
//DO WHATEVER WITH THE CURRENT STATE AND RETURN A NEW ONE
return newState;
);
Run Code Online (Sandbox Code Playgroud)
function App() {
const [nestedState,setNestedState] = React.useState({
top_level_prop: [
{
nestedProp1: "nestVal1",
nestedProp2: "nestVal2",
nestedProp3: "nestVal3",
nestedProp4: [
{
deepNestProp1: "deepNestedVal1",
deepNestProp2: "deepNestedVal2"
}
]
}
]
});
return(
<React.Fragment>
<div>This is my nestedState:</div>
<div>{JSON.stringify(nestedState)}</div>
<button
onClick={() => setNestedState((prevState) => {
prevState.top_level_prop[0].nestedProp4[0].deepNestProp1 = 'XXX';
return({
...prevState
})
}
)}
>
Click to change nestedProp4[0].deepNestProp1
</button>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));Run Code Online (Sandbox Code Playgroud)
更新:带下拉菜单
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.production.min.js"></script>
<div id="root"/>Run Code Online (Sandbox Code Playgroud)
function App() {
const [nestedState,setNestedState] = React.useState({
propA: 'foo1',
propB: 'bar'
});
function changeSelect(event) {
const newValue = event.target.value;
setNestedState((prevState) => {
return({
...prevState,
propA: newValue
});
});
}
return(
<React.Fragment>
<div>My nested state:</div>
<div>{JSON.stringify(nestedState)}</div>
<select
value={nestedState.propA}
onChange={changeSelect}
>
<option value='foo1'>foo1</option>
<option value='foo2'>foo2</option>
<option value='foo3'>foo3</option>
</select>
</React.Fragment>
);
}
ReactDOM.render(<App/>, document.getElementById('root'));Run Code Online (Sandbox Code Playgroud)
React 状态的首要规则是不要直接修改状态。这包括顶级状态对象中保存的对象,或其中保存的对象等。因此,要修改嵌套对象并使 React 可靠地处理结果,您必须复制更改的每一层。(是的,确实如此。详细信息如下,带有文档链接。)
\n另外,当您根据现有状态更新状态时,最好使用状态设置器的回调版本,因为状态更新可能是异步的(我不知道为什么他们说“可能”) ,它们是异步的)并且状态更新被合并,因此使用旧的状态对象可能会导致陈旧的信息被放回状态。
\n考虑到这一点,让我们看看第二个更改处理程序(因为它比第一个更深入),它需要更新stateObject.top_level_prop[0].nestprop4[0].deepNestProp1. 为了正确地做到这一点,我们必须复制我们正在修改的最深的对象(stateObject.top_level_prop[0].nestprop4[0])及其所有父对象;其他对象可以重复使用。所以那是:
stateObjecttop_level_proptop_level_prop[0]top_level_prop[0].nestprop4top_level_prop[0].nestprop4[0]那是因为它们都是通过改变而“改变”的top_level_prop[0].nestprop4[0].deepNestProp1。
所以:
\nonChange={({target: {value}}) => {\n // Update `stateObject.top_level_prop[0].nestprop4[0].deepNestProp1`:\n setStateObject(prev => {\n // Copy of `stateObject` and `stateObject.top_level_prop`\n const update = {\n ...prev,\n top_level_prop: prev.top_level_prop.slice(), // Or `[...prev.top_level_prop]`\n };\n // Copy of `stateObject.top_level_prop[0]` and `stateObject.top_level_prop[0].nextprop4`\n update.top_level_prop[0] = {\n ...update.top_level_prop[0],\n nextprop4: update.top_level_prop[0].nextprop4.slice()\n };\n // Copy of `stateObject.top_level_prop[0].nextprop4[0]`, setting the new value on the copy\n update.top_level_prop[0].nextprop4[0] = {\n ...update.top_level_prop[0].nextprop4[0],\n deepNestProp1: value\n };\n return update;\n });\n}}\nRun Code Online (Sandbox Code Playgroud)\n最好不要复制树中未更改的其他对象,因为渲染它们的任何组件都不需要重新渲染,但我们要更改的最深对象及其所有父对象需要复制。
\n周围的尴尬是useState尽可能保持状态对象使用较小的原因之一。
是的,让我们看一个例子。以下是一些未执行必要复制的代码:
\nonChange={({target: {value}}) => {\n // Update `stateObject.top_level_prop[0].nestprop4[0].deepNestProp1`:\n setStateObject(prev => {\n // Copy of `stateObject` and `stateObject.top_level_prop`\n const update = {\n ...prev,\n top_level_prop: prev.top_level_prop.slice(), // Or `[...prev.top_level_prop]`\n };\n // Copy of `stateObject.top_level_prop[0]` and `stateObject.top_level_prop[0].nextprop4`\n update.top_level_prop[0] = {\n ...update.top_level_prop[0],\n nextprop4: update.top_level_prop[0].nextprop4.slice()\n };\n // Copy of `stateObject.top_level_prop[0].nextprop4[0]`, setting the new value on the copy\n update.top_level_prop[0].nextprop4[0] = {\n ...update.top_level_prop[0].nextprop4[0],\n deepNestProp1: value\n };\n return update;\n });\n}}\nRun Code Online (Sandbox Code Playgroud)\r\nconst {useState} = React;\n\nconst ShowNamed = React.memo(\n ({obj}) => <div>name: {obj.name}</div>\n);\n\nconst Example = () => {\n const [outer, setOuter] = useState({\n name: "outer",\n middle: {\n name: "middle",\n inner: {\n name: "inner",\n },\n },\n });\n \n const change = () => {\n setOuter(prev => {\n console.log("Changed");\n prev.middle.inner.name = prev.middle.inner.name.toLocaleUpperCase();\n return {...prev};\n });\n };\n \n return <div>\n <ShowNamed obj={outer} />\n <ShowNamed obj={outer.middle} />\n <ShowNamed obj={outer.middle.inner} />\n <input type="button" value="Change" onClick={change} />\n </div>;\n};\n\nReactDOM.render(<Example />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)\r\n请注意,即使状态已更改,单击按钮似乎也不会执行任何操作(除了记录“已更改”之外)。那是因为传递给的对象ShowName没有改变,所以ShowName没有重新渲染。
这是进行必要更新的一个:
\n<div id="root"></div>\n\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>\n<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>Run Code Online (Sandbox Code Playgroud)\r\nconst {useState} = React;\n\nconst ShowNamed = React.memo(\n ({obj}) => <div>name: {obj.name}</div>\n);\n\nconst Example = () => {\n const [outer, setOuter] = useState({\n name: "outer",\n middle: {\n name: "middle",\n inner: {\n name: "inner",\n },\n },\n });\n \n const change = () => {\n setOuter(prev => {\n console.log("Changed");\n const update = {\n ...prev,\n middle: {\n ...prev.middle,\n inner: {\n ...prev.middle.inner,\n name: prev.middle.inner.name.toLocaleUpperCase()\n },\n },\n };\n \n return update;\n });\n };\n \n return <div>\n <ShowNamed obj={outer} />\n <ShowNamed obj={outer.middle} />\n <ShowNamed obj={outer.middle.inner} />\n <input type="button" value="Change" onClick={change} />\n </div>;\n};\n\nReactDOM.render(<Example />, document.getElementById("root"));Run Code Online (Sandbox Code Playgroud)\r\n该示例用于React.memo避免在子组件的 props 未更改时重新渲染它们。同样的事情也会发生在PureComponent任何实现它的组件上shouldComponentUpdate,并且当它的 props 没有改变时不会更新。
React.memo//PureComponent用于shouldComponentUpdate主要代码库(和抛光组件)以避免不必要的重新渲染。Na\xc3\xafve 不完整的状态更新会在使用它们时困扰你,也可能在其他时候。