与对象反应挂钩useState()


114

在带有钩子的React中,更新状态的正确方法是嵌套对象是什么?

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

一个人怎么会使用setExampleState到的更新exampleStatea(附加一个字段)?

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对于第一个示例,是的,对于第二个示例,仅更改现有对象
isaacsultan

onValueChange = {()=> setSelection({... prev,id_1:true})}
Omar bakhsh

Answers:


122

您可以像这样传递新值

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

2
很好,答案为a!您知道b部分的最佳做法吗?
isaacsultan

1
改变同样的过去,masterField而不是masterField2 setExampleState({...exampleState, masterField:{// new values}
aseferov

3
使用传播运算符时,请注意浅表复制。例如见:stackoverflow.com/questions/43638938/...
若昂·马科斯格里斯

9
如果与其Dispatcher使用相同的状态,则应使用一个函数。setExampleState( exampleState => ({...exampleState, masterField2: {...etc} }) );
Michael Harley

62

如果有人在搜索 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'
         }));

3
正是我想要的。太棒了!
StackedActor

42

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

深度克隆状态后,React将重新计算并重新渲染依赖变量的所有内容,即使它们没有更改!

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

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


您还可以使用Hookstate(免责声明:我是作者)来管理复杂的(本地和全局)状态数据,而无需进行深度克隆,也不必担心不必要的更新-Hookstate将为您处理。
安德鲁(Andrew)

8

谢谢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

我晚会晚了.. :)

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

情况:

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"

    }));

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


1
我有个问题。为什么需要将函数包装在括号中?我不知道到底发生了什么。您是否会知道,或者在哪里可以阅读更多内容?

2
@MichaelRamage为什么我们包裹功能括号:():为了简单地回答这个问题; 这是因为setInfoData 从本质上讲是高阶的,即可以接受另一个函数作为参数,这要归功于Hook提供的功能:useState。我希望本文能更清楚地说明高阶函数:sitepoint.com/higher-order-functions-javascript
Olamigoke Philip

2
非常有用,特别是对于您尝试更新嵌套在几层深的属性的情况,例如:first.second.third-其他示例涵盖了first.second,但没有涵盖first.second.third
Craig Presti-MSFT

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

1

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


0

我留下了一个实用程序函数来不变地更新对象

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


0

最初,我在useState中使用对象,但是后来我将其用于复杂情况下的useReducer挂钩。重构代码后,我感觉到了性能上的提高。

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

useReducer React文档

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

/**
 * 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);
      }
    },
  ];
}

更多相关的钩子


0

,就像下面的例子一样:

对象的第一个创建状态:

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 })}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.