前言
大家好,我是寄松,无论是在工作中还是在日常面试中,redux就像是我们一个最熟悉的陌生人,我们很多人只知道react的使用方式它内部究竟干了什么,或者说我们有些使用方式还没搞明白。希望我能 用最通俗的话,讲清楚最难的知识点 ,那今天我就带着源码来细细讲解一下redux原理吧?
什么是 redux?
简单来说,redux是一个可预测状态管理容器。react中组件间的通信方式有很多,例如通过props、ref、eventBus、createContext解决组件的通信方式。但在组件众多的项目中,我们难免会遇到这么一种情况:很多组件都要共享某个数据,比如说登陆,项目中很多组件都要知道当前用户是否登陆、当前的登陆信息,我们把信息存在哪里都不太合适,这时候redux就派上用场了。redux就好像一个容器,我们可以用它本身提供的 Api 把我们的数据存放在里面,然后每个组件都可以用redux和我们约定的方式去改变数据、获取数据,它还能帮我们记录改变数据的时间点(让我们更好地定位项目出现问题),让我们项目的数据管理井然有序。
什么是react-redux?
千万没弄混了,这可和redux不是一样的东西,它是react的一个绑定库,配合redux使用的,像我们看见的connect、useSelector, useDispatch就是这个库的。而本文整在讲解的是redux。
redux 源码目录结构
图为
redux 4.1.1 版本的目录结构
其实 redux 的目录结构并不复杂,index.js即为项目的入口文件,让我们从这开始慢慢吃透。
index.js文件
可以看到它导出了createStore.js,combineReducers.js,bindActionCreators.js,applyMiddleware.js,compose.js,actionTypes.js,接下来我们一个一个看这六个文件。
actionTypes.js
在看createStore.js之前,我们先看一个最简单的文件
// 封装了一个 ActionTypes 对象,对象跟随机数有关,后面大家就知道它的用途了。
const randomString = () =>
Math.random().toString(36).substring(7).split('').join('.')
const ActionTypes = {
INIT: `@@redux/INIT${randomString()}`,
REPLACE: `@@redux/REPLACE${randomString()}`,
PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${randomString()}`,
}
export default ActionTypes
createStore.js
createStore.js 文件导出了dispatch、subscribe、getState、replaceReducer、[$$observale],相信大家对store.dispatch,store.subscribe,store.getState都不陌生,那store.replaceReducer、store.[$$observale]又是什么呢?让我们一一揭晓。
/*
createStore 需要传入三个参数:
reducer是一个纯函数、preloadedState一般是一个对象、enhance是函数类型的中间件
*/
export default function createStore(reducer: Function, preloadedState: any, enhancer: Function) {
// 如果传入的 preloadedState 是 function 而 enhancer 是 underfined,则把第二个参数当作enhancer
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}
// 如果传入了中间件,则用中间件处理createStore,中间件下面会细谈
if (typeof enhancer !== 'undefined') {
return enhancer(createStore)(reducer, preloadedState)
}
// 传入的reducer
let currentReducer = reducer
// 传入的preloadedState
let currentState = preloadedState
// subscribe的时候会用到,放监听的函数,dispatch的时候会触发这些函数
let currentListeners = []
// currentListeners 的副本,dispatch实际操作的是它,为 dispatch 提供容错率
let nextListeners = currentListeners
// 一个标志,isDispatching 为 true 的时候说明 store 正处于 dispatch 调用中
let isDispatching = false
// 返回所有 redux 的数据,我们经常会使用 store.getState()
function getState() {
return currentState
}
// 创建 currentListeners 的副本 nextListenere
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 我们经常使用 store.subscribe(funcion...),dispatch后会触发这个function
function subscribe(listener) {
// dispatch 正在调用时不能使用 store.subscribe()
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing.'
)
}
// 创建 nextListeners,将需要监听的 listener 函数放入
ensureCanMutateNextListeners()
nextListeners.push(listener)
/*
subscribe 返回一个 unsubscribe 函数提供我们解绑监听使用,
我们经常使用这样做: var unsubscribe = store.subsribe(Function)
不再监听这个函数的时候就 unsubscribe() 解绑,以免造成额外的内存消耗。
*/
return function unsubscribe() {
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
function dispatch(action) {
try {
isDispatching = true
// 当 dispatch被调用,将 action 传入 currentReducer 更新 currentState
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 将之前加入监听的函数全部执行一遍
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
// 替换原本你一开始传入的 reducer,一般不会用到
function replaceReducer(nextReducer) {
currentReducer = nextReducer
/*
还记得之前的actionTypes.js文件吗?ActionTypes.REPLACE 是个随机数。
为什么要 dispatch 一次。因为reducer替换了。我们需要重新更新 store 中的 currentState。
我们一般创建的reducer纯函数是这样的:
myNewRedcuer = (state = {...myNewState}, action) => {...}
reducer如果替换了,state 也重传了一个新对象,肯定要重新初始化 state 的状态赋给 currentState。
方法就是 dispatch 一下让 currentState = currentReducer(currentState, action) 执行。
*/
dispatch({ type: ActionTypes.REPLACE })
}
/*
外层也有个dispatch({ type: ActionTypes.INIT }),也是用于一开始创建store初始化currentState的。
我们是这样创建store的:createStore(myReducer, myPreloadedState)
若我们没传第二个参数,那么redux容器初始化的数据就用我们myNeducer传入的第一个参数state
再重复一遍,我们的创建的reducer一般是这样的:
myRedcuer = (state = {...myNewState}, action) => {...},
一开始 dispatch 一次让 store 中的 currentState = myNewState
可以结合上面的 replaceReducer 中的 dispatch({ type: ActionTypes.REPLACE }) 一起理解。
*/
dispatch({ type: ActionTypes.INIT });
// 这个方法用的比较少,比较鸡肋,大概就是监听一个对象,相信大家能看懂。
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
},
}
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
}
}
combineReducers.js
看完了createStore.js,我们来看看combineReducers.js
// 我们经常这么用 combinReducers:
// const store = createStore(combineReducers({reducer1,reducer2,...}), myState, myEnhance)
// 接下来我们来看看它的原理
export default function combineReducers(reducers) {
// 复制一个 reducers 的副本 finalReducers,接下来我们操作的是 finalReducers
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
// 可以把 combination 看作一个全新的reducer
// 举例,当我们使用创建 createStore(combineReducers({reducer1,reducer2,...}) 会发生什么呢
// 还记得 createStore.js 有个 dispatch({ type: ActionTypes.INIT }) 吗?
// 它会初始化数据,执行这个全新的 reducer(即下面的函数combination)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
// 遍历所有的reducer,变量有点多要仔细看
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
// 一开始 state = {},所以每一次的 previousState 都是 underfined
const previousStateForKey = state[key]
// 执行 reducer(previousStateForKey, action)
const nextStateForKey = reducer(previousStateForKey, action)
/*
若我们定义的reducer传了初始值
像 const reducer1 = function(state = myNewState,action),
则 nextState[key] 是 myNewState,若我们没传初始值,则是 underfined
*/
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
/*
nextState 的结构一开始会是怎样的?
比如我们写的是 createStore(combineReducers({reducer1,reducer2,reducer3})
则它的结构如下:
nextState : {
reducer1: {},
reducer2: {},
reducer3: {},
}
*/
return nextState
}
// 明白了吗?不明白多看几遍,若是我们再执行 dispatch(action) 会怎样呢?
// 会再次执行 combineReducers,遍历每个 reducer 传入 action 执行 reducer(previousStateForKey, action)
}
compose.js
在看applyMiddldware.js之前,先看一下这个文件。
/*
你没看错,就是这么简单
举个例子:
compose(funA,funB,funC) 返回:
function new(...args){
return (...args) => (...args) => funA(funB(...args))(funC(...args))
}
其实就一直套娃,在后面用于多个中间件改造 dispatch
*/
export default function compose(...funcs) {
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
applyMiddldware.js
接下来我们看看 redux 是怎么运用中间件的,很多小伙伴可能对中间件有误解,它其实就是一个很简单的函数,我们都可以直接模拟一个。
// 我们一开始的 dispatch(action) 中 action 只能是对象,那我要执行异步操作怎么办呢?
// 这就要用到中间件了,中间件一般就是一个函数。
// 以下举个例子实现一个中间件:
/******************a.js*******************/
import createStore from './createStore';
var store = createStore(myReducer, myInitialState)
// 中间件,功能是封装一个新的 dispatch! 给原本的 store.dispatch 改造一下加功能。
function hasMiddlewareDispatch(action: Function || Object){
if(action instanceOf Function){
// 执行这个函数的同时,传入 dispatch
action(store.dispatch)
}else if(typeof action == 'object'){
// 若传的就就是 action({type: '...'}),则正常执行
store.dispatch(action)
}
}
export default { ...store, dispatch: hasMiddlewareDispatch }
/****************************************/
/******************a.js*******************/
// 使用 store 的文件(命名为b.js)
import store from './b.js';
// 当我们有异步操作获取数据,然后想存在 redux 数据的时候
async function myAsynchronous(dispatch){
const myData = await Promise((resolve, reject) => { ... })
dispatch({ type:'changeMyData', payload: myData}})
}
store.dispatch(myAsynchronous)
/****************************************/
一个简单的中间件就这样完成,然后使用了,是不是很简单?
终于可以看 applyMiddldware.js 了,我们一起看看它干了什么 :
// applyMiddleware 这样用的:
// const store = createStore(myReducer, myInitState, applyMiddleware(...middleware))
// 还记得 createStore.js 中如果传了enhance 会执行 enhancer(createStore)(reducer, preloadedState)吗?
// applyMiddleware 就是重新改造了 dispatch。
export default function applyMiddleware(...middlewares) {
return (createStore) => (...args) => {
const store = createStore(...args)
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
}
const chain = middlewares.map((middleware) => middleware(middlewareAPI))
// 套娃改造 dispatch,若真想了解细节可以找个开源的 middleware 参考看看中间件干了什么
dispatch = compose(...chain)(store.dispatch)
// 看返回值,是不是跟我们之前自己封装改造 dispatch 的方式差不多?
return {
...store,
dispatch,
}
}
}
bindActionCreators.js
最后我们来看看这个文件
/*
举个使用的例子:
import createStore from './createStore'
const store = createStore(reducer,initialState)
function myFunA(){
...做自己想做的操作
return { type: 'add'}
}
store.bindActionCreators(myFunA)
其实这个 api 的作用就是提供我们在dispatch之前再用一个函数处理一些问题,返回action能力。使用场景比较少。
*/
import { kindOf } from './utils/kindOf'
function bindActionCreator(actionCreator, dispatch) {
return function () {
return dispatch(actionCreator.apply(this, arguments))
}
}
export default function bindActionCreators(actionCreators, dispatch) {
// 传入actionCreators必须是函数或者一个对象
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
// 如果传的不止一个函数
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
结语
希望能帮到那些一直想了解redux原理的同学
如果你觉得此文对你有一丁点帮助,点个赞,鼓励一下。
如果文章有什么不足之处,欢迎指出和提供建议哈~
如果你想一起学习前端或者摸鱼,那你可以底下留言哦~