【Redux】自己动手实现redux和react-redux
1. React提供context的作用
在class组件的世界里,如果后代组件共享某些状态,比如主题色、语言键,则需要将这些状态提升到根组件,以props的方式从根组件向后代组件一层一层传递,这样则需要在每层写props.someData,这样使用起来非常麻烦。那么,有没有更好的方式,让后代组件们共享状态?
当然是有的,react提供了context解决方案,某个组件往context放入一些用于共享的状态,该组件的所有后代组件都可以直接从context取出这些共享状态,跳出一层一层传递的宿命。怎么做?以下给出示例代码
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';class App extends PureComponent {// 1. 定义静态变量childContextTypes声明context中状态对应的类型static childContextTypes = {lang: PropTypes.string,};constructor(props) {super(props);this.state = {lang: 'zh_CN',};}// 2. 通过getChildContext返回值设置contextgetChildContext() {return {lang: this.state.lang,};}render() {return (<div><SideMenu /><Content /></div>);}
}class SideMenu extends PureComponent {// 1. 定义静态变量contextTypes声明context中状态对应的类型static contextTypes = {lang: PropTypes.string,}constructor(props) {super(props);}// 2. 通过this.context取出contextrender() {return (<div lang={this.context.lang}>123</div>)}
}class Content extends PureComponent {// 1. 定义静态变量contextTypes声明context中状态对应的类型static contextTypes = {lang: PropTypes.string,}constructor(props) {super(props);}// 2. 通过this.context取出contextrender() {return (<div lang={this.context.lang}>123</div>)}
}
总结起来,就两点:
- 父组件类定义静态变量childContextTypes,通过getChildContext()方法的返回值设置context
- 后代组类定义静态变量contextTypes,通过this.context取出context
2. redux
2.1 实现createStore
redux可以比作数据中心,所有数据(即state)存于store中。如果想查询state,必须调用store.getState方法;如果想要更改state,必须调用store.dispatch方法;如果想要在更改state后执行其他额外逻辑,需要使用store.subscribe订阅。由store.subscribe订阅,由store.dispatch发布,构成订阅/发布模式。
本篇实现一个简易版的createStore,执行createStore()会返回store对象,createStore源码实现如下:
const createStore = (reducer, initialState) => {// 初始化statelet state = initialState;// 保存监听函数const listeners = [];// 返回store当前保存的stateconst getState = () => state;// 通过subscribe传入监听函数const subscribe = (listener) => {listeners.push(listener);}// 执行dispatch,通过reducer计算新的状态state// 并执行所有监听函数const dispatch = (action) => {state = reducer(state, action);for(const listener of listeners) {listener();}}!state && dispatch({});return {getState,dispatch,subscribe,}
}
2.2 Demo
初始化store(createStore)需要reducer,而reducer可以理解为更新state的方法,是一个纯函数。store.dispatch(action)一个动作后,首先,执行reducer方法,根据action.type找到对应处理逻辑,更新state;然后,执行所有由store.subscribe订阅的监听函数。
为了验证我们实现的redux功能,设计了一个简单的reducer:
const reducer = (state, action) => {if(!state) {return {menu: {text: 'menu',color: 'red',},}}switch(action.type) {case 'UPDATE_MENU_TEXT':return {...state,menu: {...state.menu,text: action.text,}};case 'UPDATE_MENU_COLOR':return {...state,menu: {...state.menu,color: action.color,}};default:return state;}
}
万事具备,创建store,作为一些简单测试,Demo源码如下:
const store = createStore(reducer);
store.subscribe(() => console.log('demo') );
const action = {type: 'UPDATE_MENU_COLOR',color: 'blue'
};
store.dispatch(action);
// 打印当前状态
console.dir(store.getState());
打印结果如下:
3. react-redux
第1节讲到context可以让react组件很方便的共享状态,第2节讲到redux统一管理状态,那是不是可以用redux统一管理react组件的共享状态呢?是可以的,react-redux就实现了context和redux的完美粘合。
3.1 改造父组件
首先是父组件部分,将父组件类“定义静态变量childContextTypes”和“通过getChildContext()方法的返回值设置context”移到Provider中,并且context的值从Provider的props获取。
import React, { PureComponent } from 'react';
import PropTypes from 'prop-types';export class Provider extends PureComponent {// Provider的props仅含store和childrenstatic propTypes = {store: PropTypes.object,children: PropTypes.any,}// 1. 定义静态变量childContextTypes声明context中状态对应的类型static childContextTypes = {store: PropTypes.object,};// 2. 通过getChildContext返回值设置contextgetChildContext() {return {store: this.props.store}}render() {return (<div>{this.props.children}</div>)}
}
3.2 改造后代组件
然后是后代组件部分,将后代组“定义静态变量contextTypes”和“通过this.context取出context”移到Connect组件中。
为什么这里就需要用到高阶组件,而不是像Provider组件一样,仅Connect组件就可以?因为Provider仅提供数据,逻辑简单:1. 用this.props.store设置context,2. 不改变this.props.chidren。但Connect组件不行,这里需要从context取出store,且不能简单将store传递后代组件(后代组件不能从自己的props取store,这会污染后代组件),而是需要从store中取出state和dispatch,根据state和dispatch映射出对应数据(即mapStateToProps和mapDispatchToProps)作为props传给后代组件。怎样映射的方式需要另外方式传进来,即高阶函数connect。
是怎么做到store.dispatch(action)一个动作后,被connect包裹的后代组件自行渲染?这是因为Connect组件中调用store.subscribe()方法订阅了() => this._updateProps(),this._updateProps中调用了this.setState,来触发更新。
export const connect = (mapStateToProps, mapDispatchToProps) => (WrappedComponent) => {class Connect extends PureComponent {static contextTypes = {store: PropTypes.object,};constructor(props) {super(props);this.state = {allProps: {}};}componentWillMount() {const { store } = this.context;this._updateProps();store.subscribe(() => this._updateProps());}_updateProps() {const { store } = this.context;const stateProps = mapStateToProps(store.getState(), this.props);const dispatchProps = mapDispatchToProps(store.dispatch, this.props);this.setState({allProps: {...stateProps, // 从store的state取状态数据...dispatchProps, // 需要更新store的state的方法,从这里传入dispatch...this.props // 透传给WrappedComponent}});}render() {return (<WrappedComponent { ...this.state.allProps } />)}}return Connect;
}
3.3 Demo
Provider作为根组件用来包含App组件,并将store传给Provider。
import React from 'react';
import { createRoot } from 'react-dom/client';import { Provider } from './react-redux';
import createStore, { reducer } from './redux';
import App from './App';const store = createStore(reducer);
const domNode = document.getElementById('root');
const root = createRoot(domNode);
root.render(<Provider store={store}><App /></Provider>
);
用connect包裹Content组件,通过mapStateToProps和mapDispatchToProps从store中取出相应的数据。
import React from 'react';
import { createRoot } from 'react-dom/client';
import { connect } from './react-redux';class Content extends PureComponent {// 2. 通过this.context取出contextrender() {return (<divlang={this.props.lang}onClick={() => this.props.onChangeLang('zh_CN')}>123</div>)}
}
// state是从store取出来的,props是传给高阶组件Connect的props
const mapStateToProps = (state, props) => {return {lang: state.lang};
}
// dispatch是从store取出来的,props是传给高阶组件Connect的props
const mapDispatchToProps = (dispatch, props) => {return {onChangeLang: (lang) => {dispatch({ type: 'UPDATE_LANG', lang })}}
}
export default connect(mapStateToProps, mapDispatchToProps)(Content);
4. 总结
react-redux是redux在React的一次成功应用,第2小节redux实现比较简单,主要是对第1小节的理解,理解第1小节后,理解第3小节就容易多了。
看最新react官方文档,已将PureComponent和Componet列为过时的API,估计后续不推荐再使用class组件,因此react-redux实现方式可能会发生变更,或新出现一种redux和函数组件结合的方式,或基于useContext、useReducer、createContext形成一种新的实现方式。
注:以上,如有不合理之处,还请帮忙指出,大家一起交流学习~
相关文章:
【Redux】自己动手实现redux和react-redux
1. React提供context的作用 在class组件的世界里,如果后代组件共享某些状态,比如主题色、语言键,则需要将这些状态提升到根组件,以props的方式从根组件向后代组件一层一层传递,这样则需要在每层写props.someData&#…...
代码随想录算法训练营day6|242.有效的字母异位词、349.两个数组的交集、202.快乐数
哈希表理论基础 建议:大家要了解哈希表的内部实现原理,哈希函数,哈希碰撞,以及常见哈希表的区别,数组,set 和map。 什么时候想到用哈希法,当我们遇到了要快速判断一个元素是否出现集合里的时…...
2024.1.4每日一题
LeetCode每日一题 2397.被列覆盖的最多行数 2397. 被列覆盖的最多行数 - 力扣(LeetCode) 题目描述 给你一个下标从 0 开始、大小为 m x n 的二进制矩阵 matrix ;另给你一个整数 numSelect,表示你必须从 matrix 中选择的 不同 …...
C++协程和线程的区别?详细介绍一下C++协程
C协程和线程的区别 线程是操作系统级别的资源,由操作系统负责调度和切换,每个线程都有自己的堆栈和执行上下文。线程之间的切换需要保存和恢复线程的执行上下文,这个过程有一定的开销。协程是用户态的轻量级线程,协程的调度完全由…...
数字信号处理期末复习——计算大题(一)
个人名片: 🦁作者简介:一名喜欢分享和记录学习的在校大学生 🐯个人主页:妄北y 🐧个人QQ:2061314755 🐻个人邮箱:2061314755qq.com 🦉个人WeChat:V…...
matlab数值计算函数--ode45
当难以求得微分方程的解析解时,可以求其数值解,Matlab中求微分方程数值解的函数有七个:ode45,ode23,ode113,ode15s,ode23s,ode23t,ode23tb。本文讲解ode45,其…...
Vue3地图选点组件
Vue3地图选点组件 <template><div style"width: 100%; height: 500px"><div class"search-container"><el-autocompletev-model"suggestionKeyWord"class"search-container__input"clearable:fetch-suggestion…...
JS之注册事件兼容性解决方案
本章介绍注册事件兼容性的解决方案 废话不多说,直接上代码: function addEventListener(element, eventName, fn) {//判断当前浏览器是否支持 addEventListener 方法if (element.addEventListener) {element.addEventListener(eventName, fn); // 第三个…...
C#中使用as关键字将对象转换为指定类型
目录 一、定义 二、示例 三、生成 使用as关键字可以将对象转换为指定类型,与is关键字不同,is关键字用于检查对象是否与给定类型兼容,如果兼容则返回true,如果不兼容则返回false。而as关键字会直接进行类型转换,如果…...
【Spring实战】21 Spring Data REST 常用功能详细介绍
文章目录 1. 资源导出(Resource Exporting)2. 查询方法(Query Methods)3. 分页和排序(Pagination and Sorting)4. 关联关系(Associations)5. 事件(Events)6. …...
05-微服务-RabbitMQ-概述
RabbitMQ 1.初识MQ 1.1.同步和异步通讯 微服务间通讯有同步和异步两种方式: 同步通讯:就像打电话,需要实时响应。 异步通讯:就像发邮件,不需要马上回复。 两种方式各有优劣,打电话可以立即得到响应&am…...
jmeter参数化的三种方式
1.用户定义变量 使用变量: ${变量名} 这个变量是全局变量,也就是在下面子节点中都可以使用; 使用场景:两个账号分别有不同的权限,A经办,B审核。等。。。 2.CSV数据文件设置 3.函数...
java基础之Java8新特性-Lambda
目录 什么是Lambda表达式 Lambda表达式规范 基本语法 参数列表 函数体 注意事项 如何定义函数接口 1.保证接口中只能有一个抽象方法 2.使用FunctionalInterface注解标记该接口为函数接口 使用Lambda调用无参函数 使用Lambda调用有参函数 使用Lambda的精简写法 使用…...
入门使用mybatis-plus
第一步:pom文件带入依赖 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.5.1</version> </dependency> 第二步:创建实体对象 TableName(&…...
ubuntu安装和配置ssh教程
一、前言 ssh服务类似于windows的远程桌面服务,可以实现对linux系统的远程管理,ssh默认端口为22端口。后面博主进行操作以ubuntu2020操作系统为例进行操作。 二、安装ssh服务 Ubuntu 2020 默认不安装 SSH 服务。它只安装了 SSH 客户端,可以用于连接到其他计算机上的 SSH 服…...
每天刷两道题——第六天
1.1字母异位词分组 给你一个字符串数组,将字母异位词组合在一起。可以按任意顺序返回结果列表。字母异位词指的是由重新排列源单词的所有字母得到的一个新单词。 输入: strs [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”] 输出: [[“bat”],[“nat”,…...
时间序列平稳性相关检验方法
理解平稳性 一般来说,平稳时间序列是指随着时间的推移具有相当稳定的统计特性的时间序列,特别是在均值和方差方面。平稳性可能是一个比较模糊的概念,将序列排除为不平稳可能比说序列是平稳的更容易。通常不平稳序列有几个特征: …...
<leetcode修炼>双指针训练-移动零
题目: 给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。 请注意 ,必须在不复制数组的情况下原地对数组进行操作。 方法1: 快慢指针 快指针负责遍历数组中所有元素,慢指针负责记录不为0的…...
Python初探:从零开始的编程奇妙之旅
一、Python是什么 Python是一门多用途的高级编程语言,以其简洁、易读的语法而脱颖而出。在深度学习领域,Python扮演着至关重要的角色。其丰富的科学计算库(如NumPy、Pandas、Matplotlib)和强大的深度学习框架(如Tenso…...
算法与数据结构之链表<一>(Java)
目录 1、链表的定义 2、链表的特点 3、为何要使用链表 4、数组与链表的区别 5、链表的增删查 5.1、在头部插入链表 5.2、在中间插入链表 5.3、删除头节点 5.4、删除中间节点 5.5、查询某个值 6、链表的应用 6.1 如何设计一个LRU缓存算法? 6.2 约瑟夫问题 1、链表的定…...
目标检测COCO数据集与评价体系mAP
1.mAP 2.IoU IoU也就是交并比,也称为 Jaccard 指数,用于计算真实边界框与预测边界框之间的重叠程度。它是真值框与预测边界框的交集和并集之间的比值。Ground Truth边界框是测试集中手工标记的边界框,用于指定对象图像的位置以及预测的边界框…...
2024最全面且有知识深度的web3开发工具、web3学习项目资源平台
在Web3技术迅速发展的时代,寻找一个综合且深入的Web3开发工具和学习项目资源平台变得至关重要。今天,我将向大家介绍一个非常有价值的网站,它就是https://web3x.world 。 Web3X是一个全面而深入的Web3开发者社区,为开发者们提供了…...
Golang - defer关键字 深入剖析
defer关键字 defer和go一样都是Go语言提供的关键字。defer用于资源的释放,会在函数返回之前进行调用。一般采用如下模式: f,err : os.Open(filename) if err ! nil {panic(err) } defer f.Close()如果有多个defer表达式,调用顺序类似于栈&a…...
如何在Spring Boot中使用@Scheduled写定时任务判断数据量是否过大,过大则进行分表操作,多张表使用临时视图查询
当数据量过大,在定时任务中执行分表操作 1、复制表结构及数据 在xml中编写复制表结构及数据(newTableName为新表名、originalTableName为原始表名) 只复制表结构: CREATE TABLE ${newTableName} AS SELECT * FROM ${originalTa…...
使用jieba库进行中文分词和去除停用词
jieba.lcut jieba.lcut()和jieba.lcut_for_search()是jieba库中的两个分词函数,它们的功能和参数略有不同。 jieba.lcut()方法接受三个参数:需要分词的字符串,是否使用全模式(默认为False)以及是否使用HMM模型&…...
C语言之分支与循环【附6个练习】
文章目录 前言一、什么是语句?1.1 表达式语句1.2 函数调用语句1.3 控制语句1.4 复合语句1.5 空语句 二、分支语句(选择结构)2.1 if语句2.1.1 悬空else2.1.2 练习(1. 判断一个数是否为奇数 2. 输出1-100之间的奇数) 2.2…...
使用通用MCU实现无人机飞行任务的快速二次开发
使用通用MCU实现无人机飞行任务的快速二次开发 ---TIDronePilot外部控制offboard模式介绍 无名小哥 2024年1月1日 传统飞控二次开发方法和主要存在的问题简介 通过对前面几讲中《零基础竞赛无人机积木式编程指南》系列开发教程的学习可知,在以往TI电赛真题的学习…...
什么是Selinux
官网地址:What is SELinux? 欢迎关注留言,我是收集整理小能手,工具翻译,仅供参考,笔芯笔芯. 概述 安全增强型 Linux (SELinux) 是Linux 系统的安全架构,允许管理员更好地控制谁可以访问系统。它最初是由美…...
计算机网络知识点
1. URI 和 URL 统一资源定位符(Uniform Resource Locator,缩写:URL),是对资源的引用和访问该资源的方法。俗称网址,就是浏览器地址栏里面的内容。 URL 语法为:protocol://userInfohost:port/p…...
Qt 连接 Mysql
Linux下安装mysql及qt连接_liunx下安装mysql及qt链接-CSDN博客...
学习网站开发/网站搭建关键词排名
● centos 7编译高版本内核: linux-3.13.10 过程相对简单: 1. 安装依赖 yum install ncurses-devel 2. 将linux内核解压缩到/usr.src/ 3. make 4. make modules_install 5. make install 6. 重起 转载于:https://www.cnblogs.com/volcanorao/p/6478092.h…...
建设与管理局网站/sem竞价代运营
1.常用事件 onload:当页面中的所有的标签,文档,图片等资源加载完毕后会触发onload事件 onclick:鼠标单击事件 ondblclick:鼠标双击事件 onmousedown:鼠标按下事件 …...
邯郸邯山区网站建设/链接制作软件
我们可以在windows上建立ASM实例。oracle给我们提供了一个很贴心的工具,来实现在windows上安装asm,这个工具就是asmtool。该工具可以在安装介质的asmtool目录中找到,也可以在安装数据库软件后,在$ORACLE_HOME/bin下找到。下面&…...
山东省建设监理网站/近期时事新闻
最近写了一个VUE的web app项目,需要实现某个部位吸顶的效果。即,页面往上滑动,刚好到达该部位时,该部分,固定在顶部显示。 image.png 1、监听滚动事件 利用VUE写一个在控制台打印当前的scrollTop, 首先,…...
站酷网官网/新闻发稿
多半的时间,都花在了格式转换上……或者在修转换bug上面。 const char* char* char[] stringQString类型常用: QString::fromStdString(string str); QString::Number(int str); QString::toString(QString str); 实际遇到的稀奇古怪的需要 1.string转…...
凡科网站怎么做链接头像logo/云南今日头条新闻
都是用到了求分别在两个数组中的两个数使之和满足某个条件 方法是先排序,然后用两个指针 i,j 分别指向两个数组的头和尾,再调整移动指针。 zoj 3631 #include <cstdio> #include <cstring> #include <cstdlib> #include …...