文档章节

redux源码学习笔记 - createStore

o
 osc_gu9d45li
发布于 2019/04/18 11:29
字数 1302
阅读 11
收藏 0

行业解决方案、产品招募中!想赚钱就来传!>>>

本篇是学习redux源码的一些记录,学习的redux版本是^4.0.1

在页面开发时,需要管理很多状态(state),比如服务器响应,缓存数据,UI状态等等···当页面的庞大时,状态就会变的混乱。redux就派上用场了,它最大的特点就是使状态变化变的可预测

redux提供一个管理state的仓库(store),并且规定了store只能通过reducer(函数)来更新,而reducer必须通过dispatch(action)来触发,action就是普通的JavaScript对象,它约定了执行的类型并且传递数据。使得state的变化是可以预测的,同样的步骤会得到同样的state。

从第一步创建仓库开始看起 createStore(reducer, preloadedState, enhancer)

开始已经提到redux是管理一个store,那么第一步就是创建store,一般最简单的就是以下形式:

let store = createStore(reducer,preloadedState,enhancer);
  • reducer是更新store的函数,必传参数,function类型
  • preloadedState为初始状态,为可选参数。如果reducer是使用 combineReducers 合并多个函数而成的,要注意preloadedState的数据格式要和 combineReducers 中的keys一致。
  • enhancer在学习applyMiddleware时一起说明。

看一下createStore 源码中的关键部分:

  // 记录reducer函数、初始状态、监听函数
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []

  // 创建store时,触发一个空的action,这样如果没有初始状态,就会返回reducer中的默认状态  
  dispatch({ type: ActionTypes.INIT })

  // 返回一个提供了多种方法的对象
  return {
    dispatch, //触发action的方法
    subscribe, //增加监听方法
    getState, //获取当前状态的方法
    replaceReducer, //更换reducer方法
    [$$observable]: observable
  }

这里是用了闭包,在createStore的作用域中创建了currentState 变量来记录状态,currentReducer来记录reducer函数,currentListeners来记录所有的监听函数。然后返回一个对象,对象中的方法可以获取currentState、触发reducer来更新currentState,添加监听函数,替换reducer等。

这个对象就是 store , 而state,reducer,listeners保存在createStore的作用域中,只有通过store中的方法可以访问到。

getState()

