redux 源码分析,实现一个迷你的redux

51次阅读

共计 5005 个字符,预计需要花费 13 分钟才能阅读完成。

实现一个 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 来使用,有很多类型检查代码都省略了
github 源码地址:https://github.com/zhuqitao/z…

正文完
 0