整个工作流程:
三大原则
1.单一数据源(Store) 整个应用的State被存放在一棵Object tree中,并且这个Object tree只存在唯一一个Store中;
2.State是只读的 唯一改变 State 的方法就是触发 Action,Action 是一个用于描述已发生事件的普通对象。 确保了所有的修改都能被集中化处理。
3.通过纯函数Reducer来修改Store, Reducer 只是一些纯函数,它接收先前的 State 和 Action,并返回新的 State。 即reducer(state, action) => new state
export default function createStore(reducer, preloadedState, enhancer) {
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// 第二个参数是一个函数,没有第三个参数的情况
enhancer = preloadedState;
preloadedState = undefined;
}
// 如果第三个参数是函数走下面的逻辑,返回一个新的createStore
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
// enhancer 不是函数就报错
throw new Error('Expected the enhancer to be a function.');
}
// enhancer就是高阶函数,强化了本身这个createStore的函数,拿到增强后的createStore函数去处理
// applyMiddleware这个函数还会涉及到这个
return enhancer(createStore)(reducer, preloadedState);
}
if (typeof reducer !== 'function') {
// reducer不是函数报错
throw new Error('Expected the reducer to be a function.');
}
// 其他代码省略
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable,
};
}
export default function applyMiddleware(...middlewares) {
return (createStore) =>
(...args) => {
const store = createStore(...args);
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.',
);
};
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args),
};
const chain = middlewares.map((middleware) => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch,
};
};
}
// 其实就是修改了dispatch
let store = applyMiddleware(middleware1,middleware2)(createStore)(rootReducer);
从执行结果看,这时候 state 已经变成了一个以这些 reducer 为 key 的对象;reducer 也变成了一个合并的 reducer;
遍历执行所有的 reducer 的时候把 action 传进去,返回新的 state;
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);
/* 返回一个整合后的reducers */
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);
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action);
throw new Error(errorMessage);
}
nextState[key] = nextStateForKey;
hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
}
return hasChanged ? nextState : state;
};
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.',
);
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?',
);
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
var listeners = (currentListeners = nextListeners);
for (var i = 0; i < listeners.length; i++) {
var listener = listeners[i];
listener();
}
return action;
}
function logger(store) {
return function (next) {
return function (action) { // 新的 dispatch 函数
console.group(action.type);
console.info('dispatching', action);
let result = next(action);
console.log('next state', store.getState());
console.groupEnd();
return result;
};
};
}
import React from 'react';
import { createStore, applyMiddleware } from 'redux';
function createLogger({ getState, dispatch }) {
return (next) => (action) => {
const prevState = getState();
console.log('createLogger1');
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;
console.log(`%c prev state`, `color: #9E9E9E`, prevState);
console.log(`%c action`, `color: #03A9F4`, action);
console.log(`%c next state`, `color: #4CAF50`, nextState);
return returnValue;
};
}
function createLogger2({ getState }) {
return (next) => (action) => {
const console = window.console;
const prevState = getState();
console.log('createLogger2');
const returnValue = next(action);
const nextState = getState();
const actionType = String(action.type);
const message = `action ${actionType}`;
console.log(`%c prev state2`, `color: #9E9E9E`, prevState);
console.log(`%c action2`, `color: #03A9F4`, action);
console.log(`%c next state2`, `color: #4CAF50`, nextState);
return returnValue;
};
}
const reducer = function (state = { number: 0 }, action) {
switch (action.type) {
case 'add':
return {
number: state.number + action.number,
};
default:
return state;
}
};
const store = createStore(
reducer,
applyMiddleware(createLogger, createLogger2),
);
store.subscribe(function () {
console.log(1111);
});
const { dispatch } = store;
const App = () => {
const handler = () => {
dispatch({ type: 'add', number: 10 });
};
return (
<div>
<button onClick={handler}>触发redux</button>
</div>
);
};
export default App;
store 的属性如下:
Redux 的数据流是这样的:
界面 => action => reducer => store => react => virtual dom => 界面
将action对象转为一个带dispatch的方法
比如connect接收的mapDispatchToProps 是对象,会使用 bindActionCreators 处理; 接收 actionCreator 和 dispatch,返回一个函数;
function bindActionCreator(actionCreator, dispatch) {
// 返回一个函数
return function() {
return dispatch(actionCreator.apply(this, arguments))
}
}
function bindActionCreators(actionCreators, dispatch) {
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
}
const mapDispatchToProps = { // actionCreators 这是个集合,
onClick: (filter) => {
type: 'SET_VISIBILITY_FILTER',
filter: filter
};
}
转换为:
const mapDispatchToProps = { // actionCreators 这是个集合,
onClick:function(filter) {
return dispatch({ // dispatch 是闭包中的方法
type: 'SET_VISIBILITY_FILTER',
filter: filter
})
}
}
函数套函数,compose(...chain)(store.dispatch)结果返回一个加强了的 dispatch;
这点和koa比较相似,这个 dispatch 在执行的时候会调用中间件。
function compose(...funcs) {
if (funcs.length === 0) {
return (arg) => arg;
}
if (funcs.length === 1) {
return funcs[0];
}
// 每一次reduce迭代都会返回一个加强版的dispatch
return funcs.reduce(
(a, b) =>
(...args) =>
a(b(...args)),
);
}
加强版 dispatch(一个方法,接收 action 参数),在中间件中用 next 表示,执行 next 之后,会形成一个链条。
// 以createStore为参数
(createStore) =>
(...args) => {};
样板代码过多 增加一个 action 往往需要同时定义相应的 actionType 然后再写相关的 reducer。例如当添加一个异步加载事件时,需要同时定义加载中、加载失败以及加载完成三个 actionType,需要一个相对应的 reducer 通过 switch 分支来处理对应的 actionType,冗余代码过多;
目前已经存在着非常多的解决方案,比如dva redux-tookit等。
更新效率问题:由于使用不可变数据模式,每次更新 state 都需要拷贝一份完整的 state 造成了内存的浪费以及性能的损耗。
其实 redux 以及 react-redux 中内部已做优化,开发的时候使用 shouldComponentUpdate 等优化方法也可以应用,也可以用不可变数据结构如 immutable、Immr 等来解决拷贝开销问题。
文章的名字我想了很久,备选项有“我再不推荐 Redux”,“Redux 为什么令我头疼”,“Redux 进化启示录”等等。通过这一系列名字我想你大概能猜到我接下来想聊的问题是什么