React Hooks useState() 与对象

React Hooks useState() with Object

在 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"
           }
        }
   })

您可以像这样传递新值:

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

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

一旦你深度克隆状态,React 将重新计算并re-render一切依赖于变量的东西,即使它们没有改变!

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

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

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

/**
 * 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

我迟到了..:)

@aseferov 当打算重新输入整个对象结构时,答案非常有效。但是,如果 target/goal 是更新对象中的特定字段值,我相信下面的方法更好。

情况:

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"

    }));

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

如果有人正在搜索 useState() 钩子更新 object

- 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'
         }));

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

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} 
/>


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>
  );
}

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

useReducer is usually preferable to useState when you have complex state logic that involves multiple sub-values or when the next state depends on the previous one.

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

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

,像这个例子那样做:

对象的第一个创建状态:

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 }])}

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

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

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>
)
}

我已经给出了 Append、Whole object update、Specific key update examples for the solve

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

这就是追加新对象的方法

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 键怎么办? 38=] 对象。您执行以下操作。

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

您要为其创建状态的对象

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

制作团队状态对象

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

像这样更新状态

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

更新后状态变为

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

根据当前状态渲染项目使用 Map 函数

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

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

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

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

updateState({ ocupation: "postman" })

2022 年

如果您在 functional components 中寻找与 this.setState(来自 class components)相同的功能,那么这个答案对您有很大帮助。

例如

你有一个像下面这样的状态,并且只想从整个状态更新特定字段,那么你需要每次都使用 object destructing,有时它会很烦人。

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 个文件中,并在需要时将其导入到每个文件中。

这里是 custom hook 如果你想要的话。

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;

如果您使用 布尔值 和数组这可以帮助您:

const [checkedOrders, setCheckedOrders] = useState<Record<string, TEntity>>({});

const handleToggleCheck = (entity: TEntity) => {
  const _checkedOrders = { ...checkedOrders };
  const isChecked = entity.id in checkedOrders;

  if (isChecked) {
    delete _checkedOrders[entity.id];
  } else {
    _checkedOrders[entity.id] = entity;
  }

  setCheckedOrders(_checkedOrders);
};