从零实现redux和react-redux

javascript/jquery

浏览数:248

2020-5-26

一、redux简介

redux本质是一个全局的状态管理器,主要用于跨级组件数据传递以及状态的统一管理。作为一个状态管理器,当其中的数据发生变化的时候,外部应该可以通过某种方式获取变化后的状态,其内部采用了发布订阅模式。外部通过调用其subscrible()方法,传递回调监听器函数,当调用dispatch()方法导致状态发生变化的时候,监听器执行,再通过getState()方法获取到最新的状态。

二、从零实现一个redux

发布订阅模式

// redux/index.js
export function createStore(initState) {
    let state = initState; // 保存初始化状态
    const listenters = []; // 保存订阅的监听器回调函数
    function subscribe(listener) {
        listenters.push(listener);
    }
    // 传递一个新的状态并替换旧的状态
    function dispatch(newState) {
        state = newState;
        listenters.forEach((listener) => { // 状态改变后遍历监听器函数并执行
            listener();
        });
    }
    function getState() { // 获取store中的最新状态
        return state;
    }
    // 返回store对象,并且让store对象具有以下订阅、改变状态、获取状态的能力
    return {
        subscribe,
        dispatch,
        getState
    }
}

这里我们让redux对外暴露了一个createStore()方法,接收一个最初的状态数据,然后将其保存起来作为store的state,返回一个store对象。store对象具有订阅改变状态获取状态的能力。当派发了一个新的状态后,保存最新状态数据并遍历订阅的监听器,让其依次执行。

reducer指定状态修改规则(action)

这里有一个问题,就是我们可以给store对象中派发任何状态数据,也就是说,store中的state可以任意改变不受约束。所以我们需要指定改变的规则,即派发指定的action操作而不是直接派发最新的状态数据。我们可以在执行dispatch()方法改变数据的时候,调用一个函数根据派发的操作执行特定的修改,而这个修改与store内部没有关系,由外部定义,所以我们需要将这个包含指定修改规则的函数传入到store内部,然后在dispatch中将action传入并调用这个函数即可,这个函数在redux中叫做reducer,并且作为createStore()方法的第一个参数传入
数组中有一个reduce方法,其作用就是遍历数组,将上一次迭代的结果数组中的元素作为参数传入,返回一个新的结果,并且将这个新的结果作为作为参数和数组的下一个元素进行下一次迭代。所以我们可以把进行reduce操作的函数叫做reducer。只不过,redux迭代的是一次又一次的action,每次action执行完成后都会返回一个新的状态,并且将这个新的状态和下一次action用于下一次迭代

export function createStore(reducer, initState) {
    let state = initState; // 保存初始化状态
    const listenters = []; // 保存订阅的监听器回调函数
    function subscribe(listenter) {
        listenters.push(listenter);
    }
    // 传递一个新的状态并替换旧的状态
    function dispatch(action) {
        state = reducer(state, action); // 相当于是遍历action,返回最新的状态并用于下次遍历,state相当于是一个累加的过程
        listenters.forEach((listenter) => { // 状态改变后遍历监听器函数并执行
            listenter();
        });
    }
    function getState() { // 获取store中的最新状态
        return state;
    }
    // 创建store后立即派发一个不存在的action,异步初始化状态数据
    dispatch({
        type: `@@redux/INIT${Math.random()}`
    });
    // 返回store对象,并且让store对象具有以下订阅、改变状态、获取状态的能力
    return {
        subscribe,
        dispatch,
        getState
    }
}

action就是带有type属性的一个普通js对象dispatch就是能够处理action的函数,即接收action作为参数的函数。这里在store对象创建好之后,会立即发起一次dispatch,传递的是一个不存在的action,目的是为了对store中的state状态数据进行初始化

用法如下所示:

import {createStore} from "./redux";
const initState = { // 最初的状态
    count: 0
}
// 将最初的状态传入reducer中
const reducer = function(state = initState, action) {
    switch(action.type) {
        case "INCREMENT":
            state.count++;
            return state;
        default:
            return state;
    }
}
const store = createStore(reducer);
store.subscribe(() => {
    console.log(store.getState());
});
console.log(store.getState()); // 由于派发了一个无法匹配的action,所有此时store中state已经初始化,不为undefined了
store.dispatch({
    type: "INCREMENT"
});

state和reducer的拆分与合并

