【问题标题】:React Hooks useState() with ObjectReact Hooks useState() with Object
【发布时间】:2019-06-06 15:03:21
【问题描述】:

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

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

如何使用setExampleStateexampleState 更新为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"
           }
        }
   })

【问题讨论】:

  • 您的意思是向现有对象添加新的对象键值?
  • @Justcode 第一个例子是的,第二个例子只是改变现有对象
  • onValueChange={ () => setSelection( { ...prev , id_1: true } ) }

标签: javascript reactjs react-hooks


【解决方案1】:

你可以像这样传递新值:

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "a",
        fieldTwo: {
           fieldTwoOne: "b",
           fieldTwoTwo: "c"
           }
        },
   })

【讨论】:

  • 太好了,回答了第一部分!你知道 b 部分的最佳实践吗?
  • masterField 之后也同样改变,而不是 masterField2 setExampleState({...exampleState, masterField: {//新值}
  • 使用展开运算符时要注意浅拷贝。例如:stackoverflow.com/questions/43638938/…
  • 如果您使用与其 Dispatcher 相同的状态,您应该使用一个函数。 setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
【解决方案2】:

如果有人在搜索 object

useState() 挂钩更新
- Through Input

        const [state, setState] = useState({ fName: "", lName: "" });
        const handleChange = e => {
            const { name, value } = e.target;
            setState(prevState => ({
                ...prevState,
                [name]: value
            }));
        };

        <input
            value={state.fName}
            type="text"
            onChange={handleChange}
            name="fName"
        />
        <input
            value={state.lName}
            type="text"
            onChange={handleChange}
            name="lName"
        />
   ***************************

 - Through onSubmit or button click
    
        setState(prevState => ({
            ...prevState,
            fName: 'your updated value here'
         }));

【讨论】:

  • 为什么需要使用回调?
  • @Yola This answer 提供了比我更好的解释。
【解决方案3】:

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

一旦你对状态进行了深度克隆,React 将重新计算并重新渲染依赖于变量的所有内容,即使它们没有改变!

因此,在尝试解决您的问题之前,请先考虑如何使状态变平。一旦你这样做了,你就会发现可以帮助处理大型状态的便捷工具,例如 useReducer()。

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

【讨论】:

  • 您也可以使用Hookstate(免责声明:我是作者)来管理复杂的(本地和全局)状态数据,无需深度克隆,也无需担心不必要的更新——Hookstate 会为您处理.
【解决方案4】:

我迟到了.. :)

@aseferov 的答案在重新输入整个对象结构时效果很好。但是,如果目标/目标是更新 Object 中的特定字段值,我相信下面的方法更好。

情况:

const [infoData, setInfoData] = useState({
    major: {
      name: "John Doe",
      age: "24",
      sex: "M",
    },

    minor:{
      id: 4,
      collegeRegion: "south",

    }

  });

更新特定记录需要召回之前的状态prevState

这里:

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      }
    }));

也许

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      },
      minor: {
        ...prevState.minor,
        collegeRegion: "northEast"

    }));

我希望这可以帮助任何试图解决类似问题的人。

【讨论】:

  • 我有一个问题。为什么我们需要将函数包装在括号中?我不确定引擎盖下发生了什么。你会碰巧知道吗?或者我可以在哪里阅读更多相关信息?
  • @MichaelRamage 为什么要把函数括在括号中:():简单回答这个问题;这是因为 setInfoData 本质上是高阶的,即它可以接受另一个函数作为参数,这要归功于 Hook 提供的功能:useState。我希望这篇文章能更清楚地说明高阶函数:sitepoint.com/higher-order-functions-javascript
  • 非常有帮助,尤其是在您尝试更新嵌套多层深度的属性的情况下,即:first.second.third - 其他示例涵盖 first.second,但不包括 first.second .第三
【解决方案5】:

感谢 Philip 这对我有帮助 - 我的用例是我有一个包含很多输入字段的表单,所以我将初始状态保持为对象,但我无法更新对象状态。上面的帖子帮助了我 :)

