redux 源码分析,实现一个迷你的redux
实现一个redux
先不考虑中间件,实现一个简洁的redux
实现createStore
createStore是redux最主要的一个API了,通过createStore可以创建一个store用来存放应用中所有的state,一个应用只能有一个store。
// 先创建一个mini-redux.js文件: export function createStore(reducer) { // 初始化store let currentStore = {}; // 初始化事件列表 let currentListeners = []; // 获取state function getState() { return currentStore; } // 订阅事件 function subscribe(listener) { currentListeners.push(listener); } // 定义dispatch方法 function dispatch(action) { currentStore = reducer(currentStore, action); currentListeners.forEach(v => v()); // return dispatch; } // 默认执行reducer type类型不要命中reducer中自定义的case dispatch({type: '@ZQT-REDUX'}); return {getState, subscribe, dispatch} }
上面创建了一个redux.js文件,并暴露了一个createStore方法,接受reducer作为参数
// 创建mini-react-redux.js import React from 'react'; import PropTypes from 'prop-types'; export const connect = (mapStateToProps = state => state, mapDispatchToProps = {}) => (WrapComponent) => { return class connentComponent extends React.Component{ static contextTypes = { store: PropTypes.object } constructor(props, context) { super(props, context); this.state = { props: {} } } componentDidMount() { const {store} = this.context; // 为什么非要订阅 因为没一个connect实际上就是一个订阅 每当dispatch执行的时候 就要重新执行以下update方法 store.subscribe(() => this.update()); this.update(); } update = () => { const {store} = this.context; const stateProps = mapStateToProps(store.getState()); // 每一个action需要用dispatch包裹一下 const stateDispatch = bindActionCreators(mapDispatchToProps, store.dispatch); this.setState({ props: { ...this.props, ...stateProps, ...stateDispatch } }) } render() { return <WrapComponent {...this.state.props}/> } } } export class Provider extends React.Component{ static childContextTypes = { store: PropTypes.object } getChildContext() { return { store: this.store } } constructor(props, context) { super(props, context); this.store = props.store; } render() { return this.props.children } } function bindActionCreators(creators, dispatch) { const bound = {}; Object.keys(creators).forEach(v => { bound[v] = bindActionCreator(creators[v], dispatch); }) return bound; } function bindActionCreator(creator, dispatch) { return (...args) => dispatch(creator(...args)) }
上面创建了mini-react-redux.js文件,主要暴露了connect方法和Provider组件。
先看Provider组件。Provider利用的react的context属性,把store注入到Provider组件,并返回this.props.children(也就是Provider组件里面嵌入的组件,一般是页面的跟组件App组件),这样所有的组件都可以共享store。
然后再看connect方法。connect方法是一个双重嵌套的方法(专业名词叫函数柯里化)里面的方法接受一个组件并且返回一个组件,正式高阶组件的用法,外面的函数接受mapStateToProps和mapDispatchToProps两个参数,mapStateToProps是用来把store里面的数据映射到组件的props属性中,mapDispatchToProps是把用户自己定义的action映射到组件的props属性中。
在componentDidMount方法里面执行了store.subscribe(() => this.update())这句代码,是因为每次使用dispatch触发一个action的时候都要执行一下update方法,即重新获取store数据并映射到组件中去,这样才能保证store数据发生变化,组件props能同时跟着变化。
bindActionCreators方法是用来把每一个action用dispatch方法包裹一下,因为action可能只是返回一个具有type属性的对象,只有用dispatch执行action才有意义。
到此为止,一个没有中间件的不支持异步dispatch的简洁版的redux已经实现了,创建一个demo,就可以看到效果了// 创建index.js 作为项目入口文件,大家可以自己添加action和reducer,就可以查看效果 import React from 'react'; import ReactDOM from 'react-dom'; import { createStore, applyMiddleware } from './mini-redux'; import { counter } from './index.redux' import { Provider } from './mini-react-redux'; import App from './App' const store = createStore(counter); ReactDOM.render( ( <Provider store={store}> <App /> </Provider> ), document.getElementById('root'))
支持中间件和异步action的redux实现
上面实现了简洁版的redux,再此基础上添加支持中间件的代码
// 修改mini-redux.js为 export function createStore(reducer, enhancer) { if(enhancer) { return enhancer(createStore)(reducer) } let currentStore = {}; let currentListeners = []; function getState() { return currentStore; } function subscribe(listener) { currentListeners.push(listener); } function dispatch(action) { currentStore = reducer(currentStore, action); currentListeners.forEach(v => v()); // return dispatch; } dispatch({type: '@ZQT-REDUX'}); return {getState, subscribe, dispatch} } export function applyMiddleware(...middlewares) { return createStore=>(...args)=> { // 这里args 就是上面createStore 传过来的reducers const store = createStore(...args) let dispatch = store.dispatch // 暴漏 getState 和 dispatch 给 第三方中间价使用 const midApi = { getState: store.getState, dispatch: (...args) => dispatch(...args) } // 创造第三方中间件使用 middlewareAPI 后返回的函数组成的数组 const middlewareChain = middlewares.map(middleware => middleware(midApi)) // 结合这一组函数 和 dispatch 组成的新的 dispatch,然后这个暴漏给用户使用,而原有的 store.dispatch 是不变的,但是不暴漏 dispatch = compose(...middlewareChain)(store.dispatch); return{ ...store, dispatch } } } export function compose(...funcs) { if(funcs.length === 0){ return arg => arg } if(funcs.length === 1) { return funcs[0] } return funcs.reduce((ret, item) => (...args) => item(ret(...args))); }
createStore方法修改了一下,多接受了一个enhancer方法,enhancer就是在index.js创建store的时候传过来的applyMiddleware方法。判断是否传了enhancer参数,如果有就return enhancer(createStore)(reducer)
applyMiddleware方法接受多个中间件作为参数,这个方法的最终目的就是创建一个新的dispatch属性,新的dispatch属性是经过中间件修饰过的,并且暴露这个新的dispatch属性,原来的dispatch属性不变。
compose方法是一个可以吧compose(fn1,fn2,fn3)(arg)转为fn3(fn2(fn1(arg)))的方法,也就是fn1的执行结果作为fn2的参数,fn2的执行结果作为fn1的参数,依次类推。正好可以利用reduce的特性实现这个效果。
const thunk = ({getState, dispatch}) => next => action => { // 如果是函数 就执行action if(typeof action === 'function') { return action(dispatch, getState) } return next(action) } export default thunk
异步action在定义的时候返回的就是一个接受一个dispatch的方法,所以如果action是一个函数,就吧dispatch和getState方法传给该action,并且执行该action。如果不是一个函数,就直接返回action。
到此为止一个支持中间件的redux就实现了,该demo只是为了学习redux的思想,不能作为真正的redux来使用,有很多类型检查代码都省略了从实现迷你版的redux可以体会到redux精巧的设计和函数式编程的魅力,有队函数式编程感兴趣的可以看一下这篇文章https://llh911001.gitbooks.io...
github源码地址:https://github.com/zhuqitao/z...