此时,我们已经基本实现了一个redux,但是其中的reducer和最初的state数据都只有一个,如果项目非常大的话,就会需要维护很多状态数据和reducer,将他们放在一起将变得非常难于维护,所以需要对state和reducer进行拆分可以根据不同的页面或组件进行拆分。比如,

// store/counter/state.js
export default {
    count: 0
}
// store/counter/reducer.js
import state from "./state";
function reducer(prevState = state, action) {
    switch(action.type) {
        case "INCREMENT":
            prevState.count++;
            return prevState;
        default:
            return prevState;
    }
}
export default reducer;
// store/user/state.js
export default {
    name: "lihb"
}
// store/user/reducer.js
import state from "./state";
function reducer(prevState = state, action) {
    switch(action.type) {
        case "CHANGE_NAME":
            prevState.name = action.name;
            return prevState;
        default:
            return prevState;
    }
}
export default reducer;
// store/reducer.js
import counter from "./counter/reducer"
import user from "./user/reducer"
import {combineReducers} from "../redux"

const reducer = combineReducers({
    counter,
    user
});
export default reducer;

以上对state和reducer进行了拆分,现在我们需要的就是实现这个combineReducers()方法,其需要返回一个合并后的reducer()函数

// redux/index.js
export function combineReducers(reducers) {
    return (state = {}, action) => { // 返回合并后的reducer函数
        const newState = {};// 执行reducer后返回一个新的状态对象
        Object.keys(reducers).forEach((key) => {
            const preState = state[key]; // 获取上一次reducer返回的state值
            const reduce = reducers[key]; // 获取对应的reduce函数
            newState[key] = reduce(preState, action); // 执行reduce函数获取新的状态并保存到newState中
        });
        return newState;
    }
}

中间件

中间件(middleware),其实就是对dispatch的增强,可以在执行dispatch之前或之后做一些额外的事情。我们可以简单的理解成,中间件就是一个可以接收dispatch作为参数并返回一个新的dispatch的函数。即传入旧的dispatch然后返回一个新的dispatch,而返回的新的dispatch又可以继续传递给下一个中间件处理,从而不断地扩展dispatch的功能,如:

const store = createStore(reducer);
const next = store.dispatch; // 保存旧的dispatch并命名为next
// 中间件定义模板
const middleware = (next) => { // 传入一个旧的dispatch函数,返回一个新的dispatch函数
    return (action) => { // 返回一个新的dispatch,能够处理action的函数就是一个dispatch
        // do something
        next(action);
        // do something
    }
}
store.dispatch = middleware(next);

① 增强一下dispatch,让dispatch可以打印状态数据变化前后的值

const store = createStore(reducer);
const next = store.dispatch; // 保存旧的dispatch并命名为next
const loggerMiddleware = (next) => { // 传入一个旧的dispatch函数,返回一个新的dispatch函数
    return (action) => { // 返回一个新的dispatch,能够处理action的函数就是一个dispatch
        console.log(`before state change, state is ${JSON.stringify(store.getState())}`);
        next(action);
        console.log(`after state change, state is ${JSON.stringify(store.getState())}`);
    }
}
store.dispatch = loggerMiddleware(next);

② 再增强一下dispatch,让dispatch可以先打印执行时间,然后开始执行loggerMiddleware

const store = createStore(reducer);
const next = store.dispatch; // 保存旧的dispatch并命名为next
// 记录log的中间件
const loggerMiddleware = (next) => { // 传入一个旧的dispatch函数,返回一个新的dispatch函数
    return (action) => { // 返回一个新的dispatch,能够处理action的函数就是一个dispatch
        console.log(`before state change, state is ${JSON.stringify(store.getState())}`);
        next(action);
        console.log(`after state change, state is ${JSON.stringify(store.getState())}`);
    }
}
// 记录时间的中间件
const timeMiddleware = (next) => {
    return (action) => {
        console.log(`time is ${Date.now()}`);
        next(action);
    }
}
// loggerMiddleware(next)返回的还是next,故可以继续交给下一个中间件处理
store.dispatch = timeMiddleware(loggerMiddleware(next));

这里中间件的定义模板其实还存在一些问题,比如loggerMiddleware中间件内部是依赖了store的,当我们想单独抽离中间件放到一个独立文件中的时候,将变得非常麻烦,所以我们可以在中间件外面再包一层,当我们使用中间件的时候传入store即可拿到可以链式调用的中间件了。如下:

