自学内容网 自学内容网

React状态管理详解

概述

React中的状态管理是其核心机制之一,它决定了组件的渲染和交互行为。以下是对React中状态管理工作原理的详细解释:

一、状态的定义与分类

在React中,状态(state)是组件记忆信息的一种方式,它决定了组件的渲染输出。状态可以是任何类型的数据,如数字、字符串、对象或数组等。根据状态的使用范围,可以将其分为本地状态和全局状态。

  1. 本地状态:本地状态是指仅在组件内部使用的状态,由组件自身维护和更新,不会被其他组件访问或修改。本地状态对于处理组件私有的数据非常有用。
  2. 全局状态:全局状态是指可以被多个组件共享和访问的状态,它通常用于存储跨组件的共享数据,如用户认证信息、主题设置等。

二、本地状态管理

本地状态管理主要通过React的useState钩子函数实现。useState是一个React Hook,它接受一个初始状态值,并返回一个包含当前状态值和一个用于更新状态的函数的数组。

  1. 初始化状态:当组件首次渲染时,useState会接受一个初始状态值,并将其作为组件的初始状态。
  2. 更新状态:当需要更新状态时,可以调用useState返回的更新函数,并传入新的状态值。React会重新渲染该组件,以反映状态的变化。

三、全局状态管理

全局状态管理在React中有多种实现方式,其中最常用的是Redux和React Context。

  1. Redux

    • 核心概念:Redux是一个独立于React的状态管理库,它提供了store(存储状态)、action(描述状态变更)和reducer(处理状态变更)三个核心概念。
    • 工作原理:通过定义一个全局的store来存储应用的状态,当需要更新状态时,通过dispatch函数发送一个action到reducer。Reducer是一个纯函数,它接受当前状态和action作为参数,并返回一个新的状态。然后,store会使用这个新状态来更新应用的状态树。
    • 优势:Redux具有强大的中间件支持(如Redux Thunk、Redux Saga等),可用于处理异步操作、日志记录等。此外,Redux DevTools等工具可用于调试和查看状态变化。
  2. React Context

    • 工作原理:React Context提供了一种在组件树中传递数据的方式,而无需在每一层组件中手动传递props。通过创建一个Context对象和一个Provider组件,可以将数据从顶层组件传递到下层组件。
    • 使用场景:React Context适用于简单的全局状态共享场景,如主题切换、用户认证等。对于复杂的状态管理需求,建议使用Redux等更强大的工具。

四、状态更新的优化

在React中,状态更新是一个性能敏感的操作。为了优化性能,React使用了一些策略来减少不必要的重新渲染。

  1. 批处理更新:React会合并同一事件循环中的所有状态更新,并在事件处理结束后只进行一次重新渲染。
  2. 纯函数组件:使用函数组件和React Hooks可以更容易地实现性能优化。例如,使用useMemouseCallback等Hooks可以避免不必要的计算和渲染。
  3. 不可变数据结构:使用不可变数据结构可以减少状态比较的开销,并提高性能。

五、总结

React中的状态管理是一个复杂而强大的机制,它支持本地状态和全局状态的管理。通过理解状态的定义、分类以及管理方式,可以更有效地开发React应用。在实际开发中,可以根据具体的需求和项目特点来选择合适的状态管理方案。

State _ 如同一张快照

     设置 state 只会为下一次渲染变更 state 的值。在第一次渲染期间,number0。这也就解释了为什么在 那次渲染中的 onClick 处理函数中,即便在调用了 setNumber(number + 1) 之后,number 的值也仍然是 0

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 1);
        setNumber(number + 1);
        setNumber(number + 1);
      }}>+3</button>
    </>
  )
}

类似!一个 state 变量的值永远不会在一次渲染的内部发生变化

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        alert(number);
      }}>+5</button>
    </>
  )
}

//结果
//setNumber(0 + 5);
//alert(0);



import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setTimeout(() => {
          alert(number);
        }, 3000);
      }}>+5</button>
    </>
  )
}

//结果
//setNumber(0 + 5);
//setTimeout(() => {
//  alert(0);
//}, 3000);

State_状态更新函数

 

在下次渲染前多次更新同一个 state 

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(n => n + 1);
        setNumber(n => n + 1);
        setNumber(n => n + 1);
      }}>+3</button>
    </>
  )
}
//每次+3

下面是 React 在执行事件处理函数时处理这几行代码的过程:

  1. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。
  2. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。
  3. setNumber(n => n + 1)n => n + 1 是一个函数。React 将它加入队列。

