实现一个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...
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。