react 更新状态中的对象

State 可以保存任何类型的 JavaScript 值,包括对象。但是你不应该直接改变你在 React 状态下持有的对象。相反,当你想更新一个对象时,你需要创建一个新对象(或复制一个现有对象),然后设置状态以使用该副本。

const [position, setPosition] = useState({ x: 0, y: 0 });

从技术上讲,可以更改对象本身的内容。这称为突变:

position.x = 5;

 

然而,尽管 React 状态中的对象在技术上是可变的,但您应该将它们视为不可变的——就像数字、布尔值和字符串一样。您应该始终替换它们,而不是改变它们。

const [position, setPosition] = useState({
    x: 0,
    y: 0
  });

 

这样的代码是一个问题,因为它修改了状态中的现有对象:

position.x = e.clientX;
position.y = e.clientY;

 

但是这样的代码绝对没问题,因为您正在改变刚刚创建的新对象:

const nextPosition = {};
nextPosition.x = e.clientX;
nextPosition.y = e.clientY;
setPosition(nextPosition);

 

事实上,它完全等同于这样写:

setPosition({
  x: e.clientX,
  y: e.clientY
});

 

只有当您更改已处于状态的现有对象时,变异才会成为问题。改变您刚刚创建的对象是可以的,因为还没有其他代码引用它。更改它不会意外影响依赖它的东西。这被称为“局部突变”。您甚至可以在渲染时进行局部突变。很方便,完全没问题!

我们常用的是使用传播语法复制对象,您可以使用... 对象传播语法,这样您就不需要单独复制每个属性。

setPerson({
  ...person, // Copy the old fields
  firstName: e.target.value // But override this one
});

 

用 Immer 编写简洁的更新逻辑

如果您的状态嵌套很深,您可能需要考虑将其展平。但是,如果您不想更改状态结构,您可能更喜欢嵌套传播的捷径。Immer是一个流行的库,它允许您使用方便但可变的语法进行编写,并负责为您生成副本。使用 Immer,您编写的代码看起来像是在“违反规则”并改变对象:

updatePerson(draft => {
  draft.artwork.city = 'Lagos';
});

 

但与常规突变不同的是,它不会覆盖过去的状态!

尝试 Immer:

运行npm install use-immer以将 Immer 添加为依赖项

然后替换 import { useState } from 'react'为import { useImmer } from 'use-immer'

例如:

import { useImmer } from 'use-immer';

export default function Form() {
  const [person, updatePerson] = useImmer({
    name: 'Niki de Saint Phalle',
    artwork: {
      title: 'Blue Nana',
      city: 'Hamburg',
      image: 'https://i.imgur.com/Sd1AgUOm.jpg',
    }
  });

  function handleNameChange(e) {
    updatePerson(draft => {
      draft.name = e.target.value;
    });
  }

  function handleTitleChange(e) {
    updatePerson(draft => {
      draft.artwork.title = e.target.value;
    });
  }

  function handleCityChange(e) {
    updatePerson(draft => {
      draft.artwork.city = e.target.value;
    });
  }

  function handleImageChange(e) {
    updatePerson(draft => {
      draft.artwork.image = e.target.value;
    });
  }

  return (
    <>
      <label>
        Name:
        <input
          value={person.name}
          onChange={handleNameChange}
        />
      </label>
      <label>
        Title:
        <input
          value={person.artwork.title}
          onChange={handleTitleChange}
        />
      </label>
      <label>
        City:
        <input
          value={person.artwork.city}
          onChange={handleCityChange}
        />
      </label>
      <label>
        Image:
        <input
          value={person.artwork.image}
          onChange={handleImageChange}
        />
      </label>
      <p>
        <i>{person.artwork.title}</i>
        {' by '}
        {person.name}
        <br />
        (located in {person.artwork.city})
      </p>
      <img 
        src={person.artwork.image} 
        alt={person.artwork.title}
      />
    </>
  );
}

 

请注意事件处理程序变得多么简洁。您可以根据需要在单个组件中混合搭配useState。useImmerImmer 是保持更新处理程序简洁的好方法,尤其是当您的状态中存在嵌套并且复制对象会导致重复代码时。

posted @ 2023-04-26 18:34  小不点灬  阅读(50)  评论(0编辑  收藏  举报