与对象反应挂钩useState()

isa*_*tan 19 javascript reactjs react-hooks

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

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

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

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

Run Code Online (Sandbox Code Playgroud)

b (改变价值)?

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

Run Code Online (Sandbox Code Playgroud)

Mah*_*rus 117

如果有人正在搜索 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'
         }));
Run Code Online (Sandbox Code Playgroud)

  • 为什么需要使用回调? (2认同)
  • 假设该对象有 20 多个属性,导致 20 多个文本输入。这仍然有效吗?因为任何输入的每次更改都会重新渲染所有 20 多个输入?或者为每个属性单独创建一个 useState 会更有效吗? (2认同)

Mic*_*ski 33

可以使用useReducerhook 来管理复杂状态,而不是useState. 为此,首先初始化状态并更新函数,如下所示:

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

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

updateState({ occupation: "postman" })
Run Code Online (Sandbox Code Playgroud)


ase*_*rov 28

您可以像这样传递新值

  setExampleState({...exampleState,  masterField2: {
        fieldOne: "c",
        fieldTwo: {
           fieldTwoOne: "d",
           fieldTwoTwo: "e"
           }
        },
   }})
Run Code Online (Sandbox Code Playgroud)

  • 如果您使用与其调度程序相同的状态,则应该使用一个函数。`setExampleState( exampleState =&gt; ({...exampleState, masterField2: {...etc} }) );` (24认同)
  • @deathfry这没有错,只是 setState 行为是异步的,如果你的逻辑很重,它可能会产生意外的行为。检查我创建的代码沙箱,看看使用直接状态和函数式之间的区别。https://4vfw6p.csb.app/ (5认同)
  • 太好了,这回答了部分!你知道 b 部分的最佳实践吗? (3认同)
  • 使用展开运算符时请注意浅复制。例如,请参阅:/sf/ask/3054725691/ (3认同)
  • 也同样改变过去的 `masterField` 而不是 `masterField2` `setExampleState({...exampleState, masterField:` {//新值} (2认同)

Nis*_*hah 21

2022年

如果您正在寻找与this.setState(来自class components)相同的功能functional 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}))
Run Code Online (Sandbox Code Playgroud)

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

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}))
Run Code Online (Sandbox Code Playgroud)
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}))
Run Code Online (Sandbox Code Playgroud)

如果需要,您可以将其存储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;
Run Code Online (Sandbox Code Playgroud)

打字稿

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;
Run Code Online (Sandbox Code Playgroud)

  • 不,在每种情况下都不相同,react 建议使用回调,因为它总是为您提供更新的结果。 (2认同)

小智 11

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

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


Run Code Online (Sandbox Code Playgroud)


Ily*_*nov 10

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

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

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

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

  • 您还可以使用 [Hookstate](https://github.com/avkonst/hookstate)(免责声明:我是作者)来管理复杂的(本地和全局)状态数据,无需深度克隆,也不用担心不必要的更新 - Hookstate将为您处理。 (2认同)

Ola*_*lip 10

我参加聚会迟到了.. :)

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

情况:

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

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

    }

  });
Run Code Online (Sandbox Code Playgroud)

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

这里:

setInfoData((prevState) => ({
      ...prevState,
      major: {
        ...prevState.major,
        name: "Tan Long",
      }
    }));
Run Code Online (Sandbox Code Playgroud)

也许

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

    }));
Run Code Online (Sandbox Code Playgroud)

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

  • 非常有帮助,特别是对于您尝试更新嵌套多层的属性的情况,即:first.second.third - 其他示例涵盖first.second,但不涵盖first.second.third (3认同)
  • @MichaelRamage为什么我们将函数括在括号中:`()`:简单地回答这个问题;这是因为 `setInfoData` 本质上是高阶的,即它可以接受另一个函数作为参数,这得益于 Hook: useState 提供的功能。我希望本文能够更清晰地介绍高阶函数:https://www.sitepoint.com/higher-order-functions-javascript (2认同)

TG_*_*___ 7

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

不起作用(缺少功能)

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

有效!

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


mos*_*ony 6

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

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

这是附加新对象的方法

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

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

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

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

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

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