// 可以传入store的中间件定义模板
const middleware = (store) => {
    return (next) => { // 传入一个旧的dispatch函数,返回一个新的dispatch函数
        return (action) => { // 返回一个新的dispatch,能够处理action的函数就是一个dispatch
            // do something
            next(action);
            // do something
        }
    }
}
// 以上简化形式为
const middleware = (store) => (next) => (action) => {
     // do something
     next(action);
     // do something
}   

所以以上两个中间件可以变成如下形式:

// ./loggerMiddleware.js
const loggerMiddleware = (store) => (next) => (action) => {
    console.log(`before state change, state is ${JSON.stringify(store.getState())}`);
    next(action);
    console.log(`after state change, state is ${JSON.stringify(store.getState())}`);
}
export default loggerMiddleware;
// ./timeMiddleware.js
export default const timeMiddleware = (store) => (next) => (action) => {
    console.log(`time is ${Date.now()}`);
    next(action);
}
export default timeMiddleware;
// 引入中间件
import loggerMiddleware from "./loggerMiddleware";
import timeMiddleware from "./timeMiddleware"
const store = createStore(reducer);
const next = store.dispatch; // 保存旧的dispatch并命名为next
// 传入store拿到可以链式调用的中间件logger
const logger = loggerMiddleware(store);
// 传入store拿到可以链式调用的中间件time
const time = timeMiddleware(store);
store.dispatch = time(logger(next));

所以中间件的使用方式就是,给中间件并传入store拿到可链式调用的中间件函数然后进行链式调用拿到新的增强后的dispatch并重新赋值给store

applyMiddleware

applyMiddleware主要是为了更加优雅的使用中间件,其是一个函数,可以给其传入多个中间件,然后将这些中间件合成一个增强的dispatch并赋值给store。redux中的使用方式为:

import {createStore, applyMiddleware} from "redux"
// 作为第二个参数
const store = createStore(reducer, applyMiddleware(middleware1, middleware2, ...));
// 或者作为第三个参数
const store = createStore(reducer, initState, applyMiddleware(middleware1, middleware2, ...));

所以我们需要给redux实现applyMiddleware函数,根据以上使用方式,applyMiddleware()后的结果可以作为createStore的第二个参数也可以作为第三个参数,所以我们需要对第二个参数进行判断,我们可以让applyMiddleware()的执行结果为一个函数,如果第二个参数是一个函数,那么我们就把第二个参数当做是applyMiddleware()的执行结果,initState直接变为undefined,即将第二个参数的值交换一下变为第三个参数

那么applyMiddleware()返回的函数需要做什么事情呢?
其主要作用就是使用传入的所有中间件然后生成一个新的dispatch,然后赋值给store就可以了,所以我们可以在这里进行createStore的一个拦截操作,直接return,然后在applyMiddleware中创建store并更新dispatch,可以让applyMiddleware()返回的函数接收createStore,然后创建出store对象,接着将store对象传入中间件拿到可以链式调用的中间件函数生成增强后的dispatch并赋值给store,最终返回这个store即可。如下:

