|-src
|-utils
|-actionType.js //
|-formatProdErrorMessage.js // 不写
|-isPlainObject.js // 判断是否为纯对象
|-kindOf.js // 用来判断类型
|-symbol-observable.js // 不写
|-warning.js // 负责控制台错误日志的输出,不写
|-applyMiddleware.js // 我们使用它应用中间件时,它返回一个 enhancer 函数作为 createStore 的第三个参数
|-bindActionCreator.js // 用得不多。把 action creators 转成拥有同名 keys 的对象,使用 dispatch 把每个 action creator 包装起来,这样可以直接调用它们。
|-combineReducers.js // 将 reducers 组合起来
|-compose.js // 函数组合,工具函数来的
|-createStore.js // 创建 store
|-index.js // 入口文件
github 仓库:https://github.com/Konsoue/redux-learn
手写 redux
- 我们从入口 index.js 开始写起。
- 我们使用 redux 的时候肯定是要创建 store,那么就来看看 createStore 做了什么事情
- 在讲函数式编程的时候,我们有了解过函数组合 compose,这里的 compose.js 就是这样
- 在使用 redux 写多个 reducer 的时候,我们会最后用 combindReducer 把这些 reducer 合在一起
- 应用中间件的时候,我们会使用 applyMiddleware,所以看看这个函数做了什么操作
index.js
import createStore from './createStore';
import compose from './compose';
import combineReducers from './combineReducers';
import applyMiddleware from './applyMiddleware';
import bindActionCreators from './bindActionCreators';
export {
createStore,
compose,
combineReducers,
applyMiddleware,
bindActionCreators
}
createStore.js
import isPlainObject from "./utils/isPlainObject"
import kindOf from "./utils/kindOf"
/**
*
* @param {Function} reducer
* @param {*} preloadedState 初始的 state
* @param {Function} enhancer 增强器,applyMiddleware 的返回值。
* @returns {Store}
*/
function createStore(reducer, preloadedState, enhancer) {
// 如果有中间件,将 createStore 交给中间件创建,并传入 reducer, preloadedState
if (kindOf(enhancer) !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState);
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = [] // 当前的 listeners ,即所有监听函数
let nextListeners = currentListeners // 在 state 数据改变前,订阅的监听函数,先放在 nextListeners,以保证 currrentListeners 数据修改前的不变性。
let isDispatching = false // reducers 正在执行的标志
/**
* 派发 action,这是改变 state 的唯一途径
* @param {Object} action 一个纯对象,代表了将要改变什么。
* @returns {Object} 为了方便,返回用户派发的 action
*/
const dispatch = (action) => {
if (!isPlainObject(action)) {
throw new Error(`Actions 必须是纯对象。而当前 Actions 的类型是${kindOf(action)}。你可以加中间件, 来处理特殊类型`)
}
if (!action.type) throw new Error('Actions 必须有一个 type 属性')
if (isDispatching) throw new Error('还不可以给 reducers 派发 action')
try {
// Reducers 开始处理 action
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 处理完 action, 触发监听事件
// 先把 nextListeners 赋值给 currentListeners
// 因为 subscribe 添加监听函数时,会将监听函数先放到 nextListeners
// 以此来保证 currentListeners 触发前,自身的不变性
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
/**
* 将 currentListeners 浅拷贝后,赋给 nextListeners
*/
const ensureCanMutateNextListeners = () => {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/**
* 订阅:添加一个监听函数
* @param {Function} listener 监听函数
* @return {Function} 返回一个函数用于移除,这个监听函数
*/
const subscribe = (listener) => {
if (isDispatching) throw new Error('reducer 正在执行, 你不能调用 store.subscribe()')
let isSubscribed = true // 标志该 listener 是否已经订阅
ensureCanMutateNextListeners()
nextListeners.push(listener)
// 返回一个取消订阅的函数
return function unsubscribe() {
if (!isSubscribed) return
if (isDispatching) throw new Error('reducer 正在执行, 你不能调用 unsubscribe()')
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null // 这里赋值为 null,释放内存。不会影响数据改变后,调用监听函数的
}
}
/**
* 读取被 store 管理的 state 树
* @returns {any} 在你的项目中,目前的 state 树
*/
const getState = () => {
if (isDispatching) throw new Error('reducer 正在执行, 你不能调用 store.getState()')
return currentState
}
/**
* 替换 reducer
* @param {Function} nextReducer
*/
const replaceReducer = (nextReducer) => {
currentReducer = nextReducer
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
export default createStore
compose.js
const compose = (...fns) => {
if (fns.length === 0) return (args) => args
if (fns.length === 1) return fns[0]
fns.reduce((pre, cur) => (...args) => pre(cur(...args)))
}
export default compose
combindReducer.js
import kindOf from "./utils/kindOf"
/**
* 将外部传进来的 reducers 合成一个总的 reducers
* @param {Object} reducers
* @returns {Function}
*/
const combineReducers = (reducers) => {
const reducerKeys = Object.keys(reducers)
const resultReducer = (state = {}, action) => {
const nextState = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
const reducer = reducers[key]
const previousStateForKey = state[key] // 拿到某个 reducer 处理前对应的 state
const nextStateForKey = reducer(previousStateForKey, action) // 拿到处理后的 state
if (kindOf(nextStateForKey) === 'undefined') {
throw new Error(`${key} reducer 处理结果不能是 undefined`)
}
nextState[key] = nextStateForKey
}
return nextState
}
return resultReducer
}
export default combineReducers
从源码 reducers[key]
和 state[key]
可以看出,我们使用 redux 创建 reducer 和 state 的时候,key 需要一一对应。
import { combindReducer, createStore } from 'redux'
const initalState = {
home: {},
login: {},
user: {}
}
const homeReducer = (state, action) => {}
const userReducer = (state, action) => {}
const loginReducer = (state, action) => {}
// reducers 的 key 要和 initalState 对应
const reducers = {
home: homeReducer,
user: userReducer,
login: loginReducer
}
const resultReducer = combineReducers(reducers)
const store = createStore(reducers, initalState)
applyMiddleware.js
import compose from './compose'
/**
* 我们使用它应用中间件,使得中间件可以调用 redux 的 store 的方法
*
* @param {...Function} middlewares
* @returns {Function} 返回的函数,用于 createStore 的第三个参数
*/
const applyMiddleware = (...middlewares) => {
const enhancer = (createStore) => {
const newCreateStore = (reducers, preloadState) => {
const store = createStore(reducers, preloadState)
// chain = [ (next) => {}, (next) => {}, ...]
// 其中的 next 方法是我们下面将要传入的 store.dispatch
const chain = middlewares.map(middleware => middleware(store))
// 一系列中间件对 dispatch 的包裹,形成一个新的 dispatch。
// 所以外界调用 store.dispatch 就是调用 newDispatch
const newDispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch: newDispatch
}
}
return newCreateStore
}
return enhancer
}
export default applyMiddleware
utils
isPlainObject.js
判断对象是否为纯对象(简单对象)。
纯对象:通过{}
或 new Object()
创建的都是纯对象。
有的认为
Object.create(null)
创建的对象也是纯对象。
/**
* @param {any} obj
* @returns {boolean} ture 表示 obj 是纯对象
*/
const isPlainObject = (obj) => {
if (typeof obj !== 'object' || obj === null) return false;
let proto = obj;
while (Object.getPrototypeOf(proto) !== null) {
proto = Object.getPrototypeOf(proto);
}
return Object.getPrototypeOf(obj) === proto
}
export default isPlainObject
为什么 redux 不直接判断 Object.prototype === Object.getPrototypeOf(obj)
呢?
答:因为 redux 考虑了页面嵌入子页面的情况,而不同页面之间的内置对象都是独立创建出来的。
比如:Parent 页面,嵌入 Child 页面。Parent 的 Object 构造函数 !== Child 的 Object 构造函数
。
如果直接使用题目的判断方式,那么如果在 Child 页面 let obj = new Object()
。
在 Parent 页面的 redux 判断时,obj 的原型是 Child 的 Object 的原型
,不等于 Parent 的 Object 的原型。
kindOf.js
redux 生产模式使用 typeof
,开发模式自行封装了 miniKindOf 判断函数。
我们直接使用生产模式就好了。
const kindOf = (val) => typeof val
export default kindOf;
开发模式的方法,如下,有兴趣就看。
// Inlined / shortened version of `kindOf` from https://github.com/jonschlinkert/kind-of
function miniKindOf(val) {
if (val === void 0) return 'undefined'
if (val === null) return 'null'
const type = typeof val
switch (type) {
case 'boolean':
case 'string':
case 'number':
case 'symbol':
case 'function': {
return type
}
default:
break
}
if (Array.isArray(val)) return 'array'
if (isDate(val)) return 'date'
if (isError(val)) return 'error'
const constructorName = ctorName(val)
switch (constructorName) {
case 'Symbol':
case 'Promise':
case 'WeakMap':
case 'WeakSet':
case 'Map':
case 'Set':
return constructorName
default:
break
}
// other
return type.slice(8, -1).toLowerCase().replace(/\s/g, '')
}
function ctorName(val) {
return typeof val.constructor === 'function' ? val.constructor.name : null
}
function isError(val) {
return (
val instanceof Error ||
(typeof val.message === 'string' &&
val.constructor &&
typeof val.constructor.stackTraceLimit === 'number')
)
}
function isDate(val) {
if (val instanceof Date) return true
return (
typeof val.toDateString === 'function' &&
typeof val.getDate === 'function' &&
typeof val.setDate === 'function'
)
}
测试用例
import { createStore, combineReducers, applyMiddleware } from '../src'
import { logMiddleware, reduxThunk } from './middleware'
const initalState = {
home: {},
user: {
name: 'jack'
},
count: 0
}
const homeReducer = (state, action) => {
return state
}
const userReducer = (state, action) => {
switch (action.type) {
case 'changeName':
return {
...state,
name: action.data
}
default:
return state
}
}
const countReducer = (state, action) => {
switch (action.type) {
case 'increment':
return state + 1
case 'decrement':
return state - 1
default:
return state
}
}
// reducers 的 key 要和 initalState 对应
const reducers = {
home: homeReducer,
user: userReducer,
count: countReducer
}
// 将 reducer 组合起来
const resultReducer = combineReducers(reducers)
// 应用中间件
const enhancer = applyMiddleware(logMiddleware, reduxThunk);
const store = createStore(resultReducer, initalState, enhancer);
// 订阅 store,当 store 的数据有任何变化,就会触发
store.subscribe(() => console.log(store.getState()))
store.dispatch({ type: 'increment' })
store.dispatch({ type: 'increment' })
const asyncAction = (dispatch, state) => {
// 异步事件执行后,再 dispatch
setTimeout(() => {
dispatch({ type: 'changeName', data: 'konsoue' })
}, 2000)
}
store.dispatch(asyncAction);
const logMiddleware = (store) => (next) => (action) => {
console.log('logMiddleware');
return next(action);
}
const reduxThunk = (store) => (next) => (action) => {
if (typeof action === 'function') {
return action(store.dispatch, store.getState);
}
return next(action);
}
export {
logMiddleware,
reduxThunk
}