React教程之Redux狀態管理

Redux

雖然是react的一個狀態管理庫,能夠單獨使用,接下來咱們詳細講解下底層代碼。react

廢話很少講,先說下基本的知識點。git

Actions

actions 是經過 store.dispatch(action)改變 state的惟一方法,不能直接經過 this.state = {} 來直接改變 stategithub

生成 actions 有兩種方式:redux

一、單純的 objectapi

const action = {
  type: 'ADD_TODO',
  text: 'Build my first Redux app'
}
dispatch(action)
複製代碼

二、action creator function數組

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
dispatch(addTodo())
複製代碼

Reducers

Actions 只能描述發生了什麼,並不能描述狀態發生了什麼變化,Reducers 指定 state tree 將要發生什麼。session

const items = (state = [], action) => {
  switch (action.type) {
    case "ADD_ITEM":
      return [...state, { text: action.text }]
    default:
      return state
  }
}
複製代碼

這就是單個 reducer 的格式,固定兩個參數 stateaction;app

多個reducers 是須要使用 combineReducers,dom

import {combineReducers} from 'redux';

const items = (state = [], action) => {
  switch (action.type) {
    case "ADD_ITEM":
      return [...state, { text: action.text }]
    default:
      return state
  }
}

export default combineReducers({
  items
});
複製代碼

接下來,咱們看看 combineReducers 裏面實現了什麼?async

combineReducers

function combineReducers(reducers) {
  // 多個reducer
  var reducerKeys = Object.keys(reducers);
  // 最終的 reducers
  var finalReducers = {};

  for (var i = 0; i < reducerKeys.length; i++) {
    var key = reducerKeys[i];

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') {
        warning("No reducer provided for key \"" + key + "\"");
      }
    }

    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key];
    }
  }

  var finalReducerKeys = Object.keys(finalReducers);
  var unexpectedKeyCache;

  if (process.env.NODE_ENV !== 'production') {
    unexpectedKeyCache = {};
  }

  var shapeAssertionError;

  try {
    //只是驗證一下語法是否有錯誤
    assertReducerShape(finalReducers);
  } catch (e) {
    shapeAssertionError = e;
  }
  
  
  //返回一個 function(state, action){}
  // dispatch 會調用這裏的 function
  return function combination(state, action) {
    if (state === void 0) {
      state = {};
    }

    if (shapeAssertionError) {
      throw shapeAssertionError;
    }

    if (process.env.NODE_ENV !== 'production') {
      var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);

      if (warningMessage) {
        warning(warningMessage);
      }
    }

    var hasChanged = false;
    var nextState = {};

    for (var _i = 0; _i < finalReducerKeys.length; _i++) {
      var _key = finalReducerKeys[_i];
      //當前 key 的 reducer
      var reducer = finalReducers[_key];
      // 當前 key 的 state
      var previousStateForKey = state[_key];
      // 即將改變後的當前key 的 state
      var nextStateForKey = reducer(previousStateForKey, action);

      if (typeof nextStateForKey === 'undefined') {
        var errorMessage = getUndefinedStateErrorMessage(_key, action);
        throw new Error(errorMessage);
      }
      // 當前 reducer 的名字做爲 key 保存在 state
      // 因此 當前例子 items 會變成 state.items = []
      nextState[_key] = nextStateForKey;
      // 判斷狀態是否發生改變,當下一個狀態不等於上一個狀態,標識狀態已改變
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
    }
    
    // 返回 state
    return hasChanged ? nextState : state;
  };
}

複製代碼

Store

store是什麼? store 是把 ActionsReducers 經過createStore結合起來的一個Object

打印一下:

Store: {
  getState,
  dispatch,
  subscribe
  replaceReducer,
}
複製代碼

createStore(reducer, preloadState, enhancer)