// redux.js createStore完成第二个参数的判断
export function createStore(reducer, initState, enhancer) { // 传入一个增强函数
    if (typeof initState === 'function' && typeof enhancer === 'undefined') { // 如果只传了第二个参数并且是一个函数,则后面两个参数互换位置
        enhancer = initState; // 将initState作为enhancer增强函数
        initState = undefined // 将initState变为undefined.
    } // 等价于createStore(reducer, undefined, initState);
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') { // 如果传了enhancer增强函数但其不是函数
            throw new Error('Expected the enhancer to be a function.')
        }
        // 使用了中间件后,直接返回进行createStore拦截,将createStore传入增强函数中处理
        return enhancer(createStore)(// 将createStore传入增强函数并产生一个新的createStore并传入reducer和initState执行生成store
            reducer,
            initState
        );
    }
}
// redux.js compose的实现
// 遍历可链式调用的中间件,然后返回一个可以接收next的函数
function compose(...funcs) {
    console.log(funcs.length);
    if (funcs.length === 0) {
        return (arg) => arg;
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => {
        return (next) => {
            return a(b(next));
        }
    });
    // return funcs.reduce((a, b) => (...args) => a(b(...args)));
}
// redux.js applyMiddleware的实现
// 批量使用所有的中间件
export function applyMiddleware(...middlewares) {
    // 返回一个函数用于createStore对第二个参数判断,并接收createStore
    // 如果createStore传入了第三个参数那么就会执行下面返回的函数,然后其会再返回一个函数接收reducer和initState
    return (createStore) => { // 返回一个函数,这个函数接收createStore作为参数
        return (reducer, initState) => { // 传入reducer和initState执行,即返回一个新的createStore并执行;
            const store = createStore(reducer, initState); // 执行原来的createStore拿到store
            let dispatch = () => {
                throw new Error("dispatch现在还不能使用");
            }; // 用于保存最新的dispatch
            // 构造一个middlewareAPI对象作为传入中间件的store,以便中间件可以拿到最新的dispatch,即合并中间件后的dispatch
            const middlewareAPI = {
                dispatch: (action, ...args) => {
                    return dispatch(action, ...args); // 使用最新的dispatch
                },
                getState: store.getState // 状态没有最新的,因为合并中间件只会修改dispatch,state不会改变,所以用store就可以获取到最新的状态数据了
            }
            // 遍历中间件并传入store拿到可以链式调用的中间件函数
            const chain = middlewares.map((middleware) => { // 遍历中间函数并传入{dispatch, getState}执行
                return middleware(middlewareAPI);
            });
            // 传入原来的dispatch,返回一个增强后的dispatch并覆盖掉原来的dispatch
            dispatch = compose(...chain)(store.dispatch);
            return { // 返回更新了dispatch的store
                ...store,
                dispatch
            }
        }
    }
}

compose()方法的作用就相当于是将一个个可以进行链式调用的中间件进行链式合并,最后一个中间件先执行,但是其中具体代码的执行顺序,要看放在next(action)之前还是之后,如:

// 给compose传入a,b,c三个中间件
compose([a, b, c])
// 将返回一个合并后的dispatch
(next) => {
    return a(b(c(next)));
}
// 此时传入旧的dispatch后将会依次执行c、b、a中间件

实现redux-thunk中间件

redux-thunk作为一个独立的中间件库,但是其代码就10行左右,非常简单,就导出了一个中间件函数,其作用就是可以让我们直接dispatch一个函数而不是action,从而可以实现延迟dispatch的效果,也就可以实现异步dispatch的效果。这个函数可以接收dispatch和getState,而函数内部则可以进行异步操作等异步操作完成后再dispatch

// redux-thunk实现
const thunk = ({dispatch, getState}) => (next) => (action) => {
    if (typeof action === "function") { // 如果派发的action是一个函数
        return action(dispatch, getState); // 将dispatch和getState传递给这个派发的函数,这样函数内部则可用延迟dispatch了
    }
    return next(action);
}
export default thunk;

实现unscrible方法

unscrible即取消订阅,当我们执行这个函数的时候,就会将监听器移除,从而取消订阅,可以在scrible订阅的时候返回一个函数,找到插入监听器的时候的索引,调用unscrible的时候移除即可,如:

// 修改subscribe返回一个取消订阅的方法即可
function subscribe(listenter) {
    listenters.push(listenter);
    return () => { // 返回一个取消订阅的方法
        const index = listenters.indexOf(listenter);
        listenters.splice(index, 1);
        console.log(`listenter${index} has been removed.`);
    }
 }

实现replaceReducer

reducer拆分后,和组件是一一对应了,当我们希望做组件按需加载的时候,reducer也可以跟着组件在必要的时候按需加载,然后用新的reducer替换掉老的reducer。其实现非常简单,就是用新的reducer替换掉老的reducer即可,然后dispatch一个无法匹配的action进行初始化即可,如:

function replaceReducer(nextReducer) {
    reducer = nextReducer;
    // 刷新一遍state的值,新来的reducer把自己的默认状态放到state树上去
    dispatch({type: Symbol()});
 }

实现bindActionCreators

我们把返回action的函数称为actionCreator把包含多个actionCreator的对象则称为actionCreators,bindActionCreators会返回一个对象,其作用就是将actionCreators对象进行映射,因为每个actionCreator是一个函数,所以可以将每个actionCreator映射为对象的一个方法,并且调用这个方法后自动将actionCreator函数返回的action进行派发出去,如:

