手写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 = []
// 获取状态并闭包缓存currentState
function getState () {
return currentState;
}
// 触发action
function 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+1
case 'decrement':
return state-1
default:
return state
}
}
// 创建store
var store = createStore(reducer,0)
document.getElementById('increment').onclick = function(){
// 触发action 让状态+1
store.dispatch({type:'increment'})
}
document.getElementById('decrement').onclick = function(){
// 触发action 让状态-1
store.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不是一个对象')
怎么判断一个数据是否是对象
- 排除基本数据类型和null
- 区分数组和对象 采用原型对象对比的方式
- 如果当前传入的对象的原型链全等于proto的最顶层原型链 那么就肯定是对象
function isPlainObject (obj) {
// 排除基本数据类型和null
if (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 让状态+1
store.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)
// 阉割版的 store
var middlewareAPI = {
getState : store.getState,
dispatch : store.dispatch
}
// 调用中间件的第一层函数 传递阉割版的store对象 把第二层函数缓存到chain中
var chain = middlewares.map(middleware => middleware(middlewareAPI))
// 为什么要穿dispatch呢? 因为中间件最后一个next就是dispatch
var dispatch = compose(...chain)(store.dispatch)
// 最后返回:所以在第一个调用dispatch时 其实执行的是logger最里层返回的函数->然后在函数内部去调用thunk->最后调用原始的dispath进入redux
return {
...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就是reduce
return 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){
// 沙盒模式 缓存key
boundActionCreators[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))