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

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

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

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

相关文章

控制障碍函数(Control Barrier Function,CBF) 二、示例

二、示例 2.1、系统模型 这里我们举一个CBF作者给出的经典示例 我们定义控制输入 u u u 为蓝色车的推力。 p p p 为蓝色车的位置&#xff0c; v v v 为蓝色车的速度&#xff0c; z z z 为蓝色车与黄色车之间的距离&#xff0c; v 0 v_0 v0​ 为黄色车的速度。我们定义系统的…

功能介绍 | 探秘Goby功能世界:点击URL,即刻畅享快速调起之旅!

​​0x01 前言 ​我们从只会点鼠标的猴子到CtrlC,CtrlV来回切换的工具人&#xff0c;但有时候遇到大量需要复制的url界面&#xff0c;真的希望能有一个可以一键整理、一键扫描URL的功能来拯救&#xff01; 好消息是&#xff0c;Goby从客户端版本2.8.6起&#xff0c;官方引入了…

CAN数据记录仪在新能源车上的应用

随着新能源汽车的快速发展&#xff0c;对车辆安全和性能的要求也越来越高。在新能源车中&#xff0c;液位传感器是必不可少的零部件之一&#xff0c;用于监测电池液位、冷却液位等关键参数。在测试阶段需要工作人员花费大量时间跟车去获取它的CAN数据&#xff0c;从而分析是否有…

web自动化测试从入门到持续集成

在很多刚学习自动化的可能会认为我只需要会运用selenium&#xff0c;我只需要在一个编辑器中实用selenium java编写了一些脚本那么就会自动化了&#xff0c;是真的吗&#xff1f;答案肯定是假的。自动化肯定是需要做到真的完全自动化&#xff0c;那如何实现呢&#xff1f;接着往…

学习Go语言Web框架Gee总结--前缀树路由Router(三)

学习Go语言Web框架Gee总结--前缀树路由Router router/gee/trie.gorouter/gee/router.gorouter/gee/context.gorouter/main.go 学习网站来源&#xff1a;Gee 项目目录结构&#xff1a; router/gee/trie.go 实现动态路由最常用的数据结构&#xff0c;被称为前缀树(Trie树) 关…

MySQL之CRUD,函数与union使用

目录 一.CRUD 1.1.SELECT(查询) 1.2.INSERT&#xff08;新增&#xff09; 1.3.UPDATE(修改) 1.4.DELETE&#xff08;删除&#xff09; 二.函数 2.1.常见函数 2.1.1.字符函数 2.1.2.数字函数 2.1.3.日期函数 2.2.流程控制函数 2.3.聚合函数 三.union与union all 四…

物流实时数仓:数仓搭建(DWS)一

系列文章目录 物流实时数仓&#xff1a;采集通道搭建 物流实时数仓&#xff1a;数仓搭建 物流实时数仓&#xff1a;数仓搭建&#xff08;DIM&#xff09; 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&#xff09;一 物流实时数仓&#xff1a;数仓搭建&#xff08;DWD&am…

JMeter CSV 参数文件的使用方法

.在 JMeter 测试中&#xff0c;参数化是非常重要的&#xff0c;参数化允许我们模拟真实世界中的各种情况。本文我们将探讨如何在 JMeter 中使用 CSV 参数文件。 创建 CSV 文件 首先&#xff0c;我们需要创建一个逗号分隔的值&#xff08;CSV&#xff09;文件&#xff0c;其中…

创建企业邮箱帐户指南:常见问题与解决方法分享

专业的电子邮件地址可以帮助客户识别商务人士&#xff0c;并了解公司给他们发邮件的目的。如果你从事管理、信息技术或人力资源工作&#xff0c;你可能会负责为一个企业建立一个企业邮箱帐户。了解如何为新员工和现有员工设置电子邮件帐户可以帮助您简化公司内部的沟通。 在这篇…

python小工具之弱密码检测工具

一、引用的python模块 Crypto&#xff1a; Python中一个强大的加密模块&#xff0c;提供了许多常见的加密算法和工具。它建立在pyc.ypodome或pyc.ypto等底层加密库之上&#xff0c;为Python程序员提供了简单易用的API&#xff0c;使其可以轻松地实现各种加密功能。 commands…

