阅读 3876

React、Redux、React-Redux

前言

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 是一个对象,内部主要存在属性:getStatedispatchsubscribereplaceReducer
(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 代表合并后的 reducerinitState 是初始状态默认值,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,就会返回最新的状态,然后将状态赋值给 currentStatecurrentState 就是 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 ,其内部同样返回一个匿名函数,接收 reducerpreloadedState 。该匿名函数会调用 createStore 来创建 store,也会声明一个变量 middlewareAPI,里面存储了 getStatedispatchgetState 用户获取最新状态,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 发生了变化,那么就会创建订阅监听,返回 storesubscription

contextValuepreviousState 发生变化以后,就会执行 useEffect 中的代码。其实就是执行订阅中的方法。

这边需要注意的是 Providervalue 值就是 storesubscription。目的是给 connect 组件提供的。而 subscription 主要负责 connectProvider 组件更新的。

connect

class BookContainer extends React.Component<IProps, IState> {
	/*...业务代码...*/
}

export default connect(
    mapStateToProps,
    mapDispatchToProps
)(BookContainer);
复制代码

如上代码:
connect 是高阶组件,接收 mapStateToPropsmapDispatchToProps 参数,mapStateToProps 的作用是将特定的 state 映射到组件的 props 上,mapDispatchToPropsdispatch(action) 映射到 props 上,内部监听了 Reduxstore 的变化,当 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 方法,用来初始化 mapDispatchToPropsmapStateToPropsmergeProps 的函数。

(2) match 内部会循环执行 factories 的方法。

(3) factories 来自于 mapStateToProps.js 和 mapDispatchToProps.js 返回的数组结果。数组的成员要么为 undefined,或者成员的结构为:(dispatch, options) => (nextState, nextOwnProps) => nextFinalProps

(4) 经过 match 得到的结果(initMapStateToPropsinitMapDispatchToPropsinitMergeProps)会传入到 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.mapToPropsdenpendsOnOwnProps 都是经过计算得到的。

(3) getDependsOnOwnProps 方法就是计算 denpendsOnOwnProps 的值,这个方法的目的在于计算 mapToProps 是否需要使用到 props。如果 mapToProps.length !== 1,说明就需要 props 进行计算,如果 mapToProps.length === 1,那么只需要计算 stateOrDispatch

(4) wrapMapToPropsConstant 方法用于计算当传入的 mapStateToPropsnull 时,返回 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 来访问到 storedispatch

(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 。在某一时刻 statebookList 属性对应的值修改了,于是 store.subscribe 监听到 state 变化。但是业务组件关注的 userList 没有变化,那么当前业务组件如果是pure模式则不应该更新,这其中的处理逻辑都在 selectorFactory.js 上。

(2) 这边注意到 options.pure ,当它为 true ,则 selectorFactory 值为 pureFinalPropsSelectorFactory ,否则为 impureFinalPropsSelectorFactory 。非pure模式下,就是合并 props 。pure模式下,初次运行会执行 handleSubsequentCalls 函数,第二次及以后会执行 handleFirstCall 方法。

(3) handleFirstCall 方法中就是将 ownPropsstatePropsdispatchProps 进行合并,同时将标识已经运行一次的变量 hasRunAtLeastOnce 设置为 true

(4) handleSubsequentCalls 中会对比 props 是否变化、state 是否变化、又或者两者都发生变化。如果只是 props 发生变化,执行 handleNewProps (该方法内部做了优化,如果 mapStateToPropsmapDispatchToProps 不依赖 props 则不会重新去计算)。只是 state 发生变化,执行 handleNewState (重新计算 stateProps ,然后严格比较,如果改变了则会重新计算 mergedProps ,如果没有变则将旧的 mergedProps 返回出去。也就是说和业务组件相关联的 state 属性值没有发生变化,那么组件就不更新。)。 stateprops 都发生变化,执行 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 接收 storeparentSub 参数,store 就是 redux 产生的 store 。在**trySubscribe** 方法中,可以看到使用了 this.store.subscribe(this.handleChangeWrapper) ,也就是说一旦 store 更新,那么就会执行订阅。而 parentSubSubscription 的实例。

(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

这边盗一张图,出自哪里忘了

文章分类
阅读
文章标签