【redux】手撸一套redux和react-redux,深入学习(一)

news/2024/7/15 18:09:18 标签: react.js, 学习, 前端

手撸一套redux和react-redux

  • Redux


Redux

TIPS: 请在对redux有一定应用后,再往下阅读。

在当前的前端框架中,状态管理是核心问题之一。项目中有多个组件,都需要用到某个状态,需要进行状态的传递,也就是组件的通信。
比如A中这样一个状态

const state = {
	count: 0
}

B需要用到这个状态,那么就需要B去监听这个状态,然后 A告诉B自己发布(更新)了状态,最后B再去获取这个状态。这样就完成了一个状态传递。
在这个过程中,我们分别用一个属性三个方法来定义这个过程。其中

  • 一个属性,是指当前状态,我们用currentState来表示
  • 三个方法,是指上面提到的对状态的监听发布获取
    • 监听,用subscribe方法来表示
    • 发布,用dispatch方法来表示
    • 获取,用getState来表示
    • 此外,既然有监听,那么,就会有取消监听的需求,我们可以在监听方法中来定义取消监听的方法。

好了,这三个方法,刚好就是redux的核心方法。

现在有个新的问题,假定当前状态中count0,即

const currentState = {
	count: 0
}

然后经过A的操作,count变成了1,如果currentState最新的值没有被保存下来,那么B去获取currentState时,得到的结果仍然是0,这怎么办呢?思考一下,在js中,什么情况下,一个值会被保存下来,你大概想到了立即执行函数和闭包。没错,redux在这里确实是用闭包来保存值的。现在我们就来实现这一步,我们用一个仓库来存储这些方法和状态,用代码表示出来就是

const createStore = () => {
	let currentState = {};
	function getState() {}
	function dispatch() {}
	function subscribe() {}

	return { getState, dispatch, subscribe };
}

我们调用createStore后,会得到一个对象,这个对象中包含了获取状态、发布状态和监听状态的对象,我们用store来表示这个对象。

const store = createStore();

currentState可以保存当前最新状态,我们只需要在获取状态时,返回这个最新状态即可

function getState() {
	return currentState;
}

用户在操作时会更新状态,然后发布给其他监听的用户,说状态变了,你们快更新一下数据。用户会有不同的操作,产生不同的数据,我们用action对象来表示用户的操作,用type属性来表示用户的操作类型。就像这样

action = {
	type: 操作类型(string)
}

在用户发布(dispatch)时,我们给他传入用户的操作,根据不同的操作,来更新不同的状态,比如增加数值或者减少数值的操作,那么dispatch应该是这样

function dispatch(action) {
	switch(action.type) {
		case "增加":
			currentState = {
				count: currentState.count + 1
			};
		case "减少":
			currentState = {
				count: currentState.count - 1;
			};
		default:
			currentState = currentState
	}
}

当用户执行增加的操作时,状态的值就加1,用户执行减少的操作时,状态就减1,其他情况,数值仍然保持初始值。但是这样写过于臃肿,且过于死板,我们优化一下。用一个reducer函数来剥离改变状态的过程,并返回变更后的新状态

const initState = {
	count: 0
}
const reducer = (state = initState, action) => {
	switch(action.type) {
		case 'increament':
			return {
				...state,
				count: state.count + 1
			}
		case 'decreament':
			return {
				...state,
				count: state.count - 1
			}
		default:
			return initState;
	}
}

此时dispatch函数就可以改写为

function dispatch(action) {
	// 每次的初始状态,都是之前的currentState
	// 把返回的新的状态重新赋值给currentState
	// currentState保存在闭包中
	currentState = reducer(currentState, action);
}

reducer需要通过参数传入createStore函数

const createStore = (reducer) => {
	let currentState = {};
	function getState() {
		return currentState;
	}
	function dispatch(action) {
		currentState = reducer(currentState, action);
	}
	function subscribe() {}

	return { getState, dispatch, subscribe };
}

现在我们来写个完整的示例来验证一下(为了方便展示,这里就不单独分开多个文件来写了,把createStore, reducer, store统一放在store.js文件里)

// src/redux/store.js

const initState = {
    count: 0
};

const reducer = (state = initState, action) => {
    switch (action.type) {
        case 'increament':
            return {
                ...state,
                count: state.count + 1
            };
        case 'decreament':
            return {
                ...state,
                count: state.count - 1
            };
        default:
            return initState;
    }
}

const createStore = (reducer) => {
    let currentState = {};
    
    function getState() {
        return currentState;
    }

    function dispatch(action) {
        currentState = reducer(currentState, action);
    }

    function subscribe() { }
    
    return { getState, dispatch, subscribe };
}

export const store = createStore(reducer);


// src/index.js

import { createRoot } from 'react-dom/client';
import App from './App';
import { store } from './redux/store';

// 先获取一次状态
console.log('store1: ', store.getState());
// 执行一次增加的操作
store.dispatch({ type: 'increament' });
// 再获取一次当前状态
console.log('store2: ', store.getState());

