redux源码分析和使用

参考文档 https://cn.redux.js.org/docs/introduction/Motivation.html
Redux是针对JavaScript应用的可预测状态容器,在项目中实现数据共享,在使用redux之前,先看模型图
在这里插入图片描述
首先我们需要弄清Redux模型中的几个组成对象:action 、reducer、store
action:官方的解释是action是把数据从应用传到 store 的有效载荷,它是 store 数据的唯一来源;要通过本地或远程组件更改状态,需要分发一个action;
reducer:action发出了做某件事的请求,只是描述了要做某件事,并没有去改变state来更新界面,reducer就是根据action的type来处理不同的事件;
store:store就是把action和reducer联系到一起的对象,store本质上是一个状态树,保存了所有对象的状态。任何组件都可以直接从store访问特定对象的状态。

在Redux中,所有的数据(比如state)被保存在一个被称为store的容器中 ,在一个应用程序中只能有一个store对象。当一个store接收到一个action,它将把这个action代理给相关的reducer。reducer是一个纯函数,它可以查看之前的状态,执行一个action并且返回一个新的状态。

工作流程
将状态统一放在一个state中,由store来管理这个state,这个store由reducer创建,reducer的作用是接受之前的状态,返回一个新的状态。外部改变state的唯一方法是通过调用store的dispatch方法,触发一个action,这个action被对应的reducer处理,于是state完成更新。可以通过subscribe在store上添加一个监听函数,store中dispatch方法被调用时,会执行这个监听函数。可以添加中间件(中间件是干什么的我们后面讲)

在这个工作流程中,redux需要提供的功能是:
1–创建store,即:createStore()
2–将多个reducer合并为一个reducer,即:combineReducers()
3–创建出来的store提供subscribe,dispatch,getState这些方法。
4–应用中间件,即applyMiddleware()

createStore的实现

function createStore(reducer, preloadedState, enhancer) {
    if(enhancer是有效的){  //这个我们后面再解释,现在可以先不管
        return enhancer(createStore)(reducer, preloadedState)
    }
    let currentReducer = reducer // 当前store中的reducer
    let currentState = preloadedState // 当前store中存储的状态
    let currentListeners = [] // 当前store中放置的监听函数
    let nextListeners = currentListeners //下一次dispatch时的监听函数
    //注意:当我们新添加一个监听函数时,只会在下一次dispatch的时候生效。
    // 获取state
    function getState() {
        //...
    }
    // 添加一个监听函数,每当dispatch被调用的时候都会执行这个监听函数
    function subscribe() {
        //...
    }
    // 触发了一个action,因此我们调用reducer,得到的新的state,并且执行所有添加到store中的监听函数。
    function dispatch() {
        //...
    }
    //...
    //dispatch一个用于初始化的action,相当于调用一次reducer
    //然后将reducer中的子reducer的初始值也获取到
    //详见下面reducer的实现。
    return {
        dispatch,
        subscribe,
        getState,
        //下面两个是主要面向库开发者的方法,暂时先忽略
        //replaceReducer,
        //observable
    }
}

可以看出,createStore方法创建了一个store,但是并没有直接将这个store的状态state返回,而是返回了一系列方法,外部可以通过这些些方法(getState)获取state,或者间接地(通过调用dispatch)改变state。至于state呢,被存在了闭包中,但是调用createStore方法需要提供reducer,让我们来思考一下reducer的作用

combineReducers
在理解combineReducers之前,我们先来想想reducer的功能:reducer接受一个旧的状态和一个action,当这个action被触发的时候,reducer处理后返回一个新状态。也就是说 ,reducer负责状态的管理(或者说更新)。在实际使用中,我们应用的状态是可以分成很多个模块的,比如一个典型社交网站的状态可以分为:用户个人信息,好友列表,消息列表等模块。理论上,我们可以手动用一个reducer去处理所有状态的更新,但是这样做的话,我们一个reducer函数的逻辑就会太多,容易产生混乱。因此我们可以将处理逻辑(reducer)也按照模块划分,每个模块再细分成各个子模块,这样我们的逻辑就能很清晰的组合起来。对于我们的这种需求,redux提供了combineReducers方法,可以把子reducer合并成一个总的reducer。

