Redux 源码实现及分析
本文将跟随 Crystal 分析并实现 Redux 的几个核心API,包括且不限于
createStore
、getState
、dispatch
、subscribe
最终效果可完成对 couter 的基本增减、异步增减功能的支持
Redux 回顾 Part
在开始手撕 Redux 实现之前我们需要先回顾一下 Redux 的几个核心API
createStore
我们使用 Redux 第一步,需要创建 Store
容器,就需要使用到 createStore
这个 API
它接收三个参数,第一个是 reducer
函数,第二个是默认状态(可选),第三个是enhancer
用来增强redux功能
返回一个状态容器,包含 getState
、 dispatch
、 subscribe
等方法
getState
我们可以通过该方法获取 store
中的状态
dispatch
我们通过 dispatch
方法来触发 Action
subscribe
当 store
中的状态发生改变,会执行 subscribe
方法
Redux 实现 及 分析 Part
通过前面部分的分析我们知道核心API 都是经过 createStore
进行返回的,那么我们第一件事情就是来实现一下这个 createStore
createStore
/* 接收三个参数, 返回**核心API */
function createStore(reducer, preloadedState, enhancer) {
return {
getState: () => {},
dispatch: () => {},
subscribe: () => {},
}
}
我们的壳子已经搭建完成,可以添加具体细节了~
getState
getState
方法的作用就是返回 store 中的状态,并且我们 createStore
第二个参数就是初始状态。故事线出来了~
function createStore(reducer, preloadedState, enhancer) {
// 状态值
var currentState = preloadedState;
// 获取状态
function getState () {
return currentState
}
return {
getState
}
}
subscribe
状态的订阅者可能有多个,也可能会被多次调用,所以需要一个数组进行存储
function createStore(reducer, preloadedState) {
// 订阅者函数
var currentListeners = []
// 订阅状态
function subscribe (listener) {
currentListeners.push(listener)
}
return {
subscribe
}
}
dispatch
dispatch
的作用是 触发 action
; 目的是为了传递给 reducer
, 让 reducer
对 action
进行处理后返回新的 state
给 store
完成状态更新;
状态更新需要通过 subscribe
来通知订阅者(📢 订阅者可以有多个)
function createStore(reducer, preloadedState, enhancer) {
...
// 触发 action
function dispatch (action) {
currentState = reducer(currentState, action)
// 调用订阅者 通知订阅者状态发生了改变
for (var i = 0; i < currentListeners.length; i++) {
// 获取订阅者
var listener = currentListeners[i];
// 调用订阅者
listener();
}
}
return {
dispatch
}
}
接下来我们加点小细节,来点小约束~
reducer 必须是一个函数
function createStore(reducer, preloadedState) {
if (typeof reducer !== 'function') throw new Error("reducer must be a function")
...
}
action 必须是一个含有 type 属性的对象
// 判断参数是否是对象类型
// 判断对象的当前原型对象是否和顶层原型对象相同
function isPlainObject (obj) {
// 排除基本类型 和 null
if (typeof obj !== 'object' || obj === null) return false;
// 区分数组和对象 原型对象对比的方式
var proto = obj;
while (Object.getPrototypeOf(proto) != null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto;
}
function createStore(reducer, preloadedState) {
...
// 触发 action
function dispatch (action) {
// 判断action是否是一个对象
if (!isPlainObject(action)) throw new Error('action must be a object');
// 判断action中的type属性是否存在
if (typeof action.type === 'undefined') throw new Error('There must be a type attribute in the action object');
...
}
}
enhancer
通过
enhancer
可以让createStore
的调用者对返回的 store 对象进行功能上的增强
规定 enhancer
必须是一个函数;
在调用 enhancer
函数的时候,将 createStore
方法本身、preloadedState
和reducer
都传递出去
function createStore(reducer, preloadedState, enhancer) {
// 判断是否传递了 enhancer 函数 && enhancer 是一个函数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer must be a function')
}
// 这里的返回想想我们写中间件的时候昂~
return enhancer(createStore)(reducer, preloadedState);
}
}
中途小憩
目前我们已经基本功能完成了,可以来一波测试验证,把demo代码发来瞅瞅哈
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>redux 实现部分测试 demo</title>
</head>
<body>
<button id="increment">+1</button>
<span id="box">0</span>
<button id="decrement">-1</button>
<script src="../redux/index.js"></script>
<script>
function reducer(state, action) {
switch (action.type) {
case "increment":
return state + 1;
case "decrement":
return state - 1;
default:
return state;
}
}
function enhancer (createStore) {
return function (reducer, preloadedState) {
var store = createStore(reducer, preloadedState);
var dispatch = store.dispatch;
// 对 dispatch 的增强:模拟实现 redux-thunk
function _dispatch (action) {
if (typeof action === 'function') {
return action(dispatch)
}
dispatch(action);
}
return {
...store,
dispatch: _dispatch
}
}
}
const store = createStore(reducer, 0, enhancer);
// 获取最新状态
store.subscribe(function () {
document.getElementById("box").innerHTML = store.getState();
});
/* action 及 action 的触发 */
document.getElementById("increment").onclick = function () {
store.dispatch({ type: "increment" });
};
document.getElementById("decrement").onclick = function () {
store.dispatch({ type: "decrement" });
};
</script>
</body>
</html>
小憩ending,我们继续
applyMiddleware
applyMiddleware
的作用是通过增强 dispatch
方法,让多个中间件函数进行组合以达到我们在触发 action
的时候让多个中间件按照顺序进行执行
里面涉及到的 next
参数需要注意一下:指向的是下一个中间件函数(最里层的函数);最后一个中间件的next 指向 dispatch
方法
function applyMiddleware (...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
// 创建 store: 拿到 store 后给中间件传递参数
var store = createStore(reducer, preloadedState);
// 简化版的 store
var middlewareAPI = {
getState: store.getState,
dispatch: store.dispatch
}
// 返回 中间件 函数的第二层函数
var chain = middlewares.map(middleware => middleware(middlewareAPI));
// 最里层函数
var dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
function compose () {
var funcs = [...arguments];
// 想要拿到 next 函数,需要倒序执行获取
return function (dispatch) {
for (var i = funcs.length - 1; i >= 0; i--) {
// 执行第二层函数,返回最里层的函数
dispatch = funcs[i](dispatch);
}
// 返回的最里层的函数
return dispatch;
}
}
bindActionCreators
bindActionCreators
的作用是将 actionCreator
函数转换成能够触发 action
的函数,它返回一个对象
function bindActionCreators (actionCreators, dispatch) {
var boundActionCreators = {};
for (var key in actionCreators) {
(function (key) {
boundActionCreators[key] = function () {
dispatch(actionCreators[key]())
}
})(key)
}
return boundActionCreators;
}
combineReducers
combineReducers
的作用是将小的 reducer
通过该方法组合成一个大的 reducer
返回值,返回一个 reducer
函数
function combineReducers (reducers) {
// 1. 检查reducer类型 它必须是函数
var reducerKeys = Object.keys(reducers);
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] !== 'function') throw new Error('reducer must be a function');
}
// 2. 调用一个一个的小的reducer 将每一个小的reducer中返回的状态存储在一个新的大的对象中
return function (state, action) {
var nextState = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
var reducer = reducers[key];
var previousStateForKey = state[key];
nextState[key] = reducer(previousStateForKey, action)
}
return nextState;
}
}
完整代码 Link
完整代码详见 Github
// 判断参数是否是对象类型
// 判断对象的当前原型对象是否和顶层原型对象相同
function isPlainObject (obj) {
// 排除基本类型 和 null
if (typeof obj !== 'object' || obj === null) return false;
// 区分数组和对象 原型对象对比的方式
var proto = obj;
while (Object.getPrototypeOf(proto) != null) {
proto = Object.getPrototypeOf(proto)
}
return Object.getPrototypeOf(obj) === proto;
}
function createStore (reducer, preloadedState, enhancer) {
// reducer 类型判断
if (typeof reducer !== 'function') throw new Error("reducer must be a function")
// 判断是否传递了 enhancer 函数 && enhancer 是一个函数
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('enhancer must be a function')
}
return enhancer(createStore)(reducer, preloadedState);
}
// 状态值
var currentState = preloadedState;
// 订阅者函数
var currentListeners = []
// 获取状态
function getState () {
return currentState
}
// 触发 action
function dispatch (action) {
// 判断action是否是一个对象
if (!isPlainObject(action)) throw new Error('action must be a object');
// 判断action中的type属性是否存在
if (typeof action.type === 'undefined') throw new Error('action对象中必须有type属性');
currentState = reducer(currentState, action)
// 调用订阅者 通知订阅者状态发生了改变
for (var i = 0; i < currentListeners.length; i++) {
// 获取订阅者
var listener = currentListeners[i];
// 调用订阅者
listener();
}
}
// 订阅状态
function subscribe (listener) {
currentListeners.push(listener)
}
return {
getState,
dispatch,
subscribe
}
}
function applyMiddleware (...middlewares) {
return function (createStore) {
return function (reducer, preloadedState) {
// 创建 store: 拿到 store 后给中间件传递参数
var store = createStore(reducer, preloadedState);
// 简化版的 store
var middlewareAPI = {
getState: store.getState,
dispatch: store.dispatch
}
// 返回 中间件 函数的第二层函数
var chain = middlewares.map(middleware => middleware(middlewareAPI));
// 最里层函数
var dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
}
}
}
}
function compose () {
var funcs = [...arguments];
// 想要拿到 next 函数,需要倒序执行获取
return function (dispatch) {
for (var i = funcs.length - 1; i >= 0; i--) {
// 执行第二层函数,返回最里层的函数
dispatch = funcs[i](dispatch);
}
// 返回的最里层的函数
return dispatch;
}
}
function bindActionCreators (actionCreators, dispatch) {
var boundActionCreators = {};
for (var key in actionCreators) {
(function (key) {
boundActionCreators[key] = function () {
dispatch(actionCreators[key]())
}
})(key)
}
return boundActionCreators;
}
function combineReducers (reducers) {
// 1. 检查reducer类型 它必须是函数
var reducerKeys = Object.keys(reducers);
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
if (typeof reducers[key] !== 'function') throw new Error('reducer must be a function');
}
// 2. 调用一个一个的小的reducer 将每一个小的reducer中返回的状态存储在一个新的大的对象中
return function (state, action) {
var nextState = {};
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i];
var reducer = reducers[key];
var previousStateForKey = state[key];
nextState[key] = reducer(previousStateForKey, action)
}
return nextState;
}
}