/* 因为使用redux-thunk之后,可以直接派发一个函数了,
   所以action也可以是一个函数,函数接收dispatch即可
*/
const actionCreators = {
    syncAdd() { // 返回action对象的actionCreator
        return {type: "INCREMENT"}
    },
    asyncAdd() { // 返回函数的actionCreator
        return (dispatch) => {
            setTimeout(() => {
                dispatch({type: "INCREMENT"});
            }, 2000);
        }
    }
    
}
const actions = bindActionCreators(actionCreators, store.dispatch);
actions.syncAdd();
actions.asyncAdd();

简单说,bindActionCreators的作用就是执行actionCreator()函数返回的action并进行dispatch派发,并且返回结果一一对应,传入的是一个函数则返回一个函数,直接执行这个函数即可自动派发action;传入的是一个对象,则返回一个具有相同属性的对象,属性值为一个函数,通过返回的对象调用对应的属性方法即可自动派发action。

// 返回一个函数,并且dispatch派发actionCreator返回的action
function bindActionCreator(actionCreator, dispatch) {
    return () => {
        return dispatch(actionCreator.apply(this, arguments));
    }
}
export function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === "function") {
        return bindActionCreator(actionCreators, dispatch)
    }
    if (typeof actionCreators !== "object") {
        throw new Error("actionCreators is not plaint object");
    }
    const boundActionCreators = {};
    Object.keys(actionCreators).forEach((key) => {
        const actionCreator = actionCreators[key]; // 拿到对应的actionCreator生成函数
        if (typeof actionCreator === "function") { // 如果是一个函数
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); // 映射到boundActionCreators
        }
    });
    return boundActionCreators;
}

redux完整实现为:

// redux.js完整实现
export function createStore(reducer, initState, enhancer) { // 传入一个增强函数
    if (typeof initState === 'function' && typeof enhancer === 'undefined') { // 如果只传了第二个参数并且是一个函数,则后面两个参数互换位置
        enhancer = initState; // 将initState作为enhancer增强函数
        initState = undefined // 将initState变为undefined.
    } // 等价于createStore(reducer, undefined, initState);
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') { // 如果传了enhancer增强函数但其不是函数
            throw new Error('Expected the enhancer to be a function.')
        }
        return enhancer(createStore)(// 将createStore传入增强函数并产生一个新的createStore并传入reducer和initState执行生成store
            reducer,
            initState
        );
    }
    let state = initState; // 保存初始化状态
    const listenters = []; // 保存订阅的监听器回调函数
    function subscribe(listenter) {
        listenters.push(listenter);
        return () => { // 返回一个取消订阅的方法
            const index = listenters.indexOf(listenter);
            listenters.splice(index, 1);
            console.log(`listenter${index} has been removed.`);
        }
    }
    // 传递一个新的状态并替换旧的状态
    function dispatch(action) {
        state = reducer(state, action); // 相当于是遍历action,返回最新的状态并用于下次遍历,state相当于是一个累加的过程
        listenters.forEach((listenter) => { // 状态改变后遍历监听器函数并执行
            listenter();
        });
    }
    function getState() { // 获取store中的最新状态
        return state;
    }
    function replaceReducer(nextReducer) {
        reducer = nextReducer;
        // 刷新一遍state的值,新来的reducer把自己的默认状态放到state树上去
        dispatch({type: Symbol()});
    }
    dispatch({
        type: `@@redux/INIT${Math.random()}`
    });
    // 返回store对象,并且让store对象具有以下订阅、改变状态、获取状态的能力
    return {
        subscribe,
        dispatch,
        getState,
        replaceReducer
    }
}
export function combineReducers(reducers) {
    return (state = {}, action) => {
        const newState = {};
        // console.log(state);
        Object.keys(reducers).forEach((key) => {
            const preState = state[key]; // 获取上一次reducer返回的state值
            const reduce = reducers[key]; // 获取对应的reduce函数
            newState[key] = reduce(preState, action); // 执行reduce函数获取新的状态并保存到newState中
        });
        return newState;
    }
}
function compose(...funcs) {
    console.log(funcs.length);
    if (funcs.length === 0) {
        return (arg) => arg;
    }
    if (funcs.length === 1) {
        return funcs[0];
    }
    return funcs.reduce((a, b) => {
        return (next) => {
            return a(b(next));
        }
    });
}
// 批量使用所有的中间件
export function applyMiddleware(...middlewares) {
    return (createStore) => { // 返回一个函数,这个函数接收createStore作为参数
        return (reducer, initState) => { // 传入reducer和initState执行,即返回一个新的createStore并执行;
            const store = createStore(reducer, initState); // 执行原来的createStore拿到store
            let dispatch = () => {
                throw new Error("dispatch现在还不能使用");
            };
            // 构造一个middlewareAPI对象作为传入中间件的store,以便中间件可以拿到最新的dispatch,即合并中间件后的dispatch
            const middlewareAPI = {
                dispatch: (action, ...args) => {
                    return dispatch(action, ...args); // 使用最新的dispatch
                },
                getState: store.getState // 状态没有最新的,因为合并中间件只会修改dispatch,state不会改变,所以用store就可以获取到最新的状态数据了
            }
            const chain = middlewares.map((middleware) => { // 遍历中间函数并传入{dispatch, getState}执行
                return middleware(middlewareAPI);
            });
            dispatch = compose(...chain)(store.dispatch); // 传入原来的dispatch,返回一个增强后的dispatch并覆盖掉原来的dispatch
            return {
                ...store,
                dispatch
            }
        }
    }
}
// 返回一个函数,并且dispatch派发actionCreator返回的action
function bindActionCreator(actionCreator, dispatch) {
    return () => {
        return dispatch(actionCreator.apply(this, arguments));
    }
}
// 根据actionCreators的类型进与dispatch进行绑定,生成一个函数或者对象,函数执行的时候会自动派发actionCreator返回的action
export function bindActionCreators(actionCreators, dispatch) {
    if (typeof actionCreators === "function") { // 如果传递的actionCreators是函数
        return bindActionCreator(actionCreators, dispatch); // 则返回一个函数,函数执行的时候会自动派发actionCreators返回的action
    }
    if (typeof actionCreators !== "object") { // 如果传递的actionCreators不是函数也不是对象则抛出错误
        throw new Error("actionCreators is not plaint object");
    }
    // 传递的actionCreators是一个对象
    const boundActionCreators = {}; // 用于保存绑定结果
    Object.keys(actionCreators).forEach((key) => { // 遍历对象的各个属性
        const actionCreator = actionCreators[key]; // 拿到对应的actionCreator生成函数
        if (typeof actionCreator === "function") { // 如果是一个函数
            boundActionCreators[key] = bindActionCreator(actionCreator, dispatch); // 映射到boundActionCreators
        }
    });
    return boundActionCreators; // 当调用其中的方法的时候就会自动派发actionCreator返回的action
}

