【Redux】自己动手实现redux和react-redux

news2025/5/24 23:28:41
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返回值设置context
    getChildContext() {
        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取出context
    render() {
        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取出context
    render() {
        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) => {
    // 初始化state
    let state = initialState;
    // 保存监听函数
    const listeners = [];
    // 返回store当前保存的state
    const 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和children
    static propTypes = {
        store: PropTypes.object,
        children: PropTypes.any,
    }
    // 1. 定义静态变量childContextTypes声明context中状态对应的类型
    static childContextTypes = {
        store: PropTypes.object,
    };
    // 2. 通过getChildContext返回值设置context
    getChildContext() {
        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取出context
    render() {
        return (
            <div
              lang={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.coloradmin.cn/o/1355876.html

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

相关文章

【模拟电路】非接触测电笔绘制、电子琴NE555

一、非接触测电笔原理图绘制 二、非接触测电笔PCB绘制 三、电子琴NE555制作过程及元器件选型 四、电子琴NE555原理图绘制 五、电子琴NE555PCB绘制 六、电子琴NE555最终效果 点击跳转 一、非接触测电笔原理图绘制 材料 电池 2032 电池底座 适用电池:CR2032 白色 C964762 NPN三…

2024年怎么提升学历?三大成人学历提升方式一次看懂!

2024年想提升到大专、本科学历该怎么做&#xff1f; 成人提升学历有自考、成考、开放大学三种途径。 不同的途径有不同的报名条件和拿证方式。 考生可以根据自己的实际情况选择适合自己的方式。 下面我们一起来看下&#xff0c;2024年怎么选适合自己的学历提升方式。 成人学…

数据结构【树篇】(二)

数据结构【树篇】(二&#xff09; 文章目录 数据结构【树篇】(二&#xff09;前言为什么突然想学算法了&#xff1f;为什么选择码蹄集作为刷题软件&#xff1f; 目录树(一)、树的存储(二)、树和森林的遍历——并查集(三)、并查集的优化 结语 前言 为什么突然想学算法了&#xf…

构建异步高并发服务器:Netty与Spring Boot的完美结合

前言 「作者主页」&#xff1a;雪碧有白泡泡 「个人网站」&#xff1a;雪碧的个人网站 ChatGPT体验地址 文章目录 前言IONetty1. 引入依赖2. 服务端4. 客户端结果 总结引导类-Bootstarp和ServerBootstrap连接-NioSocketChannel事件组-EventLoopGroup和NioEventLoopGroup 送书…

StarRocks 在小红书自助分析场景的应用与实践

作者&#xff1a;小红书 OLAP 研发负责人 王成 近两年 StarRocks 一直是小红书 OLAP 引擎体系里非常重要的部分&#xff0c;过去一年&#xff0c;小红书的 StarRocks 使用规模呈现出翻倍的增长速度&#xff0c;目前整体规模已经达到 30 个集群&#xff0c;CPU 规模已经达到了 3…

一文讲透怎样用SPSS做二项Logistic回归分析?结果如何解释?

推荐采用《SPSS统计分析入门与应用精解&#xff08;视频教学版&#xff09;》 杨维忠、张甜 清华大学出版社“7.4 二元Logistic回归分析” 的解答。 本节内容选自《SPSS统计分析入门与应用精解&#xff08;视频教学版&#xff09;》 杨维忠、张甜 清华大学出版社“7.4 二元Logi…

新手必看!STM32通用定时器-输入捕获!

一、用途与工作原理 用途&#xff1a;用于测量信号的参数&#xff0c;比如周期和频率。   工作原理&#xff1a;在输入捕获模式下&#xff0c;当捕获单元捕捉到外部信号的有效边沿(上升沿/下降 沿/双边沿)时&#xff0c;将计数器的当前值锁存到捕获/比较寄存器TIMx_CCR&#…

如何制定知识竞赛活动的竞赛规则

知识竞赛既然是知识的竞赛&#xff0c;就应该制定相应的细则或规则。竞赛规则一般应包括以下内容&#xff1a;竞赛编组、答题形式、每场题量、竞赛记分、违例处罚、观众答题、名次奖励及对参赛人员的要求。 1、竞赛编组 竞赛编组一般由抽签决定。在参赛人员较多、场次较多的…

JS数据类型转换注意事项【建议收藏】

ToPrimitive(obj, Number) 和 ToPrimitive(obj, String) 调用顺序不同在于: 区别在于调用 toString 方法和 valueOf 方法的顺序&#xff0c;区分这一点就行了。 ToPrimitive(obj, Number) > Number({}) 如果 obj 是基本类型&#xff0c;直接返回否则&#xff0c;调用 valueO…

简易机器学习笔记(九)LeNet实例 - 在眼疾识别数据集iChallenge-PM上的应用

前言 上一节大概讲了一下LeNet的内容&#xff0c;这一章就直接来用&#xff0c;实际上用一下LeNet来进行训练和分类试试。 调用的数据集&#xff1a; https://aistudio.baidu.com/datasetdetail/19065 说明&#xff1a; 如今近视已经成为困扰人们健康的一项全球性负担&…

【观察】Aginode安捷诺:坚守“长期主义”,服务中国数字经济

毫无疑问&#xff0c;随着整个社会加速数字化转型&#xff0c;尤其是5G、人工智能、大数据等技术兴起&#xff0c;以及智慧医疗、智慧金融、智能制造等应用加速落地&#xff0c;算力网络在经济社会发展中扮演了愈来愈重要的角色&#xff0c;成为支撑数字经济蓬勃发展的“新引擎…

2023-RunwayML-Gen-2 AI视频生成功能发展历程

RunwayML是一个人工智能工具&#xff0c;它为设计师、艺术家和创意人士提供了一种简单的方式来探索和应用机器学习技术。 RunwayML官方网页地址&#xff1a;Runway - Advancing creativity with artificial intelligence. RunwayML专区RunwayML-喜好儿aigcRunwayML 是一种先进…

大创项目推荐 深度学习卫星遥感图像检测与识别 -opencv python 目标检测

文章目录 0 前言1 课题背景2 实现效果3 Yolov5算法4 数据处理和训练5 最后 0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; **深度学习卫星遥感图像检测与识别 ** 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐…

如何在win7同样支持Webview2 在 WPF 中使用本地 Webview2 ,如何不依赖系统 Runtime

项目运行环境&#xff1a; .Net Framework 4.5.2 Windows 7 x64 Service Pack 1 WebView2 Microsoft.WebView2.FixedVersionRuntime.120.0.2210.91.x64 考虑到很多老项目&#xff0c;本项目使用的是.Net Framework 4.5.2&#xff0c;.Net 更高版本的其实也是可以支持的。 …

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

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

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

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

什么是负载均衡?什么情况下又会用到负载均衡

什么是负载均衡 在大型的网络应用中&#xff0c;使用多台服务器提供同一个服务是常有的事。平均分配每台服务器上的压力、将压力分散的方法就叫做负载均衡。 [利用 DNS来实现服务器流量的负载均衡&#xff0c;原理是“给网站访问者随机分配不同ip”] 什么情况下会用到负载均…

苹果Vision Pro将于1月27日上市!

在无数期待中&#xff0c;苹果全新产品Vision Pro头显终于定下上市日期。 彭博社记者马克古曼&#xff08;Mark Gurman&#xff09;于近日在X&#xff08;前推特&#xff09;平台爆料了这一信息&#xff0c;预计苹果Vision Pro头显将于2024年1月27日率先在美国上市。 在过去看…

实战SRC | api接口未授权 + 越权漏洞

本文由掌控安全学院 - zxl2605 投稿 一次在fofa上通过学习的fofa语句进行查询&#xff0c;无意中查询到了一个网址 其登录界面如下&#xff1a; 使用浏览器的F12打开开发者工具&#xff0c;查看JS寻找接口&#xff1a; 从JS代码中查询到一处接口如下&#xff1a; 发现是以p…

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

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