专注于 JetBrains IDEA 全家桶,永久激活,教程
持续更新 PyCharm,IDEA,WebStorm,PhpStorm,DataGrip,RubyMine,CLion,AppCode 永久激活教程

React教程之Redux状态管理

Redux

虽然是react的一个状态管理库,可以单独使用,接下来我们详细讲解下底层代码。

废话不多讲,先说下基本的知识点。

Actions

actions 是通过 store.dispatch(action)改变 state的唯一方法,不能直接通过 this.state = {} 来直接改变 state

生成 actions 有两种方式:

1、单纯的 object

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

2、action creator function

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}
dispatch(addTodo())

Reducers

Actions 只能描述发生了什么,并不能描述状态发生了什么变化,Reducers 指定 state tree 将要发生什么。

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

这就是单个 reducer 的格式,固定两个参数 stateaction;

多个reducers 是需要使用 combineReducers,

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 里面实现了什么?

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暂时完结,以后再详细讲解其中的一些部分。

文章永久链接:https://tech.souyunku.com/34702

未经允许不得转载:搜云库技术团队 » React教程之Redux状态管理

JetBrains 全家桶,激活、破解、教程

提供 JetBrains 全家桶激活码、注册码、破解补丁下载及详细激活教程,支持 IntelliJ IDEA、PyCharm、WebStorm 等工具的永久激活。无论是破解教程,还是最新激活码,均可免费获得,帮助开发者解决常见激活问题,确保轻松破解并快速使用 JetBrains 软件。获取免费的破解补丁和激活码,快速解决激活难题,全面覆盖 2024/2025 版本!

联系我们联系我们