最近研究了Redux源码,作为React技术栈中的一份子,保持了其特有的小巧专一。代码总共不过百来行,其中包含了状态机,发布订阅模式以及高阶函数的应用,也确实值得我们学习借鉴。(以下源码略作修改和注释)
1、三大原则
单一数据源
整个应用的state被储存在一颗object tree中,并且这个object tree只存在于唯一
State是只读的
唯一改变state的方法就是触发action, action是一个已发生事件的普通对象
使用纯函数来执行修改
为了描述action如何改变state tree,就需要reducer
源码目录
util/isPlainObject
// 判断是不是纯对象,而非构造函数export default function isPlainObject(obj) { if(typeof obj != 'object' || obj === null) { return false; } let proto = obj; // 取到obj最终_proto_指向 while(Object.getPrototypeOf(proto)) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto;}复制代码
此纯对象的判断,判断对象最初的_proto_指向和_proto_最终指向的判断是否为同一个来判断
util/actionTypes.js
const ActionTypes = { INIT: '@@redux/INIT'}export default ActionTypes;复制代码
createStore.js
作为redux中的C位,其中的方法包括getState, dispatch, subscribe方法
export default function createStore(reducer, preloadedState) { if(typeof reducer != 'function') { throw new Error('reducer不是函数') } let currentReducer = reducer; // 状态处理器 let currentState = preloadedState; // 初始状态 let currentListeners = []; // 监听函数队列 function getState() { return currentState; } function dispatch(action) { if(!isPlainObject(action)) { throw new Error('action不是纯对象') } if(typeof action.type == 'undefined') { throw new Error('action的type未定义') } // 获取当前state currentState = createReducer(currentState, action); // 发布订阅模式 for(let i=0; i
combineReducers.js
将多个reducer合并到一个对象中,方便维护
export default function(reducers) { // 返回reducers名称的数组 const reducerKeys = Object.keys(reducers); return function (state = {}, action) { const nextState = {}; for(let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i]; const reducer = reducers[key]; const previousStateForKey = state[key]; // 老状态 const nextStateForKey = reducer(previousStateForKey, action); // 新状态 nextState[key] = nextStateForKey; } return nextState; }}复制代码
bindActionCreators.js
将dispatch整合到action中
function bindActionCreator(actionCreator, dispatch) { return function() { return dispatch(actionCreator.apply(this, arguments)) }}/** * * @param {Function|Object} actionCreators * @param {Function} dispatch 对redux Store生效的dispatch方法 * @returns {Function|Object} 如果返回对象的话类似与原始对象,但是每个对象元素添加了dispatch方法 */export default function bindActionCreators(actionCreators, dispatch) { if(typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } if(typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators应该是一个对象或者函数类型, 而不是接收到${ actionCreators === null ? 'null' : typeof actionCreators }. ` + `是不是这样引用 "import ActionCreators from" 而不是 "import * as ActionCreators from"?` ) } // actionCreators为对象的时候 const boundActionCreators = {} for (const key in actionCreators) { const actionCreator = actionCreators[key] if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } } return boundActionCreators}复制代码
compose.js
这一块灵活应用了reduce方法和高阶函数,实现参数函数从右向左执行
/** * * @param {...any} funcs * @returns 包含从右向左执行的函数参数。比如,compose(f, g, h) 等同于 (...args) => f(g(h(...args))) */export default function compose(...funcs) { if(funcs.length === 0) { return arg => arg } if(funcs.length === 1) { return funcs[0] } return funcs.reduce((a, b) => (...args) => a(b(...args)))}复制代码
applyMiddleware.js
import compose from './compose'export default function applyMiddleware(...middlewares) { return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error('构造中间键的时候不能运行dispatch,否则其他中间键将接收不到dispatch') } const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) } const chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } }}复制代码
index.js
import createStore from './createStore';import combineReducers from './combineReducers';import bindActionCreators from './bindActionCreators';import applyMiddleware from './applyMiddleware';export { createStore,//创建仓库 combineReducers,//合并reducers bindActionCreators,//把actionCreator 和 dispatch方法绑定在一起 applyMiddleware}复制代码
中间键实践
redux-thunk中间键实现
function createThunkMiddleware(extraArgument) { return ({dispatch, getState}) => next => action => { if(typeof action === 'function') { return action(dispatch, getState, extraArgument) } else { next(action) } }}const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;复制代码
redux-promise
// Promise判断:有then方法的函数对象function isPromise(obj) { return !obj&&(typeof obj == 'object' || typeof obj == 'function') && (typeof obj.then == 'function')}export default function({dispatch, getState}) { return next => action => { return isPromise(action.payload)? action.payload.then(result => { dispatch({...action, payload: result}) }).catch((error) => { dispatch({...action, payload:error, error: true}); return Promise.reject(error) }) : next(action) }}复制代码
React-Redux实现
connect.js
import React, {Component} from 'react';import { bindActionCreators } from '../redux';import ReduxContext from './context';export default function(mapStateToProps, mapDispatchToProps) { return function(WrappedComponent) { return class extends Component { static contextType = ReduxContext; constructor(props, context) { super(props); this.state = mapStateToProps(context.store.getState()); } componentDidMount() { this.unsubscribe = this.context.store.subscribe(() => { this.setState(mapStateToProps(this.context.store.getState())) }) } componentWillMount() { this.unsubscribe() } render() { let actions = {} if(typeof mapDispatchToProps == 'function') { actions = mapDispatchToProps(this.context.store.dispatch) } else { actions = bindActionCreators(mapDispatchToProps, this.context.store.dispatch); } return } } }}复制代码
Provider.js
import React, { Component } from 'react';import ReduxContext from './context';export default class Provider extends Component { render() { return ( {this.props.children} ) }}复制代码
context.js
import React from 'react';const ReduxContext = React.createContext(null);export default ReduxContext;复制代码
index.js
import Provider from './Provider';import connect from './connect';export { Provider, connect}
作者:金金金呀
链接:https://juejin.im/post/5e9042ad6fb9a03c3f1ea7c0
来源:掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。