源码中combineReducers的主要逻辑:

function combineReducers(reducers) {
    //先获取传入reducers对象的所有key
    const reducerKeys = Object.keys(reducers)
    const finalReducers = {} // 最后真正有效的reducer存在这里
    //下面从reducers中筛选出有效的reducer
    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);
    
    //这里assertReducerShape函数做的事情是:
    // 检查finalReducer中的reducer接受一个初始action或一个未知的action时,是否依旧能够返回有效的值。
    let shapeAssertionError
  	try {
    	assertReducerShape(finalReducers)
  	} catch (e) {
    	shapeAssertionError = e
  	}
    
    //返回合并后的reducer
    return function combination(state= {}, action){
  		//这里的逻辑是:
    	//取得每个子reducer对应得state,与action一起作为参数给每个子reducer执行。
    	let hasChanged = false //标志state是否有变化
        let nextState = {}
        for(let i = 0; i < finalReducerKeys.length; i++) {
            //得到本次循环的子reducer
            const key = finalReducerKeys[i]
            const reducer = finalReducers[key]
            //得到该子reducer对应的旧状态
            const previousStateForKey = state[key]
            //调用子reducer得到新状态
            const nextStateForKey = reducer(previousStateForKey, action)
            //存到nextState中(总的状态)
            nextState[key] = nextStateForKey
            //到这里时有一个问题:
            //就是如果子reducer不能处理该action,那么会返回previousStateForKey
            //也就是旧状态,当所有状态都没改变时,我们直接返回之前的state就可以了。
            hasChanged = hasChanged || previousStateForKey !== nextStateForKey
        }
        return hasChanged ? nextState : state
    }
} 

为什么需要中间件
在这里插入图片描述
加入中间件
在这里插入图片描述
中间件在redux的实现
redux 的createStore()方法的第三个参数enhancer

function createStore(reducer, preloadedState, enhancer) {
    if(enhancer是有效的){  
        return enhancer(createStore)(reducer, preloadedState)
    } 
    
    //...
}

在这里,我们可以看到,enhancer(可以叫做强化器)是一个函数,这个函数接受一个’常规createStore函数’作为参数,返回一个加强后的createStore函数。这个加强的过程中做的事情,其实就是改造dispatch,添加上中间件。redux提供的applyMiddleware()方法返回的就是一个enhancer。applyMiddleware,顾名思义,应用中间件,输入为若干中间件,输出为enhancer,作用是:
1–从middleware中获取改造函数
2–把所有改造函数compose成一个改造函数
3–改造dispatch方法

这个方法的源码

function applyMiddleware(...middlewares) {
    // 返回一个函数A,函数A的参数是一个createStore函数。
    // 函数A的返回值是函数B,其实也就是一个加强后的createStore函数,大括号内的是函数B的函数体
    return createStore => (...args) => {
        //用参数传进来的createStore创建一个store
        const store  = createStore(...args)
        //注意,我们在这里需要改造的只是store的dispatch方法
        
        let dispatch = () => {  // 一个临时的dispatch
            					//作用是在dispatch改造完成前调用dispatch只会打印错误信息
            throw new Error(`一些错误信息`)
        } 
        //接下来我们准备将每个中间件与我们的state关联起来(通过传入getState方法),得到改造函数。
        const middlewareAPI = {
            getState: store.getState,
            dispatch: (...args) => dispatch(...args)
        }
        //middlewares是一个中间件函数数组,中间件函数的返回值是一个改造dispatch的函数
        //调用数组中的每个中间件函数,得到所有的改造函数
        const chain = middlewares.map(middleware => middleware(middlewareAPI))
        
        //将这些改造函数compose(翻译:构成,整理成)成一个函数
        //用compose后的函数去改造store的dispatch
        dispatch = compose(...chain)(store.dispatch)
        // compose方法的作用是,例如这样调用:
        // compose(func1,func2,func3)
        // 返回一个函数: (...args) => func1( func2( func3(...args) ) )
        // 即传入的dispatch被func3改造后得到一个新的dispatch,新的dispatch继续被func2改造...
        
        //返回store,用改造后的dispatch方法替换store中的dispatch
        return {
            ...store,
            dispatch
        }
    }
}

