Redux源码分析之基本概念

Redux源码分析之createStore

Redux源码分析之bindActionCreators

Redux源码分析之combineReducers

Redux源码分析之compose

Redux源码分析之applyMiddleware

接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象。

  return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}

同样的,我先删除一些不需要的代码,简化成如下, 注意看备注。(注:这里先无视中间件和enhancer,后篇再说)

export const ActionTypes = {
INIT: '@@redux/INIT'
}
export default function createStore(reducer, preloadedState, enhancer) {
// 初始化参数
let currentReducer = reducer // 整个reducer
let currentState = preloadedState //当前的state, getState返回的值就是他,
let currentListeners = [] // 当前的订阅,搭配 nextListeners
let nextListeners = currentListeners //下一次的订阅 ,搭配currentListeners
let isDispatching = false //是否处于 dispatch action 状态中 // 内部方法
function ensureCanMutateNextListeners() { } // 确保currentListeners 和 nextListeners 是不同的引用
function getState() { } // 获得当前的状态,返回的就是currentState
function subscribe(listener) { } //订阅监听,返回一个函数,执行该函数,取消监听
function dispatch(action) { } // dispacth action
function replaceReducer(nextReducer) { } // 替换 reducer
function observable() { } //不知道哈哈 //初始化state
dispatch({ type: ActionTypes.INIT }) //返回方法
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}

 

ensureCanMutateNextListeners

这个方法主要用在 subscribe里面,

  • 在每次订阅和取消订阅的时候,会让 nextListeners 和 currentListeners 不是同一个引用,
  • 在每次 dispatch的时候,当 reducer执行完毕,订阅执行前,让 nextListeners 和 currentListeners 是一个引用
  function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}

  为什么这么设计,在subscribe方法上有很详细的注解,我的理解是假如订阅在执行过程中,这里说的是订阅执行过程,不是reducer执行过程

有新加的订阅添加的时候,新的订阅是不会被执行的,因为是一份拷贝

有新的订阅删除的时候,被删除的还是会执行的。

       简单说,就是新的删除和添加,下次生效。

 
getState
就是返回利用闭包存的currentState
  /**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
return currentState
}

 

 subscribe

添加订阅

  • 每次添加前,如果 nextListeners 和 currentListeners 是一个引用,重新复制一个
  • 并存入 nextListeners
  • 返回一个函数,执行该函数取消订阅,
  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) // 从nextListeners里面删除,下次dispatch会生效
}
}

dispatch

派发一个action,让reducer更新数据,下面都有注释了,为啥可说的。
  •  如果上一次派发没完毕,接着派发是会出异常的,对于单线程的js来说倒是没啥大问题
  function dispatch(action) {
if (!isPlainObject(action)) { // action 必须是对象
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
} if (typeof action.type === 'undefined') { // 必须有type属性
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
} if (isDispatching) { // 正在派发,抛出异常
throw new Error('Reducers may not dispatch actions.')
} try {
isDispatching = true // 标记,正在派发
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false //标记派发完毕
} const listeners = currentListeners = nextListeners // 让nextListeners生效
for (let i = 0; i < listeners.length; i++) { // 挨个执行订阅
const listener = listeners[i]
listener()
} return action // 返回action
}

  

replaceReducer
 function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') { // 不是函数,抛出异常
throw new Error('Expected the nextReducer to be a function.')
} currentReducer = nextReducer // 替换reducer
dispatch({ type: ActionTypes.INIT }) // 重新初始化
}

  

observable 还没啥研究,暂不说了。
最后的代码为, 
  • 初始化 state
  • 返回相关方法
  dispatch({ type: ActionTypes.INIT })

  return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}

  这里说一下 dispatch({ type: ActionTypes.INIT }) 是怎么达到初始化state的,

  我们再回头看一下disptach中的一段代码

    try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}

      这里先讲非合成的reducer,带合成的后面说。

createStore的第一个参数为 reducer,第二个为初始化state的默认值,

  • 如果你传入了第二个参数,currentState就等于你传入的值,而执行一次 dispatch的时候,系统定义的 type 为@@redux/INIT的action,你肯定是没有定义的额,看下面代码,就会直接返回state,   那么执行 currentReducer(currentState, action) 得到的结果还是 currentState
  • 如果你没有传入第二个参数,在reducer的第一个参数指定了默认值,那么reducer处理type为 @@redux/INIT的action的时候,返回的就是reducer第一个参数 state的默认值,然后被赋值给了currentState
  • 如果没有传入第二个参数,同时reducer的state也没指定值,那么,你的dispatch一般都会报错,因为你的state从开始就是undefined
  • 如果recuder函数和createStore都设置了默认了,那么reducer的默认值是不会生效的
let todoReducer = function (state = todoList, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
}

  这里相对特别的是 合成recuder,后面再说。

 

到此为止,你只用redux的createStore方法,就能完成数据控制了,combineReducers,bindActionCreators,applyMiddleware,compose 都只是对redux的增强。

再回头看看我们第一篇提到的代码:(云淡风轻)

  • 初始化的state在recuder赋值,和在createStore赋值是等价的,都赋值的话,createStore的赋值会生效。 (createStore用的是显示赋值, reducer:默认参数) 
/* 简单示例 */
let { createStore } = self.Redux //默认state
let todoList = []
// reducer
let todoReducer = function (state, action) {
switch (action.type) {
case 'add':
return [...state, action.todo]
case 'delete':
return state.filter(todo => todo.id !== action.id)
default:
return state
}
} //创建store
let store = createStore(todoReducer,todoList) //订阅
function subscribe1Fn() {
console.log(store.getState())
}
let sub = store.subscribe(subscribe1Fn) store.dispatch({
type: 'add',
todo: {
id: 1,
content: '学习redux'
}
}) store.dispatch({
type: 'add',
todo: {
id: 2,
content: '吃饭睡觉'
}
}) store.dispatch({
type: 'delete',
id: 2
}) // 取消订阅
sub() console.log('取消订阅后:')
store.dispatch({
type: 'add',
todo: {
id: 3,
content: '打游戏'
}
})

   