/** * Creates a Redux store that holds the state tree. * The only way to change the data in the store is to call `dispatch()` on it. * * There should only be a single store in your app. To specify how different * parts of the state tree respond to actions, you may combine several reducers * into a single reducer function by using `combineReducers`. * * @param {Function} reducer A function that returns the next state tree, given * the current state tree and the action to handle. * * @param {any} [preloadedState] The initial state. You may optionally specify it * to hydrate the state from the server in universal apps, or to restore a * previously serialized user session. * If you use `combineReducers` to produce the root reducer function, this must be * an object with the same shape as `combineReducers` keys. * * @param {Function} [enhancer] The store enhancer. You may optionally specify it * to enhance the store with third-party capabilities such as middleware, * time travel, persistence, etc. The only store enhancer that ships with Redux * is `applyMiddleware()`. * * @returns {Store} A Redux store that lets you read the state, dispatch actions * and subscribe to changes. */
export default function createStore(reducer, preloadedState, enhancer) {
  if (
    (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
    (typeof enhancer === 'function' && typeof arguments[3] === 'function')
  ) {
    throw new Error(
      'It looks like you are passing several store enhancers to ' +
        'createStore(). This is not supported. Instead, compose them ' +
        'together to a single function'
    )
  }

  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

  /** * Reads the state tree managed by the store. * * @returns {any} The current state tree of your application. */
  function getState() {
    if (isDispatching) {
      throw new Error(
        'You may not call store.getState() while the reducer is executing. ' +
          'The reducer has already received the state as an argument. ' +
          'Pass it down from the top reducer instead of reading it from the store.'
      )
    }

    return currentState
  }

  /** * Adds a change listener. It will be called any time an action is dispatched, * and some part of the state tree may potentially have changed. You may then * call `getState()` to read the current state tree inside the callback. * * You may call `dispatch()` from a change listener, with the following * caveats: * * 1. The subscriptions are snapshotted just before every `dispatch()` call. * If you subscribe or unsubscribe while the listeners are being invoked, this * will not have any effect on the `dispatch()` that is currently in progress. * However, the next `dispatch()` call, whether nested or not, will use a more * recent snapshot of the subscription list. * * 2. The listener should not expect to see all state changes, as the state * might have been updated multiple times during a nested `dispatch()` before * the listener is called. It is, however, guaranteed that all subscribers * registered before the `dispatch()` started will be called with the latest * state by the time it exits. * * @param {Function} listener A callback to be invoked on every dispatch. * @returns {Function} A function to remove this change listener. */
  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#subscribe(listener) for more details.'
      )
    }

    let isSubscribed = true

    ensureCanMutateNextListeners()
    
    // 保存監聽 回調函數 等 dispatch 時候 統一執行
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {
        throw new Error(
          'You may not unsubscribe from a store listener while the reducer is executing. ' +
            'See https://redux.js.org/api-reference/store#subscribe(listener) for more details.'
        )
      }

      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  /** * Dispatches an action. It is the only way to trigger a state change. * * The `reducer` function, used to create the store, will be called with the * current state tree and the given `action`. Its return value will * be considered the **next** state of the tree, and the change listeners * will be notified. * * The base implementation only supports plain object actions. If you want to * dispatch a Promise, an Observable, a thunk, or something else, you need to * wrap your store creating function into the corresponding middleware. For * example, see the documentation for the `redux-thunk` package. Even the * middleware will eventually dispatch plain object actions using this method. * * @param {Object} action A plain object representing 「what changed」. It is * a good idea to keep actions serializable so you can record and replay user * sessions, or use the time travelling `redux-devtools`. An action must have * a `type` property which may not be `undefined`. It is a good idea to use * string constants for action types. * * @returns {Object} For convenience, the same action object you dispatched. * * Note that, if you use a custom middleware, it may wrap `dispatch()` to * return something else (for example, a Promise you can await). */
  function dispatch(action) {
    if (!isPlainObject(action)) {
      throw new Error(
        'Actions must be plain objects. ' +
          'Use custom middleware for async actions.'
      )
    }

	 // 這裏說明 actions 必須包含 type 不然報錯
    if (typeof action.type === 'undefined') {
      throw new Error(
        'Actions may not have an undefined "type" property. ' +
          'Have you misspelled a constant?'
      )
    }

	 // 一次只能分派一個action
    if (isDispatching) {
      throw new Error('Reducers may not dispatch actions.')
    }

    try {
      // dispatch分配時候,設置標識
      isDispatching = true
      // 使用 combination(state, action) 獲取當前 state
      currentState = currentReducer(currentState, action)
    } finally {
      // 執行完畢 設置 false
      isDispatching = false
    }

	 /** * 這裏的 listeners 是 subcribe 訂閱的 callback * 因此每次 dispatch , 都會觸發 訂閱的 callback */ 
    const listeners = (currentListeners = nextListeners)
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }

    return action
  }

  /** * Replaces the reducer currently used by the store to calculate the state. * * You might need this if your app implements code splitting and you want to * load some of the reducers dynamically. You might also need this if you * implement a hot reloading mechanism for Redux. * * @param {Function} nextReducer The reducer for the store to use instead. * @returns {void} */
  function replaceReducer(nextReducer) {
    if (typeof nextReducer !== 'function') {
      throw new Error('Expected the nextReducer to be a function.')
    }

    currentReducer = nextReducer
    dispatch({ type: ActionTypes.REPLACE })
  }

  /** * Interoperability point for observable/reactive libraries. * @returns {observable} A minimal observable of state changes. * For more information, see the observable proposal: * https://github.com/tc39/proposal-observable */
  function observable() {
    const outerSubscribe = subscribe
    return {
      /** * The minimal observable subscription method. * @param {Object} observer Any object that can be used as an observer. * The observer object should have a `next` method. * @returns {subscription} An object with an `unsubscribe` method that can * be used to unsubscribe the observable from the store, and prevent further * emission of values from the observable. */
      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
      }
    }
  }

  // When a store is created, an "INIT" action is dispatched so that every
  // reducer returns their initial state. This effectively populates
  // the initial state tree.
  dispatch({ type: ActionTypes.INIT })

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }
}
複製代碼