当你在下次渲染期间调用 useState 时,React 会遍历队列。之前的 number state 的值是 0,所以这就是 React 作为参数 n 传递给第一个更新函数的值。然后 React 会获取你上一个更新函数的返回值,并将其作为 n 传递给下一个更新函数,以此类推:

更新队列n返回值
n => n + 100 + 1 = 1
n => n + 111 + 1 = 2
n => n + 122 + 1 = 3

React 会保存 3 为最终结果并从 useState 中返回。

这就是为什么在上面的示例中点击“+3”正确地将值增加“+3”。

 

import { useState } from 'react';

export default function Counter() {
  const [number, setNumber] = useState(0);

  return (
    <>
      <h1>{number}</h1>
      <button onClick={() => {
        setNumber(number + 5);
        setNumber(n => n + 1);
        setNumber(42);
      }}>增加数字</button>
    </>
  )
}

以下是 React 在执行事件处理函数时处理这几行代码的过程:

  1. setNumber(number + 5)number 为 0,所以 setNumber(0 + 5)。React 将 “替换为 5 添加到其队列中。
  2. setNumber(n => n + 1)n => n + 1 是一个更新函数。React 将该函数添加到其队列中。
  3. setNumber(42):React 将 替换为 42 添加到其队列中。

在下一次渲染期间,React 会遍历 state 队列:

更新队列n返回值
“替换为 50(未使用)5
n => n + 155 + 1 = 6
“替换为 426(未使用)42

然后 React 会保存 42 为最终结果并从 useState 中返回。

总而言之,以下是你可以考虑传递给 setNumber state 设置函数的内容:

  • 一个更新函数(例如:n => n + 1)会被添加到队列中。
  • 任何其他的值(例如:数字 5)会导致“替换为 5”被添加到队列中,已经在队列中的内容会被忽略。

事件处理函数执行完成后,React 将触发重新渲染。在重新渲染期间,React 将处理队列。更新函数会在渲染期间执行,因此 更新函数必须是 纯函数 并且只 返回 结果。不要尝试从它们内部设置 state 或者执行其他副作用。在严格模式下,React 会执行每个更新函数两次(但是丢弃第二个结果)以便帮助你发现错误。

 更新State对象

更新嵌套对象※
setPerson({
  ...person, // 复制其它字段的数据 
  artwork: { // 替换 artwork 字段 
    ...person.artwork, // 复制之前 person.artwork 中的数据
    city: 'New Delhi' // 但是将 city 的值替换为 New Delhi!
  }
});
 Immer库
  1. 运行 npm install use-immer 添加 Immer 依赖
  2. 用 import { useImmer } from 'use-immer' 替换掉 import { useState } from 'react'
 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;
    });
  }

  

更新State中数组

避免改变原数组,推荐使用返回新数组的

//替换push改变原数组
setArtists( // 替换 state
  [ // 是通过传入一个新数组实现的
    ...artists, // 新数组包含原数组的所有元素
    { id: nextId++, name: name } // 并在末尾添加了一个新的元素
  ]
);

//替换unshift
setArtists([
  { id: nextId++, name: name },
  ...artists // 将原数组中的元素放在末尾
]);

//filter过滤不满足条件的(删除)
setArtists(
  artists.filter(a => a.id !== artist.id)
);
//数组中插入对象
   const insertAt = 1; // 可能是任何索引
    const nextArtists = [
      // 插入点之前的元素:
      ...artists.slice(0, insertAt),
      // 新的元素:
      { id: nextId++, name: name },
      // 插入点之后的元素:
      ...artists.slice(insertAt)


//翻转先拷贝,再拷贝基础上翻转
    const nextList = [...list];
    nextList.reverse();
    setList(nextList);

//更新内部对象
setMyList(myList.map(artwork => {
  if (artwork.id === artworkId) {
    // 创建包含变更的*新*对象
    return { ...artwork, seen: nextSeen };
  } else {
    // 没有变更
    return artwork;
  }
}));

 Immmer库

const [myList, updateMyList] = useImmer(
    initialList
  );
  const [yourList, updateYourList] = useImmer(
    initialList
  );

  function handleToggleMyList(id, nextSeen) {
    updateMyList(draft => {
      const artwork = draft.find(a =>
        a.id === id
      );
      artwork.seen = nextSeen;
    });
  }

  function handleToggleYourList(artworkId, nextSeen) {
    updateYourList(draft => {
      const artwork = draft.find(a =>
        a.id === artworkId
      );
      artwork.seen = nextSeen;
    });
  }


原文地址:https://blog.csdn.net/m0_55049655/article/details/143872218

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!