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

本文来自#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来维护;

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【flink番外篇】9、Flink Table API 支持的操作示例(14)- 时态表的join(java版本)

Flink 系列文章 一、Flink 专栏 Flink 专栏系统介绍某一知识点&#xff0c;并辅以具体的示例进行说明。 1、Flink 部署系列 本部分介绍Flink的部署、配置相关基础内容。 2、Flink基础系列 本部分介绍Flink 的基础部分&#xff0c;比如术语、架构、编程模型、编程指南、基本的…

Redis 哨兵主备切换的数据丢失问题

导致数据丢失的两种情况 主备切换的过程&#xff0c;可能会导致数据丢失&#xff1a; 异步复制导致的数据丢失 因为 master->slave 的复制是异步的&#xff0c;所以可能有部分数据还没复制到 slave &#xff0c; master 就宕机 了&#xff0c;此时这部分数据就丢失了…

VSCode远程开发配置和SSH免密登录

目录 概要远程开发插件安装开始连接SSH免密登录开发环境配置 概要 现在很多公司都是直接远程到服务器上写代码&#xff0c;使用远程开发&#xff0c;可以在与生产环境相同的环境中开发、测试和部署代码&#xff0c;减少因环境不同而导致的问题。本文将详细介绍如何通过VSCode连…

【MySQL】字符集与排序规则

在MySQL数据库中&#xff0c;字符集&#xff08;Character Set&#xff09;和排序规则&#xff08;Collation,也称字符集校验规则&#xff09;是重要的概念&#xff0c;它们对于正确存储和比较数据至关重要。 字符集与排序规则 字符集是一组字符的集合&#xff0c;与数字编码…

prometheus与zabbix监控的对比介绍

一、普米与zabbix基本介绍 1、prometheus介绍 Prometheus的基本原理是Prometheus Server通过HTTP周期性抓取被监控组件的监控数据&#xff0c;任意组件只要提供对应的HTTP接口并且符合Prometheus定义的数据格式&#xff0c;就可以接入Prometheus监控。 工作流程大致分为收集数…

java SSM水质历史数据可视化设计myeclipse开发mysql数据库springMVC模式java编程计算机网页设计

一、源码特点 java SSM水质历史数据可视化设计是一套完善的web设计系统&#xff08;系统采用SSM框架进行设计开发&#xff0c;springspringMVCmybatis&#xff09;&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主…

2023-我的CSDN创作之旅

1.博客内容与数量 2023年共发表博客59篇&#xff0c;内容主要集中在GIS&#xff0c;空间分析等领域 主要内容有&#xff1a; networkx学习 Geospatial Data Science Geocomputation ESDA in PySal SHAP Spatial Data Analysis BikeDNA 以下是对这几个章节主要内容的简…

Docker无法启动Postgresql容器

目录 问题描述解决问题 问题描述 拉取了一个Postgresql14.2的镜像&#xff0c;在docker run创建并运行容器之后使用docker ps发现容器没有跑起来&#xff0c;再次使用docker start也没跑起来。 docker run -d --name mypg -v psql-data:/var/lib/postgresql/data -e POSTGRES…

【Bug】Android BottomNavigationView 图标黑色色块问题

最近在研究Android Jetpack组件&#xff0c;在使用Navigation配合底部导航栏时&#xff0c;发现一个奇怪的问题&#xff0c;如下&#xff1a; 说明&#xff1a;图标来源于Iconfont开源图标库 我的第三个图标变成了一个黑色色块&#xff0c;这个问题前两天我遇见过&#xff0c…

web服务器nginx和Apache有什么区别?

随着互联网的快速发展&#xff0c;Web服务器在互联网应用中扮演着越来越重要的角色。其中&#xff0c;Nginx和Apache是两种广泛使用的Web服务器软件。尽管它们都可以实现Web服务器的功能&#xff0c;但Nginx和Apache在许多方面存在一些重要的区别。本文将探讨Nginx和Apache之间…