先看一个redux的简单使用例子:

import { createStore } from 'redux';

// 创建Redux reducer
/**
 * 这是一个 reducer,形式为 (state, action) => state 的纯函数。
 * 描述了 action 如何把 state 转变成下一个 state。
 *
 * state 的形式取决于你,可以是基本类型、数组、对象,
 * 当 state 变化时需要返回全新的对象,而不是修改传入的参数。

 *
 * 下面例子使用 `switch` 语句和字符串来做判断,但你可以写帮助类(helper)
 * 根据不同的约定(如方法映射)来判断,只要适用你的项目即可。
 */
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT': 
    return state - 1;
  default:
    return state;
  }
}

// 创建 Redux store 来存放应用的状态。
// API 是 { subscribe, dispatch, getState }。
let store = createStore(counter);

// 可以手动订阅更新,也可以事件绑定到视图层。
store.subscribe(() =>
  console.log(store.getState())
);

// 改变内部 state 惟一方法是 dispatch 一个 action。
// action 可以被序列化,用日记记录和储存下来,后期还可以以回放的方式执行
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

以上代码便是一个redux的最简单的使用
action

Action 是把数据从应用传到 store 的有效载荷。它是 store 数据的唯一来源,也就是说要改变store中的state就需要触发一个action。action本质上一个普通的JavaScript对象。action 内必须使用一个字符串类型的 type 字段来表示将要执行的动作,除了 type 字段外,action 对象的结构完全由你自己决定。多数情况下,type 会被定义成字符串常量。当应用规模越来越大时,建议使用单独的模块或文件来存放 action。

import { ADD_TODO, REMOVE_TODO } from '../actionTypes'

//action
{
  type: ADD_TODO,
  text: 'Build my first Redux app'
}

使用单独的模块或文件来定义 action type 常量并不是必须的,甚至根本不需要定义。对于小应用来说,使用字符串做 action type 更方便些。不过,在大型应用中把它们显式地定义成常量还是利大于弊的。

Action 创建函数

Action 创建函数 就是生成 action 的方法。“action” 和 “action 创建函数” 这两个概念很容易混在一起,使用时最好注意区分。在 Redux 中的 action 创建函数只是简单的返回一个 action:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

这样做将使 action 创建函数更容易被移植和测试。
reducer
reducer是根据action 修改state 将其转变成下一个 state,记住 actions 只是描述了有事情发生了这一事实,并没有描述应用如何更新 state。

(previousState, action) => newState

保持 reducer 纯净非常重要。永远不要在 reducer 里做这些操作:
1–修改传入参数;
2–执行有副作用的操作,如 API 请求和路由跳转;
3–调用非纯函数,如 Date.now() 或 Math.random()。

提示:reducer 是纯函数。它仅仅用于计算下一个 state。它应该是完全可预测的:多次传入相同的输入必须产生相同的输出。它不应做有副作用的操作,如 API 调用或路由跳转。这些应该在 dispatch action 前发生。

//reducer
function todoApp(state = initialState, action) {
  switch (action.type) {
    case SET_VISIBILITY_FILTER:
      return Object.assign({}, state, {
        visibilityFilter: action.filter
      })
    default:
      return state
  }
}

不要修改 state。 使用 Object.assign() 新建了一个副本。不能这样使用 Object.assign(state, { visibilityFilter: action.filter }),因为它会改变第一个参数的值。你必须把第一个参数设置为空对象。你也可以开启对ES7提案对象展开运算符的支持, 从而使用 { …state,visibilityFilter: action.filter } 达到相同的目的。在 default 情况下返回旧的 state。遇到未知的 action 时,一定要返回旧的 state。

拆分与合并Reducer

function onAction(state = defaultState, action) {
    switch (action.type) {
        case Types.THEME_CHANGE://主题
            return {
                ...state,
                theme: action.theme,
            };
        case Types.SHOW_THEME_VIEW://主题
            return {
                ...state,
                customThemeViewVisible: action.customThemeViewVisible,
            };
        case Types.SORT_LANGUAGE://排序
            return Object.assign({}, state, {
                checkedArray: action.checkedArray,
            });
        case Types.REFRESH_ABOUT://关于
            return Object.assign({}, state, {
                [action.flag]: {
                    ...state[action.flag],
                    projectModels: action.projectModels,
                }
            });
        case Types.ABOUT_SHOW_MORE://关于
            return Object.assign({}, state, {
                me: {
                    ...state.me,
                    [action.menuFlag]: action.menuShow
                }
            });
        default:
            return state;
    }
}

上述代码看起来有些冗长,并且主题、排序、关于的更新看起来是相互独立的,能不能将他们拆到单独的函数或文件里呢,答案是可以的

//主题 theme.js
export default function onTheme(state = defaultState, action) {
    switch (action.type) {
        case Types.THEME_CHANGE:
            return {
                ...state,
                theme: action.theme,
            };
        case Types.SHOW_THEME_VIEW:
            return {
                ...state,
                customThemeViewVisible: action.customThemeViewVisible,
            };
        default:
            return state;
    }
}

//排序 sort.js
export default function onSort(state = defaultState, action) {
    switch (action.type) {
        case Types.SORT_LANGUAGE:
            return Object.assign({}, state, {
                checkedArray: action.checkedArray,
            });
        default:
            return state;
    }
}

//关于 about.js
export default function onAbout(state = defaultState, action) {
    switch (action.type) {
        case Types.REFRESH_ABOUT:
            return Object.assign({}, state, {
                [action.flag]: {
                    ...state[action.flag],
                    projectModels: action.projectModels,
                }
            });
        case Types.ABOUT_SHOW_MORE:
            return Object.assign({}, state, {
                me: {
                    ...state.me,
                    [action.menuFlag]: action.menuShow
                }
            });
        default:
            return state;
    }
}

合并reducer

经过上述的步骤我们将一个大的reducer拆分成了不同的小的reducer,但redux原则是只允许一个根reducer,接下来我们需要将这几个小的reducer聚合到一个跟reducer中。这里我们需要用到Redux 提供的combineReducers(reducers)。

import {combineReducers} from 'redux'
import theme from './theme'
import sort from './sort'
import about from './about'

const index = combineReducers({
    theme: theme,
    sort: sort,
    about: about,
})
export default index;

combineReducers() 所做的只是生成一个函数,这个函数来调用你的一系列 reducer,每个 reducer 根据它们的 key 来筛选出 state 中的一部分数据并处理,然后这个生成的函数再将所有 reducer 的结果合并成一个大的对象。没有任何魔法。正如其他 reducers,如果 combineReducers() 中包含的所有 reducers 都没有更改 state,那么也就不会创建一个新的对象.

Store

是存储state的容器,Store 会把两个参数(当前的 state 树和 action)传入 reducer。
1–维持应用的 state;
2–提供 getState() 方法获取 state;
3–提供 dispatch(action) 方法更新 state:我们可以在任何地方调用 store.dispatch(action),包括组件中、XMLHttpRequest 回调中、甚至定时器中;
4–通过 subscribe(listener) 注册监听器;

在前一个章节中,我们使用 combineReducers() 将多个 reducer 合并成为一个。现在我们通过Redux的 createStore()来创建一个Store。

import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)

猜你喜欢

转载自blog.csdn.net/smlljet/article/details/90769999
今日推荐