正式学习React(四) ----Redux源码分析
今天看了下Redux的源码,竟然出奇的简单,好吧。简单翻译做下笔记:
喜欢的同学自己可以去github上看:点这里
createStore.js
- import isPlainObject from 'lodash/isPlainObject'
- import $$observable from 'symbol-observable'
- /**
- * These are private action types reserved by Redux.
- * For any unknown actions, you must return the current state.
- * If the current state is undefined, you must return the initial state.
- * Do not reference these action types directly in your code.
- */
- export var ActionTypes = {
- INIT: '@@redux/INIT'
- }
- /**
- * Creates a Redux store that holds the state tree.
- * The only way to change the data in the store is to call `dispatch()` on it.
- *
- * There should only be a single store in your app. To specify how different
- * parts of the state tree respond to actions, you may combine several reducers
- * into a single reducer function by using `combineReducers`.
- *
- * @param {Function} reducer A function that returns the next state tree, given
- * the current state tree and the action to handle.
- *
- * @param {any} [preloadedState] The initial state. You may optionally specify it
- * to hydrate the state from the server in universal apps, or to restore a
- * previously serialized user session.
- * If you use `combineReducers` to produce the root reducer function, this must be
- * an object with the same shape as `combineReducers` keys.
- *
- * @param {Function} [enhancer] The store enhancer. You may optionally specify it
- * to enhance the store with third-party capabilities such as middleware,
- * time travel, persistence, etc. The only store enhancer that ships with Redux
- * is `applyMiddleware()`.
- *
- * @returns {Store} A Redux store that lets you read the state, dispatch actions
- * and subscribe to changes.
- */
- export default function createStore(reducer, preloadedState, enhancer) {
- //如果没有提供初始的state,提供了enhancer,就将初试state置为undefined
- if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
- enhancer = preloadedState
- preloadedState = undefined
- }
- //确保enhancer一定是函数
- if (typeof enhancer !== 'undefined') {
- if (typeof enhancer !== 'function') {
- throw new Error('Expected the enhancer to be a function.')
- }
- //这个会返回一个store,只不过是增强版的。
- return enhancer(createStore)(reducer, preloadedState)
- }
- if (typeof reducer !== 'function') {
- throw new Error('Expected the reducer to be a function.')
- }
- //初始化一些变量,用作闭包。
- var currentReducer = reducer
- var currentState = preloadedState //undefined || 传进来的初始值
- var currentListeners = []
- var nextListeners = currentListeners
- var isDispatching = false
- //这个辅助函数是这样的,如果你没有调用dispacth,那么每次调用subscribe来添加监听器的都会被push到nextListenrs,他是currentListerns的一个副本。
- //总的来说这个函数的意义我个人觉得就是保护了currentListeners不被随意污染.保证每次dispacth前状态不变。
- function ensureCanMutateNextListeners() {
- if (nextListeners === currentListeners) {
- nextListeners = currentListeners.slice()
- }
- }
- /**
- * Reads the state tree managed by the store.
- *
- * @returns {any} The current state tree of your application.
- */
//这个就是返回当前的state- function getState() {
- return currentState
- }
- /**
- * Adds a change listener. It will be called any time an action is dispatched,
- * and some part of the state tree may potentially have changed. You may then
- * call `getState()` to read the current state tree inside the callback.
- *
- * You may call `dispatch()` from a change listener, with the following
- * caveats:
- *
- * 1. The subscriptions are snapshotted just before every `dispatch()` call.
- * If you subscribe or unsubscribe while the listeners are being invoked, this
- * will not have any effect on the `dispatch()` that is currently in progress.
- * However, the next `dispatch()` call, whether nested or not, will use a more
- * recent snapshot of the subscription list.
- *
- * 2. The listener should not expect to see all state changes, as the state
- * might have been updated multiple times during a nested `dispatch()` before
- * the listener is called. It is, however, guaranteed that all subscribers
- * registered before the `dispatch()` started will be called with the latest
- * state by the time it exits.
- *
- * @param {Function} listener A callback to be invoked on every dispatch.
- * @returns {Function} A function to remove this change listener.
- */
- //简单点说就是 设置监听函数,每次dispatch(action)的时候,这些传进去的listenr们就会全部被调用
- function subscribe(listener) {
- if (typeof listener !== 'function') {
- throw new Error('Expected listener to be a function.')
- }
- var isSubscribed = true
- ensureCanMutateNextListeners()
- nextListeners.push(listener)
- return function unsubscribe() {
- if (!isSubscribed) {
- return
- }
- isSubscribed = false
- ensureCanMutateNextListeners()
- var index = nextListeners.indexOf(listener)
- nextListeners.splice(index, 1)
- }
- }
- /**
- * Dispatches an action. It is the only way to trigger a state change.
- *
- * The `reducer` function, used to create the store, will be called with the
- * current state tree and the given `action`. Its return value will
- * be considered the **next** state of the tree, and the change listeners
- * will be notified.
- *
- * The base implementation only supports plain object actions. If you want to
- * dispatch a Promise, an Observable, a thunk, or something else, you need to
- * wrap your store creating function into the corresponding middleware. For
- * example, see the documentation for the `redux-thunk` package. Even the
- * middleware will eventually dispatch plain object actions using this method.
- *
- * @param {Object} action A plain object representing “what changed”. It is
- * a good idea to keep actions serializable so you can record and replay user
- * sessions, or use the time travelling `redux-devtools`. An action must have
- * a `type` property which may not be `undefined`. It is a good idea to use
- * string constants for action types.
- *
- * @returns {Object} For convenience, the same action object you dispatched.
- *
- * Note that, if you use a custom middleware, it may wrap `dispatch()` to
- * return something else (for example, a Promise you can await).
- */
- //通知store,我要更新state了。
- function dispatch(action) {
- if (!isPlainObject(action)) {
- throw new Error(
- 'Actions must be plain objects. ' +
- 'Use custom middleware for async actions.'
- )
- }
- if (typeof action.type === 'undefined') {
- 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
- }
- var listeners = currentListeners = nextListeners
- for (var i = 0; i < listeners.length; i++) {
- var listener = listeners[i]
- listener()
- }
- return action
- }
- /**
- * Replaces the reducer currently used by the store to calculate the state.
- *
- * You might need this if your app implements code splitting and you want to
- * load some of the reducers dynamically. You might also need this if you
- * implement a hot reloading mechanism for Redux.
- *
- * @param {Function} nextReducer The reducer for the store to use instead.
- * @returns {void}
- */
- //重置reducer,然后初始化state
- function replaceReducer(nextReducer) {
- if (typeof nextReducer !== 'function') {
- throw new Error('Expected the nextReducer to be a function.')
- }
- currentReducer = nextReducer
- dispatch({ type: ActionTypes.INIT })
- }
- /**
- * Interoperability point for observable/reactive libraries.
- * @returns {observable} A minimal observable of state changes.
- * For more information, see the observable proposal:
- * https://github.com/zenparsing/es-observable
- */
- //暂时我还不知道这个有什么吊用,先不管好了。
- function observable() {
- var outerSubscribe = subscribe
- return {
- /**
- * The minimal observable subscription method.
- * @param {Object} observer Any object that can be used as an observer.
- * The observer object should have a `next` method.
- * @returns {subscription} An object with an `unsubscribe` method that can
- * be used to unsubscribe the observable from the store, and prevent further
- * emission of values from the observable.
- */
- subscribe(observer) {
- if (typeof observer !== 'object') {
- throw new TypeError('Expected the observer to be an object.')
- }
- function observeState() {
- if (observer.next) {
- observer.next(getState())
- }
- }
- observeState()
- var unsubscribe = outerSubscribe(observeState)
- return { unsubscribe }
- },
- [$$observable]() {
- return this
- }
- }
- }
- // When a store is created, an "INIT" action is dispatched so that every
- // reducer returns their initial state. This effectively populates
- // the initial state tree.
- //给currentState设定初始状态
- dispatch({ type: ActionTypes.INIT })
- return {
- dispatch,
- subscribe,
- getState,
- replaceReducer,
- [$$observable]: observable
- }
- }
关于 createStore,我们就关注它返回的对象,subscribe是订阅监听函数的,getState是返回state的,dispacth是发布消息的,更新state的。
剩下那2个就不管他算了。
combineReducers.js
- import { ActionTypes } from './createStore'
- import isPlainObject from 'lodash/isPlainObject'
- import warning from './utils/warning'
- var NODE_ENV = typeof process !== 'undefined' ? process.env.NODE_ENV : 'development'
- function getUndefinedStateErrorMessage(key, action) {
- var actionType = action && action.type
- var actionName = actionType && `"${actionType.toString()}"` || 'an action'
- return (
- `Given action ${actionName}, reducer "${key}" returned undefined. ` +
- `To ignore an action, you must explicitly return the previous state.`
- )
- }
- function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, unexpectedKeyCache) {
- var reducerKeys = Object.keys(reducers)
- var argumentName = action && action.type === ActionTypes.INIT ?
- 'preloadedState argument passed to createStore' :
- 'previous state received by the reducer'
- if (reducerKeys.length === 0) {
- return (
- 'Store does not have a valid reducer. Make sure the argument passed ' +
- 'to combineReducers is an object whose values are reducers.'
- )
- }
- if (!isPlainObject(inputState)) {
- return (
- `The ${argumentName} has unexpected type of "` +
- ({}).toString.call(inputState).match(/\s([a-z|A-Z]+)/)[1] +
- `". Expected argument to be an object with the following ` +
- `keys: "${reducerKeys.join('", "')}"`
- )
- }
- var unexpectedKeys = Object.keys(inputState).filter(key =>
- !reducers.hasOwnProperty(key) &&
- !unexpectedKeyCache[key]
- )
- unexpectedKeys.forEach(key => {
- unexpectedKeyCache[key] = true
- })
- if (unexpectedKeys.length > 0) {
- return (
- `Unexpected ${unexpectedKeys.length > 1 ? 'keys' : 'key'} ` +
- `"${unexpectedKeys.join('", "')}" found in ${argumentName}. ` +
- `Expected to find one of the known reducer keys instead: ` +
- `"${reducerKeys.join('", "')}". Unexpected keys will be ignored.`
- )
- }
- }
- function assertReducerSanity(reducers) {
- Object.keys(reducers).forEach(key => {
- var reducer = reducers[key]
- var initialState = reducer(undefined, { type: ActionTypes.INIT })
- if (typeof initialState === 'undefined') {
- throw new Error(
- `Reducer "${key}" returned undefined during initialization. ` +
- `If the state passed to the reducer is undefined, you must ` +
- `explicitly return the initial state. The initial state may ` +
- `not be undefined.`
- )
- }
- var type = '@@redux/PROBE_UNKNOWN_ACTION_' + Math.random().toString(36).substring(7).split('').join('.')
- if (typeof reducer(undefined, { type }) === 'undefined') {
- throw new Error(
- `Reducer "${key}" returned undefined when probed with a random type. ` +
- `Don't try to handle ${ActionTypes.INIT} or other actions in "redux/*" ` +
- `namespace. They are considered private. Instead, you must return the ` +
- `current state for any unknown actions, unless it is undefined, ` +
- `in which case you must return the initial state, regardless of the ` +
- `action type. The initial state may not be undefined.`
- )
- }
- })
- }
- /**
- * Turns an object whose values are different reducer functions, into a single
- * reducer function. It will call every child reducer, and gather their results
- * into a single state object, whose keys correspond to the keys of the passed
- * reducer functions.
- *
- * @param {Object} reducers An object whose values correspond to different
- * reducer functions that need to be combined into one. One handy way to obtain
- * it is to use ES6 `import * as reducers` syntax. The reducers may never return
- * undefined for any action. Instead, they should return their initial state
- * if the state passed to them was undefined, and the current state for any
- * unrecognized action.
- *
- * @returns {Function} A reducer function that invokes every reducer inside the
- * passed object, and builds a state object with the same shape.
- */
- export default function combineReducers(reducers) {
- var reducerKeys = Object.keys(reducers)
- var finalReducers = {}
- for (var i = 0; i < reducerKeys.length; i++) {
- var key = reducerKeys[i]
- if (NODE_ENV !== 'production') {
- //排除undefined.
- if (typeof reducers[key] === 'undefined') {
- warning(`No reducer provided for key "${key}"`)
- }
- }
- //排除不是func的
- if (typeof reducers[key] === 'function') {
- finalReducers[key] = reducers[key]
- }
- }
- var finalReducerKeys = Object.keys(finalReducers)
- if (NODE_ENV !== 'production') {
- var unexpectedKeyCache = {}
- }
- var sanityError
- try {
//这里会对每个子reducer的state进行检查。返回不能为undefined- assertReducerSanity(finalReducers)
- } catch (e) {
- sanityError = e
- }
- //我们最最关心的就是这个返回函数,其实我们如果已经堆这个函数有一定的了解,就知道这个函数其实就把子reducers全部在这个函数里执行一边,
//返回最后的state- return function combination(state = {}, action) {
- if (sanityError) {
- throw sanityError
- }
- if (NODE_ENV !== 'production') {
- var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
- if (warningMessage) {
- warning(warningMessage)
- }
- }
- var hasChanged = false
- var nextState = {}
- for (var i = 0; i < finalReducerKeys.length; i++) {
- var key = finalReducerKeys[i]
- var reducer = finalReducers[key]
- var previousStateForKey = state[key]
- var nextStateForKey = reducer(previousStateForKey, action)
- if (typeof nextStateForKey === 'undefined') {
- var errorMessage = getUndefinedStateErrorMessage(key, action)
- throw new Error(errorMessage)
- }
- nextState[key] = nextStateForKey
- hasChanged = hasChanged || nextStateForKey !== previousStateForKey
- }
- return hasChanged ? nextState : state
- }
- }
compose.js
- /**
- * Composes single-argument functions from right to left. The rightmost
- * function can take multiple arguments as it provides the signature for
- * the resulting composite function.
- *
- * @param {...Function} funcs The functions to compose.
- * @returns {Function} A function obtained by composing the argument functions
- * from right to left. For example, compose(f, g, h) is identical to doing
- * (...args) => f(g(h(...args))).
- */
- export default function compose(...funcs) {
- //funcs就是我们传入的中间件,fn1,fn2...
- //如果什么都没有,就返回一个function(arg){return arg};当作默认的中间件
- if (funcs.length === 0) {
- return arg => arg
- }
- //排除所有中间件参数中不是function的
- funcs = funcs.filter(func => typeof func === 'function')
- //如果只有一个中间件参数,就把这个唯一的中间件返回。
- if (funcs.length === 1) {
- return funcs[0]
- }
- //如果中间件参数个数超过1个
- //取出最后一个中间件参数
- const last = funcs[funcs.length - 1]
- //将funcs中最开头到倒数第二个的数组复制一份,即排除了最后一个中间件
- const rest = funcs.slice(0, -1)
- //返回(...args) => f(g(h(...args))).
- return (...args) => rest.reduceRight((composed, f) => f(composed), last(...args))
- }
解释完compose.js,我们可以简单的理解为: compose就是用来将函数组合起来使用。
- 假如:compose(f, g, h)
- 分装后:(...args) => f(g(h(...args))).
applyMiddleware.js
- import compose from './compose'
- /**
- * Creates a store enhancer that applies middleware to the dispatch method
- * of the Redux store. This is handy for a variety of tasks, such as expressing
- * asynchronous actions in a concise manner, or logging every action payload.
- *
- * See `redux-thunk` package as an example of the Redux middleware.
- *
- * Because middleware is potentially asynchronous, this should be the first
- * store enhancer in the composition chain.
- *
- * Note that each middleware will be given the `dispatch` and `getState` functions
- * as named arguments.
- *
- * @param {...Function} middlewares The middleware chain to be applied.
- * @returns {Function} A store enhancer applying the middleware.
- */
- export default function applyMiddleware(...middlewares) {
- //短短几行,很简单哟。
//返回我们在creteStore里看到的enhancer
- return (createStore) => (reducer, preloadedState, enhancer) => {
- var store = createStore(reducer, preloadedState, enhancer)
- var dispatch = store.dispatch
- var chain = []
- var middlewareAPI = {
- getState: store.getState,
- dispatch: (action) => dispatch(action)
- }
- //将 当前store的getState和dispacth方法挂载到中间件里。
- chain = middlewares.map(middleware => middleware(middlewareAPI))
- dispatch = compose(...chain)(store.dispatch)
- return {
- ...store,
- dispatch
- }
- }
- }
假如我们在代码里看到:
const store = applyMiddleware(promise, thunk, observable)(createStore)(reducer);
亦或是
const store =
createStore(
reducer,state,
applyMiddleware(promise, thunk, observable)
);
我们根据上面的代码走一遍,看看是怎么个执行过程。
1: applyMiddleware(promise, thunk, observable) 会返回enhancer.
2:enhancer(createStore) 返回一个将中间件参数都闭包进来的 createStore函数,此时我们叫他加强版!我给他标注是红色;
3:
createStore
(reducer) 然后就是上面代码的21-34行。 最后我们关心一下这个返回的store里的dispatch,其实是加强版的,因为里面是执行中间件的!!但是中间件里面到底干了什么,
我们目前不得而知,总之记住,dispacth现在牛逼了就行。比如可以处理异步啦等等。
bindActionCreators.js
- function bindActionCreator(actionCreator, dispatch) {
- return (...args) => dispatch(actionCreator(...args))
- }
- /**
- * Turns an object whose values are action creators, into an object with the
- * same keys, but with every function wrapped into a `dispatch` call so they
- * may be invoked directly. This is just a convenience method, as you can call
- * `store.dispatch(MyActionCreators.doSomething())` yourself just fine.
- *
- * For convenience, you can also pass a single function as the first argument,
- * and get a function in return.
- *
- * @param {Function|Object} actionCreators An object whose values are action
- * creator functions. One handy way to obtain it is to use ES6 `import * as`
- * syntax. You may also pass a single function.
- *
- * @param {Function} dispatch The `dispatch` function available on your Redux
- * store.
- *
- * @returns {Function|Object} The object mimicking the original object, but with
- * every action creator wrapped into the `dispatch` call. If you passed a
- * function as `actionCreators`, the return value will also be a single
- * function.
- */
- export default function bindActionCreators(actionCreators, dispatch) {
- if (typeof actionCreators === 'function') {
- return bindActionCreator(actionCreators, dispatch)
- }
- if (typeof actionCreators !== 'object' || actionCreators === null) {
- throw new Error(
- `bindActionCreators expected an object or a function, instead received ${actionCreators === null ? 'null' : typeof actionCreators}. ` +
- `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`
- )
- }
- var keys = Object.keys(actionCreators)
- var boundActionCreators = {}
- for (var i = 0; i < keys.length; i++) {
- var key = keys[i]
- var actionCreator = actionCreators[key]
- if (typeof actionCreator === 'function') {
- boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
- }
- }
- return boundActionCreators
- }
这个东西唯一的好处是往下传actionCreator和dispatch的时候,少些代码,无形中在props里加了内容!!!!
具体应用场景,大家自行谷歌。因为我也才学这个第8天,这函数我自己都没用过····哈哈。不过以后肯定要用了!!!
代码十分简单,我就不分析了。
这里有一篇园内的朋友写的它的应用,凑合看吧:点这里
正式学习React(四) ----Redux源码分析的更多相关文章
- 正式学习React(五) react-redux源码分析
磨刀不误砍柴工,咱先把react-redux里的工具函数分析一下: 源码点这里 shallowEqual.js export default function shallowEqual(objA, ...
- 正式学习React (七) react-router 源码分析
学习react已经有10来天了,对于react redux react-redux 的使用流程和原理,也已经有一定的了解,在我上一篇的实战项目里,我用到了react-route,其实对它还只是 停留在 ...
- EasyUI学习总结(四)——parser源码分析
parser模块是easyloader第一个加载的模块,它的主要作用,就是扫描页面上easyui开头的class标签,然后初始化成easyui控件. /** * parser模块主要是解析页面中eas ...
- EasyUI学习总结(四)——parser源码分析(转载)
本文转载自:http://www.cnblogs.com/xdp-gacl/p/4082561.html parser模块是easyloader第一个加载的模块,它的主要作用,就是扫描页面上easyu ...
- Redux源码分析之applyMiddleware
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- Redux源码分析之createStore
接着前面的,我们继续,打开createStore.js, 直接看最后, createStore返回的就是一个带着5个方法的对象. return { dispatch, subscribe, getSt ...
- Redux源码分析之基本概念
Redux源码分析之基本概念 Redux源码分析之createStore Redux源码分析之bindActionCreators Redux源码分析之combineReducers Redux源码分 ...
- memcached学习笔记——存储命令源码分析下篇
上一篇回顾:<memcached学习笔记——存储命令源码分析上篇>通过分析memcached的存储命令源码的过程,了解了memcached如何解析文本命令和mencached的内存管理机制 ...
- memcached学习笔记——存储命令源码分析上篇
原创文章,转载请标明,谢谢. 上一篇分析过memcached的连接模型,了解memcached是如何高效处理客户端连接,这一篇分析memcached源码中的process_update_command ...
随机推荐
- 【转】node.exe调试JavaScript代码
node.exe调试JavaScript代码 目的: Console.log可以打印一些信息,光有log还不够,当程序出现问题时通过log可以定位到错误位置,但是当我们想查看错误现场的变量时,log就 ...
- Lumen 队列
队列 简介 连接 Vs. 队列 驱动的必要设置 创建任务类 生成任务类 任务类结构 分发任务 延迟分发 任务链 自定义队列 & 连接 指定任务最大尝试次数 / 超时值 频率限制 错误处理 运行 ...
- 为什么要使用自增ID作为主键
1.从业务上来说 在设计数据库时不需要费尽心思去考虑设置哪个字段为主键.然后是这些字段只是理论上是唯一的,例如使用图书编号为主键,这个图书编号只是理论上来说是唯一的,但实践中可能会出现重复的 情况.所 ...
- AOSP5.0换8G eMMC不能开机问题
AOSP5.0 MT6572平台.用H9TP32A4GDBCPR_KGM这颗4G的eMMC就能够.可是用H9TP65A8JDACPR_KGM这个8G的就开不了机,一直是重新启动.用串口抓LOG发现以下 ...
- mstsc远程登录设置
mstsc终于可以连上了, 1.系统属性 远程允许, 2.开启三个服务: Remote Desktop ConfigurationRemote Desktop ServicesRemote Deskt ...
- Poj1482
It's not a Bug, It's a Feature! Time Limit: 5000MS Memory Limit: 30000K Total Submissions: 1428 ...
- iis express worker process已停止工作
以管理员方式运行命令提示符工具,然后执行以下语句 netsh winsock reset 重启电脑
- 如何使用android studio及夜神模拟器开发调试
android studio 只安装sdk(不安装自带模拟器)1.下载并安装夜神模拟器 2.先启动夜神模拟器 3.然后运行cmd命令,cd到夜神安装目录(bin目录下),执行命令: nox_adb.e ...
- 【转】windows 下 goprotobuf 的安装与使用
1. 安装 在网上看了很多教程,都提到要安装 protoc 与 protoc-gen-go,但通过尝试之后并不能正确安装 protoc,一下记录能够顺利安装 protoc 与 protoc-gen-g ...
- transaction 数据库事务 roolback 回滚
事务是恢复和并发控制的基本单位 https://baike.baidu.com/item/数据库事务/9744607 事务有三种模型: 1.隐式事务是指每一条数据操作语句都自动地成为一个事务,事务的开 ...