ux核心思路和代码解析

最近在公司内部培训的时候,发现很多小伙伴只是会用redux、react-redux、redux-thunk的api,对于其中的实现原理和数据真正的流向不是特别的清楚,知其然,也要知其所以然,其实redux的源代码非常简介,下面逐一介绍,

1.先看一个简单的redux应用的例子:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

import { createStore, combineReducers } from 'redux';

const year = (state, action) => {
    let defaultState = {
        year: 2017
    }
    state = state || defaultState;    switch (action.type) {        case 'add':            return {
                year: state.year + 1
            };            break;        case 'sub':            return {
                year: state.year - 1
            };            break;        default:            return state;            break;
    }
}

const count = (state, action) => {
    let defaultState = {
        count: 1
    }
    state = state || defaultState;    switch (action.type) {        case 'addone':            return {
                count: state.count + 1
            };            break;        case 'subone':            return {
                count: state.count - 1
            };            break;        default:            return state;            break;
    }
}

const reducer = combineReducers({
    year: year,
    count: count
})

const store = createStore(reducer);

store.subscribe(() => {
    console.log('the year is ' + store.getState().year.year);
});
store.subscribe(() => {
    console.log('数字是' + store.getState().count.count);
});

const action1 = {
    type: 'add'}
const action2 = {
    type: 'addone'}
const action3 = {
    type: 'hello'}

store.dispatch(action1);
store.dispatch(action2);
store.dispatch(action3);

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

上面代码执行后打印出来的结果是:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

想必大部分小伙伴都觉得很简单。

2.从上面的实例可以看到redux的核心内容就是:

  A.createStore方法以及执行之后返回的store对象,定义在store对象上的store.subscribe()监听某个函数,store.getState()获取当前store中state(一个复杂的对象)的值,store.dispatch()推送某个action

  B.combineReducers将多个reducer合并成一个rootReducer,本质上为合并对象,并返回一个新的总的的reducer函数

2.1 createStore 方法简介

     createStore方法内部根据传入的三个参数(reducer、初始状态、增强者),在内部创建了几个核心函数,然后把这几个函数放到一个对象里面返回,也就是我们示例代码中常用到的store对象

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

export default function createStore(reducer, preloadedState, enhancer) {      function getState() {}      function subscribe(listener) {}      function dispatch(action) {}      function replaceReducer(nextReducer) {}
      ...      return {
          dispatch,
          subscribe,
          getState,
          replaceReducer,
          ...
     }
     
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

2.1.1  subscribe方法的核心代码,就是把一个一个的监听函数放入到Listeners的数组,然后返回一个unsubscribe函数,一个闭包函数,包含着传入的listeners函数,执行该函数从监听数组中移除该listeners

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

function subscribe(listener) {    if (typeof listener !== 'function') {        throw new Error('Expected listener to be a function.')
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)    return function unsubscribe() {        if (!isSubscribed) {            return
        }

        isSubscribed = false

        ensureCanMutateNextListeners()
        const index = nextListeners.indexOf(listener)
        nextListeners.splice(index, 1)
    }
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

2.1.2  getState 直接返回当前的state的值,如果没有进行任何操作,直接返回默认值,如果是经过dispatch(action)之后,在dispatch中触发了reducer函数,生成了新的state,也是直接返回

function getState() {    return currentState
}

2.1.3 dispatch store上的最核心方法, 两部分组成,第一部分直接把当前的state和传入的action直接传入reducer函数,执行,生成新的state,供getState使用,第二部分是直接循环执行subscribe中添加到listeners数组中的监听函数,也就是触发监听函数,

逻辑非常简单。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function dispatch(action) {
    try {
        isDispatching = true
        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
}

2.2  combineReducers,将多个reducer合并成一个rootReducer,本质上为合并对象,并返回一个新的总的的reducer函数

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

function combineReducers(reducers) {
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {}    for (let i = 0; i < reducerKeys.length; i++) {
        const key = reducerKeys[i]        if (typeof reducers[key] === 'function') {
            finalReducers[key] = reducers[key]
        }
    }
    const finalReducerKeys = Object.keys(finalReducers)    return function combination(state = {}, action) {
        let hasChanged = false
        const nextState = {}        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)            if (typeof nextStateForKey === 'undefined') {
                const errorMessage = getUndefinedStateErrorMessage(key, action)                throw new Error(errorMessage)
            }
            nextState[key] = nextStateForKey
            hasChanged = hasChanged || nextStateForKey !== previousStateForKey
        }        return hasChanged ? nextState : state
    }
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

  combineReducers的一个简单实现方式,更能明白其中的工作原理:

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

const combineReducers = (reducers) => {  return (state = {}, action) => {    return Object.keys(reducers).reduce(
      (nextState, key) => {
        nextState[key] = reducers[key](
          state[key],
          action);        return nextState;
      },
      {})
  }
}

电脑培训,计算机培训,平面设计培训,网页设计培训,美工培训,Web培训,Web前端开发培训

 

2.3 applyMiddleware 中间件,对dispatch的增强,一般为添加异步操作等,例如redux-thunk中间件,实际上就是如果action是个方法,则执行这个方法,如果不是则正常dispatch(action)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
function applyMiddleware(...middlewares) {
    return (createStore) => (reducer, preloadedState, enhancer) => {
        const store = createStore(reducer, preloadedState, enhancer)
        let dispatch = store.dispatch
        let chain = []
 
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        }
        chain = middlewares.map(middleware => middleware(middlewareAPI))
        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;

3.react-redux

3.1通过provider 提供store,react通过Context属性,可以将props直接给子孙component,无须通过props层层传递,Provider仅仅起到获得store, 然后将其传递给子孙元素而已

3.2 connect  这个是最关键的方法,

connect 是一个高阶函数,首先传入mapStateToProps、mapDispatchToProps,然后返回一个生产Component的函数(高阶组件HOC),然后再将真正的component作为参数传给这个函数,这样就生产了一个经过包裹的connect组件,该组件具有以下特点:

    通过this.context获取祖先component的store

    props包括stateProps、dispatchProps、parentProps,合并在一起得到nextState,作为props传给真正的Component

    componentDidMount时,添加事件this.store.subscribe(this.handleChange),实现页面交互

    shouldComponentUpdate时判断是否有避免进行渲染,提升页面性能,并得到nextState

    componentWillUnmount时移除注册的事件this.handleChange