這裏的createStroe,在新建store的時候,會默認執行

dispatch({ type: ActionTypes.INIT })
複製代碼

而後執行,

currentState = currentReducer(currentState, action)
複製代碼

這裏的 currentReducer 就是 createStrore(reducer) 裏面的參數 reducer, 也就是combineReducers 函數的返回函數 combination(state, action), 讓咱們再回顧一下 combination 裏面說的什麼;

combination(state, action)

function combination(state, action) {
	// 設置 state = {}
	if (state === void 0) {
	  state = {};
	}
	
	if (shapeAssertionError) {
	  throw shapeAssertionError;
	}
	
	// 錯誤提示
	if (process.env.NODE_ENV !== 'production') {
	  var warningMessage = getUnexpectedStateShapeWarningMessage(state, finalReducers, action, unexpectedKeyCache);
	
	  if (warningMessage) {
	    warning(warningMessage);
	  }
	}
	
	var hasChanged = false;
	var nextState = {};// 設置下一狀態
	   
	/** * 例子中有兩個 reducer (items, location) * const items = (state = [], action) => { switch (action.type) { case "ADD_ITEM": return [...state, { text: action.text }] default: return state } } * const location = (state = window.location, action) => state; * * combineReducers({items, location}) * 此時的 finalReducerKeys = [items, location] * finalReducers={ items: function items(){}, location: function location() } * 實現過程: * * loop 1: * _key = items; * reducer = function items(){} * previousStateForKey = undefined * nextStateForKey = items(undefined, {type: '@@redux/INIT.....'}) = [] * nextState = {items: []} * * loop 2: * _key = location; * reducer = function location(){} * previousStateForKey = undefined * nextStateForKey = location(undefined, {type: '@@redux/INIT.....'}) = window.location * nextState = {items: [], location: window.location} * */
	for (var _i = 0; _i < finalReducerKeys.length; _i++) {
	  var _key = finalReducerKeys[_i];
	  var reducer = finalReducers[_key];
	  // 讀取 state 裏面的上一個狀態
	  var previousStateForKey = state[_key];
	  // 獲取 下一個 state 狀態 而且 合併數據
	  var nextStateForKey = reducer(previousStateForKey, action);
	
	  if (typeof nextStateForKey === 'undefined') {
	    var errorMessage = getUndefinedStateErrorMessage(_key, action);
	    throw new Error(errorMessage);
	  }
	
	  // 賦值返回 nextState
	  nextState[_key] = nextStateForKey;
	  hasChanged = hasChanged || nextStateForKey !== previousStateForKey;
	}
	
	return hasChanged ? nextState : state;
};
複製代碼