const [projectGroupDetails, setProjectGroupDetails] = useState({
    "projectGroupId": "",
    "projectGroup": "DDD",
    "project-id": "",
    "appd-ui": "",
    "appd-node": ""    
});

const inputGroupChangeHandler = (event) => {
    setProjectGroupDetails((prevState) => ({
       ...prevState,
       [event.target.id]: event.target.value
    }));
}

<Input 
    id="projectGroupId" 
    labelText="Project Group Id" 
    value={projectGroupDetails.projectGroupId} 
    onChange={inputGroupChangeHandler} 
/>


【讨论】:

    【解决方案6】:

    您必须使用 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
    }));
    

    【讨论】:

      【解决方案7】:

      我已经给出了 Append、Whole object update、Specific key update 的求解示例

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

      这是追加新对象的方法

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

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

      更新整个对象 - 所以这里键 ma​​sterField2 的整个值将被更新。 p>

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

      但是,如果您只想更改 ma​​sterField2 对象内的 fieldTwoOne 键怎么办。您执行以下操作。

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

      【讨论】:

        【解决方案8】:
        function App() {
        
          const [todos, setTodos] = useState([
            { id: 1, title: "Selectus aut autem", completed: false },
            { id: 2, title: "Luis ut nam facilis et officia qui", completed: false },
            { id: 3, title: "Fugiat veniam minus", completed: false },
            { id: 4, title: "Aet porro tempora", completed: true },
            { id: 5, title: "Laboriosam mollitia et enim quasi", completed: false }
          ]);
        
          const changeInput = (e) => {todos.map(items => items.id === parseInt(e.target.value) && (items.completed = e.target.checked));
         setTodos([...todos], todos);}
          return (
            <div className="container">
              {todos.map(items => {
                return (
                  <div key={items.id}>
                    <label>
        <input type="checkbox" 
        onChange={changeInput} 
        value={items.id} 
        checked={items.completed} />&nbsp; {items.title}</label>
                  </div>
                )
              })}
            </div>
          );
        }
        

        【讨论】:

          【解决方案9】:

          最初我在 useState 中使用对象,但后来我转移到 useReducer 钩子来处理复杂的情况。重构代码时,我感觉性能有所提升。

          当您有涉及多个子值的复杂状态逻辑或下一个状态取决于前一个状态时,useReducer 通常比 useState 更可取。

          useReducer React docs

          我已经实现了这样的钩子供我自己使用:

          /**
           * Same as useObjectState but uses useReducer instead of useState
           *  (better performance for complex cases)
           * @param {*} PropsWithDefaultValues object with all needed props 
           * and their initial value
           * @returns [state, setProp] state - the state object, setProp - dispatch 
           * changes one (given prop name & prop value) or multiple props (given an 
           * object { prop: value, ...}) in object state
           */
          export function useObjectReducer(PropsWithDefaultValues) {
            const [state, dispatch] = useReducer(reducer, PropsWithDefaultValues);
          
            //newFieldsVal={[field_name]: [field_value], ...}
            function reducer(state, newFieldsVal) {
              return { ...state, ...newFieldsVal };
            }
          
            return [
              state,
              (newFieldsVal, newVal) => {
                if (typeof newVal !== "undefined") {
                  const tmp = {};
                  tmp[newFieldsVal] = newVal;
                  dispatch(tmp);
                } else {
                  dispatch(newFieldsVal);
                }
              },
            ];
          }
          

          更多相关hooks

          【讨论】:

            【解决方案10】:

            ,像这个例子一样:

            对象的第一个创建状态:

            const [isSelected, setSelection] = useState([{ id_1: false }, { id_2: false }, { id_3: false }]);
            

            然后更改它们的值:

            // if the id_1 is false make it true or return it false.
            
            onValueChange={() => isSelected.id_1 == false ? setSelection([{ ...isSelected, id_1: true }]) : setSelection([{ ...isSelected, id_1: false }])}
            

            【讨论】:

              【解决方案11】:

              可以使用useReducer 挂钩来管理复杂状态,而不是useState。为此,首先像这样初始化状态和更新函数:

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

              然后您可以通过只传递部分更新来更新状态,如下所示:

              updateState({ ocupation: "postman" })
              

              【讨论】:

                【解决方案12】:

                我认为最好的解决方案是Immer。它允许您像直接修改字段一样更新对象(masterField.fieldOne.fieldx = 'abc')。但它当然不会改变实际对象。它收集草稿对象的所有更新,并在最后为您提供最终对象,您可以使用它来替换原始对象。

                【讨论】:

                  【解决方案13】:

                  我留给你一个实用函数来不变地更新对象

                  /**
                   * Inmutable update object
                   * @param  {Object} oldObject     Object to update
                   * @param  {Object} updatedValues Object with new values
                   * @return {Object}               New Object with updated values
                   */
                  export const updateObject = (oldObject, updatedValues) => {
                    return {
                      ...oldObject,
                      ...updatedValues
                    };
                  };
                  

                  所以你可以这样使用它

                  const MyComponent = props => {
                  
                    const [orderForm, setOrderForm] = useState({
                      specialities: {
                        elementType: "select",
                        elementConfig: {
                          options: [],
                          label: "Specialities"
                        },
                        touched: false
                      }
                    });
                  
                  
                  // I want to update the options list, to fill a select element
                  
                    // ---------- Update with fetched elements ---------- //
                  
                    const updateSpecialitiesData = data => {
                      // Inmutably update elementConfig object. i.e label field is not modified
                      const updatedOptions = updateObject(
                        orderForm[formElementKey]["elementConfig"],
                        {
                          options: data
                        }
                      );
                      // Inmutably update the relevant element.
                      const updatedFormElement = updateObject(orderForm[formElementKey], {
                        touched: true,
                        elementConfig: updatedOptions
                      });
                      // Inmutably update the relevant element in the state.
                      const orderFormUpdated = updateObject(orderForm, {
                        [formElementKey]: updatedFormElement
                      });
                      setOrderForm(orderFormUpdated);
                    };
                  
                    useEffect(() => {
                        // some code to fetch data
                        updateSpecialitiesData.current("specialities",fetchedData);
                    }, [updateSpecialitiesData]);
                  
                  // More component code
                  }
                  

                  如果没有,这里有更多实用程序:https://es.reactjs.org/docs/update.html

                  【讨论】:

                    【解决方案14】:

                    您要为其创建状态的对象

                    let teams = {
                      team: [
                        {
                          name: "one",
                          id: "1"
                        },
                      ]
                    }
                    

                    将 State of Teams 设为对象

                    const [state, setState] = useState(teams);
                    

                    像这样更新状态

                    setState((prevState)=>({...prevState,team:[
                         ...prevState.team,
                         {
                         name: "two",
                          id: "2"
                         }
                    ]}))
                    

                    更新后状态变为

                    {
                      team: [
                        {
                          name: "one",
                          id: "1"
                        },
                        {
                          name: "two",
                          id: "2"
                        }
                      ]
                    }
                    

                    使用地图功能根据当前状态渲染项目

                    {state.team.map((curr_team) => {
                          return (
                            <div>
                               <p>{curr_team.id}</p>
                               <p>{curr_team.name}</p>
                            </div>
                          )
                    })}
                    

                    【讨论】:

                      【解决方案15】:

                      我认为更优雅的解决方案是创建更新后的状态对象,同时保留之前的状态值。需要更新的 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>
                      )
                      }
                      

                      【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 1970-01-01
                        • 2020-12-28
                        • 2020-03-23
                        • 2020-07-20
                        • 1970-01-01
                        • 1970-01-01
                        • 1970-01-01
                        • 2019-08-15
                        相关资源
                        最近更新 更多