手写Redux核心原理

一.createStore核心逻辑实现

createStore的三个参数

  • reducer:根据actions类型对store中数据状态进行更改,返回一个新的状态
  • preloadedState:预存储store的状态(初始状态)
  • enhancer:对store的功能进行增强

createStore的三个返回值

  • getState:获取状态
  • dispatch:触发action
  • subscribe:订阅数据状态的变化

具体实现

function createStore (reducer,preloadedState){// currentState:store对象中存储的状态,需要用闭包对其进行缓存var currentState = preloadedState// 用来存放订阅者函数var currentListeners = []// 获取状态并闭包缓存currentStatefunction getState () {return currentState;}// 触发actionfunction dispatch (action) { currentState = reducer(currentState,action)// 循环数组for (var i= 0 ; i < currentListeners.length ;i++ ){// 获取订阅者const listen = currentListeners[i]// 调用订阅者listen()}}// 订阅数据状态的变化function subscribe (listener) {currentListeners.push(listener)}return { getState,dispatch,subscribe }
}

具体使用

<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=<device-width>, initial-scale=1.0"><title>Document</title>
</head>
<body><button id="increment">+1</button><span id="count">0</span><button id="decrement">-1</button><script src="./myRedux.js"></script><script>// 创建reduce函数function reducer (state,action) {switch (action.type) {case 'increment':return state+1case 'decrement':return state-1default:return state}}// 创建storevar store = createStore(reducer,0)document.getElementById('increment').onclick = function(){// 触发action 让状态+1store.dispatch({type:'increment'})}document.getElementById('decrement').onclick = function(){// 触发action 让状态-1store.dispatch({type:'decrement'})}// 订阅store.subscribe(function(){document.getElementById('count').innerHTML = store.getState()})console.log(store.getState())</script>
</body>
</html>

二. 参数类型约束

约束reduce必须是一个函数

if (typeof reducer !== 'function') throw new Error('reducer必须是函数')

约束action必须是一个对象

if (!isPlainObject(action)) throw new Error('action不是一个对象')

怎么判断一个数据是否是对象

  1. 排除基本数据类型和null
  2. 区分数组和对象 采用原型对象对比的方式
  3. 如果当前传入的对象的原型链全等于proto的最顶层原型链 那么就肯定是对象
function isPlainObject (obj) {// 排除基本数据类型和nullif (typeof obj !== 'object' || obj === null) return false;// 区分数组和对象 采用原型对象对比的方式var proto = obj;while (Object.getPrototypeOf(proto) !== null){// 获取到proto的最顶层原型链proto = Object.getPrototypeOf(proto)}// 如果当前传入的对象的原型链全等于proto的最顶层原型链 那么就肯定是对象return Object.getPrototypeOf(obj) === proto
}

约束action必须有type属性

if(typeof action.type === 'undefined') throw new Error('action对象中必须有type属性')

三.enhancer功能增强

它属于createStore的第三个参数,它本质上就是对dispatch当增强。(比如异步处理、中间件)

enhancer特性

  • 目的是让用rudux库的人能可以对返回的store对象进行功能增强操作
  • 是createStore的第三个参数
  • 可传可不传,如果传enhancer约束必须是一个函数它的形参接收createStore
  • enhancer内部返回一个函数接收reducer和preloadedState,在函数内部定义了一个增强型函数_dispatch,最后赋值给dispatch
  • 最后同样返回{ getState,dispatch,subscribe }

在createStore中对其约束

