# react-redux原理解析

作者:HerryLo (opens new window) 博客原文链接 (opens new window)

在之前的一篇文章中已讲过redux 原理解析 (opens new window),我将redux返回的store对象挂载在window中,不是太懂的同学可以看看之前的redux 原理解析 (opens new window)

const reducer = combineReducers({
    home: homeNumber,
    number: addNumber
})
const store = createStore(reducer)
window.$reduxStore = store

// 使用
window.$reduxStore.dispatch(action);
let { state } = window.$reduxStore.getState()
1
2
3
4
5
6
7
8
9
10

但在平时的开发中,一般是将redux+react-redux+react配合使用,那么,下面就一起来看看react-redux中的常见方法,它具体做了什么。【下面是以最新版本react-redux@7.1.3库解析】(下面的源码部分省略)

# Provider函数

react-redux提供了<Provider />组件用来挂载redux返回的store对象,同时将整个应用作为Provider的子组件。

ReactDOM.render(
  <Provider store={store}>
    <App />
  </Provider>,
  rootElement
)
1
2
3
4
5
6

下面看看<Provider />组件做了什么:

function Provider({ store, context, children }) {
  // 依赖项store变化触发,返回store和subscription
  const contextValue = useMemo(() => {
    // 订阅监听Subscription函数,下面会专门说到
    const subscription = new Subscription(store)
    subscription.onStateChange = subscription.notifyNestedSubs
    return {
      store,
      subscription
    }
  }, [store])
  //...
  
  // ReactReduxContext = React.createContext(null)
  const Context = context || ReactReduxContext

  return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
// 源码地址:https://github.com/reduxjs/react-redux/blob/master/src/components/Provider.js#L6
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

Provider接收三个参数,store参数接收store对象,context参数接收上下文对象,children参数接收ReactElement元素; 在原应用组件上包裹一层,使原来整个应用成为Provider的子组件,Context.Provider API (opens new window)只有当 Provider 的 value 值发生变化时,它内部的所有消费组件都会重新渲染

contextValue挂载在Provider上,contextValue包含store对象和订阅发布subscription对象,以备connect组件使用获取。Subscription主要负责connect、Provider组件的更新,属于核心内容,这些将在下面讲到。

# connect函数

connect的常见使用示例:

return connect(mapStateToProps, mapDispatchToProps)(Component)
1

connect函数就是一个高阶组件,将以上的参数传入函数,返回一个新的组件,主要就是将statedispatch等属性挂载Component的props上。

import hoistStatics from 'hoist-non-react-statics'
import { ReactReduxContext } from './Context';

// 核心函数 return组件
function ConnectFunction (props) {
  //  ...
  // 判断props 是否存在store & getState & dispatch,一般都为false
  var didStoreComeFromProps = Boolean(props.store) && Boolean(props.store.getState) && Boolean(props.store.dispatch);
  // 获取Provider组件挂载的contextValue
  var ContextToUse = useMemo(function () {
    return propsContext &&
          propsContext.Consumer &&
          isContextConsumer(<propsContext.Consumer />)
          ? propsContext
          : Context
  })
  // contextValue = { store, subscription }
  var contextValue = useContext(ContextToUse)

  //  ...
  // 依赖返回 contextValue
  var overriddenContextValue = useMemo(function () {
    if (didStoreComeFromProps) {
      return contextValue
    }
    return {
      ...contextValue,
      subscription
    }
  }, [didStoreComeFromProps, contextValue, subscription]);

  //  ...
  // actualChildProps == props,上挂载了```state```和```dispatch```等属性
  const renderedWrappedComponent = useMemo(
    () => <WrappedComponent {...actualChildProps} ref={forwardedRef} />,
    [forwardedRef, WrappedComponent, actualChildProps]
  )
  // 返回渲染ReactElement
  var renderedChild = useMemo(function () {
    // 判断是否存在mapStateToProps函数
    if (shouldHandleStateChanges) {
      return (
        <ContextToUse.Provider value={overriddenContextValue}>
          {renderedWrappedComponent}
        </ContextToUse.Provider>
      )
    }
    // renderedWrappedComponent 渲染容器组件
    return renderedWrappedComponent
  }, [ContextToUse, renderedWrappedComponent, overriddenContextValue]);
  return renderedChild;
}
//  ...
// 与Object.assign类似操作
return hoistStatics(ConnectFunction, WrappedComponent)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

hoistStatics (opens new window)函数的作用就是类似于Object.assign,可以这样理解hoistStatics(targetComponent, sourceComponent)hoist-non-react-statics库 (opens new window)。上面代码中ConnectFunction是核心函数,虽然中间部分代码省略,不过留下的都是精华。

ConnectFunction函数中,通过hooks useContext获取Provider组件的contextValue对象;renderedWrappedComponentactualChildProps (opens new window)作为props传入,actualChildProps是已经处理过的props属性,上面已经挂载了dispatch函数和state状态等属性;而renderedChild组件,其实connect函数最后返回的实际内容。(中间部分代码省略了源码链接跳转 (opens new window))

以上就是Provice组件和connect组件初次调用时,所经过的实际代码,当然在其中有一些删减,不过基本都有说到。

当dispatch被调用时,会发生什么呢?上面部分完全没有说到,下面我们就来看看当dispatch(action)调用后,react-redux内部,是如何更新connect组件。

# connect如何更新?

当在React应用中调用dispatch函数时,redux中实际调用的就是reducer函数,同时返回新的state。那么redux-react中的connect组件如何更新呢,下面我们来一起看看更新的流程:

dispatch(action)
1

下面内容不是精读代码,只是聊一下基本的流程

# 初始化

Provider组件被调用注册时,订阅更新Subscription函数被注册:

const subscription = new Subscription(store)
1

在redux-react中,订阅发布函数Subscription (opens new window)是其中的核心函数(订阅发布模式是其核心),它有效的保证connect组件的更新渲染。store作为参数传入到Subscription构造函数内部,作用就是Subscription内部使用

# 订阅

return connect(mapStateToProps, mapDispatchToProps)(Component)
1
// react-redux中,checkForUpdates函数,负责更新connect组件
subscription.onStateChange = checkForUpdates

// redux保存触发 通知函数
store.subscribe(subscription.notifyNestedSubs);

// react-redux保存 更新函数
subscription.listeners.subscribe(subscription.onStateChange)
1
2
3
4
5
6
7
8

在connect组件被使用时,react-redux中的subscription对象,会将connect组件的checkForUpdates更新函数作为参数,传入保存在subscription的订阅数组next中;同时,redux也会发生相同的操作【在react-redux中也有使用到redux中方法】。(代码为简化版)

// redux中 subscribe函数
let nextListeners = []
subscribe(listener) {
   // ... 
   nextListeners.push(listener)
}
// react-redux中 subscribe函数
let next = []
subscribe(listener) {
   // ... 
   next.push(listener)
}

1
2
3
4
5
6
7
8
9
10
11
12
13

checkForUpdates (opens new window)函数负责connect组件的内部的状态更新。

notifyNestedSubs函数是作用是触发react-redux中的subscription对象的更新函数,而它被保存在nextListeners数组中,是为了当redux的dispatch函数被触发时,调用notifyNestedSubs通知函数,进而触发react-redux的connect组件的checkForUpdates更新函数。

react-redux:checkForUpdates 函数源码 (opens new window)

react-redux:Subscription 函数源码 (opens new window)

# 发布(更新)

dispatch(action)发起触发操作后,当然是触发store中dispatch函数了,修改store中state的值,更新遍历redux的nextListeners订阅数组,触发通知函数notifyNestedSubs调用;同时,这会导致react-redux中的next数组,被触发遍历调用。两个库基本都是下面的代码

let next = listeners;for (let i = 0; i < listeners.length; i++) {
  listeners[i]()
}
1
2
3
4

以上细节可能有所省略,但基本就是先订阅,将更新函数保存进入订阅数组,然后当dispatch函数被调用时,遍历调用订阅数组,完成connect组件的更新。

在最新的react-redxu库中 ,有大量的React Hooks出现,中间我木有过多的说明,有兴趣可以自行研究。(高阶组件、React Hooks、订阅发布模式)

ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新。不断分享,不断进步

🥰Me

男性,武汉工作,会点Web,擅长Javascript.

技术或工作问题交流,可联系微信:1169170165.

🚀小程序&公众号
🚀运行
2024年2月19日星期一下午2点01分
博客已运行:--天--时--分--秒
上次更新: 7/18/2023, 6:00:41 PM

评 论: