react中的useState和useImmer的用法

文章目录

  • 一、useState
    • [1. 更新基本类型数据](#1. 更新基本类型数据)
    • [2. 更新对象](#2. 更新对象)
    • [3. 更新嵌套对象](#3. 更新嵌套对象)
    • [4. 更新数组](#4. 更新数组)
    • 5.更新数组对象
  • 二、Immer
    • [1. 什么是Immer](#1. 什么是Immer)
    • [2. 使用use-immer更新嵌套对象](#2. 使用use-immer更新嵌套对象)
    • [3. 使用useImmer更新数组内部的对象](#3. 使用useImmer更新数组内部的对象)

一、useState

react中文官网教程

1. 更新基本类型数据

在函数式组件中,可以使用 useState 这个 Hook 来定义和管理组件的状态。useState 接受一个初始状态作为参数,并返回一个包含 state 和更新 state 的方法的数组。

下面是一个例子,展示了如何在函数式组件中定义自己的 state:

js 复制代码
import React, { useState } from "react";

function MyComponent() {
  // 定义一个名为 count 的 state 变量,并将初始值设置为 0
  const [count, setCount] = useState(0);

  const handleClick = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>Count: {count}</p>
      <button onClick={handleClick}>Increase Count</button>
    </div>
  );
}

export default MyComponent;

在上面的例子中,我们使用 useState 创建了一个名为 count 的状态变量,并将其初始值设为 0。然后,我们将 count 的值展示在 <p> 元素中,并在按钮的点击事件中调用 setCount 来更新 count 的值。

每当我们调用 setCount 来更新 count 的值时,React 会重新渲染组件并传递更新后的值给 count

2. 更新对象

在函数式组件中使用useState更新state中的对象可以通过以下步骤:

  1. 导入useState:
javascript 复制代码
import React, { useState } from 'react';
  1. 创建一个对象作为初始状态(state):
javascript 复制代码
const initialState = { name: 'John', age: 30, email: 'john@example.com' };
  1. 在组件内部使用useState钩子声明state:
javascript 复制代码
const [person, setPerson] = useState(initialState);

这里person是状态对象,setPerson是一个更新状态的函数。useState(initialState)是useState的初始值参数。

  1. 在组件中使用状态对象:
javascript 复制代码
return (
  <div>
    <p>Name: {person.name}</p>
    <p>Age: {person.age}</p>
    <p>Email: {person.email}</p>
  </div>
);
  1. 使用setPerson函数来更新状态对象的值:
javascript 复制代码
const updateEmail = () => {
  setPerson({ ...person, email: 'updated@example.com' });
};

这里使用了ES6的展开语法(spread syntax)来复制现有的person对象,并更新email属性的值。

  1. 在组件中调用updateEmail函数来更新状态:
javascript 复制代码
<button onClick={updateEmail}>Update Email</button>

完整的示例代码如下:

javascript 复制代码
import React, { useState } from 'react';

const App = () => {
  const initialState = { name: 'John', age: 30, email: 'john@example.com' };

  const [person, setPerson] = useState(initialState);

  const updateEmail = () => {
    setPerson({ ...person, email: 'updated@example.com' });
  };

  return (
    <div>
      <p>Name: {person.name}</p>
      <p>Age: {person.age}</p>
      <p>Email: {person.email}</p>
      <button onClick={updateEmail}>Update Email</button>
    </div>
  );
};

export default App;

通过点击"Update Email"按钮,可以更新email属性的值为"updated@example.com"。

在React的函数式组件中使用useState来更新一个对象的state时,有几个注意点需要特别注意:

  1. useState函数仅仅是按照传入的初始值来初始化state,并不会合并对象。因此,在更新state时,需要先使用解构方式取出旧的对象属性值,然后再设置新的对象属性值。

  2. 由于useState是按照值的变化来判断是否重新渲染组件的,因此在更新state时,需要保证每一次更新都是返回一个新的对象。直接对原对象进行修改并返回会导致state没有发生变化,从而不会触发重新渲染。

  3. 可以使用展开运算符扩展现有对象,再进行属性的更改。这样可以确保每次都返回一个新的对象。

为什么要注意这些点呢?

首先,useState是基于值的比较来判断是否重新渲染组件的,如果不注意每次都返回一个新的对象,可能会导致state没有发生变化,从而不会触发重新渲染,无法更新组件视图。

其次,由于函数式组件没有实例的概念,每次组件渲染都是独立的,因此无法像类组件中使用this.setState那样自动合并对象属性。因此,在更新state时,需要手动合并旧的对象属性值和新的对象属性值,以确保不丢失任何旧的state属性。

最后,使用展开运算符扩展对象的方式可以确保每次都返回一个全新的对象,这样可以避免直接对原对象进行修改而导致state没有发生变化的问题。

3. 更新嵌套对象

在React的函数式组件中使用useState更新state中的嵌套对象,可以使用扩展运算符(spread operator)来实现。

举例说明,假设有一个包含嵌套对象的state,如下所示:

javascript 复制代码
const [state, setState] = useState({
  name: "John",
  age: 30,
  address: {
    street: "123 Main St",
    city: "New York",
    state: "NY"
  }
});

要更新state中的嵌套对象,可以使用useState的setState函数,传递一个新的对象,并使用扩展运算符将原有的state进行展开,再覆盖需要更新的属性。例如,要更新address对象的city属性,可以使用以下代码:

javascript 复制代码
setState({
  ...state,
  address: {
    ...state.address,
    city: "Los Angeles"
  }
});

上面的代码首先使用扩展运算符将原有的state展开,然后再针对需要更新的嵌套对象进行展开,并更新特定的属性。在本例中,我们更新了address对象的city属性,将其值从"New York"更新为"Los Angeles"。

完整的示例代码如下所示:

javascript 复制代码
import React, { useState } from "react";

function App() {
  const [state, setState] = useState({
    name: "John",
    age: 30,
    address: {
      street: "123 Main St",
      city: "New York",
      state: "NY"
    }
  });

  const updateCity = () => {
    setState({
      ...state,
      address: {
        ...state.address,
        city: "Los Angeles"
      }
    });
  };

  return (
    <div>
      <p>Name: {state.name}</p>
      <p>Age: {state.age}</p>
      <p>Street: {state.address.street}</p>
      <p>City: {state.address.city}</p>
      <p>State: {state.address.state}</p>
      <button onClick={updateCity}>Update City</button>
    </div>
  );
}

export default App;

在上述示例中,我们首先使用useState定义了包含嵌套对象的state,然后在组件中显示了state中的属性。最后,我们使用一个按钮来调用updateCity函数,该函数会更新state中的嵌套对象。点击按钮后,更新后的city属性值将显示为"Los Angeles"。

4. 更新数组

在函数式组件中使用useState来更新state中的数组,可以通过结构赋值的方式获取数组和更新数组的函数。一般情况下,使用useState更新数组时需要注意以下几点:

  1. 为了保持state的不可变性,应该使用数组的展开运算符(spread operator)来创建一个新的数组。

  2. 当更新数组时,需要将要更新的元素和其余元素区分开来。通常使用Array.map函数来遍历数组,并找到要更新的元素。

  3. 可以使用Array.filter函数过滤数组中的元素,从而可以删除特定的元素。

  4. 注意在更新数组时,在判断相等性时使用严格相等运算符(===),而不是用"浅比较"运算符(==)

下面是一个更新数组的例子:

javascript 复制代码
import React, { useState } from "react";

function Example() {
  const [list, setList] = useState([1, 2, 3, 4, 5]);

  const handleUpdate = () => {
    // 通过展开运算符创建一个新的数组,并更新数组的第一个元素
    setList([...list.slice(0, 1), 100, ...list.slice(2)]);
  };

  const handleDelete = (item) => {
    // 使用filter过滤数组,删除特定的元素
    setList(list.filter((el) => el !== item));
  };

  return (
    <div>
      <ul>
        {list.map((item) => (
          <li key={item}>
            {item}
            <button onClick={() => handleDelete(item)}>删除</button>
          </li>
        ))}
      </ul>
      <button onClick={handleUpdate}>更新</button>
    </div>
  );
}

export default Example;

在这个例子中,list是一个包含数字的数组。点击列表项后的"删除"按钮会删除相应的元素,点击"更新"按钮会将数组的第一个元素更新为100。每次更新数组时,使用展开运算符创建一个新的数组,并把更新的元素插入到适当的位置,这样可以保持state的不可变性。在删除元素时,使用filter函数过滤掉特定的元素。

5.更新数组对象

在函数式组件中使用 useState 来更新 state 数组中的内部对象,可以使用解构赋值的方式来获取数组中的指定对象,并使用 useState 来更新该对象的属性。

首先,在函数式组件中使用 useState 定义一个状态变量,可以设置初始状态为一个包含内部对象的数组。例如,使用以下代码定义一个状态变量 items,并设置初始状态为一个包含两个内部对象的数组:

javascript 复制代码
const [items, setItems] = useState([
  { name: "apple", quantity: 1 },
  { name: "banana", quantity: 2 }
]);

然后,通过解构赋值的方式获取数组中的指定对象,并使用 useState 更新该对象的属性。例如,通过以下代码获取数组中的第一个对象 item 并更新其 name 属性:

javascript 复制代码
const [item, setItem] = useState(items[0]);

const handleChangeName = () => {
  setItem(prevItem => ({ ...prevItem, name: "orange" }));
};

在上述代码中,setItem 函数通过接收一个回调函数来更新 item 对象的属性。该回调函数使用展开运算符 ... 将之前的属性拷贝到一个新的对象中,并更新 name 属性为 "orange"

需要注意的是,在更新数组时,不能直接修改数组中的对象。要更新数组中的对象,需要先拷贝数组,然后再更新其中的对象。可以使用 Array.map() 方法来拷贝数组并更新其中的对象。例如,使用以下代码将数组中的第一个对象的 name 属性改为 "orange"

javascript 复制代码
const updatedItems = items.map((item, index) => {
  if (index === 0) {
    return { ...item, name: "orange" };
  }
  return item;
});

setItems(updatedItems);

上述代码中,Array.map() 方法遍历数组,当索引为 0 的时候,返回一个新的对象,并更新其 name 属性为 "orange";否则,返回原来的对象。然后,将得到的新数组 updatedItems 设置为新的状态值。

二、Immer

1. 什么是Immer

Immer是一个JavaScript库,用于管理不可变状态。它通过基于原始状态创建一个新的状态树来实现不可变性。它提供了一种简单而直观的方式来处理复杂的状态更新逻辑,使代码更易于理解和维护。Immer可以与任何JavaScript框架或库一起使用,并且对于处理大型数据结构和深层嵌套的对象非常有用。它是一个流行的工具,用于管理JavaScript应用程序中的状态。

在React的函数式组件中,推荐使用Immer来处理复杂的数据state,特别是深层嵌套的数组对象,有以下几个原因:

  1. 操作不可变数据更加方便:Immer提供了一种简洁的方式来直接对不可变数据进行修改。通过使用Immer的produce函数,可以在不直接修改原始数据的情况下,创建一个可变的草稿副本,并且在草稿上进行所有的更改操作。这样可以避免因为直接修改原始数据而导致的引用问题和副作用问题。

  2. 性能优化:Immer采用了一种优化技术,称为"结构共享",它能够在不实际复制数据的情况下实现不可变数据的修改。这种技术可以大大提高性能,特别是对于大型、深层嵌套的数据结构。

  3. 减少模板嵌套深度:在处理深层嵌套的数组对象时,使用Immer可以减少模板嵌套的深度。因为Immer提供了一种更简洁、更直观的方式来修改深层嵌套的数据,这能够提高代码的可读性和可维护性。

总之,使用Immer可以帮助我们更方便地处理复杂的数据state,尤其是深层嵌套的数组对象。它简化了对不可变数据的操作,并提供了性能优化的机制,使得我们能够更加高效地开发React应用。

2. 使用use-immer更新嵌套对象

在React的函数式组件中,可以使用use-immer库来更新嵌套对象的state。use-immer是基于Immer库的React Hook封装,它可以方便地进行不可变状态更新。

以下是一个示例,展示如何使用use-immer更新嵌套对象的state:

首先,安装use-immer库:

bash 复制代码
npm install use-immer

然后,导入useImmer函数并在函数式组件中使用它:

js 复制代码
import React, { useState } from 'react';
import { useImmer } from 'use-immer';

function App() {
  const [state, setState] = useImmer({
    user: {
      name: 'John',
      age: 25
    }
  });

  // 更新嵌套对象的示例函数
  const updateUserName = () => {
    setState(draft => {
      draft.user.name = 'Tom';
    });
  };

  return (
    <div>
      <h1>{state.user.name}</h1>
      <p>{state.user.age}</p>
      <button onClick={updateUserName}>Update Name</button>
    </div>
  );
}

export default App;

在上面的示例中,我们使用useState和useImmer来声明和初始化state。state是一个嵌套对象,包含一个user对象。然后,我们在updateUserName函数中使用setState函数来更新嵌套对象的state。在setState的回调函数中,我们可以使用draft参数来修改状态。

使用use-immer库的好处是,我们可以在回调函数中直接修改draft对象,就像在原始状态上进行修改一样。useImmer会负责处理不可变性,最终生成一个新的状态,并将其应用于组件。

以上是使用use-immer库在React的函数式组件中更新嵌套对象状态的示例。可以根据实际需求进行相应的修改和扩展。

3. 使用useImmer更新数组内部的对象

可以使用useImmer hook结合immer来更新函数式组件中数组内部的对象。useImmer hook是在React中使用Immer库的推荐方式。

以下是一个示例,说明如何使用useImmer更新数组内部的对象:

javascript 复制代码
import React, { useState } from 'react';
import { useImmer } from 'use-immer';

const MyComponent = () => {
  // 使用useState hook创建数组状态
  const [data, setData] = useState([
    { id: 1, name: 'John' },
    { id: 2, name: 'Jane' },
    { id: 3, name: 'Bob' },
  ]);

  // 使用useImmer hook创建可变的数据副本draft和更新函数setDraft
  const [draft, setDraft] = useImmer(data);

  // 更新对象的名称
  const updateName = (id, name) => {
    setDraft(draft => {
      // 使用immer的produce函数来更新draft
      const item = draft.find(item => item.id === id);
      if (item) {
        item.name = name;
      }
    });
  };

  return (
    <div>
      {draft.map(item => (
        <div key={item.id}>
          <span>{item.name}</span>
          {/* 在组件中触发更新名称的函数 */}
          <button onClick={() => updateName(item.id, 'Updated Name')}>
            Update Name
          </button>
        </div>
      ))}
    </div>
  );
};

export default MyComponent;

在上面的代码中,我们首先使用useState hook创建了一个数组状态data,然后使用useImmer hook创建了一个可变的数据副本draft和一个更新函数setDraft。在更新函数中,我们使用immer的produce函数来更新draft,修改特定ID对应的对象的名称。

在组件的返回值中,我们使用draft.map循环遍历数组内部的对象,显示每个对象的名称,并使用按钮来触发更新的函数。

当你点击"Update Name"按钮时,会更新draft中特定ID对应的对象的名称,由于draft是可变的,所以React会自动更新组件的渲染。

相关推荐
ekskef_sef28 分钟前
32岁前端干了8年,是继续做前端开发,还是转其它工作
前端
sunshine6411 小时前
【CSS】实现tag选中对钩样式
前端·css·css3
真滴book理喻1 小时前
Vue(四)
前端·javascript·vue.js
蜜獾云1 小时前
npm淘宝镜像
前端·npm·node.js
dz88i81 小时前
修改npm镜像源
前端·npm·node.js
Jiaberrr1 小时前
解锁 GitBook 的奥秘:从入门到精通之旅
前端·gitbook
程序员_三木2 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
顾平安3 小时前
Promise/A+ 规范 - 中文版本
前端
聚名网3 小时前
域名和服务器是什么?域名和服务器是什么关系?
服务器·前端
桃园码工3 小时前
4-Gin HTML 模板渲染 --[Gin 框架入门精讲与实战案例]
前端·html·gin·模板渲染