import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
// 是否压缩代码,如果运行环境在非生成环境但是代码被压缩了,警告用户
function isCrushed() {}
// 判断环境是否是生成环境,如果是生成环境使用此代码就给出警告提示
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
// 如果初始化的state是一个方法并且enhancer也是方法就会报错
// 如果enhancer是方法如果第4个参数是方法就会报错
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function'
)
}
// 如果初始化的state是方法,enhancer的参数为undefined
if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
// enhancer赋值初始话的stae
enhancer = preloadedState
// preloadedState赋值为undefined
preloadedState = undefined
// 这里是一个兼容2个参数的处理,当参数仅为2个 第二个参数为enhcaner时的处理
}
// 如果enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 如果enhancer不是方法会报错
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
// 如果reducer不是方法 则报错
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
// rootReducer赋值到currentReducer当中 实际是一个函数
let currentReducer = reducer
// 当前store中的state 默认是初始化的state
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
// 浅拷贝一个数组 虽然是浅拷贝 但是currentListener不会被nextListener改变
nextListeners = currentListeners.slice()
}
}
function getState() {
// 省略代码...
}
function subscribe(listener) {
// 省略代码...
}
function dispatch(action) {
// 省略代码...
}
function replaceReducer(nextReducer) {
// 省略代码...
}
function observable() {
// 省略代码...
}
// 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.
// 执行dispatch 来初始化store中的state
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
看完之后,我们可能在这个地方有一点疑惑,就是这里
// 如果enhancer 不是undefined
if (typeof enhancer !== 'undefined') {
// 如果enhancer不是方法会报错
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
// 返回enhancer的方法
return enhancer(createStore)(reducer, preloadedState)
}
/**
* Reads the state tree managed by the store.
*
* @returns {any} The current state tree of your application.
*/
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
// 返回state
return currentState
}
subscribe
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
function subscribe(listener) {
// listener必须是一个方法
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
// 把listenr加入到nextListeners的数组当中
nextListeners.push(listener)
// 解除观察
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
)
}
isSubscribed = false
// 这里做了个拷贝 做的所有操作不影响currentListener
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
// 在nextListener将它去除
nextListeners.splice(index, 1)
}
}
function dispatch(action) {
// 如果dispatch的参数不是action
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
// action必须得有type属性,如果没有会报错
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
// 执行reducer 遍历过滤后的reducer,随后依次赋值到state当中
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
// 获取当前的监听器
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
// 依次执行监听器回调
listener()
}
// dispatch默认返回action
return action
}
replaceReducer
/**
* 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
*
* 动态替换原有的reducer
*/
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
// 将reducer赋值
currentReducer = nextReducer
// 发送一个dispatch 随后重置store
dispatch({ type: ActionTypes.REPLACE })
}
/**
* Interoperability point for observable/reactive libraries.
* @returns {observable} A minimal observable of state changes.
* For more information, see the observable proposal:
* https://github.com/tc39/proposal-observable
*/
function observable() {
const 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.
*/
/**
* @param {Object} 任何对象都可以当做observer
* observer应该有一个`next`方法
* @returns {subscription} 一个对象,它有`unsubscribe`方法能够
* 用来从store中unsubscribe observable
*/
subscribe(observer) {
// observer必须是一个非空的object
if (typeof observer !== 'object' || observer === null) {
throw new TypeError('Expected the observer to be an object.')
}
function observeState() {
if (observer.next) {
observer.next(getState())
}
}
observeState()
const unsubscribe = outerSubscribe(observeState)
return { unsubscribe }
},
[$$observable]() {
return this
}
}
}
bindActionCreators
在讲这个方法前,先看下文档对它的使用说明
Example
TodoActionCreators.js
我们在文件中创建了2个普通的action。
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
}
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
}
}
SomeComponent.js
import { Component } from 'react'
import { bindActionCreators } from 'redux'
import { connect } from 'react-redux'
import * as TodoActionCreators from './TodoActionCreators'
console.log(TodoActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
class TodoListContainer extends Component {
constructor(props) {
super(props)
const { dispatch } = props
// Here's a good use case for bindActionCreators:
// You want a child component to be completely unaware of Redux.
// We create bound versions of these functions now so we can
// pass them down to our child later.
this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch)
console.log(this.boundActionCreators)
// {
// addTodo: Function,
// removeTodo: Function
// }
}
componentDidMount() {
// Injected by react-redux:
let { dispatch } = this.props
// Note: this won't work:
// TodoActionCreators.addTodo('Use Redux')
// You're just calling a function that creates an action.
// You must dispatch the action, too!
// This will work:
let action = TodoActionCreators.addTodo('Use Redux')
dispatch(action)
}
render() {
// Injected by react-redux:
let { todos } = this.props
return <TodoList todos={todos} {...this.boundActionCreators} />
// An alternative to bindActionCreators is to pass
// just the dispatch function down, but then your child component
// needs to import action creators and know about them.
// return <TodoList todos={todos} dispatch={dispatch} />
}
}
export default connect(state => ({ todos: state.todos }))(TodoListContainer)
redux 源码分析
背景
在之前的文章Redux从入门到实践当中对redux的使用进行了说明,这次就来看下它的源码,从而进一步的熟悉它。
构建
相关git地址
构建文档是
CONTRBUTING.md
package.json
从
package.json
当中可以看到redux
的入口文件是lib/redux.js
,这个文件是通过打包出来的。那我们看下打包配置文件rollup.config
可以看到入口文件应该是
src/index.js
我们来看下
src/index.js
src/index.js
主要是将方法暴露出来,给使用者使用createStore
用于创建storecombineReducers
用于组合成rootReducers,因为在外部初始化store时,只能传入一个reducersbindActionCreators
组装了dispatch方法applyMiddleware
合并多个中间件compose
将中间件(middleware)和增强器(enhancer)合并传入到createStore
中combineReducers
src/combineReducers.js
combineReducers
方法会先对传入的reducers进行校验,reducer的类型只能是function
,最后返回的是个方法,这个方法很关键,因为在disptach时,最后执行的就是这个方法。这个方法有2个参数state
和action
,方法内会根据传入的action返回state,最后会比较新旧的state,如果不相等,则会返回新的state,如果相等会返回新的state。那么如果我们直接对store的state进行操作而不是通过dispatch会发生呢,比如说我们这样
我们看一下
combineReducers
中的getUnexpectedStateShapeWarningMessage
这个方法,它会检查store中初始化的state的key有没有在各个子reducer当中,如果没有就会报错。compose
compose会返回一个方法,这个方法可以将传入的方法依次执行
createStore
我们接下来看下
createStore.js
这个文件,它只暴露出了createStore的方法,在createStore
中,初始化了一些参数,同时返回了一个store,store中包括了dispatch
,subscribe
,getState
,replaceReducer
,[$$observable]: observable
看完之后,我们可能在这个地方有一点疑惑,就是这里
这个返回的是什么呢,我们知道
applyMiddleware
返回的其实就是enhancer,那我们结合在一起看一下applyMiddleware
如果直接返回了
enhancer
那么返回的其实也是store
,但是这个store
中的dispatch
被包装过,当dispatch
被执行时,会将所有中间件也依次执行。接下来分析一下
createStore
中的方法getState
很简单,就是返回
currentState
subscribe
这是将一个回调加入到监听数组当中,同时,它会返回一个注销监听的方法。
dispatch
dispatch首先会检查参数,随后会执行
currentReducer(currentState, action)
,而这个方法实际就是combineReducers
的replaceReducer
observable
这里不谈太多observable
这里有个使用例子
bindActionCreators
在讲这个方法前,先看下文档对它的使用说明
Example
TodoActionCreators.js
我们在文件中创建了2个普通的action。
SomeComponent.js
bindActionCreators.js
我们接下来来看它的源码
使用bindActionCreators实际可以创建一个充满dispatch方法的对象。然后可以将这个对象传递子组件来使用。
总结
看完源码后我们大致了解到为什么reducer必须是function,store中的state为什么会创建和reducer相应的对象名的state,为什么只能通过dispatch来对store进行操作。另外redux的一个核心不可变性,redux本身并不能保证。所以我们在自己写的reducer当中必须要保证不能改变store原有的对象,必须得重新创建。
广而告之
本文发布于薄荷前端周刊,欢迎Watch & Star ★,转载请注明出处。
欢迎讨论,点个赞再走吧 。◕‿◕。 ~