getState()

getState 這個方法就很簡單了,只是返回 currentState

subscribe(listener)

訂閱這塊,就是保持每個listener callbacklisteners 數組裏,等到 執行 dispatch(action), 再一個個循環執行。最有意思的就是返回值是一個 unsubscribe function, 顧名思義就是解除訂閱,用法稍後再說。

replaceReducer(nextReducer)

其實就替換reducer, 通常熱加載的時候會用到。

Usage with React

咱們大體已經瞭解了 store 了, 可是如何結合 react 使用呢?

這裏須要瞭解一下 react-redux 提供的 <Provide /> 組件,這是一個 container component, 用例:

import React from 'react'
import { render } from 'react-dom'
import { Provider } from 'react-redux'
import { createStore } from 'redux'
import todoApp from './reducers'
import App from './components/App'

const store = createStore(todoApp)

render(
  <Provider store={store}> <App /> </Provider>,
  document.getElementById('root')
)
複製代碼

Provider

import React, { Component } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'

class Provider extends Component {
  constructor(props) {
    super(props)
    
    // 獲取 stroe
    const { store } = props
    this.state = {
      // 當前 state
      storeState: store.getState(),
      store
    }
  }

  componentDidMount() {
    this._isMounted = true
    this.subscribe()
  }

  componentWillUnmount() {
    if (this.unsubscribe) this.unsubscribe()

    this._isMounted = false
  }

  componentDidUpdate(prevProps) {
    if (this.props.store !== prevProps.store) {
      if (this.unsubscribe) this.unsubscribe()

      this.subscribe()
    }
  }

  subscribe() {
    const { store } = this.props

    this.unsubscribe = store.subscribe(() => {
      const newStoreState = store.getState()

      if (!this._isMounted) {
        return
      }

      this.setState(providerState => {
        // If the value is the same, skip the unnecessary state update.
        if (providerState.storeState === newStoreState) {
          return null
        }

        return { storeState: newStoreState }
      })
    })

    // Actions might have been dispatched between render and mount - handle those
    const postMountStoreState = store.getState()
    if (postMountStoreState !== this.state.storeState) {
      this.setState({ storeState: postMountStoreState })
    }
  }

  render() {
    const Context = this.props.context || ReactReduxContext

    return (
      <Context.Provider value={this.state}> {this.props.children} </Context.Provider> ) } } Provider.propTypes = { store: PropTypes.shape({ subscribe: PropTypes.func.isRequired, dispatch: PropTypes.func.isRequired, getState: PropTypes.func.isRequired }), context: PropTypes.object, children: PropTypes.any } export default Provider 複製代碼

如圖:

[圖片上傳失敗...(image-6cc47-1558534168137)]

Provide 能夠傳遞 Store 給子組件, 可是此時 <App />

<App />
  props: {}
  state: {}
複製代碼

若是獲取state<App />呢? 此時咱們須要瞭解一下 connect

