【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 unexpectedKeyCacheif (process.env.NODE_ENV !== 'production') {unexpectedKeyCache = {}}let shapeAssertionErrortry {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 = falseconst 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] = nextStateForKeyhasChanged = hasChanged || nextStateForKey !== previousStateForKey}hasChanged =hasChanged || finalReducerKeys.length !== Object.keys(state).lengthreturn 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.hqwc.cn/news/323177.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

集群渲染是?渲染农场是?两者与云渲染关联是什么

在数字化浪潮不断推进的当下&#xff0c;渲染技术在多个行业中发挥着至关重要的作用&#xff0c;尤其体现在电影制作、建筑可视化以及电子游戏开发等领域。在众多渲染技术中&#xff0c;集群渲染、渲染农场以及云渲染特别受到业界的重视。本文旨在阐述这些概念的含义以及它们之…

查询json数组

步骤一&#xff1a;创建表格 首先&#xff0c;我们需要创建一个表格来存储包含JSON对象数组的数据。可以使用以下代码创建一个名为 my_table 的表格&#xff1a; CREATE TABLE my_table (id INT PRIMARY KEY AUTO_INCREMENT,json_data JSON ); 上述代码创建了一个包含两个列的…

Ansible、Saltstack、Puppet自动化运维工具介绍

本文主要是分享介绍三款主流批量操控工具Ansible、Saltstack、Puppet主要对比区别&#xff0c;以及Ansible和saltstack的基础安装和使用示例&#xff0c;如果觉得本文对你有帮助&#xff0c;欢迎点赞、收藏、评论&#xff01; There are many things that can not be broken&am…

三分钟弄清数据传输方式

数据传输方式是指在计算机网络和通信系统中&#xff0c;数据如何在发送端和接收端之间进行传输和交换的方法和技术。不同的数据传输方式可以影响到数据传输的效率、安全性和可靠性&#xff0c;因此在实际应用中选择合适的数据传输方式至关重要。本文将从数据传输方式的基本概念…

Python | Iter/genartor | 一文了解迭代器、生成器的含义\区别\优缺点

前提 一种技术的出现&#xff0c;需要考虑&#xff1a; 为了实现什么样的需求&#xff1b;遇到了什么样的问题&#xff1b;采用了什么样的方案&#xff1b;最终接近或达到了预期的效果。 概念 提前理解几个概念&#xff1a; 迭代 我们经常听到产品迭代、技术迭代、功能迭代…

2024程序员应对挑战新方式竟然是……

2024年即将来临&#xff0c;无论2023是顺心还是不如意&#xff0c;一切都已经成为了过去式。无论在过去我们是陷入了一时的困窘&#xff0c;还是沉浸在繁花似锦的喜悦&#xff0c;我们都要保持头脑冷静&#xff0c;不被眼前迷障所困&#xff1b;我们任然要勇往直前&#xff0c;…

IP代理检测:关于Ping测试你需要知道的一切

您在访问互联网时是否遇到过持续滞后或花费很长时间等待网站加载的情况&#xff1f;为了避免这种情况&#xff0c;您可以测试 ping 以查看连接速度。如果您使用代理&#xff0c;此 ping 测试还会显示代理服务器的响应速度。 ping 测试是一个很有价值的工具&#xff0c;可以帮助…

android存储存储空间划分

参考链接&#xff1a;一文读懂 Android 分区存储 - 掘金

拼题A 跨年挑战赛 2024 赛后提交入口 + 题目 + 题解

赛后也想提交&#xff1f;点击进入 拼题A教育超市 周三&#xff0c;搞学长&#xff1a;“小柳进前十了&#xff01;想要奖品过来拿&#xff01;” 等了好几天的比赛结果终于出来了&#xff0c;四年来的跨年挑战赛第一次做满分&#xff0c;第一次进前十&#xff01;&#xff0…

【elfboard linux开发板】8. watchdog 和 mpu

1. 用文件IO的方式使用watchdog a. ioctl 命令说明 需要的头文件#include <sys/ioctl.h> 格式&#xff1a;int ioctl(int fd, unsigned long request, …) fd 必须被打开 第二个参数是一个依赖设备的请求代码 第三个参数是一个无类型的内存指针 返回值&#xff1a;成功为…

(leetcode)判断字符是否唯一 -- 使用位图(位运算)

个人主页&#xff1a;Lei宝啊 愿所有美好如期而遇 目录 本题链接 输入描述 输出描述 算法分析 算法一&#xff1a;哈希表 算法二&#xff1a;位运算(位图) 解题源码 本题链接 力扣&#xff08;LeetCode&#xff09; 输入描述 接口&#xff1a;bool isUnique(st…

洛谷 P1873 砍树 (二分 简单)

【二分答案】是分治的一种&#xff0c;这类问题很经典&#xff0c;接下来几篇文章会关于二分答案相关的文章&#xff0c;希望同学们可以完成10道以上的【二分答案】相关问题&#xff0c;以此来加深对【二分答案】这类问题的个人理解。 原公众号链接&#xff1a;分治第二讲&…