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. PHP基础入门(四)---PHP数组实用基础知识

    PHP数组 数组是特殊的变量,它可以同时保存一个以上的值. ***关键词:数组基础.数组遍历.超全局数组.数组功能.数组函数. 下面来和大家分享一下有关PHP的数组基础知识,希望对你PHP的学习有所帮 ...

  2. Lambda(Linq)

    在谈到lambda表达式之前,首先要说一下委托,在下一章会详细介绍委托,在这里就是简单说明一下. 委托的关键字段delegate,声明委托 public delegate void NoReturnN ...

  3. 【Android Developers Training】 40. 序言:通过NFC共享文件

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好. 原文链接:http://developer ...

  4. usaco 2008 月赛 lites 开关灯 题解

      题目:     Farmer John尝试通过和奶牛们玩益智玩具来保持他的奶牛们思维敏捷. 其中一个大型玩具是 牛栏中的灯. N (2 <= N <= 100,000) 头奶牛中的每一 ...

  5. mysql基础之yum安装mysql5.7.18

    2017-04-19 一.实验环境 centos7_x64 由于centos7的yum源里默认使用了mariadb替代了mysql,所有我们还得先配置一下yum源.当然mariadb和mysql是兼容 ...

  6. (转)基于 Token 的身份验证

    原文:https://ninghao.net/blog/2834 最近了解下基于 Token 的身份验证,跟大伙分享下.很多大型网站也都在用,比如 Facebook,Twitter,Google+,G ...

  7. js字符串与数组的相互转换

    一.数组转字符串,通过join()拼接数组元素 var a, b,c; a = new Array(a,b,c,d,e); b = a.join('-'); c = a.join('');consol ...

  8. Java 反射在实际开发中的应用

    运行时类型识别(RTTI, Run-Time Type Information)是Java中非常有用的机制,在java中,有两种RTTI的方式,一种是传统的,即假设在编译时已经知道了所有的类型:还有一 ...

  9. .net 类型转换

    在.net  平台下类型有两种方式可以进行类型转换,强转或as转换.(有的说法是两种方式都进行强转,有两种强转方式). 如:typeA objA = (typeA)objB 或者使用另一种typeA ...

  10. win10 运行sqlplus报错“SP2-1503: 无法初始化 Oracle 调用界面”

    解决方法: 1.临时方案:此时可以以“管理员身份”运行cmd,然后再执行sqlplus就行了. 长久方案: 请看原文:http://blog.csdn.net/bisal/article/detail ...