Redux is a predictable state container for JavaScript apps. redux借鉴了函数编程的思想,采用了单向数据流的理念。用户不能直接改变store,只能通过派发action来更新store,做到了数据的可追溯。本片文章一步步实现redux,实现对redux原理的理解。
createStore的实现
首先createStore是使用方式是:
const store = createStore(reducer);
// 获取完整的store对象
store.getStore();
// 派发更新,dispatch接收一个action,会更新store
store.dispatch(actions)
// 监听store改变,store改变时调用listner
store.subscribe(listner);
完整的实现方式为:
const createStore = (reducer) => {
let store = {};
let listners = [];
const getStore = () => store;
const dispatch = (action) => {
store = reducer(store, action);
listners.forEach(item => item())
}
const subscribe = (listner) => {
listners.push(listner);
return () => {
// 可以取消订阅
listners = listners.filter(item => item !== listner);
}
}
return {
getStore,
dispatch,
subscribe
}
}
上述实现其实就是一个简单的发布订阅者设计模式。使用示例:
const reducers = (state = { counter: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return {...state, counter: state.counter + action.payload};
case 'DECREASE':
return {...state, counter: state.counter - action.payload};
default:
return state;
}
}
// 创建
const store = createStore(reducers);
// 订阅
const subscribe1 = store.subscribe(() => {
console.log('--订阅1--');
console.log(store.getStore().counter)
});
// 订阅
const subscribe2 = store.subscribe(() => {
console.log('--订阅2--');
console.log(store.getStore().counter)
});
// 派发更新,输出订阅1和订阅2
store.dispatch({
type: 'INCREMENT',
payload: 2
})
// 取消订阅
subscribe2();
// 派发更新,只输出订阅1
store.dispatch({
type: 'DECREASE',
payload: 1
})
实现日志记录
实现能够在dispatch前后打印出来store的信息。
// 创建
const store = createStore(reducers);
// 拦截旧的dispatch,在其调用前后插入日志代码
const logger = (store) => {
const oldDispatch = store.dispatch;
return (action) => {
console.log('--更新前--');
console.log(store.getStore());
oldDispatch(action);
}
}
store.dispatch = logger(store);
store.dispatch({
type: 'INCREMENT',
payload: 2
})
middlewares
假设还有一个logger2
打印更新后的日志,调用方法就是
store.dispatch = logger(store);
store.dispatch = logger2(store);
实现
const appliMiddlewares = (store, middlewares) => {
middlewares.forEach(middleware => {
store.dispatch = middleware(store)(store.dispatch)
})
}
那么改写原来的logger
实现为
// 创建
const store = createStore(reducers);
// 拦截旧的dispatch,在其调用前后插入日志代码。
const logger = (store) => (oldDispatch) => {
return (action) => {
console.log('--更新前--');
console.log(store.getStore());
const returnStore = oldDispatch(action);
return returnStore;
}
}
// 拦截旧的dispatch,在其调用前后插入日志代码
const logger1 = (store) => (oldDispatch) => {
return (action) => {
const returnStore = oldDispatch(action);
console.log('--更新后--');
console.log(store.getStore());
return returnStore;
}
}
const appliMiddlewares = (store, middlewares) => {
middlewares.forEach(middleware => {
store.dispatch = middleware(store)(store.dispatch)
});
}
appliMiddlewares(store, [logger, logger1]);
// 测试代码
store.dispatch({
type: 'INCREMENT',
payload: 2
})
更优雅的写法是定义oldDispatch为next.
redux中间件的实现
实际项目中,使用中间件的方式是:
import createLogger from 'redux-logger';
import { createStore, applyMiddlewares} from 'redux';
const initialState = {};
const configStore = () => {
return createStore(reducers, initialState, applyMiddlewares(...middlewares));
}
实现createStore
// createStore(reducer, [preloadedState], [enhancer])
const createStore = (reducer, preloadedState, enhancer) => { // 1
if (enhancer !== undefined && typeof enhancer === 'function') {
return enhancer(createStore)(reducer, preloadedState); // 2
}
let store = preloadedState;
let currentReducer = reducer;
let listners = [];
let isDispatching = false;
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
isDispatching = true;
const getState = () => store;
const dispatch = (action) => {
store = currentReducer(store, action);
return action;
}
// subscribe省略
return { // 4
getState,
dispatch
}
}
// (...args) => chain1(chain2(dispatch)(...args))
const 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)); // 6
}
// applyMiddlewares接收middlewares,返回一个函数接收createStore
const applyMiddlewares = (...middlewares) => {
// 这里的next相当于接收的createStore
return (next) => {
return (reducers, preloadedState) => {
const store = next(reducers, preloadedState); // 3
let dispatch = store.dispatch;
const middlewareAPI = {
getState: store.getState,
dispatch: (action) => dispatch(action)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI)); // 5
// chain: next => action => {}
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch // 7
}
}
}
}
上图中的数字表示执行createStore时候的执行顺序。这几行代码包含有高阶函数、函数柯里化等概念。使用示例
// 示例
const loggerMiddleware = store => next => action => {
console.log('更新前');
console.log(store.getState());
const returnValue = next(action);
return returnValue;
}
const loggerMiddleware2 = store => next => action => {
const returnValue = next(action);
console.log('更新后');
console.log(store.getState());
return returnValue;
}
// 创建
const reducers = (state = { counter: 0 }, action) => {
switch (action.type) {
case 'INCREMENT':
return { ...state, counter: state.counter + action.payload };
case 'DECREASE':
return { ...state, counter: state.counter - action.payload };
default:
return state;
}
}
const store = createStore(reducers, {}, applyMiddlewares(...[loggerMiddleware, loggerMiddleware2]));
// store.dispatch = loggerMiddleware({getState, dispatch})(loggerMiddleware2({getState, dispatch})(store.dispatch)({type: ''...}));
store.dispatch({
type: 'INCREMENT',
payload: 2
})
react-redux connect
使用示例
connect(mapStateToProps?, mapDispatchToProps?, mergeProps?, options?)(component)
- connect方法返回一个函数,该函数接口外部传入的业务组件,并且返回一个注入了store的React组件
- 返回的React组件重新渲染外部传入的原始业务组件,并把connect中传入的mapStateToProps等于组件中原有的props合并
大致实现如下
const connect = (mapStateToProps, mapDispatchToProps, mergeProps, options = {}) => Component => {
class WrappedComponent extends Component {
constructor (props, context) {
this.store = props.store || context.store;
this.nextState = {}; // 把connect中传入的mapStateToProps等于组件中原有的props合并
}
render() {
return <Component {...this.nextState} />
}
}
// 返回WrappedComponent
return WrappedComponent;
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。