Redux源码分析之createStore的更多相关文章

  1. Redux源码分析之applyMiddleware

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  2. Redux源码分析之基本概念

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  3. Redux源码分析之bindActionCreators

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  4. Redux源码分析之combineReducers

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  5. Redux源码分析之compose

    Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...

  6. redux源码图解:createStore 和 applyMiddleware

    在研究 redux-saga时,发现自己对 redux middleware 不是太了解,因此,便决定先深入解读一下 redux 源码.跟大多数人一样,发现 redux源码 真的很精简,目录结构如下: ...

  7. redux源码学习笔记 - createStore

    本篇是学习redux源码的一些记录,学习的redux版本是^4.0.1. 在页面开发时,需要管理很多状态(state),比如服务器响应,缓存数据,UI状态等等···当页面的庞大时,状态就会变的混乱.r ...

  8. 史上最全的 Redux 源码分析

    前言 用 React + Redux 已经一段时间了,记得刚开始用Redux 的时候感觉非常绕,总搞不起里面的关系,如果大家用一段时间Redux又看了它的源码话,对你的理解会有很大的帮助.看完后,在回 ...

  9. 正式学习React(四) ----Redux源码分析

    今天看了下Redux的源码,竟然出奇的简单,好吧.简单翻译做下笔记: 喜欢的同学自己可以去github上看:点这里 createStore.js import isPlainObject from ' ...

随机推荐

  1. Docker Daemon 参数最佳实践

    1. Docker Daemon 配置参数 限制容器之间网络通信 在同一台主机上若不限制容器之间通信,容器之间就会暴露些隐私的信息,所以推荐关闭 docker daemon –icc=false 使用 ...

  2. python网络爬虫之使用scrapy自动爬取多个网页

    前面介绍的scrapy爬虫只能爬取单个网页.如果我们想爬取多个网页.比如网上的小说该如何如何操作呢.比如下面的这样的结构.是小说的第一篇.可以点击返回目录还是下一页 对应的网页代码: 我们再看进入后面 ...

  3. 详解 RAC 中各种IP和监听的意义

    一.SCAN 概念 SCAN(Single Client Access Name)是 Oracle从11g R2开始推出的,客户端可以通过 SCAN 特性负载均衡地连接到 RAC数据库 SCAN 最明 ...

  4. Android 4.0以后正确的获取外部sd卡存储目录

    刚解决这个棘手的问题 找了很久,随笔记下. 网上搜索 android 获取外部sd卡存储目录 普遍都是: 1) Environment.getExternalStorageDirectory() 这个 ...

  5. linux下swoole的安装

    //官方推荐的安装方式1:下载压缩包 wget https://github.com/swoole/swoole-src/archive/swoole-1.8.4-stable.zip 2:解压缩 u ...

  6. linux vim编辑

    常用vim操作自我总结,大神请回避: vim 打开退出保存 打开:直接打开 vim practice_1.txt也可以先打开vim,后:e 文件路径 保存::w 文件名或者退出保存:wq! 普通模式下 ...

  7. v2013调试无法访问此网站 localhost 拒绝了我们的连接请求

    问题描述:          别人给的服务器代码,在本地部署以后调试的,localhost:8080 可以访问,localhost:2524访问不了需要改什么配置吗 解决思路:           这 ...

  8. Vijos 1006 晴天小猪历险记之Hill 单源单汇最短路

    背景 在很久很久以前,有一个动物村庄,那里是猪的乐园(^_^),村民们勤劳.勇敢.善良.团结-- 不过有一天,最小的小小猪生病了,而这种病是极其罕见的,因此大家都没有储存这种药物.所以晴天小猪自告奋勇 ...

  9. Linux配置LNMP环境(二)配置PHP

    前言:本教程安装的PHP版本php-5.6.30(官方最后更新日期2017-01-19),教程编写日期2017-07-02.本教程中的下载地址是在写教程的时候从官方复制的,时间过长可能会有变化. 安装 ...

  10. VB6之WebBrowser控件

    UI短手或者GDI+用烦的童鞋可以借用WebBrowser打造漂亮的程序界面,只需要下载一个好看点的html代码就够了. 引用: Microsoft Html Object Library 部件: M ...