const root = createRoot(document.getElementById('root'));
root.render(
    <App />
)

看下打印结果
在这里插入图片描述
奇怪了,一个是空值,一个是NaN,为啥呢?我们回头看,整个过程发生了什么。首先我们通过执行createStore生成了store对象,然后调用了getState获取当前状态,此时currentState是空值。然后我们执行了一次增加的操作,此时在reducer中,state{ },增加操作就是undefined + 1,结果就是NaN。问题就出在currentState初始是个空值,然而我们并不可能在源码中给currentState去设置一个非空值,因为我们不可能在每次状态变更后去修改源码。注意看,initState是我们给reducer设置的初始状态,这个是开发者自己去设置的,在reducer函数中,如果action.type既不是增加,也不是减少,currentState就会被赋值为initState,这样就可以让currentState成为非空值了(这也是reducer中不写default就会报错的原因)。所以,我们需要在执行createStore去创建store对象前,就先执行一次dispatch函数,让currentState初始化。因此我们在createStore中加入一句代码

const createStore = (reducer) => {
    let currentState = {};
    
    function getState() {
        return currentState;
    }

    function dispatch(action) {
        currentState = reducer(currentState, action);
    }

    function subscribe() { }
    
    // 加入该代码
	// 先执行一次状态修改,让curretState获取到初始化的状态
	// 这里的type值一定不能跟实际项目中的type值重复
	// 在源码中,type值还加入了随机数
    dispatch({ type: '@@REDUX__INIT' });
    
    return { getState, dispatch, subscribe };
}

此时再执行一遍上面的例子,就可以得到我们想要的结果
在这里插入图片描述
到这里,我们实现了状态的初始化、保存最新状态、获取状态以及状态的更新。


下一篇继续实现redux和react-redux


http://www.niftyadmin.cn/n/163628.html

相关文章

[架构之路-137]-《软考-系统架构设计师》-软件工程-7-详解UML视图中各种实体之间的关系

前言&#xff1a;在软件设计中&#xff0c;一个设计的基本原则就是&#xff1a;高内聚、低耦合。这句话的本质就是要降低模块与模块&#xff0c;实体与实体之间的关系&#xff0c;特别是相互之间的关系&#xff0c;尽可能把实体对外关系的数量和复杂度降低到最低。实体之间的关…

.vscode 文件夹是什么,里面有什么?

文章目录背景extensions.jsonsetting.jsonlaunch.jsontask.json代码片段背景 项目根目录中经常会出现一个.vscode文件夹&#xff0c;它是干什么的&#xff1f; 众所周知&#xff0c;vscode 的配置分两类&#xff0c;一是全局的用户配置&#xff0c;二是当前工作区配置。vscode…

代码随想录动态规划 || 583 72

力扣题目链接给定两个单词 word1 和 word2&#xff0c;找到使得 word1 和 word2 相同所需的最小步数&#xff0c;每步可以删除任意一个字符串中的一个字符。思路可以转化为求两个字符串的最小子序列长度&#xff0c;然后两个字符串长度和减去最小子序列长度的两倍即可也可以直接…

ORBSLAM3 --- 地图融合(惯性模式下)LoopClosing::MergeLocal2函数解析

目录 1.函数作用 2.函数流程 3.代码详细注释 4.代码解析 4.1 中止全局BA&#xff0c;结束局部建图线程&#xff0c;获得地图指针 4.2 利用前面计算的坐标系变换位姿&#xff0c;把整个当前地图&#xff08;关键帧及地图点&#xff09;变换到融合帧所在地图 4.3 如果当前地…

WEB安全 HTML基础

1.简单的HTML页面架构 <!DOCTYPE html> <html><head><meta charset"UTF-8"><title></title></head><body></body> </html>charset 编码 gbk gbk2312 utf-8 保存内容的快捷方式 crtls 2.HTML常见标签…

Python3实现AI版贪吃蛇

导语利用Python简单地实现AI版的贪吃蛇。。。just for fun...没有用深度学习。。。算法是由一个叫Hawstein的人在好多好多年以前提出&#xff0c;感觉很有趣&#xff0c;就花了点时间复现了一下他的想法。。。至于效果。。。看脸。。。真的只是觉得他的想法很有趣&#xff0c;仅…

小白学Pytorch系列--Torch API (12)

小白学Pytorch系列–Torch API (12) Utilities compiled_with_cxx11_abi result_type result_type promote_types use_deterministic_algorithms are_deterministic_algorithms_enabled is_deterministic_algorithms_warn_only_enabled set_deterministic_debug_mode …

【Docker】容器卷常规安装

文章目录容器数据卷(持久化)是什么&#xff1f;特点宿主vs容器之间映射添加容器卷查看数据卷是否挂载成功容器和宿主机之间数据共享读写规则映射添加 :rw、ro读写(默认) docker run -it --privilegedtrue -v /宿主机绝对路径目录:/容器内目录:rw容器实例内部被限制&#xff0c…