代码版本 4.0.0
shopping-cart
src/index.js
import React from 'react'
import { render } from 'react-dom'
import { createStore, applyMiddleware } from 'redux'
import { Provider } from 'react-redux'
import { createLogger } from 'redux-logger'
import thunk from 'redux-thunk'
import reducer from './reducers'
import { getAllProducts } from './actions'
import App from './containers/App'
const middleware = [ thunk ];
if (process.env.NODE_ENV !== 'production') {
middleware.push(createLogger());
}
const store = createStore(
reducer,
applyMiddleware(...middleware)
)
store.dispatch(getAllProducts())
render(
,
document.getElementById('root')
)
actions/index.js
import shop from '../api/shop'
import * as types from '../constants/ActionTypes'
const receiveProducts = products => ({
type: types.RECEIVE_PRODUCTS,
products: products
})
//这个很有意思返回的居然不是上面的{type:''}对象,而是个函数
export const getAllProducts = () => dispatch => {
shop.getProducts(products => {
dispatch(receiveProducts(products))
})
}
getAllProducts action返回的是函数,匪夷所思,开始怀疑,是不是reducer第二个参数不是action单纯的对象,debug代码后才知道这和thunk中间件有关
第二张图可以清晰的看到action依旧是单纯的包含type属性的普通对象
发现一小哥也是产生了同样的疑问由redux 异步数据流引发的血案
1. bindActionCreators
TodoActionCreators.js
export function addTodo(text) {
return {
type: 'ADD_TODO',
text
};
}
export function removeTodo(id) {
return {
type: 'REMOVE_TODO',
id
};
}
SomeComponent.js
// 这样做行得通:
let action = TodoActionCreators.addTodo('Use Redux');
dispatch(action);
由此可见dispatch是个带有type属性的对象
bindActionCreators源码:
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args))
}
//参数可为单个actionCreator函数,也可以为多个actionCreator函数组成的对象
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"?`
)
}
const keys = Object.keys(actionCreators)
const boundActionCreators = {}
for (let i = 0; i < keys.length; i++) {
const key = keys[i]
const actionCreator = actionCreators[key]
if (typeof actionCreator === 'function') {
boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
}
}
return boundActionCreators
}
app.js
const mapDispatchToProps = dispatch => ({
actions: bindActionCreators(TodoActions, dispatch)
})
rodomvc mainsection.js
//这样组件就直接调用函数了,而不需要再去dispatch一个actionCreator
handleClearCompleted = () => {
this.props.actions.clearCompleted()
}
2. action和请求是怎么建立的关系?也就是和reudcers怎么建立关系?
今天在看todmvc实例的时候再次看到了combineReducers
import { combineReducers } from 'redux'
import todos from './todos'
const rootReducer = combineReducers({
todos
})
export default rootReducer
然后带着疑问去查看redux文档Reducer逐层对reducer的拆分进行了讨论,最后就是将多个reducer函数使用combineReducers组合成了一个大的reducer函数,查阅redux createStore代码:
export default function createStore(reducer, preloadedState, enhancer) {
let currentReducer = reducer//唯一的reducer函数
let currentState = preloadedState//初始全局state
function dispatch(action) {
try {
isDispatching = true
//调用注册好的reducer函数,action是包含type属性的纯对象,
//currentReducer会返回新的state,从而达到更新state的目的
//currentReducer会根据不同的action的type去更新对应的state
//也就是在这里action和reducer建立了联系,reducer是在
//createStore调用的时候传递进来的,见下文
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
}
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
currentReducer就是被多个reducer函数组合后的函数,说白了就是使用switch去判断action.type更新对应的state
createStore的调用
import reducer from './reducers'
//将组合后的reducer函数传到createStore内
const store = createStore(reducer)
render(
,
document.getElementById('root')
)
简化版的redux 从0实现一个tinyredux说的比较清楚
源码:
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
}
const finalReducerKeys = Object.keys(finalReducers)
//返回一个拼装后的reducer
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
//这个地方循环判断前后两次局部state是不是同一个数据
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
//如果有一条局部state更新了,那么整个state都会是新的nextState,
//如果全部的局部state都没更新,那么就返回原来的state,不对全局的state进行更新
return hasChanged ? nextState : state
}
}
combineReducers和没有使用该函数的前后对比,按照官网的说法
你也可以给它们设置不同的 key,或者调用不同的函数。下面两种合成 reducer 方法完全等价
const reducer = combineReducers({
a: doSomethingWithA,
b: processB,
c: c
})
function reducer(state = {}, action) {
return {
a: doSomethingWithA(state.a, action),
b: processB(state.b, action),
c: c(state.c, action)
}
}
3.state更新后react组件是怎么更新的?
前面两条说了dispatch(action)对state进行更新,那么state更新了,react组件是怎么更新的呢?下面就这个问题展开讨论:
4.为什么reducer每次都返回新的对象或者新数组?
比如中文官网Reducer里面的例子
import {
ADD_TODO,
TOGGLE_TODO,
SET_VISIBILITY_FILTER,
VisibilityFilters
} from './actions'
...
function todoApp(state = initialState, action) {
switch (action.type) {
case SET_VISIBILITY_FILTER:
return Object.assign({}, state, { // 返回新对象
visibilityFilter: action.filter
})
case ADD_TODO:
return Object.assign({}, state, { // 返回新数组
todos: [
...state.todos,
{
text: action.text,
completed: false
}
]
})
default:
return state
}
}
SET_VISIBILITY_FILTER和ADD_TODO都是返回新的对象或者数组,而没有在原来的state上直接修改引用,原因有两点:来自中文官网Reducer 基础概念
突变是一种不鼓励的做法,因为它通常会打乱调试的过程,以及 React Redux 的 connect 函数:
- 对于调试过程, Redux DevTools 期望重放 action 记录时能够输出 state 值,而不会改变任何其他的状态。突变或者异步行为会产生一些副作用,可能使调试过程中的行为被替换,导致破坏了应用。
- 对于 React Redux connect 来说,为了确定一个组件(component)是否需要更新,它会检查从 mapStateToProps 中返回的值是否发生改变。为了提升性能,connect 使用了一些依赖于不可变 state 的方法。并且使用浅引用(shallow reference)来检测状态的改变。这意味着直接修改对象或者数组是不会被检测到的并且组件不会被重新渲染。
对于第二点,react-redux在做前后redux state对比的时候,reducer的返回结果直接决定了connect的高阶组件的shouldComponentUpdate返回true还是false。
react-redux 版本 5.0.6 selectorFactory.js
selector的意思就是从redux 中筛选出你想要的数据。selectorFactory.js的作用就是根据mapStateToProps和mapDispatchToProps以及ownProps生成最后被connect组件最终的props
export function pureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
{ areStatesEqual, areOwnPropsEqual, areStatePropsEqual }
) {
let hasRunAtLeastOnce = false
let state
let ownProps
let stateProps
let dispatchProps
let mergedProps
function handleFirstCall(firstState, firstOwnProps) {
state = firstState
ownProps = firstOwnProps
stateProps = mapStateToProps(state, ownProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
hasRunAtLeastOnce = true
return mergedProps
}
function handleNewPropsAndNewState() {
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleNewProps() {
if (mapStateToProps.dependsOnOwnProps)
stateProps = mapStateToProps(state, ownProps)
if (mapDispatchToProps.dependsOnOwnProps)
dispatchProps = mapDispatchToProps(dispatch, ownProps)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
// redux 根state变化,执行该函数
function handleNewState() {
//根据redux根state以及父组件传递进来的ownProps,比如路由组件的match参数,
// 计算出组件需要的props
const nextStateProps = mapStateToProps(state, ownProps)
// areStatePropsEqual就是shallowEqual
// 通过浅比较前后计算出的redux的state,来决定要不要重新合并redux state,ownProps,dispatch props
// mapStateToProps每次都返回新对象,但是没关系,shallowEqual会判断他第一层的引用有没有变化,
// 也就是判断拿到的redux的state的某个子项有没有发生变化
// 换句话说,这里shallowEqual比对的是redux的state子项,并不是mapStateToProps直接返回的对象
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
// 如果拿到的redux的state子项有变化,就重新计算被connect组件的props
if (statePropsChanged)
// mergeProps函数总是返回新的对象
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
// redux state的子项的变化导致根state也变化,然后执行的mergeProps函数也是返回新对象,
// 在connectAdvanced.js runComponentSelector函数中进行前后mergedProps 对象的时候
// 前后mergedProps 不是同一个引用,shouldComponentUpdate设置为true,
// 引起被connect组件的更新
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
// 判断父组件传递过来的props有没有改变
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
// 判断前后redux根state有没有改变
const stateChanged = !areStatesEqual(nextState, state)
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()// 如果redux的根state发生改变
// 如果redux 的根state和父组件传递来的ownProps都没改变,
// 返回之前mapStateToProps,mapDispatchToProps以及ownProps合并后的props
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce // 这里的nextState就是redux根state
? handleSubsequentCalls(nextState, nextOwnProps)
: handleFirstCall(nextState, nextOwnProps)
}
}
export default function finalPropsSelectorFactory(dispatch, {
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
...options
}) {
const mapStateToProps = initMapStateToProps(dispatch, options)
const mapDispatchToProps = initMapDispatchToProps(dispatch, options)
const mergeProps = initMergeProps(dispatch, options)
if (process.env.NODE_ENV !== 'production') {
verifySubselectors(mapStateToProps, mapDispatchToProps, mergeProps, options.displayName)
}
const selectorFactory = options.pure// 默认pure为true,所以一般都是执行pureFinalPropsSelectorFactory
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
areStatesEqual就是connect.js 53行传递进来的function strictEqual(a, b) { return a === b }
,该行代码const stateChanged = !areStatesEqual(nextState, state)
首先判断前后根state有没有发生改变,然后再决定要不要进行handleNewState函数内部对下面一层的浅比较。一般项目都会使用combineReducers对根reducer进行拆分,从而不会直接去改变根state,另一方面来说就是使用了combineReducers后,子的reducer不会直接改变根state那么const stateChanged = !areStatesEqual(nextState, state)
就永远为false,这样被connect包裹的组件就永远不会更新,这样肯定是不对的,
但是根state一般都是初始化好的,dispatch的时候是怎么改变到根对象的呢?
再去看redux源码,dispatch
function dispatch(action) {
try {
isDispatching = true
currentState = currentReducer(currentState, action)//每次dispatch都会执行这段代码
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
每次dispatch都会执行currentState = currentReducer(currentState, action)
如果想currentReducer函数每次都返回新对象,一般项目中我们都是用的combineReducers函数将根reducer拆分成多个子reducer,再去看combineReducers
export default function combineReducers(reducers) {
const reducerKeys = Object.keys(reducers)
const finalReducers = {}
for (let i = 0; i < reducerKeys.length; i++) {
const key = reducerKeys[i]
if (process.env.NODE_ENV !== 'production') {
if (typeof reducers[key] === 'undefined') {
warning(`No reducer provided for key "${key}"`)
}
}
if (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
}
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
}
let hasChanged = false
const nextState = {}
for (let i = 0; i < finalReducerKeys.length; i++) {
const key = finalReducerKeys[i]
const reducer = finalReducers[key]
const previousStateForKey = state[key]// 根state下某个对象
const nextStateForKey = reducer(previousStateForKey, action)// 根state下某个通过reducer计算后的对象
if (typeof nextStateForKey === 'undefined') {
const errorMessage = getUndefinedStateErrorMessage(key, action)
throw new Error(errorMessage)
}
nextState[key] = nextStateForKey
// 因为每个子reducer,根据action都会返回新的引用,所以hasChanged 为true
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
return hasChanged ? nextState : state// 有子对象引用发生改变,就返回新的根state对象
}
}
从上面combineReducers源码可以看出,子reducer根据action必须返回新引用,不然的话,根state永远是同个引用,那么const stateChanged = !areStatesEqual(nextState, state)
永远返回false,被connect的组件就永远没法更新,到这里就完整的解释了为什么reducer要返回新引用。