学习Vue 03-03 为TypeScript使用defineComponent支持

03 为TypeScript使用defineComponent支持 The defineComponent() method is a wrapper function that accepts an object of configurations and returns the same thing with type inference for defining a component. defineComponent() 方法是一个封装函数&#xff0c;它…

win2003搭建DNS服务器域名解析方法

可以搭建DNS服务器的系统有很多&#xff0c;这里以win2003举例。 要在Windows 2003上搭建DNS服务器&#xff0c;需要按照以下步骤操作&#xff1a; 一 配置DNS服务器 1、打开“控制面板”,选择“添加/删除程序”,点击“添加/删除Windows组件”。 2、在“Windows组件向导”中…

【技能---500G硬盘-Ubuntu 20.04安装分区参考】

文章目录 Ubuntu 20.04安装分区指导安装分区流程Ubuntu 系统分区关键一步----- 选择安装启动引导器的设备 Ubuntu 20.04安装分区指导 安装Ubuntu 20.04的时候可以自己指定各个内存空间的占用&#xff0c;值得注意的是&#xff0c;这里的分区有一定的技巧&#xff01;&#xff0…

深度学习 Day24——J3-1DenseNet算法实战与解析

&#x1f368; 本文为&#x1f517;365天深度学习训练营 中的学习记录博客&#x1f356; 原作者&#xff1a;K同学啊 | 接辅导、项目定制&#x1f680; 文章来源&#xff1a;K同学的学习圈子 文章目录 前言1 我的环境2 pytorch实现DenseNet算法2.1 前期准备2.1.1 引入库2.1.2 设…

Spring MVC RequestMappingInfo路由条件匹配

前言 我们已经知道&#xff0c;被RequestMapping标注的方法会被解析为 HandlerMethod&#xff0c;它也是 Spring MVC 中最常用的 Handler 类型。现在的问题是&#xff0c;HTTP 请求是如何路由到对应的 HandlerMethod&#xff1f;你可能脱口而出&#xff1a;根据请求的 Url 匹配…

知识图谱 vs GPT

简介&#xff1a; 当我们谈论知识图谱时&#xff0c;我们指的是一种结构化的知识表示形式&#xff0c;是一种描述真实世界中事物及其关系的语义模型&#xff0c;用于描述实体之间的关系。它通过将知识组织成图形结构&#xff0c;提供了一种更全面、准确和智能的信息处理方式。知…

【论文阅读笔记】Mip-NeRF 360: Unbounded Anti-Aliased Neural Radiance Fields

目录 概述摘要引言参数化效率歧义性 mip-NeRF场景和光线参数化从粗到细的在线蒸馏基于区间的模型的正则化实现细节实验限制总结&#xff1a;附录退火膨胀采样背景颜色 paper&#xff1a;https://arxiv.org/abs/2111.12077 code&#xff1a;https://github.com/google-research/…

分布式系统架构设计之分布式事务的概述和面临的挑战

在当今大规模应用和服务的背景下&#xff0c;分布式系统的广泛应用已经成为了一种必然的主流趋势。然后&#xff0c;伴随着分布式系统的应用范围的增长&#xff0c;分布式事务处理成为了一个至关重要的关键话题。在传统的单体系统中&#xff0c;事务处理通常相对简单&#xff0…

opencv006 绘制直线、矩形、⚪、椭圆

绘制图形是opencv经常使用的操作之一&#xff0c;库中提供了很多有用的接口&#xff0c;今天来学习一下吧&#xff01; &#xff08;里面的函数和参数还是有点繁琐的&#xff09; 最终结果显示 函数介绍 直线 line(img, pt1, pt2, color, thickness, lineType, shift) img: 在…

django websocket

目录 核心代码 consumers.py from channels.generic.websocket import WebsocketConsumer from channels.exceptions import StopConsumer import datetime import time from asgiref.sync import async_to_sync class ChatConsumer(WebsocketConsumer):def websocket_conne…