在嵌套对象中,在React with Hooks中更新状态的正确方法是什么?

export Example = () => {
  const [exampleState, setExampleState] = useState(
  {masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b"
           fieldTwoTwo: "c"
           }
        }
   })

如何使用setExampleState将exampleState更新为a(附加字段)?

const a = {
masterField: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b",
           fieldTwoTwo: "c"
           }
        },
  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }
}

b(改变值)?

const b = {masterField: {
        fieldOne: "e",
        fieldTwo: {
           fieldTwoOne: "f"
           fieldTwoTwo: "g"
           }
        }
   })

当前回答

一般来说,你应该注意React状态下嵌套很深的对象。为了避免意外的行为,状态应该不可更改地更新。当你有深层对象时,你最终会为了不变性而对它们进行深层克隆,这在React中是相当昂贵的。为什么?

一旦你深度克隆状态,React将重新计算和重新渲染所有依赖于变量的东西,即使它们没有改变!

因此,在尝试解决问题之前,首先考虑如何将状态变平。一旦您这样做了,您就会发现有助于处理大型状态的方便工具,例如useReducer()。

如果你想过,但仍然确信你需要使用深度嵌套的状态树,你仍然可以使用useState()与immutable.js和Immutability-helper等库一起使用。它们使得更新或克隆深层对象变得简单,而不必担心可变性。

其他回答

2022年

如果您正在寻找与此相同的功能。函数组件中的setState(来自类组件),那么这是对您有很大帮助的答案。

例如

你有一个像下面这样的状态,想要从整个状态中更新特定的字段,那么你需要每次都使用对象解构,有时它会令人恼火。

const [state, setState] = useState({first: 1, second: 2});

// results will be state = {first: 3} instead of {first: 3, second: 2}
setState({first: 3})

// To resolve that you need to use object destructing every time
// results will be state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))

为了解决这个问题,我提出了useReducer方法。请检查useReducer。

const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
});
const [state, setState] = useReducer(stateReducer, {first: 1, second: 2});

// results will be state = {first: 3, second: 2}
setState({first: 3})

// you can also access the previous state callback if you want
// results will remain same, state = {first: 3, second: 2}
setState(prev => ({...prev, first: 3}))

您可以将该stateReducer存储在utils文件中,如果需要,也可以将其导入到每个文件中。

如果你需要,这里是自定义钩子。

import React from 'react';

export const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === 'function' ? action(state) : action),
});

const useReducer = (initial, lazyInitializer = null) => {
  const [state, setState] = React.useReducer(stateReducer, initial, init =>
    lazyInitializer ? lazyInitializer(init) : init
  );

  return [state, setState];
};

export default useReducer;

打印稿

import React, { Dispatch } from "react";

type SetStateAction<S> = S | ((prev: S) => S);

type STATE<R> = [R, Dispatch<SetStateAction<Partial<R>>>];

const stateReducer = (state, action) => ({
  ...state,
  ...(typeof action === "function" ? action(state) : action),
});

const useReducer = <S>(initial, lazyInitializer = null): STATE<S> => {
  const [state, setState] = React.useReducer(stateReducer, initial, (init) =>
    lazyInitializer ? lazyInitializer(init) : init,
  );

  return [state, setState];
};

export default useReducer;

我认为更优雅的解决方案是创建更新后的状态对象,同时保留以前的state值。需要更新的Object属性可以以这样的数组形式提供

import React,{useState, useEffect} from 'react'
export default function Home2(props) {
    const [x, setX] = useState({name : '',add : {full : '', pin : '', d : { v : '' }}})
    const handleClick = (e, type)=>{
        let obj = {}
        if(type.length > 1){
            var z = {}
            var z2 = x[type[0]]
        
        type.forEach((val, idx)=>{
            if(idx === type.length - 1){
                z[val] = e.target.value
            }
            else if(idx > 0){
                Object.assign(z , z2) /*{...z2 , [val]:{} }*/
                z[val] = {}
                z = z[val]
                z2 = z2[val]
            }else{
                z = {...z2}
                obj = z
            }
        })
    }else obj = e.target.value
    setX( { ...x ,   [type[0]] : obj  } )
    
}
return (
    <div>
        <input value = {x.name} onChange={e=>handleClick(e,["name"])}/>
        <input value = {x.add.full} onChange={e=>handleClick(e,["add","full"])}  />
        <input value = {x.add.pin} onChange={e=>handleClick(e,["add","pin"])}  /><br/>
        <input value = {x.add.d.v} onChange={e=>handleClick(e,["add","d","v"])}  /><br/>
        {x.name} <br/>
        {x.add.full} <br/>
        {x.add.pin} <br/>
        {x.add.d.v}
    </div>
)
}

可以使用useReducer钩子来管理复杂的状态,而不是useState。要做到这一点,首先初始化状态和更新函数如下所示:

const initialState = { name: "Bob", occupation: "builder" };
const [state, updateState] = useReducer(
  (state, updates) => ({ ...state, ...updates }),
  initialState
);

然后你可以通过只传递部分更新来更新状态,就像这样:

updateState({ occupation: "postman" })

我已经给出了两个追加,整个对象更新,具体的关键更新的解决方案的例子

追加和修改都可以通过一个简单的步骤来完成。我认为这是更稳定和安全的,没有不可变或可变的依赖。

这就是追加新对象的方法

setExampleState(prevState => ({
    ...prevState,
    masterField2: {
        fieldOne: "c",
        fieldTwo: {
            fieldTwoOne: "d",
            fieldTwoTwo: "e"
        }
    },
}))

假设您想再次修改masterField2对象。可能有两种情况。您想要更新整个对象或更新对象的特定键。

更新整个对象-这里masterField2键的整个值将被更新。

setExampleState(prevState => ({
    ...prevState,
    masterField2: {
        fieldOne: "c",
        fieldTwo: {
            fieldTwoOne: "d",
            fieldTwoTwo: "e"
        }
    },
}))

但是如果你只想改变masterField2对象中的fieldTwoOne键呢?你可以这样做。

let oldMasterField2 = exampleState.masterField2
oldMasterField2.fieldTwo.fieldTwoOne = 'changed';
setExampleState(prevState => ({
    ...prevState,
    masterField2: oldMasterField2
}))

您必须使用Rest参数和扩展语法(https://javascript.info/rest-parameters-spread),并设置一个以preState作为setState参数的函数。

不起作用(功能缺失)

[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState({
  ...state,
  [key]: value
});

确实工作!

[state, setState] = useState({})
const key = 'foo';
const value = 'bar';
setState(prevState => ({
  ...prevState,
  [key]: value
}));