【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形成一种新的实现方式。

注:以上,如有不合理之处,还请帮忙指出,大家一起交流学习~  

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

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

相关文章

【如何选择Mysql服务器的CPU核数及内存大小】

文章目录 &#x1f50a;博主介绍&#x1f964;本文内容&#x1f4e2;文章总结&#x1f4e5;博主目标 &#x1f50a;博主介绍 &#x1f31f;我是廖志伟&#xff0c;一名Java开发工程师、Java领域优质创作者、CSDN博客专家、51CTO专家博主、阿里云专家博主、清华大学出版社签约作…

提升代码托管,助力大数据学习!Git学习网站等你来挑战!

介绍&#xff1a;Git是一个开源的分布式版本控制系统&#xff0c;可以高效地处理各种规模项目的版本管理。它是Linus Torvalds为了帮助管理Linux内核开发而开发的开放源码版本控制软件。在Git中&#xff0c;你可以掌握工作区、暂存区和版本库等核心概念&#xff0c;并学会使用常…

VSCode远程连接centos

1 下载remote -ssh插件 2 在上方打开命令面板&#xff0c;输入>,再输入ssh&#xff0c;选择设置 Remote-SSH:Settings 那行 3 勾选下面这个选项 4 点击加号旁边的那个齿轮&#xff0c;选择.ssh\config ,配置连接信息&#xff0c;保存好后&#xff0c;刷新。 4 连接centos,然…

Spring Boot 完善订单【五】集成接入支付宝沙箱支付

1.1.什么是沙箱支付 支付宝沙箱支付&#xff08;Alipay Sandbox Payment&#xff09;是支付宝提供的一个模拟支付环境&#xff0c;用于开发和测试支付宝支付功能的开发者工具。在真实的支付宝环境中进行支付开发和测试可能涉及真实资金和真实用户账户&#xff0c;而沙箱环境则提…

[MySQL] 数据库的主从复制和读写分离

一、mysql主从复制和读写分离的相关知识 1.1 什么是读写分离? 读写分离&#xff0c;基本的原理是让主数据库处理事务性增、改、删操作( INSERT、UPDATE、DELETE) &#xff0c;而从数据库处理SELECT查询操作。数据库复制被用来把事务性操作导致的变更同步到集群中的从数据库。…

weblogic中间件安装

1.下载jdk Java Archive Downloads - Java SE 6 下载jdk-6u45-linux-x64.bin 2.配置防火墙和SELINUX Redhat7操作系统配置防火墙&#xff0c;开放应用端口&#xff0c;例如7001&#xff1b; # firewall-cmd --permanent --add-port7001/tcp # firewall-cmd --reload 关闭selinu…

解决SyntaxError: future feature annotations is not defined,可适用其他包

方法&#xff1a;对报错的包进行降级 pip install tikzplotlib0.9.8site-packages后面是使用pip install安装的包&#xff0c;根据这个找到报错的包 想法来源&#xff1a; 环境是python3.6&#xff0c;完全按照作者要求进行环境配置&#xff0c;但仍报错。 我在网上找的解决…

CV必备的15个多尺度模型分享,涵盖特征融合、多尺度预测等4种网络结构

在卷积神经网络中&#xff0c;感受野的大小会影响到模型能够捕捉到的特征的尺度&#xff0c;从而影响模型的性能。因此我们在设计网络时&#xff0c;需要合理地控制感受野的大小。 那么问题来了&#xff1a;怎样才能合理控制&#xff1f; 到目前为止&#xff0c;已有很多学者…

Linux驱动学习—平台总线模型

1、平台总线模型介绍 ①什么是平台总线模型&#xff1f; 平台总线模型也叫platform总线模型。是Linux内核虚拟出来的一条总线&#xff0c;不是真实的导线。 平台总线模型就是把原来的驱动C文件给分成两个C文件&#xff0c;一个是device.c&#xff0c;一个是driver.c 把稳定…

5.1 QThread的两种使用方式

5.1 QThread的两种使用方式 QThread类用于创建和管理线程,它并不是线程本身。通过使用QThread,我们可以在应用程序中实现并发执行的任务,从而提高应用程序的性能和响应能力,能够有效地利用CPU资源,提高程序运行效率。且QThread创建和管理线程的方式是独立于平台的,不管是…

亚马逊促销效果不好怎么办?亚马逊促销规则是什么?-站斧浏览器

亚马逊促销效果不好怎么办&#xff1f; 分析原因&#xff1a;首先需要深入分析促销效果不佳的原因。可能是促销活动的设计不够吸引人&#xff0c;或者是目标受众定位不准确。 调整策略&#xff1a;根据分析结果调整促销策略。例如&#xff0c;优化广告文案、更改推广时段或调…

【C#】知识点实践序列之Lock的锁定代码块

大家好&#xff0c;我是全栈小5&#xff0c;欢迎来到《小5讲堂之知识点实践序列》文章。 2024年第1篇文章&#xff0c;此篇文章是C#知识点实践序列之Lock知识点&#xff0c;博主能力有限&#xff0c;理解水平有限&#xff0c;若有不对之处望指正&#xff01; 本篇验证Lock锁定代…