三、react-redux简介

react-redux是一个帮助redux与UI层进行连接的库。如果我们不用react-redux,那么我们在项目中应该如何使用redux呢?
每个需要与redux结合的组件,即需要用到redux中状态数据的组件,都需要做以下几件事:

  • 在组件中引入store对象
  • 在组件挂载完成后订阅store中状态数据的变化,即监听store中状态数据变化,然后在数据改变的时候调用setState()函数进行组件的更新
  • 在组件卸载的时候,移除对store状态数据变化的监听,即取消订阅
// ① 在组件中引入store对象
import store from "../../store"
class Counter extends React.Component {
    //② 组件挂载完成后监听store状态数据的变化
    componentDidMount() { 
        this.unsub = store.subscribe(() => {
            if (this.state.count ===  store.getState().counter.count) {
                return;
            }
            // 监听到数据变化后调用setState触发组件更新
            this.setState({
                count: store.getState().counter.count
            });
        });
    }
    componentWillUnmount() {
        this.unsub();// 组件卸载后取消对store状态数据的监听
    }
}

react-redux上是如何实现的呢?

  • 定义一个Provider组件,Provider组件接收store对象作为属性,然后在Provider组件内声明并创建其子组件的上下文对象,以便其子组件(Connect组件)能够拿到store对象。
  • 对外暴露一个connect函数用于连接组件,然后通过mapStateToProps和mapDispatchToProps将组件需要绑定的state和需要绑定的action,以便组件可以直接通过 this.props.属性名 获取到状态数据以及组件可以直接通过 this.props.方法名 进行派发action

四、从零实现一个redux-redux

① 实现Provider组件

新建一个redux-redux/components/Provider.js,其作用就是接收外部传递的store属性,然后生成其子组件上下文以便Connect组件能够获取到store对象

