前言
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方法,每一次dispatchaction的时候,都会执行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
这边盗一张图,出自哪里忘了