为什么需要 Redux

宗旨,就是为了解决状态难以管理,给出统一的管理方式,使得状态更加追踪、维护

  • JavaScript 的应用,本身状态越来越多,比如用户操作的数据、UI 状态等等
  • 状态之间可能存在依赖,一个状态需要依赖另一个状态的变化

Redux 三大原则

  • 单一数据源:整个应用的 state 被存储到 Store 中,让 state 方便维护、追踪、修改
  • state 是只读的:不可以直接修改 state,只能通过 dispatch 一个 action 去改变 state
  • 纯函数修改 State:通过 Reducer 将 state 和 action 联系起来,返回新的 state,Reducer 应是纯函数,不产生任何副作用

Redux 基本概念

  • Store:存储所有 state,可以理解仓库
  • Action:一个动作,表示操作 state 的“动作”
  • Reducer:用于接收 Action,并按照“自己的方式”去改变 state
  • Dispatch:用于发出 Action

Redux 核心 API

  • 1.createStore 可以用来创建 store 对象
  • 2.store.dispatch 派发 action , action 会执行 reducer
  • 3.reducer 接收 action,reducer 计算出新的状态并返回
  • 4.store.getState 这个方法可以帮助获取 store 里边所有的数据内容
  • 5.store.subscribe 方法可以让让我们订阅 store 的改变,触发函数
  • 6.combineReducer 将多个 Reducer 合并成一个新的 reducer
  • 7.applyMiddleware redux 中间件,合并 middleware,并返回新的 dispatch 和 state
  • 8.bindActionCreators actions 及 dispatch 混入

Redux 官方流程图

img

简单手写 Redux

首先,我们基于上面的流程,先创建一个 Store,用来存储 State

// createStore.js
export default function createStore(reducer, initState) {
    let state = initState;
    let listeners = [];
    function subscribe(listener) {
        listeners.push(listener);
    };
    function getState() {
        return state;
    }
    function dispatch(action) {
        state = reducer(state, action);
        for (let i = 0; i < listeners.length; i++) {
            listeners[i]();
        }
    }
    return {
        subscribe,
        getState,
        dispatch,
    }
}

可以看到,核心就是一个发布订阅,接着我们去定义简单的

// counter.js
let initState = {
    count: 0
}
export default function counterReducer(state, action) {
    if (!state) {
        state = initState;
    }
    switch (action.type) {
        case 'changeCount':
            return {
                ...state,
                count: action.payload.count
            }
        default:
            return state;
    }
}

Reducer 比较简单,我们只需要去判断 Reducer 的场景,然后去更新 state,接着,我们需要去思考,我们的应用不止一个 State,如果都将业务逻辑写到一个 Reducer 中,改变一个 State,可能造成整个 State 的变更,为了解决这个问题,我们要将 Reducer 组合起来

// combineReducers.js
export default function combineReducer(reducers) {
    const reducerKeys = Object.keys(reducers);
    // 合并reducers,形成新的reducer
    return function combine(state = {}, action) {
        const nextState = {};
        for (let i = 0; i < reducerKeys.length; i++) {
            const key = reducerKeys[i];
            const reducer = reducers[key];
            const prevState = state[key];
            const nextStateForKey = reducer(prevState, action);
            nextState[key] = nextStateForKey;
        }
        return nextState;
    }
}
// useage
// const reducer = combineReducers({
//     info,
//     counter
// })

在 combineReducers 中,我们将我要要合并的 Reducer 对象,返回新的 Reducer 方法,执行每个 Reducer,返回的 State,这样,我们就实现了多个 Reducer 的合并,可以拆分我们的业务逻辑,接着,我们看 Redux 中间件如何开发

// loggerMiddleware.js
const loggerMiddleware = (store) => (dispatch) => (action) => {
    console.log('state', store.getState());

    try {
        dispatch(action);
        console.log('action', action);

    } catch (e) {
        console.error('Error=>', e)
    }
    console.log('next state', store.getState());
}

export default loggerMiddleware;

middleWare 其实就是一个柯里化函数,目的就是增强 Store 能力,扩展 dispatch 的能力,比如我们可以通过 middleWare 拦截 dispatch 和 action,做一些同步异步的处理、状态监听等等操作,middleWare 有了,我们要如何赋予到 Store 上呢,那就是 applyMiddleware

// applyMiddleware.js
import compose from './compose.js'

