前言
React:组件的UI界面渲染
Redux:数据状态管理
React-Redux:将React、Redux关联
Redux
基本概念
你也可以先查看一下redux相关的中文文档 戳我跳转
- 三大原则
单一数据源:整个应用的 state 被储存在一个 object 中,便于调试和数据的管理。
State 是只读的:唯一改变 state 的方法就是触发 action,action 里面存储了要更新的数据信息。
使用纯函数来执行修改:这个纯函数就是 reducer ,reducer 的作用就是接收 action 传递过来的数据,然后根据业务场景返回相关的 state。
- action
action 是 store 数据的唯一来源。action 中需要包含 type 字段,其余结构可以根据项目和业务需求自己定义。
// 如下代码为一个action创建函数的结构,作用就是返回action对象
import * as ActionTypes from '../actionType';
/**
* @func
* @desc 更新用户信息
* @param data
*/
export function updateUserInfo(data: { [key: string]: any }) {
return {
type: ActionTypes.USERINFO_UPDATE,
data
};
};
- reducer
reducer
就是一个纯函数,接收旧的 state 和 action,返回新的 state。
注意,不要在
reducer
里做这些操作:
(1)修改传入参数
(2)执行有副作用的操作,如 API 请求和路由跳转
(3)调用非纯函数,如Date.now()
或Math.random()
理由:因为reducer
一定要保持纯净。只要传入参数相同,返回计算得到的下一个state
就一定相同。没有特殊情况、没有副作用,没有 API 请求、没有变量修改,单纯执行计算。
// 如下代码,userInfo就是纯函数,也就是reducer
import * as ActionTypes from '../actionType';
import { combineReducers } from 'redux';
interface IUserinfoAction {
type: string;
[key: string]: any;
}
const initialState = {};
function userInfo(state = initialState, action: IUserinfoAction) {
switch(action.type) {
case ActionTypes.USERINFO_UPDATE:
return action.data;
default:
return state;
}
}
const rootReducer = combineReducers({
userInfo
});
export default rootReducer;
- store
store
是一个对象,内部主要存在属性:getState
、dispatch
、subscribe
、replaceReducer
。
(1)getState
用于获取state
(2)dispatch(action)
用于更新state
(3)subscribe(listener)
用于注册监听器,同时返回的函数可用于注销监听器
- 简单的 redux 案例
项目文件目录:
// actionType.ts
//定义 action 的 type 类型
export const USERINFO_UPDATE = 'USERINFO_UPDATE';
export const SEARCHBOOK_UPDATE = 'SEARCHBOOK_UPDATE';
export const CHAPTER_UPDATE = 'CHAPTER_UPDATE';
// user/action.ts
import * as ActionTypes from '../actionType';
/**
* @func
* @desc 更新用户信息
* @param data
*/
export function updateUserInfo(data: { [key: string]: any }) {
return {
type: ActionTypes.USERINFO_UPDATE,
data
};
};
// user/reducer.ts
import * as ActionTypes from '../actionType';
import { combineReducers } from 'redux';
interface IUserinfoAction {
type: string;
[key: string]: any;
}
const initialState = {};
function userInfo(state = initialState, action: IUserinfoAction) {
switch(action.type) {
case ActionTypes.USERINFO_UPDATE:
return action.data;
default:
return state;
}
}
const rootReducer = combineReducers({
userInfo
});
export default rootReducer;
// rootReducer.ts
// 合并所有的reducer
import { combineReducers } from 'redux';
import userReducer from './user/reducer';
const rootReducer = combineReducers({
userReducer,
/*...其他的reducer...*/
});
export default rootReducer;
// index.ts
//
import rootReducer from './rootReducer';
import { createStore, applyMiddleware } from 'redux';
const initState: { [key: string]: any } = {/*...初始化状态的值...*/};
export const store = createStore(rootReducer, initState, applyMiddleware(
/*...中间件,增强dispatch...*/
));
// index.tsx 项目index的位置
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { store } from 'store/index';
ReactDOM.render(
<Provider store= {store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
源码解析
createStore.js
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
/*.....*/
/*.....*/
/*
判断enhancer是否为function,如果是的话
return enhancer(createStore)(reducer, preloadedState)
enhancer(增强器) 就是 applyMiddleware(/*...中间件,增强dispatch...*/)
*/
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
/*.....*/
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
/*
返回最新的状态值
*/
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.'
)
}
return currentState
}
/*
顾名思义,就是订阅,传入监听者函数,然后存储到nextListeners容器中,并且返回
一个取消订阅的函数,用于卸载订阅函数。使用了闭包的方式,找到对应的listener的
index值,然后将它从nextListeners容器中剔除
*/
function subscribe(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#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
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#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
/*
每一次dispatch一个action的时候,都会执行currentReducer
而currentReducer就是最初传递进来的reducer的集合
执行完currentReduce,就会返回最新的状态,然后将状态赋值给currentState
而currentState就是getState方法返回所需要的值也就是最新的状态
同时循环遍历listeners,执行其中的监听方法。
listeners的数据来自于subscribe,subscribe就是订阅。
*/
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
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
function observable() {
const outerSubscribe = subscribe
return {
subscribe(observer) {
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
}
}
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
[$$observable]: observable
}
}
如上代码: 我们在使用
createStore
的时候,会这样写:export const store = createStore(rootReducer, initState, applyMiddleware(/*...*/));
其中rootReducer
代表合并后的reducer
,initState
是初始状态默认值,applyMiddleware(/*...*/)
代表增强器,该方法的参数为中间件(例如:redux-thunk
)
createStore
执行最初,会判断判断enhancer是否为function,如果是的话return enhancer(createStore)(reducer, preloadedState)
。而enhancer
(增强器) 就是applyMiddleware(/*...中间件,增强dispatch...*/)
createStore
内部提供getState
方法,它用于返回最新的状态值
createStore
内部提供subscribe
方法,顾名思义,就是订阅,传入监听者函数,然后存储到nextListeners容器中,并且返回一个取消订阅的函数,用于卸载订阅函数。使用了闭包的方式,找到对应的listener的index值,然后将它从nextListeners容器中剔除
createStore
内部提供dispatch
方法,每一次dispatch
action
的时候,都会执行currentReducer
,而currentReducer
就是最初传递进来的reducer
的集合执行完currentReduce
,就会返回最新的状态,然后将状态赋值给currentState
而currentState
就是getState
方法返回所需要的值也就是最新的状态。同时循环遍历listeners
,执行其中的监听方法。listeners
的数据来自于subscribe
方法的执行
applyMiddleware.js
import compose from './compose'
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
const store = createStore(...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
如上代码:
applyMiddleware
返回一个 function ,接收createStore
,其内部同样返回一个匿名函数,接收reducer
和preloadedState
。该匿名函数会调用createStore
来创建store
,也会声明一个变量middlewareAPI
,里面存储了getState
和dispatch
,getState
用户获取最新状态,dispatch
是为了后面经过传递进来的中间件map 返回得到增强的dispatch
。
combineReducers.js
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 (typeof reducers[key] === 'function') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers)
let unexpectedKeyCache
/*.....*/
let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
}
return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
}
/*.....*/
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
hasChanged = hasChanged || nextStateForKey !== previousStateForKey
}
hasChanged =
hasChanged || finalReducerKeys.length !== Object.keys(state).length
return hasChanged ? nextState : state
}
}
如上代码:
combineReducers
的作用就是将所有的reducer
合并。会先获取传递进来的所有reducer
的key,然后循环遍历,将reducer
都挂载到一个对象上,这个对象叫finalReducers
。然后返回一个叫combination
的方法。combination
的作用就是运行以后产生state
,该方法接收初始状态值和action
作为参数。其内部就是循环遍历finalReducers
。将执行产生状态挂载到nextState
上。最终会判断状态是否发生了变化(其实就是判断新旧两个对象的地址是否一致),如果发生变化,就返回最新的状态,否则返回旧的状态。
compose.js
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
如上代码: 目的就是返回一个增强后的
dispatch
,执行的效果如同:compose(a, b, c)(/*..参数..*/) => a(b(c(/*..参数...*/)))
React-Redux
一个简单的案例
// index.tsx 项目index的位置
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import * as serviceWorker from './serviceWorker';
import { Provider } from 'react-redux';
import { store } from 'store/index';
ReactDOM.render(
<Provider store= {store}>
<App />
</Provider>,
document.getElementById('root')
);
serviceWorker.unregister();
// book.tsx
import React from 'react';
import {connect} from 'react-redux';
import { bindActionCreators } from 'redux';
/*...其余import...*/
class BookContainer extends React.PureComponent<IProps, IState> {
/*...业务代码...*/
}
/*可以将redux的状态数据映射到组件的props中*/
function mapStateToProps(state: IReduxState) {
return {
searchContent: state.searchContent
};
}
/*将action映射到组件的props中*/
function mapDispatchToProps(dispatch: any) {
return {
updateChapterMaterial: bindActionCreators(updateChapterMaterial, dispatch)
}
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookContainer);
provider
function Provider({ store, context, children }) {
const contextValue = useMemo(() => {
const subscription = new Subscription(store)
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription
}
}, [store])
const previousState = useMemo(() => store.getState(), [store])
useEffect(() => {
const { subscription } = contextValue
subscription.trySubscribe()
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
}, [contextValue, previousState])
const Context = context || ReactReduxContext
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
如上代码:
Provider
组件接收store对象,context
(上下文对象),children
(React节点元素)。内部有一个存在的变量叫contextValue
,借助了useMemo
,一旦依赖项store
发生了变化,那么就会创建订阅监听,返回store
和subscription
。
contextValue
和previousState
发生变化以后,就会执行useEffect
中的代码。其实就是执行订阅中的方法。
这边需要注意的是Provider
的value
值就是store
和subscription
。目的是给connect
组件提供的。而subscription
主要负责connect
、Provider
组件更新的。
connect
class BookContainer extends React.Component<IProps, IState> {
/*...业务代码...*/
}
export default connect(
mapStateToProps,
mapDispatchToProps
)(BookContainer);
如上代码:
connect
是高阶组件,接收mapStateToProps
和mapDispatchToProps
参数,mapStateToProps
的作用是将特定的state
映射到组件的props
上,mapDispatchToProps
将dispatch(action)
映射到props
上,内部监听了Redux
的store
的变化,当state
变化时,被connect
的所有组件都会进行一次render
。
match、initMapStateToProps、initMapDispatchToProps、initMergeProps
import defaultMapDispatchToPropsFactories from './mapDispatchToProps'
import defaultMapStateToPropsFactories from './mapStateToProps'
import defaultMergePropsFactories from './mergeProps'
/*...其他导入模块...*/
function match(arg, factories, name) {
for (let i = factories.length - 1; i >= 0; i--) {
const result = factories[i](arg)
if (result) return result
}
return (dispatch, options) => {
throw new Error(
`Invalid value of type ${typeof arg} for ${name} argument when connecting component ${
options.wrappedComponentName
}.`
)
}
}
export function createConnect({
connectHOC = connectAdvanced,
/*......*/
}={}){
/*......*/
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
}
如上代码:
(1)connect
函数在执行的时候,会调用三次match
方法,用来初始化mapDispatchToProps
、mapStateToProps
和mergeProps
的函数。
(2)match
内部会循环执行factories
的方法。
(3)factories
来自于 mapStateToProps.js 和 mapDispatchToProps.js 返回的数组结果。数组的成员要么为undefined
,或者成员的结构为:(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps
。
(4) 经过match
得到的结果(initMapStateToProps
,initMapDispatchToProps
,initMergeProps
)会传入到connectAdvanced
中使用,connectAdvanced
函数会根据传入的参数和store
对象计算出最终组件需要的props
。
mapStateToProps.js
// mapStateToProps.js
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
export function whenMapStateToPropsIsFunction(mapStateToProps) {
return typeof mapStateToProps === 'function'
? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
: undefined
}
export function whenMapStateToPropsIsMissing(mapStateToProps) {
return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}
export default [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
如上代码:
whenMapStateToPropsIsFunction
:当我们传递的mapStateToProps
是一个方法,就会返回结果值。whenMapStateToPropsIsMissing
:当我们传入的mapStateToProps
经过转换为false
值时,会返回一个默认结果。两个结果值下面代码介绍。
// wrapMapToProps.js
export function wrapMapToPropsConstant(getConstant) {
return function initConstantSelector(dispatch, options) {
const constant = getConstant(dispatch, options)
function constantSelector() {
return constant
}
constantSelector.dependsOnOwnProps = false
return constantSelector
}
}
export function getDependsOnOwnProps(mapToProps) {
return mapToProps.dependsOnOwnProps !== null &&
mapToProps.dependsOnOwnProps !== undefined
? Boolean(mapToProps.dependsOnOwnProps)
: mapToProps.length !== 1
}
export function wrapMapToPropsFunc(mapToProps, methodName) {
return function initProxySelector(dispatch, { displayName }) {
const proxy = function mapToPropsProxy(stateOrDispatch, ownProps) {
return proxy.dependsOnOwnProps
? proxy.mapToProps(stateOrDispatch, ownProps)
: proxy.mapToProps(stateOrDispatch)
}
// allow detectFactoryAndVerify to get ownProps
proxy.dependsOnOwnProps = true
proxy.mapToProps = function detectFactoryAndVerify(
stateOrDispatch,
ownProps
) {
proxy.mapToProps = mapToProps
proxy.dependsOnOwnProps = getDependsOnOwnProps(mapToProps)
let props = proxy(stateOrDispatch, ownProps)
if (typeof props === 'function') {
proxy.mapToProps = props
proxy.dependsOnOwnProps = getDependsOnOwnProps(props)
props = proxy(stateOrDispatch, ownProps)
}
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(props, displayName, methodName)
return props
}
return proxy
}
}
如上代码:
(1)wrapMapToPropsFunc
函数中,根据格式(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps,return function initProxySelector(dispatch, { displayName })
对应(dispatch, options)
,function mapToPropsProxy(stateOrDispatch, ownProps)
对应(nextState, nextOwnProps)
,proxy.mapToProps
对应nextFinalProps
。
(2) 第一次运行时将proxy.dependsOnOwnProps
设置成true
。于是detectFactoryAndVerify
方法在运行的时,能获得第二个参数(ownProps
),等第二次运行时,proxy.mapToProps
和denpendsOnOwnProps
都是经过计算得到的。
(3)getDependsOnOwnProps
方法就是计算denpendsOnOwnProps
的值,这个方法的目的在于计算mapToProps
是否需要使用到props
。如果mapToProps.length !== 1
,说明就需要props
进行计算,如果mapToProps.length === 1
,那么只需要计算stateOrDispatch
。
(4)wrapMapToPropsConstant
方法用于计算当传入的mapStateToProps
为null
时,返回constantSelector
方法,该方法内部是返回常量constant
为空对象。参考function whenMapStateToPropsIsMissing(mapStateToProps) {return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined }
,里面传入的是()=>{}
代表的就是getConstant
。因为没有传入mapStateToProps
,所以设置dependsOnOwnProps
属性为false
,也是不需要依赖ownProps
了
ownProps
class Demo extends React.Component<IProps, IState> {
constructor() {
/*....*/
}
render(){
return <div>用户名:{this.props.user.name}</div>
}
}
const mapStateToProps = (state, ownProps) => {
return {
user: {
id: _.find(state.userList, {id: ownProps.userId})
}
}
}
export default connect(
mapStateToProps
)(Demo);
如上代码:
看到这里你会疑惑ownProps
是什么,ownProps
就是业务组件自己的props
。如果store
中维护了一个userList
列表,但是业务组件只关心某一个user
。那么当state
变化,或者ownProps
变化的时候,mapStateToProps
都会被调用,计算出一个新的stateProps
。
mapDispatchToProps.js
import { bindActionCreators } from 'redux'
import { wrapMapToPropsConstant, wrapMapToPropsFunc } from './wrapMapToProps'
export function whenMapDispatchToPropsIsFunction(mapDispatchToProps) {
return typeof mapDispatchToProps === 'function'
? wrapMapToPropsFunc(mapDispatchToProps, 'mapDispatchToProps')
: undefined
}
export function whenMapDispatchToPropsIsMissing(mapDispatchToProps) {
return !mapDispatchToProps
? wrapMapToPropsConstant((dispatch) => ({ dispatch }))
: undefined
}
export function whenMapDispatchToPropsIsObject(mapDispatchToProps) {
return mapDispatchToProps && typeof mapDispatchToProps === 'object'
? wrapMapToPropsConstant((dispatch) =>
bindActionCreators(mapDispatchToProps, dispatch)
)
: undefined
}
export default [
whenMapDispatchToPropsIsFunction,
whenMapDispatchToPropsIsMissing,
whenMapDispatchToPropsIsObject,
]
如上代码:
(1) 当传入mapDispatchToProps
的类型为函数,其内部逻辑就是走wrapMapToProps.js中的wrapMapToPropsFunc
(2) 当传入mapDispatchToProps
的类型为null,调用wrapMapToPropsConstant
方法,并且默认传入(dispatch)=>({dispatch})
,这样我们就可以在业务组件内部自己通过this.props.dispatch
来访问到store
的dispatch
了
(3) 当传入mapDispatchToProps
的类型为对象,则调用bindActionCreators
,最终就是合并props
mergeProps.js
import verifyPlainObject from '../utils/verifyPlainObject'
export function defaultMergeProps(stateProps, dispatchProps, ownProps) {
return { ...ownProps, ...stateProps, ...dispatchProps }
}
export function wrapMergePropsFunc(mergeProps) {
return function initMergePropsProxy(
dispatch,
{ displayName, pure, areMergedPropsEqual }
) {
let hasRunOnce = false
let mergedProps
return function mergePropsProxy(stateProps, dispatchProps, ownProps) {
const nextMergedProps = mergeProps(stateProps, dispatchProps, ownProps)
if (hasRunOnce) {
if (!pure || !areMergedPropsEqual(nextMergedProps, mergedProps))
mergedProps = nextMergedProps
} else {
hasRunOnce = true
mergedProps = nextMergedProps
if (process.env.NODE_ENV !== 'production')
verifyPlainObject(mergedProps, displayName, 'mergeProps')
}
return mergedProps
}
}
}
export function whenMergePropsIsFunction(mergeProps) {
return typeof mergeProps === 'function'
? wrapMergePropsFunc(mergeProps)
: undefined
}
export function whenMergePropsIsOmitted(mergeProps) {
return !mergeProps ? () => defaultMergeProps : undefined
}
export default [whenMergePropsIsFunction, whenMergePropsIsOmitted]
如上代码:
目的就是将 stateProps、dispatchProps 和 ownProps 合并。
connect.js
// connect.js
import connectAdvanced from '../components/connectAdvanced'
/*......*/
export function createConnect({
connectHOC = connectAdvanced,
mapStateToPropsFactories = defaultMapStateToPropsFactories,
mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories,
mergePropsFactories = defaultMergePropsFactories,
selectorFactory = defaultSelectorFactory
} = {}) {
return function connect(
mapStateToProps,
mapDispatchToProps,
mergeProps,
{
pure = true,
areStatesEqual = strictEqual,
areOwnPropsEqual = shallowEqual,
areStatePropsEqual = shallowEqual,
areMergedPropsEqual = shallowEqual,
...extraOptions
} = {}
) {
const initMapStateToProps = match(
mapStateToProps,
mapStateToPropsFactories,
'mapStateToProps'
)
const initMapDispatchToProps = match(
mapDispatchToProps,
mapDispatchToPropsFactories,
'mapDispatchToProps'
)
const initMergeProps = match(mergeProps, mergePropsFactories, 'mergeProps')
return connectHOC(selectorFactory, {
methodName: 'connect',
getDisplayName: name => `Connect(${name})`,
shouldHandleStateChanges: Boolean(mapStateToProps),
initMapStateToProps,
initMapDispatchToProps,
initMergeProps,
pure,
areStatesEqual,
areOwnPropsEqual,
areStatePropsEqual,
areMergedPropsEqual,
...extraOptions
})
}
}
export default /*#__PURE__*/ createConnect()
如上代码:
运行createConnect()
以后会返回connect
方法。当我们使用connect
并执行以后,会返回一个高阶组件connectHOC
(connectHOC
来自于createConnect
方法中传递进来的参数connectAdvanced
)。
selectorFactory.js
export function impureFinalPropsSelectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch
) {
return function impureFinalPropsSelector(state, ownProps) {
return mergeProps(
mapStateToProps(state, ownProps),
mapDispatchToProps(dispatch, ownProps),
ownProps
)
}
}
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
}
function handleNewState() {
const nextStateProps = mapStateToProps(state, ownProps)
const statePropsChanged = !areStatePropsEqual(nextStateProps, stateProps)
stateProps = nextStateProps
if (statePropsChanged)
mergedProps = mergeProps(stateProps, dispatchProps, ownProps)
return mergedProps
}
function handleSubsequentCalls(nextState, nextOwnProps) {
const propsChanged = !areOwnPropsEqual(nextOwnProps, ownProps)
const stateChanged = !areStatesEqual(nextState, state)
state = nextState
ownProps = nextOwnProps
if (propsChanged && stateChanged) return handleNewPropsAndNewState()
if (propsChanged) return handleNewProps()
if (stateChanged) return handleNewState()
return mergedProps
}
return function pureFinalPropsSelector(nextState, nextOwnProps) {
return hasRunAtLeastOnce
? 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
? pureFinalPropsSelectorFactory
: impureFinalPropsSelectorFactory
return selectorFactory(
mapStateToProps,
mapDispatchToProps,
mergeProps,
dispatch,
options
)
}
如上代码:
(1)store
中的无关变动是借助 selectorFactory 来阻止的。例如state:{userList: [/*...*/], bookList:[/*...*/]}
。而某个业务组件只是需要状态的某一部分即userList
。在某一时刻state
中bookList
属性对应的值修改了,于是store.subscribe
监听到state
变化。但是业务组件关注的userList
没有变化,那么当前业务组件如果是pure模式则不应该更新,这其中的处理逻辑都在 selectorFactory.js 上。
(2) 这边注意到options.pure
,当它为true
,则selectorFactory
值为pureFinalPropsSelectorFactory
,否则为impureFinalPropsSelectorFactory
。非pure模式下,就是合并props
。pure模式下,初次运行会执行handleSubsequentCalls
函数,第二次及以后会执行handleFirstCall
方法。
(3)handleFirstCall
方法中就是将ownProps
、stateProps
、dispatchProps
进行合并,同时将标识已经运行一次的变量hasRunAtLeastOnce
设置为true
。
(4)handleSubsequentCalls
中会对比props
是否变化、state
是否变化、又或者两者都发生变化。如果只是props
发生变化,执行handleNewProps
(该方法内部做了优化,如果mapStateToProps
和mapDispatchToProps
不依赖props
则不会重新去计算)。只是state
发生变化,执行handleNewState
(重新计算stateProps
,然后严格比较,如果改变了则会重新计算mergedProps
,如果没有变则将旧的mergedProps
返回出去。也就是说和业务组件相关联的state
属性值没有发生变化,那么组件就不更新。)。state
和props
都发生变化,执行handleNewPropsAndNewState
。
(5) selectorFactory.js 使用依赖注入,顶层函数finalPropsSelectorFactory
中需要用的元素都是通过参数注入进来的,需要找的话需要往上两级才能找到来源。
(6) 个人认为的依赖注入就是:比如class A要使用class B中的某些属性,然后有专门的容器将class B实例化,当classA需要使用B的属性的时候,直接问容器要到B的实例,就可以了。其目的是为了降低class之间的耦合,不用四处new实例,便于后期的维护和管理。
connectAdvanced.js
// connectAdvanced.js
import Subscription from '../utils/Subscription'
/*
subscribeUpdates,也就是当store发生变化以后就会执行
shouldHandleStateChanges代表是否处理状态变化,初始化的值为true,如果为false,subscribeUpdates是不会执行的
初始化时候,会默认执行checkForUpdates(),checkForUpdates的作用就是获取到最新的状态
*/
function subscribeUpdates(
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch
) {
if (!shouldHandleStateChanges) return
let didUnsubscribe = false
let lastThrownError = null
const checkForUpdates = () => {
if (didUnsubscribe) {
return
}
const latestStoreState = store.getState()
let newChildProps, error
try {
newChildProps = childPropsSelector(
latestStoreState,
lastWrapperProps.current
)
} catch (e) {
error = e
lastThrownError = e
}
if (!error) {
lastThrownError = null
}
if (newChildProps === lastChildProps.current) {
if (!renderIsScheduled.current) {
notifyNestedSubs()
}
} else {
lastChildProps.current = newChildProps
childPropsFromStoreUpdate.current = newChildProps
renderIsScheduled.current = true
forceComponentUpdateDispatch({
type: 'STORE_UPDATED',
payload: {
error,
},
})
}
}
subscription.onStateChange = checkForUpdates
subscription.trySubscribe()
checkForUpdates()
const unsubscribeWrapper = () => {
didUnsubscribe = true
subscription.tryUnsubscribe()
subscription.onStateChange = null
if (lastThrownError) {
throw lastThrownError
}
}
return unsubscribeWrapper
}
// 调用selectorFactory,
function createChildSelector(store) {
return selectorFactory(store.dispatch, selectorFactoryOptions)
}
// connect的核心代码
function ConnectFunction(props) {
const [
propsContext,
reactReduxForwardedRef,
wrapperProps,
] = useMemo(() => {
const { reactReduxForwardedRef, ...wrapperProps } = props
return [props.context, reactReduxForwardedRef, wrapperProps]
}, [props])
const ContextToUse = useMemo(() => {
return propsContext &&
propsContext.Consumer &&
isContextConsumer(<propsContext.Consumer />)
? propsContext
: Context
}, [propsContext, Context])
const contextValue = useContext(ContextToUse)
const didStoreComeFromProps =
Boolean(props.store) &&
Boolean(props.store.getState) &&
Boolean(props.store.dispatch)
const didStoreComeFromContext =
Boolean(contextValue) && Boolean(contextValue.store)
if (
process.env.NODE_ENV !== 'production' &&
!didStoreComeFromProps &&
!didStoreComeFromContext
) {
throw new Error(
`Could not find "store" in the context of ` +
`"${displayName}". Either wrap the root component in a <Provider>, ` +
`or pass a custom React context provider to <Provider> and the corresponding ` +
`React context consumer to ${displayName} in connect options.`
)
}
const store = didStoreComeFromProps ? props.store : contextValue.store
const childPropsSelector = useMemo(() => {
return createChildSelector(store)
}, [store])
const [subscription, notifyNestedSubs] = useMemo(() => {
if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
const overriddenContextValue = useMemo(() => {
if (didStoreComeFromProps) {
return contextValue
}
return {
...contextValue,
subscription,
}
}, [didStoreComeFromProps, contextValue, subscription])
const [
[previousStateUpdateResult],
forceComponentUpdateDispatch,
] = useReducer(storeStateUpdatesReducer, EMPTY_ARRAY, initStateUpdates)
if (previousStateUpdateResult && previousStateUpdateResult.error) {
throw previousStateUpdateResult.error
}
const lastChildProps = useRef()
const lastWrapperProps = useRef(wrapperProps)
const childPropsFromStoreUpdate = useRef()
const renderIsScheduled = useRef(false)
const actualChildProps = usePureOnlyMemo(() => {
if (
childPropsFromStoreUpdate.current &&
wrapperProps === lastWrapperProps.current
) {
return childPropsFromStoreUpdate.current
}
return childPropsSelector(store.getState(), wrapperProps)
}, [store, previousStateUpdateResult, wrapperProps])
useIsomorphicLayoutEffectWithArgs(captureWrapperProps, [
lastWrapperProps,
lastChildProps,
renderIsScheduled,
wrapperProps,
actualChildProps,
childPropsFromStoreUpdate,
notifyNestedSubs,
])
useIsomorphicLayoutEffectWithArgs(
subscribeUpdates,
[
shouldHandleStateChanges,
store,
subscription,
childPropsSelector,
lastWrapperProps,
lastChildProps,
renderIsScheduled,
childPropsFromStoreUpdate,
notifyNestedSubs,
forceComponentUpdateDispatch,
],
[store, subscription, childPropsSelector]
)
const renderedWrappedComponent = useMemo(
() => (
<WrappedComponent
{...actualChildProps}
ref={reactReduxForwardedRef}
/>
),
[reactReduxForwardedRef, WrappedComponent, actualChildProps]
)
const renderedChild = useMemo(() => {
if (shouldHandleStateChanges) {
return (
<ContextToUse.Provider value={overriddenContextValue}>
{renderedWrappedComponent}
</ContextToUse.Provider>
)
}
return renderedWrappedComponent
}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])
return renderedChild
}
如上代码:
connectAdvanced.js 核心代码是ConnectFunction
。这边注意到,childPropsSelector
变量借助useMemo
,一旦store
发生变化,就会生成最新的props
。因为childPropsSelector
调用的是selectorFactory
,所以最终结果的结构为:(nextState, nextOwnProps) => nextFinalProps
。而变量actualChildProps
就是最终计算得到的props(nextFinalProps)
。而当props
发生改变时候,在renderedWrappedComponent
位置就会返回一个新的WrappedComponent
,来重新渲染组件。
Subscriptions
Subscriptions 在
react-redux
的作用就是对组件使用的store
进行订阅,一旦store
发生改变,就会告诉组件去更新。
const nullListeners = { notify() {} }
function createListenerCollection() {
const batch = getBatch()
let first = null
let last = null
return {
clear() {
first = null
last = null
},
notify() {
batch(() => {
let listener = first
while (listener) {
listener.callback()
listener = listener.next
}
})
},
get() {
let listeners = []
let listener = first
while (listener) {
listeners.push(listener)
listener = listener.next
}
return listeners
},
subscribe(callback) {
let isSubscribed = true
let listener = (last = {
callback,
next: null,
prev: last
})
if (listener.prev) {
listener.prev.next = listener
} else {
first = listener
}
return function unsubscribe() {
if (!isSubscribed || first === null) return
isSubscribed = false
if (listener.next) {
listener.next.prev = listener.prev
} else {
last = listener.prev
}
if (listener.prev) {
listener.prev.next = listener.next
} else {
first = listener.next
}
}
}
}
}
export default class Subscription {
constructor(store, parentSub) {
this.store = store
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
addNestedSub(listener) {
this.trySubscribe()
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
this.listeners.notify()
}
handleChangeWrapper() {
if (this.onStateChange) {
this.onStateChange()
}
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
: this.store.subscribe(this.handleChangeWrapper)
this.listeners = createListenerCollection()
}
}
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
如上代码:
(1)createListenerCollection
函数,就是用于添加订阅方法的,结构为{ callback, next, prev }
即链表。一旦触发notify
方法,就会通过next
指向下一个listener
执行callback
。
(2)Subscription
类,其constructor
接收store
和parentSub
参数,store
就是redux
产生的store
。在**trySubscribe
** 方法中,可以看到使用了this.store.subscribe(this.handleChangeWrapper)
,也就是说一旦store
更新,那么就会执行订阅。而parentSub
是Subscription
的实例。
(3) 综上,react-redux
借助Subscription
,对store
进行订阅并注册回调函数。一旦store
发生改变就会执行回调,达到更新相关的业务组件。
下面的代码就是实现组件订阅 Store 数据更新的,它在 connectAdvanced.js 中的。
const [subscription, notifyNestedSubs] = useMemo(() => {
const subscription = new Subscription(
store,
didStoreComeFromProps ? null : contextValue.subscription
)
const notifyNestedSubs = subscription.notifyNestedSubs.bind(
subscription
)
return [subscription, notifyNestedSubs]
}, [store, didStoreComeFromProps, contextValue])
React、Redux、React-Redux
这边盗一张图,出自哪里忘了