只有store.getState()能获取到仓库的state --> currentState变量

  function getState() {
    if (isDispatching) { //··· }
    return currentState
  }

dispatch()

只有store.dispatch(action)可以触发更新state。注意在redux中action必须是一个纯对象,而且必须有type字段指定动作类型,dispatch中有对与这些的校验。

  function dispatch(action) {
    /*-- action必须是对象 --*/
    if (!isPlainObject(action)) { //··· }
    /*-- action必须有type字段--*/
    if (typeof action.type === 'undefined') { //··· }

    if (isDispatching) { //··· }

    /*-- 触发reducer,更新state --*/
    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
  }

从这一段currentState = currentReducer(currentState, action)看出currentState是通过执行reducer函数更新的。

而且也知道了reducer函数的参数情况:

  • 当前状态
  • action对象,且有type字段

如果想在state变化时做点什么,就需要用到subscribe方法添加监听函数

subscribe(listener)

只看关键代码,其实就是维护了一个保存监听函数的数组。从上面dispatch的代码listener()可以看出,这些函数会在dispatch(action)的时候触发。

而且每次新增listener的时候都会返回一个取消监听的方法unsubscribe,可以在适当的时候取消监听。


  function subscribe(listener) {
    /*-- 增加监听 --*/
    nextListeners.push(listener)

    /*-- 返回一个取消监听的函数 --*/
    return function unsubscribe() {

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

replaceReducer(nextReducer)

可以更改reducer函数,很简单,重新赋值。

  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') { //··· }
    // 更新reducer函数
    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

实例

上面对createStore的实现有了大致的了解,再看看栗子

action中需要type字段来标记要执行的字段,会定义为字符串常量;一般会使用单独的模块来管理。

    /*-- 动作类型 actionTypes.js --*/
    export const ADD_TODO = 'ADD_LIST';
    export const COMPLETE_TODO = 'COMPLETE_TODO';

action 必须是一个纯JavaScript对象。可以通过创建action的函数返回,这样就方便传递数据。

  /*-- action对象 action.js --*/

    import { ADD_TODO, COMPLETE_TODO} from "./actionTypes";

    export function addList (text) {
        // 返回action对象
        return {
            type: ADD_TODO,
            text
        }
    }

    export function completeList (id,bl = true) {
        return {
            type: COMPLETE_TODO,
            id,
            bl
        }
    }

reducer 是一个纯函数,接受旧的state和action,返回新的state。只要传入参数相同,返回计算得到的下一个 state 就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。

    /*-- reducer函数 todolist.js --*/
    import {ADD_TODO, COMPLETE_TODO} from '../actionTypes';

    let id = 0;

    export default function todoList(state = [],action) {
        // 根据type区分如何更新state
        switch (action.type) {
            case ADD_TODO :
                // 不要直接修改state,要返回一个新的state
                return [...state,{
                    id: id++,
                    text: action.text,
                    complete:false
                }]
            case COMPLETE_TODO :
                return state.map( item => {
                    if (item.id === action.id){
                        return Object.assign({},item,{complete:action.bl})
                    }
                    return item;
                })
            default:
                return state;
        }
    }

使用 createStore 创建 store

    /*-- 创建store --*/
    import {createStore} from 'redux';
    import reducer from './reducer/todolist';

    let store = createStore(reducer);

更新 state

    import {addList,completeList} from './store/actions';
    
    store.dispatch(addList('测试数据'));

addList('测试数据')返回的是 action {type: ADD_TODO,text:'测试数据'}。 在todoList函数中,执行完后返回新的state [{id:0,text:'测试数据',complete:false}]

获取 state

    console.log(store.getState()); // [{id:0,text:'测试数据',complete:false}]

添加/删除 监听函数

    // 添加
    unsubscribe =  store.subscribe(function(){ // 每次更新state都会执行此函数
        console.log(store.getState());
    });

    // 取消
    unsubscribe()
o
粉丝 0
博文 500
码字总数 0
作品 0
私信 提问
加载中
请先登录后再评论。
我的架构演化笔记 功能1: 基本的用户注册

“咚咚”,一阵急促的敲门声, 我从睡梦中惊醒,我靠,这才几点,谁这么早, 开门一看,原来我的小表弟放暑假了,来南京玩,顺便说跟我后面学习一个网站是怎么做出来的。 于是有了下面的一段...

强子哥哥
2014/05/31
976
3
Nutch学习笔记4-Nutch 1.7 的 索引篇 ElasticSearch

上一篇讲解了爬取和分析的流程,很重要的收获就是: 解析过程中,会根据页面的ContentType获得一系列的注册解析器, 依次调用每个解析器,当其中一个解析成功后就返回,否则继续执行下一个解...

强子哥哥
2014/06/26
712
0
程序猿媛一:Android滑动翻页+区域点击事件

滑动翻页+区域点击事件 ViewPager+GrideView 声明:博文为原创,文章内容为,效果展示,思路阐述,及代码片段。文尾附注源码获取途径。 转载请保留原文出处“http://my.oschina.net/gluoyer...

花佟林雨月
2013/11/09
4.1K
1
桌面即时贴软件--GloboNote

GloboNote 是一个桌面记事软件,可帮你创建待办事宜、提醒和其他笔记信息。无限制即时贴的数量,可分组整理,支持搜索,可定制文本的显示格式(字体、颜色和大小),可将某个即时贴始终显示在...

匿名
2013/01/21
6.7K
1
WebUI自动化测试框架--Dagger

Dagger是网易杭州研究院QA团队开发的一个轻量级、运行稳定的WebUI自动化测试框架,主要基于Selenium及TestNg可以认为是对Selenium进行二次封装的一个框架(俗称 造轮子 )。之所以把这个轮子...

ChenKan
2013/03/05
2.8W
6

没有更多内容

加载失败,请刷新页面

加载更多

代理服务器和反向代理服务器之间有什么区别? - What's the difference between proxy server and reverse proxy server?

问题: 代理服务器和反向代理服务器有什么区别? 解决方案: 参考一: https://stackoom.com/question/wRc/代理服务器和反向代理服务器之间有什么区别 参考二: https://oldbug.net/q/wRc/W...

技术盛宴
34分钟前
16
0
第八讲:配置外界可以访问虚拟机里面的HDFS

本节通过配置实现外界访问虚拟机Centos6.4里面的HDFS。为后续的java读写HDFS做准备 步骤有: 1、修改主机Windos7的网络配置 2、修改虚拟机Centos6.4里面的网络配置 3、修改虚拟机Centos6.4里...

刘日辉
52分钟前
26
0
OSChina 周四乱弹 —— 不劳而获的饭好吃么?好吃!非常好吃!

Osc乱弹歌单(2020)请戳(这里) 【今日歌曲】 小小编辑推荐:《世界上不存在的歌 (2020重唱版)》- 陈奕迅 《世界上不存在的歌 (2020重唱版)》- 陈奕迅 手机党少年们想听歌,请使劲儿戳(这...

小小编辑
今天
31
2
从 GPU、TPU,到 Web 端、移动端,深度学习框架部署训练开始变简单

本文作者:o****0 早些时候的统计显示,今年3月,深度学习框架集中爆发。5月,有人发布可以直接在 iphone11上训练神经网络的开源项目。日前,百度开源国内首个可直接运行在 Web 端的深度学习...

百度开发者中心
昨天
16
0
如何从Git存储库中删除文件? - How can I delete a file from a Git repository?

问题: I have added a file named "file1.txt" to a Git repository. 我已将名为"file1.txt"的文件添加到Git存储库中。 After that, I committed it, added a couple of directories called......

富含淀粉
今天
31
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部