const applyMiddleware = function (...middlewares) {
    return function (Store) {
        return function (reducer, initialState) {
            const store = Store(reducer, initialState); // createStore();
            const state = { getState: store.getState };
            const chain = middlewares.map((middleware) => {
                return middleware(state)  // 我们这里只需要store中的getState,当然,我们也可以将store传入
            });
            const dispatch = compose(...chain)(store.dispatch);
            return {
                ...store,
                dispatch
            }
        }
    }
}

export default applyMiddleware;

现在,applyMiddleware 要实现上面的柯里化函数,首先我们需要 store 和 dispatch,我们可以利用 createStore,来获取我们要用到的 store 和 dispatch,但是发现 applyMiddleware 也是一个柯里化函数,我们怎么执行呢,那么就要在 createStore 中去判断,我们的目的就是扩展 dispatch,所以修改下 createStore.js

export default function createStore(reducer, initState, newDispatch) {
    // 传递Store,返回一个新的Store
    if (newDispatch) {
        const newCreateStore = newDispatch(createStore);
        return newCreateStore(reducer, initState);
    }
    let state = initState;
    let listeners = [];
    function subscribe(listener) {
        listeners.push(listener);
    };
    function getState() {
        return state;
    }
    function dispatch(action) {
        state = reducer(state, action);
        for (let i = 0; i < listeners.length; i++) {
            listeners[i]();
        }
    }

    function replaceReducer(nextReducer) {
        reducer = nextReducer;
    }
    return {
        subscribe,
        getState,
        dispatch,
        replaceReducer
    }
}

如果有 middleware 需要组合,那么我们就重载掉之前的 dispatch,并传入我们需要的 Reducer 和 state,但是目前多个 middleware 还未解决,我们看下 componse 函数

// componse.js
export default function compose(...funcs) {
    if (funcs.length === 0) {
        return (arg) => arg;
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => {
        return (...args) => a(b(...args))
    })
}

我们可以看到,componse 就是组合函数,通过 reduce 实现,类似 Koa 的洋葱模型,组合函数,但是我们的函数将自上而下执行

// 目前我们的middleware是一个数组,虽然传入了store,但是是一个Function的Array
// (next)=>(action)=>{}
// (next)=>(action)=>{}
// (next)=>(action)=>{}
// (next)=>(action)=>{}

/**
* 如果我们使用了函数组合,就是下面这样的,因为我们的middleware都需要dispatch和action,而且返回值也是diapatch和action
*/
(next)=>(action)=>{
    (next)=>(action)=>{
        (next)=>(action)=>{

        }
    }
}

上面,我们已经实现了一个简单的 Redux,接着我们去扩展 action,让我们的 action 通过反柯里化的方式,更加易用,那么就是我们要实现的 bindActionCreators

// bindActionCreators.js
function boundActionCreator(actionCreator, dispatch) {
    return function () {
        return dispatch(actionCreator.apply(this, arguments));
    }
}

export default function bindActionCreators(bindActionCreators, dispatch) {
    const boundActionCreators = {};
    for (const key in bindActionCreators) {
        const actionCreator = bindActionCreators[key];
        if (typeof actionCreator === 'function') {
            boundActionCreators[key] = boundActionCreator(actionCreator, dispatch);
        }
    }
    return boundActionCreators;

}

我们通过反柯里化的方式,将待执行函数挂载到对象上,更加明确我们的业务逻辑,简单看下用法

// usage
const actions = bindActionCreators({
    setCounter,
    setInfo,
}, store.dispatch);
actions.setCounter();
actions.setInfo();

// counter.js
const setCounter = () => {
    return {
        count: 99
    }
}

export {
    setCounter
}

// info.js
const setInfo = () => {
    return {
        name: '🍊 duanxinlei action',
        description: '🍊 duanxl action desc'
    }
}

export {
    setInfo
}

Redux 总结

  • Redux 是一个状态机,我们通过内部的 Store 存储了我们的 State,为了统一管理 State,我们只能通过 dispatch(action)去改变 state
  • subscribe 方法注册回调函数,用于监听 State 的变化,就是通过发布订阅的模式
  • Redux 的 middleware 是一个装饰者模式,传入 dispatch,返回了扩展后的 dispatch
  • Redux 目前只能存储逻辑状态,并不能存储 UI,如果我们要使用到 React 中,并且圆滑的应用到 component 中,比如数据监听,改变试图,那么还需要去深入 react-redux,之后计划去深入 react-redux
0