connect

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')
  
  /** * 默認: * connectHOC = connectAdvanced mapStateToPropsFactories = defaultMapStateToPropsFactories mapDispatchToPropsFactories = defaultMapDispatchToPropsFactories mergePropsFactories = defaultMergePropsFactories selectorFactory = defaultSelectorFactory */
  return connectHOC(selectorFactory, {
    // used in error messages
    methodName: 'connect',

    // used to compute Connect's displayName from the wrapped component's displayName.
    getDisplayName: name => `Connect(${name})`,

    // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes
    shouldHandleStateChanges: Boolean(mapStateToProps),

    // passed through to selectorFactory
    initMapStateToProps,
    initMapDispatchToProps,
    initMergeProps,
    pure,
    areStatesEqual,
    areOwnPropsEqual,
    areStatePropsEqual,
    areMergedPropsEqual,

    // any extra options args can override defaults of connect or connectAdvanced
    ...extraOptions
  })
}

複製代碼
// 在子組件
export default connect(
  ((state, ownProps) => {
    return {
      data: state.items
    }
  }),
  (dispatch, ownProps) => {
    return {
      addItem: () => {
        dispatch(addItem(ownProps.name))
      }
    }
  }
)(App);

複製代碼

connet(mapStateToProps, mapDispatchToProps, mapProps)(App) => connectHoc(App, {...opts}) => connectAdvanced(App)

