一、首先让咱们看看都有哪些内容javascript
二、让咱们看看redux的流程图html
Store:一个库,保存数据的地方,整个项目只有一个java
建立storereact
Redux提供 creatStore 函数来生成 Storegit
// 引入redux import { createStore } from 'redux'; //建立Store 须要传递一个函数fn 这里的fn是以后会说起的reducers const store = createStore(fn);
State:状态,某时刻的数据便是Store的状态github
获取状态的方法是store.getState()json
Action:行为,它有一个不可或缺的type属性
redux
action还能够携带其余内容api
咱们可使用action来改变State的值,数组
从而将咱们须要的数据经过Action“运输”到 Store;
dispatch:发送action
dispatch(action)接受一个action对象为参数,并将它发送出去,
Store接受Action,接受以后须要返回一个新的State(状态)
Reducer:处理器
dispatch(action)接受一个action对象为参数,并将它发送出去,
Store接受Action,接受以后须要返回一个新的State(状态)
而建立这个新的状态的过程就是reducer
三、从isPlainObject.js开始
/** * @param {any} obj The object to inspect. * @returns {boolean} True if the argument appears to be a plain object. */ export default function isPlainObject(obj) { if (typeof obj !== 'object' || obj === null) return false let proto = obj while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto) } return Object.getPrototypeOf(obj) === proto }
· 这个函数的核心思想在于什么呢?
在于判断一个值是否为一个普通的对象
此处的普通对象指的是直接经过字面量(let obj={})或者new Object()建立出来的对象
· 那么他是怎么作判断的呢?
if (typeof obj !== 'object' || obj === null) return false
这行代码排除掉确定不是对象的值
注意:typeof null 的返回值为 "object". 因此只使用 typeof obj !== 'object' 不能将 null 值排除掉.
所以应使用 typeof obj !== 'object' || obj === null 进行判断.
再往下就是经过原型链判断了.
经过 while 不断地判断 Object.getPrototypeOf(proto) !== null 并执行,
最终 proto 会指向 Object.prototype. 这时再判断 Object.getPrototypeOf(obj) === proto,
若是为 true 的话就表明 obj 是经过字面量或调用 new Object() 所建立的对象了.
Object.getPrototypeOf()
方法用于获取一个对象的原型属性指向的是哪一个对象.
举个🌰: 假设有一个构造器:function Fun(){} 建立一个对象:var f = new Fun() Object.getPrototypeOf(f) 获得的返回值 和访问 f.__proto__ 是同样的 这个值指向 Fun.prototype.
假如一个对象是普通对象
那么这个对象的 __proto__ 必定是指向 Object.prototype 的,
而非普通对象, 例如 f, 其 __proto__ 是指向其构造函数的 prototype 属性.
所以比较 Object.getPrototypeOf(obj) 与 proto 相等, 则断定 obj 是普通对象.
四、接下来是createStore.js
//若是第二个参数为方法且第三个参数为空,则将两个参数交换
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') { enhancer = preloadedState preloadedState = undefined }
//不支持这样写
里面的几个函数
getState()
//返回当前state树
function getState() { if (isDispatching) { throw new Error( 'You may not call store.getState() while the reducer is executing. ' + 'The reducer has already received the state as an argument. ' + 'Pass it down from the top reducer instead of reading it from the store.' ) } return currentState }
subscribe()
//这个函数用于给store添加监听函数,把须要添加的监听函数做为参数传入便可
//nextListeners 即为目前的监听函数列表,添加了以后,subscribe方法会返回一个unsubscribe()方法
//此方法用于注销刚才添加的监听函数。
function subscribe(listener) { if (typeof listener !== 'function') { throw new Error('Expected the listener to be a function.') } if (isDispatching) { throw new Error( 'You may not call store.subscribe() while the reducer is executing. ' + 'If you would like to be notified after the store has been updated, subscribe from a ' + 'component and invoke store.getState() in the callback to access the latest state. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } let isSubscribed = true ensureCanMutateNextListeners() nextListeners.push(listener) return function unsubscribe() { if (!isSubscribed) { return } if (isDispatching) { throw new Error( 'You may not unsubscribe from a store listener while the reducer is executing. ' + 'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.' ) } isSubscribed = false ensureCanMutateNextListeners() const index = nextListeners.indexOf(listener) nextListeners.splice(index, 1) } }
dispatch()
function dispatch(action) { //action必须是一个包含type的对象 if (!isPlainObject(action)) { throw new Error( 'Actions must be plain objects. ' + 'Use custom middleware for async actions.' ) } if (typeof action.type === 'undefined') { throw new Error( 'Actions may not have an undefined "type" property. ' + 'Have you misspelled a constant?' ) } //若是正处于isDispatching状态,报错 if (isDispatching) { throw new Error('Reducers may not dispatch actions.') } try { isDispatching = true //这里就是调用咱们reducer方法的地方,返回一个新的state做为currentState currentState = currentReducer(currentState, action) } finally { isDispatching = false } //调用全部的监听函数 const listeners = currentListeners = nextListeners for (let i = 0; i < listeners.length; i++) { const listener = listeners[i] listener() } return action }
function replaceReducer(nextReducer) { if (typeof nextReducer !== 'function') { throw new Error('Expected the nextReducer to be a function.') } currentReducer = nextReducer dispatch({ type: ActionTypes.REPLACE }) }
替换reducer以后从新初始化状态树
//是一种观察者模式的思想
function observable() { const outerSubscribe = subscribe return { /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */ subscribe(observer) { if (typeof observer !== 'object' || observer === null) { throw new TypeError('Expected the observer to be an object.') }
//观察者模式的链式结构,传入当前的state
function observeState() { if (observer.next) { observer.next(getState()) } } observeState() const unsubscribe = outerSubscribe(observeState) return { unsubscribe } }, [$$observable]() { return this } } }
五、接下来就是compose.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) => (...args) => a(b(...args))) }
reduce方法接受2个参数,第一个参数是一个callback函数,第二个是一个初始值initValue
第一个函数有四个参数
若是有initValue,initValue将做为第一次的previousValue,若没有,则数组第一个元素将做为previousValue,
后面一个元素将做为currentValue,而后执行callback的函数体,将返回的值做为previousValue,
将下一个元素做为currentValue,一直到最后一个数组最后一个元素执行完位置,再返回最终的结果。
好比有一个数组arr=[1,2,3,4,5],咱们使用reduce来求和:
let sum = [1,2,3,4,5].reduce((a,b)=>a+b);
它巧妙的地方在于数组的每一个元素都是函数,
callback返回一个复合函数做为previousValue,在reduce方法执行完以后,
也就返回了一个将整个数组中全部函数串式调用的一个函数。
六、而后是applyMiddleware.js
export default function applyMiddleware(...middlewares) {
//return一个函数,能够接收createStore方法做为参数
//给返回的store的dispatch方法再进行一次包装
return createStore => (...args) => { const store = createStore(...args) let dispatch = () => { throw new Error( `Dispatching while constructing your middleware is not allowed. ` + `Other middleware would not be applied to this dispatch.` ) }
//暴露两个方法给外部
const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args) }
//传入middlewareAPI参数并执行每个外部函数,返回结果汇聚成数组
const chain = middlewares.map(middleware => middleware(middlewareAPI))
//用到了上面的compose方法
dispatch = compose(...chain)(store.dispatch) return { ...store, dispatch } } }
function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;
最终export了一个接受{ dispatch, getState }做为参数的function thunk,
这个thunk方法也就是传给applyMiddleware方法的参数,
此时的middlewares只有thunk一个方法,
那么applyMiddleware中的chain也就很显然的是执行了thunk方法后返回的结果,
咱们再看redux-thunk的代码,返回了一个接受next做为参数的方法!
applyMiddleware的下一行,
dispatch = compose(...chain)(store.dispatch),
chain只有一个function,因此这里能够忽略compose,
那么这一句就是将store.dispatch 做为next参数传给了刚才的方法A,
终于,方法A返回了咱们熟悉的dispatch方法。
可是注意,此时的dispatch方法仍是原来的dispatch方法吗?
它已经不是原来的它了。通过thunk方法的包装,早已物是人非。
咱们来看一下redux-thunk的代码,第三行以后的4行,
若是dispatch方法接受的参数不是一个function,
那么这个dispatch就和普通的dispatch没什么不一样,
但若是此时的action是一个方法,那么就会执行此方法,且第一个参数是store.dispatch。
这意味着咱们的action建立函数再也不只能建立一个包含type的Object,而能够是一个方法。
你可能会问有什么用呢?当你在action中须要一个异步操做,并须要在回调中改变state的状态的时候,这就是一个绝佳的解决方案。
因此说,applyMiddleware实际上作了一件事,就是根据外部函数(中间件函数)包装原来的dispatch函数,而后将新的dispatch函数暴露出去。
再回头去看createStore.jsx中的 return enhancer(createStore)(reducer, preloadedState)这句代码,是否是明白了不少事情?
//很简单却很关键,我就不解释了~ function bindActionCreator(actionCreator, dispatch) { return (...args) => dispatch(actionCreator(...args)) } /** * 将action与dispatch函数绑定,生成直接能够触发action的函数, * 能够将第一个参数对象中全部的action都直接生成能够直接触发dispatch的函数 * 而不须要一个一个的dispatch,生成后的方法对应原来action生成器的函数名 * */ export default function bindActionCreators(actionCreators, dispatch) { if (typeof actionCreators === 'function') { return bindActionCreator(actionCreators, dispatch) } //actionCreators必须为object类型 if (typeof actionCreators !== 'object' || actionCreators === null) { throw new Error( `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` + `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?` ) } const keys = Object.keys(actionCreators) const boundActionCreators = {} for (let i = 0; i < keys.length; i++) { const key = keys[i] const actionCreator = actionCreators[key] //给actionCreators的每个成员都绑定dispatch方法生成新的方法, //而后注入新的对象中,新方法对应的key即为原来在actionCreators的名字 if (typeof actionCreator === 'function') { boundActionCreators[key] = bindActionCreator(actionCreator, dispatch) } else { warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`) } } return boundActionCreators 这个方法主要的做用就是将action与dispatch函数绑定,生成直接能够触发action的函数。代码比较简单注释也比较明白,就过去了~
八、bindActionCreators.js
//根据key和action生成错误信息 function getUndefinedStateErrorMessage(key, action) { //... } //一些警告级别的错误 function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) { const reducerKeys = Object.keys(reducers) const argumentName = action && action.type === ActionTypes.INIT ? 'preloadedState argument passed to createStore' : 'previous state received by the reducer' //判断reducers是否为空数组 //判断state是不是对象 //给state中存在而reducer中不存在的属性添加缓存标识并警告 //... } //这个方法用于检测用于组合的reducer是不是符合redux规定的reducer function assertReducerSanity(reducers) { Object.keys(reducers).forEach(key => { const reducer = reducers[key] //调用reducer方法,undefined为第一个参数 //使用前面说到过的ActionTypes.INIT和一个随机type生成action做为第二个参数 //若返回的初始state为undefined,则这是一个不符合规定的reducer方法,抛出异常 //... }) } export default function combineReducers(reducers) { const reducerKeys = Object.keys(reducers) //全部的键名 const finalReducers = {} for (let i = 0; i < reducerKeys.length; i++) { const key = reducerKeys[i] if (process.env.NODE_ENV !== 'production') { if (typeof reducers[key] === 'undefined') { warning(`No reducer provided for key "${key}"`) } } //finalReducers是过滤后的reducers,它的每个属性都是一个function if (typeof reducers[key] === 'function') { finalReducers[key] = reducers[key] } } const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache if (process.env.NODE_ENV !== 'production') { unexpectedKeyCache = {} } let sanityError //检测每一个reducer是不是符合标准的reducer try { assertReducerSanity(finalReducers) } catch (e) { sanityError = e } return function combination(state = {}, action) { if (sanityError) { throw sanityError } //若是不是成产环境,作一些警告判断 if (process.env.NODE_ENV !== 'production') { const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache) if (warningMessage) { warning(warningMessage) } } let hasChanged = false const nextState = {} //下一个state树 //遍历全部reducers,而后将每一个reducer返回的state组合起来生成一个大的状态树,因此任何action,redux都会遍历全部的reducer for (let i = 0; i < finalReducerKeys.length; i++) { const key = finalReducerKeys[i] const reducer = finalReducers[key] const previousStateForKey = state[key] const nextStateForKey = reducer(previousStateForKey, action) //若是此reducer返回的新的state是undefined,抛出异常 if (typeof nextStateForKey === 'undefined') { const errorMessage = getUndefinedStateErrorMessage(key, action) throw new Error(errorMessage) } nextState[key] = nextStateForKey hasChanged = hasChanged || nextStateForKey !== previousStateForKey } //若是当前action对应的reducer方法执行完后,该处数据没有变化,则返回原来的流程树 return hasChanged ? nextState : state } }
九、Demo详细解析
新建一个react项目
我项目大概长这个样子
9.1先给本身创建一个Store库,这就是你redux数据的仓库了
store文件夹下有两个文件,
reducers,把你各个页面的reducer汇合起来,给他们起不一样的好听的名字,
我这里只有一个home页面
import { combineReducers } from 'redux' import home from 'pages/home/reducer' export default combineReducers({ home })
另外一个文件是index.js
主要是用来建立你的库,建立库的时候我这里用到了两个参数并且还引入了一个中间件
没有中间件的Redux的过程是:,
而有了中间件的过程就是,
使用中间件咱们能够对action也就是对dispatch方法进行装饰,
咱们能够用它来实现异步action、打印日志、错误报告等功能。
import { createStore, applyMiddleware } from 'redux' import thunk from 'redux-thunk' import reducers from './reducers' const store = createStore(reducers, applyMiddleware(thunk)) export default storeaction -> reduceraction -> middleware -> reducer
这时候你能够回头去看看上面对redux-thunk源码的解析,
你会发现这样包装后的dispatch很是可爱
当你在action中须要一个异步操做,并须要在回调中改变state的状态的时候,这就是一个绝佳的解决方案。
9.二、给你的组件注入这个库
在index.html里
import React from 'react'; import ReactDOM from 'react-dom'; import App from './App'; import { Provider } from 'react-redux' import store from './store' ReactDOM.render( <Provider store={store}> <App /> </Provider>, document.getElementById('root') );
咱们能够参考源码哦
//这里须要传store因此咱们使用Provider的时候把store传入
//那么咱们引入了Provider它为咱们作了什么呢?
export function createProvider(storeKey = 'store') { const subscriptionKey = `${storeKey}Subscription` class Provider extends Component {
//将外部的store对象放入context对象中,使子孙组件上的connect能够直接访问到context对象中的store。
getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } }
//constructor是Provider初始化时,用于获取props的store对象
constructor(props, context) { super(props, context) this[storeKey] = props.store; }
//首先,它把它引入的内容所有变成它的子级元素,
//而且因为它处于整个index.html的最外层
//因此被它包裹的每个元素均可以接收redux的store数据做为props
render() { return Children.only(this.props.children)
//this.props.children用于获取当前组件的全部子组件
//children.only表示用于获取仅有的一个子组件,没有或者超过一个均会报错.
//因此注意: 确保Provider组件的直接子级为单个封闭元素,切勿多个组件平行放置
//引伸问题:当这个项目须要用到router时该怎么办?把router包在倒数第二层,Provider在最外层 } } if (process.env.NODE_ENV !== 'production') { Provider.prototype.componentWillReceiveProps = function (nextProps) { if (this[storeKey] !== nextProps.store) { warnAboutReceivingStore() } } } Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, } return Provider } export default createProvider()
9.三、page下的home页面有三个文件
actionTypes.js
export const GET_HOME_DATA = 'home/get_home_data'
你起什么名字均可以,只要不重复,你开心就行了。
这个名字贯穿了一条修改路线,你会发现接下来你的actionCreator.js和你的reducer.js里都用到了这个名字,不一样的名字对应不一样的数据操做,记号它,为了便于记号它,我为它专门设置了本身的actionType.js
actionCreator.js
import { GET_HOME_DATA } from './actionTypes' export const loadHomeDataSync = (home) => { return { type: GET_HOME_DATA, home } }
//先异步获取数据,为了不麻烦我这里用mock数据代替了
//再同步返回获取到的数据
export const loadHomeDataAsync = (dispatch) => { return () => { fetch('/mock/data.json') .then(response => response.json()) .then(result => { dispatch(loadHomeDataSync(result.data)) }) } }
reducer.js
//给你要用的数据设置初值,而且当新的数据来了之后,对数据作你想要的处理
//我这里是当原数据为空,state为新数据,原数据有的话,和新数据进行合并返回一个新的state
import { GET_HOME_DATA } from './actionTypes' const defaultState = { home:null } export default (state=defaultState, action) => { if (action.type === GET_HOME_DATA) { if(!!state.home){ return { home: [...state.home,...action.home] } } else{ return { ...state, home: [...action.home] } } } return state }
9.4 page下的view下的Home.js
import React, { Component } from 'react'; import { loadHomeDataAsync } from '../actionCreator'
//connect做用:链接React组件与 Redux store
import { connect } from 'react-redux' const mapState = (state) => { return { home: state.home.home } } const mapDispatch = (dispatch) => { return { loadCategories () { dispatch(loadHomeDataAsync(dispatch)) } } } class Home extends Component { componentDidMount(){ this.props.loadCategories()
//在这里调用,固然,你想在哪调用均可以 } render() { console.log(this.props.home) return ( <div>home</div> ); } } export default connect(mapState,mapDispatch)(Home);
//记得在这里把他们connect起来
那么connect他究竟为咱们作了什么呢?
为何connect后面跟两个括号?
它的基础做用是:
a、从context里获取store
b、在componentWillMount 里经过mapStateToProps获取stateProp的值
c、在componentWillMount 里经过mapDispatchToProps获取dispatchProps的值
d、在componentWillMount 里订阅store的变化
e、将得到的stateProp,dispatchProps,还有自身的props合成一个props传给下面的组件
参考源码:
export function createConnect({ connectHOC = connectAdvanced, mapStateToPropsFactories = defaultMapStateToPropsFactories, mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories, mergePropsFactories = defaultMergePropsFactories, selectorFactory = defaultSelectorFactory } = {}) { return function connect( mapStateToProps, mapDispatchToProps, mergeProps, { pure = true, areStatesEqual = strictEqual, areOwnPropsEqual = shallowEqual, areStatePropsEqual = shallowEqual, areMergedPropsEqual = shallowEqual, ...extraOptions } = {} ) { const initMapStateToProps = match(mapStateToProps, mapStateToPropsFactories, 'mapStateToProps') const initMapDispatchToProps = match(mapDispatchToProps, mapDispatchToPropsFactories, 'mapDispatchToProps') const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps') return connectHOC(selectorFactory, { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions }) } }
connect接收四个参数:mapStateToProps,mapDispatchToProps,mergeProps,optipons
返回:一个注入了 state 和 action creator 的 React 组件
传入:state,ownProps
输出:stateProps
这个很是关键,若是定义了这个参数,就会监听redux store的变化,没有的话,就不会。
该回调函数必须返回一个纯对象,这个对象会与组件的 props 合并。
同时,若是指定了第二个ownProps,这个参数的值为传入到组件的props,只要组件接受到新的props,mapStateToProps也会被调用
stateProps,dispatchProps,自身的props将传入到这个函数中。
默认是Object.assign({}, ownProps, stateProps, dispatchProps)
完整:
connect(mapStateToProps, mapDispatchToProps, mergeProps)(MyComponent)
结果
demo地址:
https://github.com/yangTwo100/reduxAsync_demo
之后再更。