Redux is a predictable state container for JavaScript apps.
redux 三大原则
单一数据源 Single source of truth:整个应用的 state 被储存在一棵 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
state 只读 State is read-only:惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。这样确保了视图和网络请求都不能直接修改 state,相反它们只能表达想要修改的意图。
使用纯函数来执行修改 Changes are made with pure functions:为了描述 action 如何改变 state tree ,你需要编写 reducers。Reducer 只是一些纯函数,它接收先前的 state 和 action,并返回新的 state。
redux 一些概念
Action: 是把数据传到 store 的唯一手段,表达修改 state 的意图。它是 store 数据的唯一来源。一般通过 store.dispatch() 将 action 传到 store。
{
type:types.RECEIVE_CASES,
cases
}
Action Creator:是一个创建 action 的纯函数。
export function receiveCases(cases) {
return {
type:types.RECEIVE_CASES,
cases
}
}
Reducer:是一个纯函数,接收旧的 state 和 action,通常包含了switch结构,根据dispatch传来的state和type来生成新的state。
Store:构建顺序:
reducers => 'combineReducers'
=> rootReducer => 'createStore' (+ 'applyMiddleware')
|
===> **Store**
createStore.js
redux.createStore(reducer, initialState) 传入了reducer、initialState,并返回一个store对象。
store对象对外暴露了dispatch、getState、subscribe方法
store对象通过getState() 获取内部状态
initialState为 store 的初始状态,如果不传则为undefined
store对象通过reducer来修改内部状态
store对象创建的时候,内部会主动调用dispatch({ type: ActionTypes.INIT });来对内部状态进行初始化。通过断点或者日志打印就可以看到,store对象创建的同时,reducer就会被调用进行初始化。
/**
* Creates a Redux store that holds the state tree.
* The only way to change the data in the store is to call `dispatch()` on it.
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
* --- reducer 是 Function,用于构建 state 树
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
* --- preloadedState 初始化 state 树
*
* @param {Function} enhancer The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
* --- enhancer 相当于 AOP 插件
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
* --- 最终返回 Store,能获取 state、分发action、订阅变化
*/
export default function createStore(reducer, preloadedState, enhancer) {
... // --- 校验参数,及执行 enhancer
var currentReducer = reducer
var currentState = preloadedState // --- state 树
var currentListeners = [] // --- 注册的监听器列表,实时处理 dispatch 事件
var nextListeners = currentListeners // --- 注册的监听器列表,实时接收 subscribe 事件
var isDispatching = false // --- 如果 reducer 正在执行,会抛出异常
// --- 将 nextListeners 做为 currentListeners 的副本
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
// --- 返回 state
function getState() {
... // --- 校验
return currentState
}
// --- 注册监听器
// 很常见的监听函数添加方式,当store.dispatch 的时候被调用
// store.subscribe(listener) 返回一个方法(unscribe),可以用来取消监听
function subscribe(listener) {
listeners.push(listener);
var isSubscribed = true;
return function unsubscribe() {
if (!isSubscribed) {
return;
}
isSubscribed = false;
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
// --- 分发 action,修改 state 的唯一方式
function dispatch(action) {
// 以下情况会报错
// 1. 传入的action不是一个对象
// 2. 传入的action是个对象,但是action.type 是undefined
try {
isDispatching = true
// 就是这一句啦, 将 currentState 设置为 reducer(currentState, action) 返回的值
currentState = currentReducer(currentState, action) // 执行 reducer,state 被更新
} finally {
isDispatching = false
}
// --- 如果有监听函数,就顺序调用监听器方法
//listeners.slice().forEach(listener => listener());
var listeners = currentListeners = nextListeners
for (var i = 0; i < listeners.length; i++) {
listeners[i]()
}
// --- 返回结果还是 action
return action
}
// --- 替换 reducer,用于热替换、按需加载等场景
function replaceReducer(nextReducer) {
... // --- 校验
currentReducer = nextReducer
dispatch({ type: ActionTypes.INIT })
}
function observable() {
// --- 略
}
// --- 创建时初始化应用状态
// redux.createStore(reducer, initialState) 的时候, 内部会 自己调用 dispatch({ type: ActionTypes.INIT });
// 来完成state的初始化
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
combineReducers.js
function TodoReducer(state, action) {}
function FilterReducer(state, action) {}
var finalReducers = redux.combineReducers({
todos: TodoReducer,
filter: FilterReducer
});
redux.combineReducers(reducerMap) 用来合成多个reducer,其实就是把所有输入的reducer闭包封装,返回一个combination函数,这个函数会接受state和action,依次调用封装的reducer,分发state和action,来生成新的state
combineReducers(reducerMap) 传入一个对象,并返回一个全新的reducer。调用方式跟跟普通的reducer一样,也是传入state、action。
通过combineReducers,对 store 的状态state进行拆分,
reducerMap的key,就是 state 的key,而 调用对应的reducer返回的值,则是这个key对应的值。如上面的例子,state.todos == TodoReducer(state, action)
redux.createStore(finalReducers, initialState) 调用时,同样会对 state 进行初始化。这个初始化跟通过普通的reducer进行初始化没多大区别。举例来说,如果 initialState.todos = undefined,那么 TodoReducer(state, action) 初始传入的state就是undefined;如果initialState.todos = [],那么 TodoReducer(state, action) 初始传入的state就是[];
store.dispatch(action),finalReducers 里面,会遍历整个reducerMap,依次调用每个reducer,并将每个reducer返回的子state赋给state对应的key。
/**
* Turns an object whose values are different reducer functions, into a single
* reducer function. It will call every child reducer, and gather their results
* into a single state object, whose keys correspond to the keys of the passed
* reducer functions.
*
* @param {Object} reducers An object whose values correspond to different
* reducer functions that need to be combined into one. One handy way to obtain
* it is to use ES6 `import * as reducers` syntax. The reducers may never return
* undefined for any action. Instead, they should return their initial state
* if the state passed to them was undefined, and the current state for any
* unrecognized action.
*
* @returns {Function} A reducer function that invokes every reducer inside the
* passed object, and builds a state object with the same shape.
*/
export default function combineReducers(reducers) {
// --- 过滤 reducer
var reducerKeys = Object.keys(reducers)
var finalReducers = {}
for (var i = 0; i < reducerKeys.length; i++) {
var key = reducerKeys[i]
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
var finalReducerKeys = Object.keys(finalReducers)
// --- 返回 combination 函数
return function combination(state = {}, action) {
// --- 校验
// --- 根据 reducer key 及执行结果构造 state 树
var hasChanged = false
var nextState = {}
for (var i = 0; i < finalReducerKeys.length; i++) {
var key = finalReducerKeys[i]
var reducer = finalReducers[key]
var previousStateForKey = state[key]
var nextStateForKey = reducer(previousStateForKey, action) // --- 执行 reducer
// --- 校验
nextState[key] = nextStateForKey
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state
}
}
applyMiddleware.js
applyMiddleware 传入 middleware 链,并返回应用这些 middleware 的 store enhancer
export default function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
// 内部先创建一个store (相当于直接调用 Redux.createStore(reducer, initialState))
var store = next(reducer, initialState);
// 保存最初始的store.dispatch
var dispatch = store.dispatch;
var chain = [];
var middlewareAPI = {
getState: store.getState,
// 最后面, dispatch 被覆盖, 变成包装后的 dispatch 方法
dispatch: (action) => dispatch(action)
};
// 返回一个数组
// 贴个例子在这里做参考,redux-thunk
// function thunkMiddleware(store) {
// var dispatch = store.dispatch;
// var getState = store.getState;
//
// 这里的next其实就是dispatch
// return function (next) {
// return function (action) {
// return typeof action === 'function' ? action(dispatch, getState) : next(action);
// };
// };
//}
/*
chain 是个数组, 参考上面的 middlleware (redux-thunk),可以看到,chain的每个元素为如下形式的function
并且, 传入的 store.getState 为原始的 store.getState,而 dispatch则是包装后的 dispatch(不是原始的store.dispatch)
似乎是为了确保, 在每个middleware里调用 dispatch(action), 最终都是 用原始的 store.dispatch(action)
避免 store.dispatch 被覆盖, 导致middleware 顺序调用的过程中, store.dispatch的值变化 --> store.dispatch 返回的值可能会有不同
违背 redux 的设计理念
这里的 next 则为 原始的 store.dispatch (见下面 compose(...chain)(store.dispatch) )
function (next) {
return function (action) {
}
}
*/
chain = middlewares.map(middleware => middleware(middlewareAPI));
// compose(...chain)(store.dispatch) 返回了一个function
// 伪代码如下,
// function (action) {
// middleware(store)(store.dispatch);
// }
dispatch = compose(...chain)(store.dispatch); // 从右到左, middleware1( middleware2( middleware3(dispatch) ) )
// 于是,最终调用 applyMiddleware(...middlewares)(Redux.createStore)
// 返回的 store, getState,subscribe 方法都是原始的那个 store.getState, store.subscribe
// 至于dispatch是封装过的
return {
...store,
dispatch
};
};
}
export default function applyMiddleware(...middlewares) {
// --- 传入 createStore,返回增强版的 store
return (createStore) => (reducer, preloadedState, enhancer) => {
var store = createStore(reducer, preloadedState, enhancer)
var dispatch = store.dispatch
var chain = []
// --- middleware 中的参数
var middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
}
// --- 给 middleware 传参
chain = middlewares.map(middleware => middleware(middlewareAPI))
// --- 组合 chain,传入原始的 dispatch
dispatch = compose(...chain)(store.dispatch)
return {
...store, // --- 保留 subscribe, getState, replaceReducer, [$$observable]: observable
dispatch // --- 用新 dispatch 覆盖,之后调用 dispatch 就会触发 chain 内的 middleware 链式执行
}
}
}
Provider.js
React-Redux是用在连接React和Redux上的。如果你想同时用这两个框架,那么React-Redux基本就是必须的了。
export default class Provider extends Component {
getChildContext() {
// 将其声明为 context 的属性之一
return { store: this.store }
}
constructor(props, context) {
super(props, context)
// 接收 redux 的 store 作为 props
this.store = props.store
}
render() {
return Children.only(this.props.children)
}
}
Provider.propTypes = {
store: storeShape.isRequired,
children: PropTypes.element.isRequired
}
Provider.childContextTypes = {
store: storeShape.isRequired
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。