關於 initMapStateToPropsinitMapDispatchToPropswrapMapToPropsFunc 初始化的方法稍微講解一下

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 }.`
    )
  }
}

複製代碼

initMapStateToProps

mapStateToProps: (state, ownProp)=> {return {state}}
 mapStateToPropsFactories: [whenMapStateToPropsIsFunction, whenMapStateToPropsIsMissing]
 
 //此時mapStateToProps 是個 function, 因此執行 whenMapStateToPropsIsFunction
  
複製代碼

whenMapStateToPropsIsFunction | whenMapStateToPropsIsMissing

export function whenMapStateToPropsIsFunction(mapStateToProps) {
  return typeof mapStateToProps === 'function'
    ? wrapMapToPropsFunc(mapStateToProps, 'mapStateToProps')
    : undefined
}

export function whenMapStateToPropsIsMissing(mapStateToProps) {
  return !mapStateToProps ? wrapMapToPropsConstant(() => ({})) : undefined
}

// Used by whenMapStateToPropsIsFunction and whenMapDispatchToPropsIsFunction,
// this function wraps mapToProps in a proxy function which does several things:
//
// * Detects whether the mapToProps function being called depends on props, which
// is used by selectorFactory to decide if it should reinvoke on props changes.
//
// * On first call, handles mapToProps if returns another function, and treats that
// new function as the true mapToProps for subsequent calls.
//
// * On first call, verifies the first result is a plain object, in order to warn
// the developer that their mapToProps function is not returning a valid result.
//
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
      //判斷是否 帶有 props 做爲參數
      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
  }
}
複製代碼

wrapMapToPropsFunc 返回一個 initProxySelector(dispatch, {displayName}) => proxy(stateOrDispatch, ownProps)

其他兩個再也不贅述,有空再講解。

connectAdvanced

function connectAdvanced( selectorFactory, // 默認參數 { // the func used to compute this HOC's displayName from the wrapped component's displayName. // probably overridden by wrapper functions such as connect() getDisplayName = name => `ConnectAdvanced(${name})`, // shown in error messages // probably overridden by wrapper functions such as connect() methodName = 'connectAdvanced', // REMOVED: if defined, the name of the property passed to the wrapped element indicating the number of // calls to render. useful for watching in react devtools for unnecessary re-renders. renderCountProp = undefined, // determines whether this HOC subscribes to store changes shouldHandleStateChanges = true, // REMOVED: the key of props/context to get the store storeKey = 'store', // REMOVED: expose the wrapped component via refs withRef = false, // use React's forwardRef to expose a ref of the wrapped component forwardRef = false, // the context consumer to use context = ReactReduxContext, // additional options are passed through to the selectorFactory ...connectOptions } = {}
) {

  /** * connectHOC 以前代碼 一一對應 * { // used in error messages methodName: 'connect', // used to compute Connect's displayName from the wrapped component's displayName. // connect(App) getDisplayName: name => `Connect(${name})`, // if mapStateToProps is falsy, the Connect component doesn't subscribe to store state changes shouldHandleStateChanges: Boolean(mapStateToProps), // passed through to selectorFactory initMapStateToProps, initMapDispatchToProps, initMergeProps, pure, areStatesEqual, areOwnPropsEqual, areStatePropsEqual, areMergedPropsEqual, // any extra options args can override defaults of connect or connectAdvanced ...extraOptions } */
  
  //React.createContext(null)
  const Context = context

  return function wrapWithConnect(WrappedComponent) {
    ....
    
    // 結果是 App
    const wrappedComponentName =
      WrappedComponent.displayName || WrappedComponent.name || 'Component'

    // Connect(App)
    const displayName = getDisplayName(wrappedComponentName)
    
    
    /** * 合併 options: * * WrappedComponent: ƒ App(props) areMergedPropsEqual: ƒ shallowEqual(objA, objB) areOwnPropsEqual: ƒ shallowEqual(objA, objB) areStatePropsEqual: ƒ shallowEqual(objA, objB) areStatesEqual: ƒ strictEqual(a, b) displayName: "Connect(App)" getDisplayName: ƒ getDisplayName(name) initMapDispatchToProps: ƒ initProxySelector(dispatch, _ref) initMapStateToProps: ƒ initProxySelector(dispatch, _ref) initMergeProps: ƒ () methodName: "connect" pure: true renderCountProp: undefined shouldHandleStateChanges: true storeKey: "store" wrappedComponentName: "App" */
    const selectorFactoryOptions = {
      ...connectOptions,
      getDisplayName,
      methodName,
      renderCountProp,
      shouldHandleStateChanges,
      storeKey,
      displayName,
      wrappedComponentName,
      WrappedComponent
    }
    
    // true
    const { pure } = connectOptions

    let OuterBaseComponent = Component

    if (pure) {
      OuterBaseComponent = PureComponent
    }

    // 合併 props
    function makeDerivedPropsSelector() {
      let lastProps
      let lastState
      let lastDerivedProps
      let lastStore
      let lastSelectorFactoryOptions
      let sourceSelector

      return function selectDerivedProps( state, props, store, selectorFactoryOptions ) {
        if (pure && lastProps === props && lastState === state) {
          return lastDerivedProps
        }

        if (
          store !== lastStore ||
          lastSelectorFactoryOptions !== selectorFactoryOptions
        ) {
          lastStore = store
          lastSelectorFactoryOptions = selectorFactoryOptions
          sourceSelector = selectorFactory(
            store.dispatch,
            selectorFactoryOptions
          )
        }

        lastProps = props
        lastState = state

        const nextProps = sourceSelector(state, props)

        lastDerivedProps = nextProps
        return lastDerivedProps
      }
    }


    // 合併 props 到 組件 App 上
    function makeChildElementSelector() {
      let lastChildProps, lastForwardRef, lastChildElement, lastComponent

      return function selectChildElement( WrappedComponent, childProps, forwardRef ) {
        if (
          childProps !== lastChildProps ||
          forwardRef !== lastForwardRef ||
          lastComponent !== WrappedComponent
        ) {
          lastChildProps = childProps
          lastForwardRef = forwardRef
          lastComponent = WrappedComponent
          lastChildElement = (
            <WrappedComponent {...childProps} ref={forwardRef} />
          )
        }

        return lastChildElement
      }
    }

    class Connect extends OuterBaseComponent {
      constructor(props) {
        super(props)
        invariant(
          forwardRef ? !props.wrapperProps[storeKey] : !props[storeKey],
          'Passing redux store in props has been removed and does not do anything. ' +
            customStoreWarningMessage
        )
        this.selectDerivedProps = makeDerivedPropsSelector()
        this.selectChildElement = makeChildElementSelector()
        this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(
          this
        )
      }

      indirectRenderWrappedComponent(value) {
        // calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
        return this.renderWrappedComponent(value)
      }
      
      // 此時value = {storeState, store} 
      renderWrappedComponent(value) {
        invariant(
          value,
          `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 { storeState, store } = value

        let wrapperProps = this.props
        let forwardedRef

        if (forwardRef) {
          wrapperProps = this.props.wrapperProps
          forwardedRef = this.props.forwardedRef
        }
        
        // 派生混合 props = {dataa: {}, items = {}}
        let derivedProps = this.selectDerivedProps(
          storeState,
          wrapperProps,
          store,
          selectorFactoryOptions
        )
        
        // 植入到 childElement
        return this.selectChildElement(
          WrappedComponent,
          derivedProps,
          forwardedRef
        )
      }

      render() {
        // 這裏 ContextToUse == Context
        const ContextToUse =
          this.props.context &&
          this.props.context.Consumer &&
          isContextConsumer(<this.props.context.Consumer />)
            ? this.props.context
            : Context

        return (
          <ContextToUse.Consumer>
            {this.indirectRenderWrappedComponent}
          </ContextToUse.Consumer>
        )
      }
    }

    Connect.WrappedComponent = WrappedComponent
    Connect.displayName = displayName

    if (forwardRef) {
      const forwarded = React.forwardRef(function forwardConnectRef(
        props,
        ref
      ) {
        return <Connect wrapperProps={props} forwardedRef={ref} />
      })

      forwarded.displayName = displayName
      forwarded.WrappedComponent = WrappedComponent
      return hoistStatics(forwarded, WrappedComponent)
    }

    return hoistStatics(Connect, WrappedComponent)
  }
}


