繁体   English   中英

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

[英]React: Setting State for Deeply Nested Objects w/ 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>

上面代码的结果给了我一个“nestProp1”和“deepNestProp1”是未定义的,大概是因为它们永远不会被每个选择器到达/改变它们的状态。 我的预期输出将是与选择器当前 val 的值匹配的选定选项(状态更改后)。 任何帮助将不胜感激。

我认为你应该使用setState的函数形式,这样你就可以访问当前状态并更新它。

喜欢:

setState((prevState) => 
  //DO WHATEVER WITH THE CURRENT STATE AND RETURN A NEW ONE
  return newState;
);

看看是否有帮助:

 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'));
 <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"/>

更新:带下拉菜单

 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'));
 <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"/>

另一种方法是使用useReducer 钩子

 const App = () => { const reducer = (state, action) =>{ return {...state, [action.type]: action.payload} } const [state, dispatch] = React.useReducer(reducer,{ propA: 'foo1', propB: 'bar1' }); const changeSelect = (prop, event) => { const newValue = event.target.value; dispatch({type: prop, payload: newValue}); } return( <React.Fragment> <div>My nested state:</div> <div>{JSON.stringify(state)}</div> <select value={state.propA} onChange={(e) => changeSelect('propA', e)} > <option value='foo1'>foo1</option> <option value='foo2'>foo2</option> <option value='foo3'>foo3</option> </select> <select value={state.propB} onChange={(e) => changeSelect('propB', e)} > <option value='bar1'>bar1</option> <option value='bar2'>bar2</option> <option value='bar3'>bar3</option> </select> </React.Fragment> ); } ReactDOM.render(<App/>, document.getElementById('root'));
 <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"/>

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

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

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

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

那是因为它们都通过更改top_level_prop[0].nestprop4[0].deepNestProp1 “更改”。

所以:

onChange={({target: {value}}) => {
    // Update `stateObject.top_level_prop[0].nestprop4[0].deepNestProp1`:
    setStateObject(prev => {
        // Copy of `stateObject` and `stateObject.top_level_prop`
        const update = {
            ...prev,
            top_level_prop: prev.top_level_prop.slice(), // Or `[...prev.top_level_prop]`
        };
        // Copy of `stateObject.top_level_prop[0]` and `stateObject.top_level_prop[0].nextprop4`
        update.top_level_prop[0] = {
            ...update.top_level_prop[0],
            nextprop4: update.top_level_prop[0].nextprop4.slice()
        };
        // Copy of `stateObject.top_level_prop[0].nextprop4[0]`, setting the new value on the copy
        update.top_level_prop[0].nextprop4[0] = {
            ...update.top_level_prop[0].nextprop4[0],
            deepNestProp1: value
        };
        return update;
    });
}}

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

围绕这一点的尴尬是在可能的情况下保持与useState使用的状态对象较小的原因之一。

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

是的,让我们看一个例子。 下面是一些没有做必要副本的代码:

 const {useState} = React; const ShowNamed = React.memo( ({obj}) => <div>name: {obj.name}</div> ); const Example = () => { const [outer, setOuter] = useState({ name: "outer", middle: { name: "middle", inner: { name: "inner", }, }, }); const change = () => { setOuter(prev => { console.log("Changed"); prev.middle.inner.name = prev.middle.inner.name.toLocaleUpperCase(); return {...prev}; }); }; return <div> <ShowNamed obj={outer} /> <ShowNamed obj={outer.middle} /> <ShowNamed obj={outer.middle.inner} /> <input type="button" value="Change" onClick={change} /> </div>; }; ReactDOM.render(<Example />, document.getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

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

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

 const {useState} = React; const ShowNamed = React.memo( ({obj}) => <div>name: {obj.name}</div> ); const Example = () => { const [outer, setOuter] = useState({ name: "outer", middle: { name: "middle", inner: { name: "inner", }, }, }); const change = () => { setOuter(prev => { console.log("Changed"); const update = { ...prev, middle: { ...prev.middle, inner: { ...prev.middle.inner, name: prev.middle.inner.name.toLocaleUpperCase() }, }, }; return update; }); }; return <div> <ShowNamed obj={outer} /> <ShowNamed obj={outer.middle} /> <ShowNamed obj={outer.middle.inner} /> <input type="button" value="Change" onClick={change} /> </div>; }; ReactDOM.render(<Example />, document.getElementById("root"));
 <div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

该示例使用React.memo来避免在子组件的 props 没有改变时重新渲染它们。 同样的事情发生在PureComponent或任何实现shouldComponentUpdate并且在其 props 没有改变时不更新的组件。

React.memo / PureComponent / shouldComponentUpdate用于主要代码库(和完善的组件)以避免不必要的重新渲染。 朴素的不完整状态更新会在使用它们时咬你,在其他时候也可能如此。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM