【React系列】Redux(三) state如何管理

news2024/11/26 10:19:08

本文来自#React系列教程:https://mp.weixin.qq.com/mp/appmsgalbum?__biz=Mzg5MDAzNzkwNA==&action=getalbum&album_id=1566025152667107329)

一. reducer拆分

1.1. reducer代码拆分

我们来看一下目前我们的reducer

function reducer(state = initialState, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };
    case CHANGE_BANNER:
      return { ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return { ...state, recommends: action.recommends };
    default:
      return state;
  }
}
  • 当前这个reducer既有处理counter的代码,又有处理home页面的数据;
  • 后续counter相关的状态或home相关的状态会进一步变得更加复杂;
  • 我们也会继续添加其他的相关状态,比如购物车、分类、歌单等等;

如果将所有的状态都放到一个reducer中进行管理,随着项目的日趋庞大,必然会造成代码臃肿、难以维护。

因此,我们可以对reducer进行拆分:

我们先抽取一个对counter处理的reducer

// counter相关的状态
const initialCounter = {
  counter: 0
}

function counterReducer(state = initialCounter, action) {
  switch (action.type) {
    case ADD_NUMBER:
      return { ...state, counter: state.counter + action.num };
    case SUB_NUMBER:
      return { ...state, counter: state.counter - action.num };
    default:
      return state;
  }
}

再抽取一个对home处理的reducer

// home相关的状态
const initialHome = {
  banners: [],
  recommends: []
}

function homeReducer(state = initialHome, action) {
  switch (action.type) {
    case CHANGE_BANNER:
      return { ...state, banners: action.banners };
    case CHANGE_RECOMMEND:
      return { ...state, recommends: action.recommends };
    default:
      return state;
  }
}

如果将它们合并起来呢?

const initialState = {
}

function reducer(state = initialState, action) {
  return {
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}

1.2. reducer文件拆分

目前我们已经将不同的状态处理拆分到不同的reducer中,我们来思考:

  • 虽然已经放到不同的函数了,但是这些函数的处理依然是在同一个文件中,代码非常的混乱;
  • 另外关于reducer中用到的constant、action等我们也依然是在同一个文件中;

所以,接下来,我们可以对文件结构再次进行拆分:

./store
├── counter
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── home
│   ├── actioncreators.js
│   ├── constants.js
│   ├── index.js
│   └── reducer.js
├── index.js
├── reducer.js
└── saga.js

这里不再给出代码,每个文件中存放的内容即可:

  • home/actioncreators.js:存放home相关的action
  • home/constants.js:存放home相关的常量;
  • home/reducer.js:存放分离的reducer代码;
  • index.js:统一对外暴露的内容;

1.3. combineReducers

目前我们合并的方式是通过每次调用reducer函数自己来返回一个新的对象:

import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';

const initialState = {
}

function reducer(state = initialState, action) {
  return {
    counterInfo: counterReducer(state.counterInfo, action),
    homeInfo: homeReducer(state.homeInfo, action),
  }
}

事实上,redux给我们提供了一个combineReducers函数可以方便的让我们对多个reducer进行合并:

import { combineReducers } from 'redux';

import { reducer as counterReducer } from './counter';
import { reducer as homeReducer } from './home';

const reducer = combineReducers({
  counterInfo: counterReducer,
  homeInfo: homeReducer
})

export default reducer;

那么combineReducers是如何实现的呢?

  • 事实上,它也是讲我们传入的reducers合并到一个对象中,最终返回一个combination的函数(相当于我们之前的reducer函数了);
  • 在执行combination函数的过程中,它会通过判断前后返回的数据是否相同来决定返回之前的state还是新的state
  • 新的state会触发订阅者发生对应的刷新,而旧的state可以有效的组织订阅者发生刷新;
export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // This is used to make sure we don't warn about the same
  // keys multiple times.
  let unexpectedKeyCache
  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {}
  }

  let shapeAssertionError
  try {
    assertReducerShape(finalReducers)
  } catch (e) {
    shapeAssertionError = e
  }

  return function combination(state = {}, action) {
    if (shapeAssertionError) {
      throw shapeAssertionError
    }

    if (process.env.NODE_ENV !== 'production') {
      const warningMessage = getUnexpectedStateShapeWarningMessage(
        state,
        finalReducers,
        action,
        unexpectedKeyCache
      )
      if (warningMessage) {
        warning(warningMessage)
      }
    }

    let hasChanged = false
    const nextState = {}
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    hasChanged =
      hasChanged || finalReducerKeys.length !== Object.keys(state).length
    return hasChanged ? nextState : state
  }
}

二. ImmutableJS

2.1. 数据可变性的问题

在React开发中,我们总是会强调数据的不可变性:

  • 无论是类组件中的state,还是redux中管理的state
  • 事实上在整个JavaScript编码过程中,数据的不可变性都是非常重要的;

数据的可变性引发的问题:

const obj = {
  name: "why",
  age: 18
}

console.log(obj); // {name: "why", age: 18}
const obj2 = obj;
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}
  • 我们明明没有修改obj,只是修改了obj2,但是最终obj也被我们修改掉了;
  • 原因非常简单,对象是引用类型,它们指向同一块内存空间,两个引用都可以任意修改;

有没有办法解决上面的问题呢?

  • 进行对象的拷贝即可:Object.assign或扩展运算符
console.log(obj); // {name: "why", age: 18}
const obj2 = {...obj};
obj2.name = "kobe";
console.log(obj); // {name: "kobe", age: 18}

这种对象的浅拷贝有没有问题呢?

  • 从代码的角度来说,没有问题,也解决了我们实际开发中一些潜在风险;
  • 从性能的角度来说,有问题,如果对象过于庞大,这种拷贝的方式会带来性能问题以及内存浪费;

有人会说,开发中不都是这样做的吗?

  • 我比较喜欢一句话:从来如此,便是对的吗?

2.2. 认识ImmutableJS

为了解决上面的问题,出现了Immutable对象的概念:

  • Immutable对象的特点是只要修改了对象,就会返回一个新的对象,旧的对象不会发生改变;
    但是这样的方式就不会浪费内存了吗?

  • 为了节约内存,又出现了一个新的算法:Persistent Data Structure(持久化数据结构或一致性数据结构);

当然,我们一听到持久化第一反应应该是数据被保存到本地或者数据库,但是这里并不是这个含义:

  • 用一种数据结构来保存数据;
  • 当数据被修改时,会返回一个对象,但是新的对象会尽可能的利用之前的数据结构而不会对内存造成浪费;

如何做到这一点呢?结构共享

在这里插入图片描述

2.3. ImutableJS常见API

我们来学习一下ImmutableJS常用的API:

  • 注意:我这里只是演示了一些API,更多的方式可以参考官网;
const imjs = Immutable;

// 1.定义JavaScript的Array和转换成immutable的List
const friends = [
  { name: "why", age: 18 },
  { name: "kobe", age: 30 }
]

// 不会进行深层转换
const imArray1 = imjs.List(friends);
// 会进行深层转换
const imArray2 = imjs.fromJS(friends);
// console.log(imArray1);
// console.log(imArray2);

// 1.定义JavaScript的Object和转换成immutable的Map
const info = {
  name: "coderwhy",
  age: 18,
  friend: {
    name: "kobe",
    age: 30
  }
}

const imObj1 = imjs.Map(info);
const imObj2 = imjs.fromJS(info);
// console.log(imObj1);
// console.log(imObj2);

// 3.对immutable操作
// 3.1.添加数据
// 产生一个新的immutable对象
console.log(imArray1.push("aaaa"));
console.log(imArray1.set(2, "aaaa"));
// 原来的是不变的
console.log(imArray1);

// 3.2.修改数据
console.log(imArray1.set(1, "aaaa"));
console.log(imArray2.set(2, imjs.fromJS("bbbb")));

// 3.3.删除数据
console.log(imArray1.delete(0).get(0)); // {name: "kobe", age: 30}

// 3.4.查询数据
console.log(imArray1.get(1));
console.log(imArray2.get(1));
console.log(imArray1.get(1).name);
console.log(imArray2.getIn([1, "name"]));

// 4.转换成JavaScript对象
const array = imArray1.toJS();
const obj = imObj1.toJS();
console.log(array);
console.log(obj);

2.4. ImmutableJS重构redux

目前Redux已经学习了过多的内容了,很多同学已经认识到redux的难度,所以如何将ImmutableJS和redux结合使用我们这里先不讲解。

具体ImmutableJS和Redux的结合使用,放到后续项目练习时,再详细说明。

三. Redux FAQ

是否将所有的状态应用到redux

我们学习了Redux用来管理我们的应用状态,并且非常好用(当然,你学会前提下,没有学会,好好回顾一下)。

目前我们已经主要学习了三种状态管理方式:

  • 方式一:组件中自己的state管理;
  • 方式二:Context数据的共享状态;
  • 方式三:Redux管理应用状态;

在开发中如何选择呢?

  • 首先,这个没有一个标准的答案;
  • 某些用户,选择将所有的状态放到redux中进行管理,因为这样方便追踪和共享;
  • 有些用户,选择将某些组件自己的状态放到组件内部进行管理;
  • 有些用户,将类似于主题、用户信息等数据放到Context中进行共享和管理;
  • 做一个开发者,到底选择怎样的状态管理方式,是你的工作之一,可以一个最好的平衡方式(Find a balance that works for you, and go with it.);

redux作者建议:
在这里插入图片描述

目前项目中我采用的state管理方案:

  • UI相关的组件内部可以维护的状态,在组件内部自己来维护;
  • 只要是需要共享的状态,都交给redux来管理和维护;
  • 从服务器请求的数据(包括请求的操作),交给redux来维护;

当然,根据不同的情况会进行适当的调整,在后续学习网易云音乐项目时,我也会再次讲解以实战的角度来设计数据的管理方案。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1357611.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Spring实战】21 Spring Data REST 常用功能详细介绍

文章目录 1. 资源导出&#xff08;Resource Exporting&#xff09;2. 查询方法&#xff08;Query Methods&#xff09;3. 分页和排序&#xff08;Pagination and Sorting&#xff09;4. 关联关系&#xff08;Associations&#xff09;5. 事件&#xff08;Events&#xff09;6. …

【Go】excelize库实现excel导入导出封装(二),基于map、多个sheet、多级表头、树形结构表头导出,横向、纵向合并单元格导出

前言 大家好&#xff0c;这里是符华~ 之前写了一篇 go excelize库封装导入导出 的博客&#xff0c;然后那篇博客还挖了个坑&#xff0c;结果这个坑差点就填不上了&#x1f923;还好经过我的不懈努力&#xff0c;总算是把坑给填上了。。。 挖坑 上一篇文章中&#xff0c;我们…

使用fabric.js实现对图片涂鸦、文字编辑、平移缩放与保存功能

文章目录 背景1.初始化画布1.创建画布2.设置画布大小 2.渲染图片3.功能&#xff1a;开启涂鸦4.功能&#xff1a;添加文字5.旋转图片6.画布平移7.画布缩放8.保存图片9.上传图片10.销毁实例11.总结 背景 项目中有个需求&#xff0c;需要对图片附件进行简单的编辑操作&#xff0c…

网络故障问题一般性检查排查思路

一、基本连通性检查 在网络中ping是一个十分强大的TCP/IP工具。它可以用来检测网络的连通情况和分析网络速度、也可以ping网址根据域名得到服务器IP、同时我们根据ping返回的TTL值来判断对方所使用的操作系统及数据包经过路由器数量。 ping 网址&#xff0c;有几种输出情况&a…

【OpenCV】在MacOS上使用OpenCvSharp

前言 OpenCV是一个基于Apache2.0许可&#xff08;开源&#xff09;发行的跨平台计算机视觉和机器学习软件库&#xff0c;它具有C&#xff0c;Python&#xff0c;Java和MATLAB接口&#xff0c;并支持Windows&#xff0c;Linux&#xff0c;Android和Mac OS。OpenCvSharp是一个Op…

Flume基础知识(七):Flume 事务与 Flume Agent 内部原理

1. Flume 事务详解 2. Flume Agent 内部原理 重要组件&#xff1a; 1&#xff09;ChannelSelector ChannelSelector 的作用就是选出 Event 将要被发往哪个 Channel。其共有两种类型&#xff0c; 分别是 Replicating&#xff08;复制&#xff09;和 Multiplexing&#xff08;多…

如何计算非线性负载的功率需求?

非线性负载的功率需求计算是一个相对复杂的过程&#xff0c;因为非线性负载的电流和电压之间的关系不是简单的正比关系。在计算非线性负载的功率需求时&#xff0c;需要考虑负载的特性、工作状态以及电源电压等因素。 确定负载的类型&#xff1a;首先需要了解负载的具体类型&am…

MS713/MS713T:CMOS 低压、4Ω四路单刀单掷开关,替代ADG713

产品简述 MS713/MS713T 是一款单芯片 CMOS 4 路可选择开关&#xff0c;具有低 功耗、高开关速度、低导通阻抗、低漏电和高带宽特性。其工作 电压范围是 1.8V 到 5.5V &#xff0c;可以广泛应用在电池供电仪器仪表、新 一代的模数转换和数模转换系统中。其高带宽特性可用在 …

接口测试基础知识总结