複製代碼

wrapWithConnect 這裏最終生成的也是一個connect組件

[圖片上傳失敗...(image-f8bbb9-1558534168138)]

如今咱們從頭梳理一下整個過程

首先經過 <Provider></Provider>包裹 <App />,

<Provider store={store}>
  <App /> </Provider>
複製代碼

Provider 中首先經過Props 獲取到 store,

const { store } = props
this.state = {
  storeState: store.getState(),
  store
}
複製代碼

componentDidMount 以後發佈了一個訂閱去同步更新 state.storeState,

subscribe() {
  const { store } = this.props

  this.unsubscribe = store.subscribe(() => {
    const newStoreState = store.getState()

    if (!this._isMounted) {
      return
    }
    //同步更新state 而後同步 context value 更新後代組件
    this.setState(providerState => {
      // If the value is the same, skip the unnecessary state update.
      if (providerState.storeState === newStoreState) {
        return null
      }

      return { storeState: newStoreState }
    })
  })

  // Actions might have been dispatched between render and mount - handle those
  const postMountStoreState = store.getState()
  if (postMountStoreState !== this.state.storeState) {
    this.setState({ storeState: postMountStoreState })
  }
}
複製代碼

最後重點部分,state 是如何下傳給後代組件<App />,

import { ReactReduxContext } from './Context'
render() {
  // this.props.context 顯然爲空,因此 Context = React.createContext(null)
  const Context = this.props.context || ReactReduxContext

  return (
    <Context.Provider value={this.state}> // 後代組件能夠經過 value 去獲取 state {this.props.children} </Context.Provider> ) } 複製代碼

此時的React Dom 結構是:

/** * Provider: * props: { * store: {getState, dispatch, replaceReducer, subscribe}, * chilren: ... * } * state: { * store: {getState, dispatch, replaceReducer, subscribe}, * storeState: {item, location} * } * */
<Provider>
  /** * Context.Provider: * props:{ * children, * value: {store, storeState} * } */
  <Context.Provider>
    <App /> </Context.Provider> </Provider>
複製代碼

Provider組件的做用到此結束,接下就要說明 connect 是如何把 state 變成後代組件的 props

首先,須要調用 connect 方法把當前組件<App /> 進行包裝處理:

export default connect(
  ((state, ownProps) => {
    return {
      data: state.items
    }
  }),
  (dispatch, ownProps) => {
    return {
      addItem: () => {
        dispatch(addItem(ownProps.name))
      }
    }
  }
)(App);
複製代碼

connet(mapStateToProps, mapDispatchToProps)(App), 最終通過一系列中間過程最終執行的其實就是 wrapWithConnect,這時候

import { ReactReduxContext } from './Context'
context = ReactReduxContext
複製代碼

注意:

此處的contextProvider 裏面引用的 ReactReduxContext 是同一個,因此該組件將能夠和 Provider 關聯起來,能夠獲取最近的 <Context.Provider> 裏面的 value = {store, storeState} 值。

wrapWithConnect

因此在 react-redux/src/components/connectAdvanced.js 中 wrapWithConnect 裏面有這樣的一個 render:

class Connect extends OuterBaseComponent {
  constructor(props) {
    super(props)
    ...
    this.selectDerivedProps = makeDerivedPropsSelector()
    this.selectChildElement = makeChildElementSelector()
    this.indirectRenderWrappedComponent = this.indirectRenderWrappedComponent.bind(
      this
    )
  }

  indirectRenderWrappedComponent(value) {
    // calling renderWrappedComponent on prototype from indirectRenderWrappedComponent bound to `this`
    return this.renderWrappedComponent(value)
  }

  renderWrappedComponent(value) {
    ....
    const { storeState, store } = value

    let wrapperProps = this.props
    let forwardedRef

    if (forwardRef) {
      wrapperProps = this.props.wrapperProps
      forwardedRef = this.props.forwardedRef
    }

    let derivedProps = this.selectDerivedProps(
      storeState,
      wrapperProps,
      store,
      selectorFactoryOptions
    )

    //生成新的組件
    return this.selectChildElement(
      WrappedComponent,
      derivedProps,
      forwardedRef
    )
  }

  render() {
    // 這裏明顯是 Context 仍是剛纔的那個 ReactReduxContext
    const ContextToUse =
      this.props.context &&
        this.props.context.Consumer &&
        isContextConsumer(<this.props.context.Consumer />)
        ? this.props.context
        : Context

    return (
      //Context.Consumer 能夠綁定 value 到 component function 裏面,也就是this.indirectRenderWrappedComponent(value)
      <ContextToUse.Consumer>
        {this.indirectRenderWrappedComponent}
      </ContextToUse.Consumer>
    )
  }
}
複製代碼

簡單看下 this.indirectRenderWrappedComponent :

//生成新的組件
return this.selectChildElement(
  WrappedComponent,// <App />
  derivedProps,// {data, items}
  forwardedRef
)
複製代碼
function selectChildElement( WrappedComponent, childProps, forwardRef ) {
  if (
    childProps !== lastChildProps ||
    forwardRef !== lastForwardRef ||
    lastComponent !== WrappedComponent
  ) {
    lastChildProps = childProps
    lastForwardRef = forwardRef
    lastComponent = WrappedComponent
    lastChildElement = (
      <WrappedComponent {...childProps} ref={forwardRef} /> ) } return lastChildElement } 複製代碼

前面通過一系列 mapStateToProps 和 mapDispatchToProps 的處理,最終:

<WrappedComponent {...childProps} ref={forwardRef} />
也就是
<App {...childProps} ref={forwardRef} />
複製代碼

到如今爲止,state 怎麼變成 props 已經很明顯了。

最終 React Dom 結構以下:

/**
* Provider:
*   props: {
*     store: {getState, dispatch, replaceReducer, subscribe},
*     chilren: ...
*   }
*   state: {
*     store: {getState, dispatch, replaceReducer, subscribe},
*     storeState: {item, location}
*   }
*   
*/
<Provider>
  /**
  * Context.Provider: 
  *    props:{
  *      children,
  *      value: {store, storeState}
  *    }
  */
  <Context.Provider>
    // props: {}
    <Connect>
      // props:{children:bound indirectRenderWrappedComponent()}
      <Context.Consumer>
        // props: {data, items}
        <App />
      </Context.Consumer>
    </Connect>
  </Context.Provider>
</Provider>

複製代碼

Redux暫時完結,之後再詳細講解其中的一些部分。

相關文章
相關標籤/搜索