function createStore (reducer,preloadedState,enhancer){// 判读enhancer参数是否存在,判断它是不是函数if (typeof enhancer !== "undefined") {if (typeof enhancer !== 'function') {throw new Error('enhancer必须是函数')}return enhancer(createStore)(reducer,preloadedState)}...

增强dispatch代码实现异步处理

function enhancer (createStore) {return function(reducer,preloadedState) {var store = createStore(reducer,preloadedState)var dispatch = store.dispatch;function _dispatch (action) {if(typeof action === 'function') {// 这里就可以处理异步了return action(dispatch)}dispatch(action)}return {...store,dispatch:_dispatch}}
}
// 创建store
var store = createStore(reducer,0,enhancer)
document.getElementById('increment').onclick = function(){// 触发action 让状态+1store.dispatch(function(dispatch){setTimeout(function(){store.dispatch({type:'increment'})},1000)})
}

四.实现applyMiddleware

就是一个enhancer当实现函数,作为中间件

中间件原理

在视图中触发action时会先被中间件接收到,中间件一个一个执行完成之后才会触发原始的dispath进入reducer. 中间件就是允许我们在action被发出之后在reducer`接收之前让我们去做一些事情

applyMiddleware实现

  • applyMiddleware其实就是一个内置的enhancer函数
  • 它的参数是接收若干中间件
  • 它的返回值和enhancer一样,只不过dispatch是第一层中间件返回函数
// applyMiddleware其实就是一个内置的enhancer函数,它用来对store增强
function applyMiddleware (...middlewares) {return function (createStore) {return function(reducer,preloadedState) {// 创建store 给中间件传参var store = createStore(reducer,preloadedState)// 阉割版的 storevar middlewareAPI = {getState : store.getState,dispatch : store.dispatch}// 调用中间件的第一层函数 传递阉割版的store对象 把第二层函数缓存到chain中var chain = middlewares.map(middleware => middleware(middlewareAPI))// 为什么要穿dispatch呢? 因为中间件最后一个next就是dispatchvar dispatch = compose(...chain)(store.dispatch)// 最后返回:所以在第一个调用dispatch时 其实执行的是logger最里层返回的函数->然后在函数内部去调用thunk->最后调用原始的dispath进入reduxreturn {...store,dispatch}}}
}

实现compose给第二层函数传参

function compose () {var funcs = [...arguments]return function(dispatch) {for (let index = funcs.length-1; index >= 0; index--) {// 倒着调用第二层函数,得到它的返回值 当做参数传给上一层dispatch = funcs[index](dispatch);}// 这个dispatch就是第一个中间件函数return dispatch}
}

logger

function logger (store){// logger的下一个中间件函数是thunk 所以next就是thunk最里层的函数return function (next){return function (action){console.log('logger')next(action)}}
}

thunk

function thunk (store){// thunk是最后一个中间件函数 所以next就是reducereturn function (next){return function (action){console.log('thunk')next(action)// => 其实就是 dispatch({ type: "increment" });}}
}

使用方式

// 创建store
var store = createStore(reducer,0,applyMiddleware(logger,thunk))
store.dispatch({type:'increment'}) // 输出 logger thunk

五.bindActionCreators函数

主要用于父组件给子组件传参,子组件直接调用函数即可。而不需要知道Redux的使用方式。

bindActionCreators特性

  • actionCreators函数转换成可以触发actions的函数
  • 第一个参数接收一个函数对象,第二个参数接收的是dispatch

实现原理

// 将actionCreators函数转换成可以触发actions的函数
function bindActionCreators (actionCreators,dispatch) {var boundActionCreators = {};for (const key in actionCreators) {(function(key){// 沙盒模式 缓存keyboundActionCreators[key] = function(){ dispatch(actionCreators[key]())}})(key)}return boundActionCreators;
}

使用方式

function increment () {return {type:'increment'}
}
function decrement () {return {type:'decrement'}
}
var actions = bindActionCreators({increment,decrement},store.dispatch)actions.increment() //+1
actions.decrement() //-1

五.实现combineReducers

可以理解为是对reducer的一个增强,将一个个小的reducer汇总成一个大的reducer,同时将所有小的state也汇总在一起。

特性

  • combineReducers 将一个个小的reduce转换成一个大的reduce
  • 返回值是一个reducer函数 reducer函数它有两个参数一个是state一个是action
  • 第一件事是循环第一个参数对象 拿到reducer 看是不是函数类型
  • 第二件事依然循环它 并调用小的reducer 并把对应的状态赋值给count 最后把它赋值给一个大的对象 并返回

实现

function combineReducers (reducers) {var reduceKeys = Object.keys(reducers)// 第一件事是循环第一个参数对象 拿到reduce 看是不是函数类型 for (let index = 0; index < reduceKeys.length; index++) {const key = reduceKeys[index];if (typeof reducers[key] !== 'function') throw new Error('reducer必须是一个函数')}// 返回的是一个增强型当reducer,在dispatch里调用return function (state,action) {var nextState = {}// 第二件事依然循环它 并调用小的reduce 并把对应的状态赋值给count 最后把它赋值给一个大的对象 并返回for (let index = 0; index < reduceKeys.length; index++) {const key = reduceKeys[index];nextState[key] = reducers[key](state[key],action)}console.log(nextState)return nextState}
}

使用

var rootReducer = combineReducers({counter:reducer})
var store = createStore(rootReducer,{counter:100},applyMiddleware(logger,thunk))

手写Redux核心原理相关推荐

  1. babel原理_手写webpack核心原理,再也不怕面试官问我webpack原理

    手写webpack核心原理 一.核心打包原理 1.1 打包的主要流程如下 1.2 具体细节 二.基本准备工作 三.获取模块内容 四.分析模块 五.收集依赖 六.ES6转成ES5(AST) 七.递归获取 ...

  2. 手写Vuex核心原理,再也不怕面试官问我Vuex原理

    手写Vuex核心原理 文章目录 手写Vuex核心原理 一.核心原理 二.基本准备工作 三.剖析Vuex本质 四.分析Vue.use 五.完善install方法 六.实现Vuex的state 七.实现g ...

  3. 手写Hooks核心原理

    手写实现useState useEffect useReducer 一. useState 钩子函数的实现原理 // import { useState } from 'react' import R ...

  4. 定时器和promise_手写Promise核心原理,再也不怕面试官问我Promise原理

    整体流程的介绍 整体流程的介绍 1. 定义整体结构 2. 实现Promise构造函数 3. 实现then方法 3.实现catch方法 4. 实现Promise.resolve 5.实现Promise. ...

  5. 05. 手写Spring核心框架

    目录 05 手写Spring核心框架 Pt1 手写IoC/DI Pt1.1 流程设计 Pt1.2 基础配置 application.properties pom.xml web.xml Pt1.3 注 ...

  6. 一起手写Vue3核心模块源码,掌握阅读源码的正确方法

    最近和一个猎头聊天,说到现在前端供需脱节的境况.一方面用人方招不到想要的中高级前端,另一方面市场上有大量初级前端薪资要不上价. 特别是用 Vue 框架的,因为好上手,所以很多人将 Vue 作为入门框架 ...

  7. 【springboot】手写SpringBoot核心流程

    通过手写模拟实现一个Spring Boot,就能以非常简单的方式就能知道Spring Boot大概是如何工作的. 工程与依赖 建一个工程,两个Module: spring-boot模块:表示sprin ...

  8. 手写识别底层原理_LinkedList底层原理和手写单链表

    2.1 单链表技能点 · 认识单链表 o 特点 数据元素的存储对应的是不连续的存储空间,每个存储结点对应一个需要存储的数据元素. 每个结点是由数据域和指针域组成. 元素之间的逻辑关系通过存储节点之间的 ...

  9. 手写promise核心功能

    手写promise 源码地址:https://github.com/CONOR007/Handwriting-Promise.git 手写promise首先需要掌握promise的基本用法,然后根据用 ...

最新文章

  1. 图像滤波常用算法实现及原理解析
  2. java使用url和tns两种方式连接数据库执行存储过程
  3. PHP5操作MySQL数据库
  4. python有哪些作用-python的函数有什么作用
  5. TensorBoard使用
  6. codeigniter钩子的使用
  7. 解决eclipse中Findbugs检查不生效的问题
  8. C语言中的回调函数(Callback Function)
  9. Android.mk的一些FAQ
  10. Github Projects 项目管理 怎么用
  11. innodb_file_per_table参数
  12. 《物联网通信》知识提纲-第1章 概述
  13. 几句代码让Ios系统内核崩溃
  14. 码农、程序员、工程师这三者之间有什么区别?
  15. matlab空间杜宾模型命令,空间计量模型,包括空间滞后模型、空间误差模型、空间杜宾模型的matlab代码...
  16. HDU - 5510 Bazinga
  17. VS将复制过来的文件或文件夹显示到解决方案管理
  18. 10分钟!Mac配置Win主机上的共享打印机
  19. AD9910使用心得-fanfanStudio
  20. 摘要、引言和结论的六项区别

热门文章

  1. [软件工具][windows]OCR指定区域图片自动识别内容重命名软件使用教程
  2. 阿斯利康疫苗研发教授和全球疫苗免疫联盟获得2022年鲜鹤平和赏
  3. 计算机毕业设计JAVA多特蒙德周边商城系统mybatis+源码+调试部署+系统+数据库+lw
  4. 如何入门单片机电子技术、学习方法、建议
  5. Andrej Karpathy的炼丹技巧
  6. 中国三氟乙醇行业研究与投资预测报告(2022版)
  7. 我的世界给钻石最多的服务器,我的世界:MC快速挖取钻石的方法,老玩家告诉你第几层的钻石最多...
  8. 打破区块链不可能三角!2 华人专家论文将登 NSDI 2019 计算机顶会
  9. 嵌入式linux学习笔记-- linux 开机总时间记录的一种方案
  10. 索爱X9挂脖式蓝牙耳机,佩戴轻盈音质纯正真正享受音乐世界