import {Component} from "react";
import PropTypes from "prop-types";

class Provider extends Component { // 定义一个Provider组件,以便Connect组件能够获取到store对象
    static childContextTypes = { // 定义子组件上下文对象的数据结构,里面有一个store对象
        store: PropTypes.shape({
            subscribe: PropTypes.func.isRequired,
            dispatch: PropTypes.func.isRequired,
            getState: PropTypes.func.isRequired
        }).isRequired
    }
    constructor(props) {
        super(props);
        this.store = props.store; // 保存通过props属性注入到Provider组件的store对象
    }
    getChildContext() { // 获取子组件的上下文对象
        return { // 返回子组件上下文对象
            store: this.store
        }
    }
    render() { // Provider组件仅仅是包裹一下其中的子组件,直接渲染其中的子组件即可
        return this.props.children;
    }
}
export default Provider;

② 实现connect函数

新建一个redux-redux/connect/connect.js,其作用就是对外暴露一个connect函数,接收mapStateToProps和mapDispatchToProps作为参数,再返回一个函数用于接收需要连接的组件,然后返回一个新的组件,即连接后的Connect组件Connect组件通过Provider组件提供的上下文拿到store,然后将store中的数据传递给mapStateToProps和mapDispatchToProps,返回需要的state和action,最后再以props的方式传递给组件,以便可以同this.props的方式使用。

import React, {Component} from "react";
import PropTypes from "prop-types";
import {bindActionCreators} from "redux"
export function connect(mapStateToProps, mapDispatchToProps) { // 接收mapStateToProps和mapDispatchToProps返回一个函数用于接收要连接的组件
    if (!mapStateToProps) { // 如果没有传递mapStateToProps,给其一个默认值
        mapStateToProps = (state, props) => ({}); // 只返回一个{}对象
    }
    if (!mapDispatchToProps) { // 如果没有传递mapDispatchToProps,给其一个默认值
        mapDispatchToProps = (dispatch, props) => ({dispatch}); 
    }
    return (WrapperComponent) => { // 接收到要连接的组件后,执行函数返回一个新的Connect组件
        return class Connect extends Component {
            // 父子组件都要定义上下文对象的数据结构,才能通过context拿到上下文中的数据
            static contextTypes = { // 定义上下文对象的数据结构,里面有一个store对象
                store: PropTypes.shape({
                    subscribe: PropTypes.func.isRequired,
                    dispatch: PropTypes.func.isRequired,
                    getState: PropTypes.func.isRequired
                }).isRequired
            }
            constructor(props, context) { // 被Connect组件包裹后,传递到原来组件上的props其实就传到了Connect组件上,只不过Connect组件和原来的组件同名而已
                super(props, context);
                this.store = context.store; // 保存上下文传入的store
                this.state = mapStateToProps(this.store.getState(), this.props);
                if (typeof mapDispatchToProps === "function") { // 如果传入的mapDispatchToProps是一个函数
                    this.mappedDispatch = mapDispatchToProps(this.store.dispatch, this.props); // 将dispatch传递出去由组件自己派发
                } else { // 如果传递的mapDispatchToProps是一个对象,比如由actionCreator组成的对象actionCreators
                    // 将这个对象进行映射,并且执行actionCreator拿到action,然后通过dispatch派发出去,也就是说传递对象,那么可以自动派发action
                    this.mappedDispatch = bindActionCreators(mapDispatchToProps, this.store.dispatch);
                }
            }
            componentDidMount() {
                this.unsub = this.store.subscribe(() => { // 监听store数据变化
                    const mappedState = mapStateToProps(this.store.getState()); // 数据变化后重新映射
                    this.setState(mappedState); // 通过setState进行更新组件。
                });
            }
            componentWillUnmount() {
                this.unsub();
            }
            render() {
                return (
                    // 将映射后的state和action以props的方式再次注入给组件,方便通过props进行调用
                    <WrapperComponent {...this.state} {...this.props} {...this.mappedDispatch}></WrapperComponent>
                );
            }
        }
    }
}

③ 对外暴露connect和Provider

新建一个redux-redux/index.js,对外暴露connect函数和Provider组件即可。

import Provider from "./components/Provider";
import {connect} from "./connect/connect";
export {
    Provider,
    connect
}

作者:JS_Even_JS