牵绳遛狗你我他文明家园每一天,助力共建文明社区,基于YOLOv5开发构建公共场景下未牵绳遛狗检测识别系统

遛狗是每天要打卡的事情&#xff0c;狗狗生性活泼爱动&#xff0c;一天不遛就浑身难受&#xff0c;遛狗最重要的就是要拴绳了&#xff0c;牵紧文明绳是养犬人的必修课。外出遛狗时&#xff0c;主人手上的牵引绳更多是狗狗生命健康的一道重要屏障。每天的社区生活中&#xff0c;…

秋招复习之数据结构

目录 前言 1 数据结构分类 2 基本数据类型 3 数字编码 4 字符编码 总结 前言 秋招复习之数据结构&#xff0c;数据结构分类、基本数据类型、字符编码、数字编码等基础知识。 1 数据结构分类 数据结构分为逻辑结构和物理结构。 逻辑结构分为线性数据结构&#xff08;数组链表…

在word文档中自制代码段样式

附&#xff1a; 在word中插入高亮代码的网站 HighlightCode&#xff1a;https://highlightcode.com/ CodeInWord&#xff1a;http://codeinword.com/ 一、新建代码段样式 点击下拉按钮&#xff0c;选择创建样式&#xff0c;命名为代码段&#xff0c;然后点击修改 点击格式&a…

CSS transition详解

文章目录 属性transition-propertytransition-durationtransition-timing-functiontransition-delaytransition 简写属性 方法Element&#xff1a;transitionrun 事件Element&#xff1a;transitionstart 事件Element&#xff1a;transitionend 事件Element&#xff1a;transit…

数据表示和进制转换

输入计算机的数字、字符、符号等信息必须转换成0、1组合的数据形式才能被计算机接收、存储并进行运算。能够进行算术运算并且得到明确的数值的数据概念的信息叫数值数据&#xff0c;其余的信息成为非数值数据。 权&#xff1a;每位数的数值。 基数&#xff1a;指该进位制中允…

【C初阶——指针2】鹏哥C语言系列文章,基本语法知识全面讲解——指针(2)

崩刃的剑&#xff0c;依旧致命&#xff0c;锈蚀的盾&#xff0c;屹立如初&#xff08;王者荣耀李信&#xff09; 本文由睡觉待开机原创&#xff0c;转载请注明出处。 本内容在csdn网站首发 欢迎各位点赞—评论—收藏 如果存在不足之处请评论留言&#xff0c;共同进步&#xff0…

Elasticsearch:结合 ELSER 和 BM25 文本查询的相关搜索

Elastic Learned Spare EncodeR (ELSER) 允许你执行语义搜索以获得更相关的搜索结果。 然而&#xff0c;有时&#xff0c;将语义搜索结果与常规关键字搜索结果相结合以获得最佳结果会更有用。 问题是&#xff0c;如何结合文本和语义搜索结果&#xff1f; 首先&#xff0c;让我…

揭秘Linux软链接:如何轻松创建、删除和修改

揭秘Linux软链接&#xff1a;如何轻松创建、删除和修改 一、简介二、创建软链接三、删除软链接四、修改软链接五、Linux软链接的高级用法六、总结 一、简介 在Linux中&#xff0c;软链接&#xff08;Symbolic Link&#xff09;是一种特殊的文件类型&#xff0c;它是一个指向另…

初识对抗生成网络(GAN)

在研究语义通信的时候&#xff0c;发现解码端很多都是用GAN或基于GAN来完成的。带着对GAN的好奇&#xff0c;对GAN进行了一个初步学习。这篇文章介绍一下和GAN相关的一些常识吧~   本文围绕以下几个内容展开&#xff1a;     1.什么是GAN&#xff1f;     2.为什么要…

03-微服务-Ribbon负载均衡

Ribbon负载均衡 1.1.负载均衡原理 SpringCloud底层其实是利用了一个名为Ribbon的组件&#xff0c;来实现负载均衡功能的。 那么我们发出的请求明明是http://userservice/user/1&#xff0c;怎么变成了http://localhost:8081的呢&#xff1f; 1.2.源码跟踪 为什么我们只输入…