最近项目中常用到redux,闲暇时间阅读了以下redux源码,结合自己使用的经验和习惯再去看源码以及设计者的设计思想,使得自己对redux的理解更加清晰,同时也对阅读源码有了一定的经验。
为什么想写这篇文章?
本来是在新的团队中参与项目开发,但是项目中的redux使用的流程和文件目录划分相对混乱,想结合自己的经验做一些改善,但是发现要做到这一点,需要先对redux有个清晰的认识,才能更好的完成这个目标。
首先看看redux库的index文件的导出
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}
其实这样看是很简单的,redux提供的API就这么几个,创建store、合并redudcer、action创造器、应用中间件工具函数compose。
下面一一来看,先来看一下redux的核心API createStore
1. createStore
先来说一下这个函数,接受参数是reducer,preloadedState,enhancer。
reducer,是一个函数,传入当前的state和action,返回新的state,后面combineReducers的时候细说。
preloadedState,可以理解为初始化的state。
enhancer是中间件函数,applyMiddleware中细说。
export default function createStore(reducer, preloadedState, enhancer) {
// 一堆的逻辑
return {
dispatch,
subscribe,
getState,
replaceReducer,
[?observable]: observable
}
}
其实这个函数导出的就是我们要用的store对象,常用的API就是dispatch、subscribe、getState。
下面再继续深挖各个函数
1.1 getState
这个函数很简单,作用就是返回当前的状态
function getState() {
return currentState
}
1.2 dispatch
先来简单看一下这个函数的骨架
function dispatch(action) {
// 很多的错误检测和逻辑优化
// currentListeners、nextListeners是局部的变量,监听着
try {
isDispatching = true
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
}
这个函数接受一个action,然后执行reducer更新state,然后让每个订阅者执行。
1.3 subscribe
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
这个函数是用来订阅state变化的, 接受一个订阅者,然后将该订阅者加入到订阅者列表中。订阅完成后返回一个取消订阅的函数,一般在组件销毁的时候调用,可提高性能。
1.4 replaceReducer
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
用来更行reducer的,开发中很少用到这个API。
所以综合以上几个函数,可以将createStore函数的核心简化理解为以下这样:
// 初始化store的action
export const ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer // 当前的执行者currentReducer
let currentState = preloadedState // 当前的state
let currentListeners = [] // 订阅者列表
let nextListeners = currentListeners // 新的订阅者列表
let isDispatching = false // action派发状态,可以优化性能
// 更新订阅者列表
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// 获取当前state
function getState() {
return currentState
}
// 订阅
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected listener to be a function.')
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
}
}
// 派发action
function dispatch(action) {
try {
isDispatching = true
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) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
// 初始化store
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer
}
}
函数最终返回了一个对象,通常我们会命名为 store ,store.dispatch()这样去使用。
2. combineReducers
开发中统通常我们会把reducer按功能或按模块进行拆分成多个简单的reducer,combineReducers 的功能很简单,把多个简单的reducer进行合并,合并成一个大的reducer,一般的应用中都会用到。
单一的reducer一般是这样的:
import { GET_TASK_LIST } from '../actions/taskList'
let initState = {
taskList: []
}
export default (state = initState, action = {}) => {
const { type } = action;
switch (type) {
case GET_TASK_LIST:
return {...state, taskList: action.list };
default:
return state;
}
}
combineReducers 的功能就是把多个这样的reducer进行合并,进行统一管理。
combineReducers 实现的核心代码如下:
export default function combineReducers(reducers) {
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)
return function combination(state = {}, action) {
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
可以看出函数接受的是一个reducers对象,每个key代表的是一个小的reducer,combineReducers返回一个新的reducer,每次这个新的reducer执行会遍历key组成的数组,并执行每个key对应的reducer,并将返回的新的state按照key进行合并组装成新的state对象。
combineReducers({
tranning,
taskList
})
// 返回的state将会是
{
tranning: {},
taskList: {}
}
// 要取得对应的state值需要向这样取
state.taskList.xxx
state.tranning.xxx
注意:一般开发的时候会将action和reducer进行对应拆分,这样合并之后就可能会出现action.type重复的现象,导致reducer执行混乱。所以在写action的时候需要按模块名或者功能进行区分
3. bindActionCreators
这个API运用场景很少,以至于我也不怎么理解它,官方是这样实现的:
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
export default function bindActionCreators(actionCreators, dispatch) {
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
把 action creators 转成拥有同名 keys 的对象,使用 dispatch 把每个 action creator 包围起来,这样可以直接调用它们。唯一使用 bindActionCreators 的场景是当你需要把 action creator 往下传到一个组件上,却不想让这个组件觉察到 Redux 的存在,而且不希望把 Redux store 或 dispatch 传给它。为方便起见,你可以传入一个函数作为第一个参数,它会返回一个函数。
4. applyMiddleware
这个函数是使用中间件使dispatch函数更加强大,一般在需要在派发墙厚做一些额外操作的时候会用到它,比如dispatch一个函数等。
export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer, preloadedState, enhancer) => {
const store = createStore(reducer, preloadedState, enhancer)
let dispatch = store.dispatch
let chain = []
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
它将所有的中间件进行合并,并重写了createStore返回的dispatch函数,然后覆盖掉store的dispatch。
返回的函数也就是enhancer,传给createStore。
const store = createStore(reducer, preloadedState, enhancer)
5. compose
工具函数,将多个相互依赖的函数进行合并,在applyMiddleware函数中有用到。
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
这是redux的核心实现方式,也都是源码中的代码片段,源码中有很多的工具函数和错误类型的判断,以及各种参数类型的兼容,可以在源码中进行查看。