Redux的设计思想很简单,1、web应用是一个状态机,视图与状态一一对应;2、所有状态保存在一个对象里
基本概念和API
-
Store 应用状态的管理者,可以看作一个数据库,包含以下函数
const store = { dispatch(action), // 触发state改变的唯一方法 subscribe(listener), // 订阅函数,store变化触发的订阅的监听函数,返回取消订阅的方法 getState(), // 获取state的方法 replaceReducer(nextReducer), // 替换reducer [?observable]: observable }
一个应用只应有一个单一的 store,其管理着唯一的应用状态 state Redux提供createStore函数,用于生成store
import { createStore } from 'redux'; const store = createStore(reducer);
-
state
一个对象,包含所有数据,可以看作数据库中的数据,通过
const state = store.getState()
获取 -
Action
一个包含type属性的对象,作用是描述如何修改state,它会运输数据到store,作为reducer函数的参数
-
Action Creator
生成Action的函数
-
dispatch
view发出Action的唯一方法,redux规定不能直接在业务代码中修改state,若想要修改,只能通过store.dispatch(Action)实现
-
Reducer
一个纯函数,会根据Acion的type值生成新的state替换调原来调state
源码解析
只对关键代码进行解读。
-
目录结构
-
index.ts
先看入口文件
import createStore from './createStore' import combineReducers from './combineReducers' import bindActionCreators from './bindActionCreators' import applyMiddleware from './applyMiddleware' import compose from './compose' import warning from './utils/warning' import __DO_NOT_USE__ActionTypes from './utils/actionTypes' //... export { createStore, combineReducers, bindActionCreators, applyMiddleware, compose, __DO_NOT_USE__ActionTypes }
很简单,导出一个对象,接下来一个个分析
-
createStore.ts
import ?observable from './utils/symbol-observable' import { Store, PreloadedState, StoreEnhancer, Dispatch, Observer, ExtendState } from './types/store' import { Action } from './types/actions' import { Reducer } from './types/reducers' import ActionTypes from './utils/actionTypes' import isPlainObject from './utils/isPlainObject' export default function createStore ( reducer, // 因为dispatch会自动触发reducer的执行,所以在生成store的时候需要把reducer函数传递进来 preloadedState?, // 初始state enhancer? // 增强,通过一系列中间件增强dispatch函数,例如对于异步Action对处理,后面详细介绍 ) { // ...对参数的判断 if (typeof enhancer !== 'undefined') { if (typeof enhancer !== 'function') { throw new Error('Expected the enhancer to be a function.') } return enhancer(createStore)( reducer, preloadedState as PreloadedState<S> ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext } let currentReducer = reducer // reducer函数 let currentState = preloadedState // 当前state let currentListeners = [] // 用于存储订阅的回调函数,dispatch 后逐个执行 let nextListeners = currentListeners // 下一个事件循环的回调函数数组 let isDispatching = false // 是否正在dispatch // 判断当前的回调函数队列是否与下一个回调函数队列相同,不能影响本次的执行 function ensureCanMutateNextListeners() { if (nextListeners === currentListeners) { nextListeners = currentListeners.slice() } } // 获取state function getState() { // 直接返回state return currentState } // 订阅--取消订阅 function subscribe(listener) { 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) currentListeners = null } } // 派发一个Action用于改变state // 如果dispatch的不是一个对象类型的action(同步),而是一个Promise/Thunk(异步), // 就需要引入redux-thunk 等中间件来反转控制权,具体会在applyMiddlewares()方法中解析 function dispatch(action) { // 确保是一个普通的对象,若非普通对象,原型链上会出现其他属性 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } // 确保拥有type属性 if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } // 是否正在dispatch if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true // dispatch 触发reducer函数,返回state,赋值给currentState,这也是为什么我们要在创建store对象的时候传入reducer 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 dispatch({ type: ActionTypes.REPLACE }) return store } // 这是留给 可观察/响应式库 的接口,关于响应式编程我了解甚微,具体可以自行百度一下 function observable() { const outerSubscribe = subscribe return { subscribe(observer) { if (typeof observer !== 'object' || observer === null) { throw new TypeError('Expected the observer to be an object.') } function observeState() { const observerAsObserver = observer if (observerAsObserver.next) { observerAsObserver.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [?observable]() { return this } } } // 初始化state dispatch({ type: ActionTypes.INIT }) // 返回store对象 const store = { dispatch, subscribe, getState, replaceReducer, [?observable]: observable } return store }
createStore(reducer, preloadState?, enhancer?)是Redux的核心代码,它给我们提供了获取state的方法getState()、触发state改变的方法dispatch(Action)等
这里重点讲下为什么要设置了currentListeners,还要设置nextListeners
举个🌰:
const store = createStore(reducer)
let A = store.subscribe(() => {
// ...
})
let B = store.subscribe(() => {
// ...
A()
})
let C = store.subscribe(() => {
// ...
store.subscribe(() => { //...})
})
结合源码中subscribe函数和dispatch部分,初始状态下currentListeners = nextListeners = []
,当我们开始注册订阅事件时,即向nextListeners数组中添加子集(函数),
ensureCanMutateNextListeners()
nextListeners.push(listener)
而subscribe函数返回的时unsubscribe函数,用于取消订阅,实际就是从nextListeners数组中移除对应的函数
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
那上面我们注册了3个订阅事件,即nextListeners = [订阅函数1, 订阅函数2, 订阅函数3]
,而A = 取消订阅函数1
当我们dispatch(Action)时,就会执行以下代码
// 依次执行订阅的回调函数
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
如果只有一个listeners,当执行到订阅函数2时,它又会取消订阅函数,就会出现从listener = [订阅函数1, 订阅函数2, 订阅函数3]
变成listener = [订阅函数2, 订阅函数3]
,但此时在for循环中i = 1
了,而订阅函数3还没有执行循环就结束了,这么不受重视的吗!所以这种情况是觉对不允许出现的。
这也是ensureCanMutateNextListeners这个函数存在的意义。
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice() // 返回一个新的数组,用于改变而不会影响当前
}
}
- combineReducers.ts
import { Reducer } from './types/reducers'
import { AnyAction, Action } from './types/actions'
import ActionTypes from './utils/actionTypes'
import warning from './utils/warning'
import isPlainObject from './utils/isPlainObject'
import {
ReducersMapObject,
StateFromReducersMapObject,
ActionFromReducersMapObject
} from './types/reducers'
import { CombinedState } from './types/store'
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers: ReducersMapObject = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
}
// 所有reducer的key集合
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
// 返回合成后的 reducer
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] // 获取当前子的state
const nextStateForKey = reducer(previousStateForKey, action) // 执行起对应的reducer
nextState[key] = nextStateForKey // 将子nextState挂载到对应的键名
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state // 返回总的state
}
}
假如我们的应用有两个状态需要管理,一个是打印日志,另一个是计数器功能,我们可以把它们写在一个reducer中,但如果还有其他动作,这些堆在一起会让代码变得难以维护,所以最好但办法是拆分reducer,使每一个动作变得单一,最后在整合在一起,combineReducers()就是为此而生的。需要注意的是,子reducer的名称应该与起对应的state相同。
比如我们的目录结构如下
reducers/
├── index.js
├── counterReducer.js
├── todosReducer.js
就可以这样实现
/* reducers/index.js */
import { combineReducers } from 'redux'
import counterReducer from './counterReducer'
import todosReducer from './todosReducer'
const rootReducer = combineReducers({
counter: counterReducer, // 键名就是该 reducer 对应管理的 state
todos: todosReducer
})
export default rootReducer
-------------------------------------------------
/* reducers/counterReducer.js */
export default function counterReducer(counter = 0, action) { // 传入的 state 其实是 state.counter
switch (action.type) {
case 'INCREMENT':
return counter + 1 // counter 是值传递,因此可以直接返回一个值
default:
return counter
}
}
-------------------------------------------------
/* reducers/todosReducers */
export default function todosReducer(todos = [], action) { // 传入的 state 其实是 state.todos
switch (action.type) {
case 'ADD_TODO':
return [ ...todos, action.payload ]
default:
return todos
}
}
这样做的好处不言而喻。无论您的应用状态树有多么的复杂,都可以通过逐层下分管理对应部分的 state:
counterReducer(counter, action) -------------------- counter
↗ ↘
rootReducer(state, action) —→∑ ↗ optTimeReducer(optTime, action) ------ optTime ↘ nextState
↘—→∑ todo ↗
↘ todoListReducer(todoList,action) ----- todoList ↗
注:左侧表示 dispatch 分发流,∑ 表示 combineReducers;右侧表示各实体 reducer 的返回值,最后汇总整合成 nextState
- bindActionCreator.ts
import { Dispatch } from './types/store'
import {
AnyAction,
ActionCreator,
ActionCreatorsMapObject
} from './types/actions'
function bindActionCreator<A extends AnyAction = AnyAction>(
actionCreator: ActionCreator<A>,
dispatch: Dispatch
) {
return function (this: any, ...args: any[]) {
// 给Action Creator 加上dispatch技能,也就是包裹一下
return dispatch(actionCreator.apply(this, args))
}
}
export default function bindActionCreators(
actionCreators,
dispatch
) {
// 处理actionCreators是函数的情况
if (typeof actionCreators === 'function') {
return bindActionCreator(actionCreators, dispatch)
}
if (typeof actionCreators !== 'object' || actionCreators === null) {
throw new Error(
`bindActionCreators expected an object or a function, instead received ${
actionCreators === null ? 'null' : typeof actionCreators
}. ` +
`Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
)
}
// 处理actionCreators是对象的情况
const boundActionCreators = {}
for (const key in actionCreators) {
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
bindActionCreators(actionCreators, dispatch)这个函数的功能也比较单一,就是dispatch(ActionCreator(XXX))
,这个方法在异步情况下,没什么卵用。。。
- compose.ts
export default function compose(...funcs) =>
// 复合函数 compose(func1, func2, func3)(0) => func3(func2(func1(0)))
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: any) => a(b(...args)))
}
这个函数非常简单,功能也很单一,主要用了数组的reduce方法,将函数作为reduce的回调函数的参数,参考文档
array.reduce(function(total, currentValue, currentIndex, arr), initialValue)
- applyMiddlewares.ts
中间件就是一个函数,对dispatch进行了改造,在发出 Action 和执行 Reducer 这两步之间,添加了其他功能。
它是 Redux 的原生方法,作用是将所有中间件组成一个数组,依次执行。目的是为了在dispatch前后,统一处理想做对事。
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'
export default function applyMiddleware(
...middlewares
) {
return (createStore) => (
reducer,
...args
) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
}
// 中间件增加api
const middlewareAPI = {
getState,
dispatch
}
// chain存放中间件的数组,逐个增加api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose复合函数 a(b(c(0))) => compose(a, b, c)(0)
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
从源码可以看出,这个函数的主要作用就是通过各种中间件对dispatch进行增强,借此我们就可以处理异步Action等问题了。
我们可以思考一下为什么处理异步Action就需要对dispatch进行增强,原因如下:
(1)Reducer:纯函数,只承担计算 State 的功能,不合适承担其他功能,也承担不了,因为理论上,纯函数不能进行读写操作。
(2)View:与 State 一一对应,可以看作 State 的视觉层,也不合适承担其他功能。
(3)Action:存放数据的对象,即消息的载体,只能被别人操作,自己不能进行任何操作。
所以只有发送 Action 的这个步骤,即store.dispatch()方法,可以添加功能。
异步操作的基本思路是什么呢?
同步操作只需要发出一种Action,异步操作则需要发出3种,发送时、成功时、失败时,例如:
{ type: 'FETCH_POSTS' }
{ type: 'FETCH_POSTS', status: 'error', error: 'Oops' }
{ type: 'FETCH_POSTS', status: 'success', response: { ... } }
同样的state也需要进行对应的改造
let state = {
// ...
isFetching: true, // 是否正在抓去数据
didInvalidate: true, // 数据是否过时
lastUpdated: 'xxxxxxx' // 更新数据时间
}
所以异步操作的思路应该是:
- 操作开始时,送出一个 Action,触发 State 更新为"正在操作"状态,View 重新渲染
- 操作结束后,再送出一个 Action,触发 State 更新为"操作结束"状态,View 再一次重新渲染
Redux-Thunk
前面说到处理异步Action需要借助到redux-thunk等中间件,现在具体分析一下redux-thunk是怎么处理异步请求的。
异步操作至少要送出两个 Action:用户触发第一个 Action,这个跟同步操作一样,没有问题;如何才能在操作结束时,系统自动送出第二个 Action 呢?
最主要的就是在Action Creator中处理,例如store.dispatch(fetchPosts())
fetchPosts()就是一个Action Creator,看一下内部实现:
const fetchPosts = () => (dispatch, getState) => {
// 先发送第一个Action,同步的
dispatch({type: 'FETCH_BIGIN'});
return fetch(`/some/API`)
.then(response => response.json())
.then(json =>
// 请求到结果后,发送第二个Action,异步
dispatch({type: 'FETCH_END', payload: json});
)
};
};
值得注意的是,Action Creator返回的是一个函数,而正常返回应该是一个对象,并且这个函数的参数是dispatch和getState两个方法。
这个我们就需要用到redux-thunk帮我们处理一下,看下源码
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
竟是如此简单的函数,这里我们结合applyMiddleware分析一下:
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
{},
applyMiddleware(thunk)
);
applyMiddleware函数的参数是thunk函数 ==>
({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
}
回顾一下applyMiddleware函数 ==>
import compose from './compose'
import { Middleware, MiddlewareAPI } from './types/middleware'
import { AnyAction } from './types/actions'
import { StoreEnhancer, StoreCreator, Dispatch } from './types/store'
import { Reducer } from './types/reducers'
export default function applyMiddleware(
...middlewares
) {
return (createStore) => (
reducer,
...args
) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
}
// 中间件增加api
const middlewareAPI = {
getState,
dispatch
}
// chain存放中间件的数组,逐个增加api
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// compose复合函数 a(b(c(0))) => compose(a, b, c)(0)
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
应用了redux-thunk再通过enhancer(createStore)({reducer, preloadedState})
调用,即applyMiddleware(thunk)(createStore)({reducer, preloadedState})
,dispatch函数就会变成,
dispatch = (store.dispatch) => (action) => {
// 处理action是函数的情况
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
// 处理action是对象的情况
return store.dispatch(action)
}
React-Redux
Redux的作者封装了一个React专用的库,React-Redux
看一个例子:
import React from 'react'
import { createStore } from 'redux'
import { Provider, connect } from 'react-redux'
// React component
const Counter = (props) => {
const { value, changeVal } = props
return (
<div>
<span>{value}</span>
<button onClick={changeVal} >change</button>
</div>
)
}
// Action
const rideAction = {
type: 'ride',
payload: {
num: 2
}
}
// Reducer
function reducer (state = {count: 0}, action) {
const count = state.count
switch(action.type) {
case 'ride':
return { count: count * action.payload.num };
default:
return state;
}
}
// Store
const store = createStore(reducer)
// map Redux state to component props
function mapStateToProps (state) {
return {
value: state.count
}
}
// map Redux actions to component props
function mapDispatchToProps (dispatch) {
return {
changeVal: () => dispatch(rideAction)
}
}
// connect component
const App = redux.connect(mapStateToProps, mapDispatchToProps)(Counter)
// Provider
React.ReactDom.render(
<Provider store={store} >
<App />
</Provider>,
document.getElementById('root')
)
- connect 用于将ui组件与Redux建立连接
const app = Redux.connect(mapStateToProps, mapDispatchToProps)(component)
-
mapStateToProps函数构建从state对象到props对象的映射关系
-
mapDispatchToProps函数用于构建UI组件的参数到store.dispatch方法的映射
-
<Provider>组件 connet方法生成容器组件以后,需要让容器组件拿到state对象,才能生成UI组件的参数。如果作为props传递是在太麻烦,所以React-Redux提供Provider组件。
原理是利用React组件的context属性
class Provider extends Component { getChildContext() { return { store: this.props.store } } render() { return this.props.children } } Provider.childContextTypes = { store: React.PropTypes.object }
总结了这些,如有不对的地方还望大家指正,希望有兴趣的朋友一起交流交流。