一、HTTP 1、http请求头和响应头包含那些内容&#xff1f; 请求头信息 请求报头允许客户端向服务器端传递请求的附加信息以及客户端自身的信息。 2、常用的请求报头如下&#xff1a; Accept&#xff1a;浏览器可接受的MIME类型。 l MIME用于设定某种扩展名的文件用哪种应…

ubuntu远程桌面连接之vnc

一、前言 ubuntu安装图形化桌面以后,有些时候出于需要会想要进行远程桌面连接。ubuntu想要进行远程桌面连接就需要vnc服务的支持,安装vnc的方法有很多,博主也试过一些方式,但是安装完后使用vnc连接工具发现是花屏,无法正常使用。后来发现一种简单的方式即可配置好vnc连接,…

计算机毕业设计 基于Java的供应商管理系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍&#xff1a;✌从事软件开发10年之余&#xff0c;专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精…

【赠书第16期】码上行动:用ChatGPT学会Python编程

文章目录 前言 1 ChatGPT简介 2 Python编程简介 3 使用ChatGPT学习Python编程 4 如何使用ChatGPT学习Python编程 5 推荐图书 6 粉丝福利 前言 随着人工智能技术的不断发展&#xff0c;聊天机器人已经成为我们日常生活和工作中不可或缺的一部分。其中&#xff0c;ChatGP…

MEW-UNet:医学图像分割中的频域多轴表示学习

文章目录 摘要1、简介2、相关研究2.1、医学图像分割2.2、基于ViT的技术 3、我们的方法3.1、准备工作3.2、多轴外部权重块3.3、外部权重生成器 4、实验4.1、数据集4.2、实现细节4.3、与当前最佳方法的比较4.4、消融实验4.5、可视化 5、结论声明 摘要 https://arxiv.org/pdf/231…

uniapp 无限级树形结构面包屑、单选-多选、搜索、移除功能插件,基于【虚拟列表】高性能渲染海量数据,加入动态高度、缓冲区

hyq-tree-vtw 无限级树形结构面包屑、单选-多选、搜索、移除功能 示例项目 单选-user 单选-任意一项 多选-关联下级 多选-任意一项 已选择数据弹框 说明 本插件需要使用uni-popup、uni-transition用于已选择数据弹框&#xff0c;因此需要有这些依赖,请自行导入本插件基于【虚…

Maven多模块项目架构配置介绍和实战

项目采用的是Maven多模块架构&#xff0c;项目的部分子模块的pom.xml中重复引用了相同的JAR包。很明显&#xff0c;当初在配置Maven模块的时候&#xff0c;没有考虑清楚各个模块的架构职责&#xff0c;同时也不了解Maven模块依赖的传递性。主要介绍一下Maven多模块的配置思路和…

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译

基于深度学习大模型实现离线翻译模型私有化部署使用,通过docker打包开源翻译模型,可到内网或者无网络环境下运行使用,可以使用一千多个翻译模型语言模型进行翻译,想要什么语种直接进行指定和修改就行。 环境要求,电脑内存低于8G建议不要尝试了,有无GPU都可以运行,但是有…

geemap学习笔记041:Landsat Collection2系列数据去云算法总结

前言 去云算法是进行数据处理中所要进行一步重要操作&#xff0c;Sentinal-2数据中已经提供了去云算法&#xff0c;但是Landsat Collection2系列数据中并没有提供去云算法&#xff0c;下面就以Landsat 8 Collection2为例进行介绍。 1 导入库并显示地图 import ee import gee…

http 503 错误

503错误是一种HTTP状态码&#xff0c;表示你请求的网站或服务暂时不可用&#xff0c;通常是因为服务器过载或维护&#xff0c;你可能会看到类似这样的提示&#xff1a;503 Service Unavailable、503 Service Temporarily Unavailable、HTTP Server Error 503、HTTP Error 503 I…

Linux_CentOS_7.9配置时区及NTPdate同步之简易记录

前言&#xff1a;ntpdate命令来自英文词组”NTPdate“的拼写&#xff0c;其功能是用于设置日期和时间。ntpdate命令能够基于NTP协议设置Linux系统的本地日期和时间&#xff0c;利用NTP服务的时钟过滤器来选择最优方案&#xff0c;大大提高了可靠性和精度&#xff0c;让系统时间…

Django 快速整合 Swagger:实用步骤和最佳实践

Django &#xff0c;作为 Python 编写的一个优秀的开源 Web 应用框架&#xff0c;特别适用于快速开发的团队。对于很多场景来说&#xff0c;我们需要一份 API 文档&#xff0c;好处实在太多了&#xff1a; 提高开发效率&#xff1a;开发者可以基于 API 文档 快速学习和尝试 AP…