React:为带钩子的深度嵌套对象设置状态

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)


T.J*_*der 6

React 状态的首要规则是不要直接修改状态。这包括顶级状态对象中保存的对象,或其中保存的对象。因此,要修改嵌套对象并使 React 可靠地处理结果,您必须复制更改的每一层。(是的,确实如此。详细信息如下,带有文档链接。)

\n

另外,当您根据现有状态更新状态时,最好使用状态设置器的回调版本,因为状态更新可能是异步的(我不知道为什么他们说“可能”) ,它们异步的)并且状态更新被合并,因此使用旧的状态对象可能会导致陈旧的信息被放回状态。

\n

考虑到这一点,让我们看看第二个更改处理程序(因为它比第一个更深入),它需要更新stateObject.top_level_prop[0].nestprop4[0].deepNestProp1. 为了正确地做到这一点,我们必须复制我们正在修改的最深的对象(stateObject.top_level_prop[0].nestprop4[0])及其所有父对象;其他对象可以重复使用。所以那是:

\n
    \n
  • stateObject
  • \n
  • top_level_prop
  • \n
  • top_level_prop[0]
  • \n
  • top_level_prop[0].nestprop4
  • \n
  • top_level_prop[0].nestprop4[0]
  • \n
\n

那是因为它们都是通过改变而“改变”的top_level_prop[0].nestprop4[0].deepNestProp1

\n

所以:

\n
onChange={({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}}\n
Run Code Online (Sandbox Code Playgroud)\n

最好不要复制树中未更改的其他对象,因为渲染它们的任何组件都不需要重新渲染,但我们要更改的最深对象及其所有父对象需要复制。

\n

周围的尴尬是useState尽可能保持状态对象使用较小的原因之一。

\n

但我们真的必须这样做吗?

\n

是的,让我们看一个例子。以下是一些未执行必要复制的代码:

\n

\r\n
\r\n
onChange={({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}}\n
Run Code Online (Sandbox Code Playgroud)\r\n
const {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
\r\n
\r\n

\n

请注意,即使状态已更改,单击按钮似乎也不会执行任何操作(除了记录“已更改”之外)。那是因为传递给的对象ShowName没有改变,所以ShowName没有重新渲染。

\n

这是进行必要更新的一个:

\n

\r\n
\r\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\n
const {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
\r\n
\r\n

\n

该示例用于React.memo避免在子组件的 props 未更改时重新渲染它们。同样的事情也会发生在PureComponent任何实现它的组件上shouldComponentUpdate,并且当它的 props 没有改变时不会更新。

\n

React.memo//PureComponent用于shouldComponentUpdate主要代码库(和抛光组件)以避免不必要的重新渲染。Na\xc3\xafve 不完整的状态更新会在使用它们时困扰你,也可能在其他时候。

\n