# 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()
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
)
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
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)
connect
函数就是一个高阶组件,将以上的参数传入函数,返回一个新的组件,主要就是将state
和dispatch
等属性挂载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)
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
对象;renderedWrappedComponent
将actualChildProps (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)
下面内容不是精读代码,只是聊一下基本的流程。
# 初始化
当Provider
组件被调用注册时,订阅更新Subscription函数被注册:
const subscription = new Subscription(store)
在redux-react中,订阅发布函数Subscription (opens new window)是其中的核心函数(订阅发布模式是其核心),它有效的保证connect组件的更新渲染。store
作为参数传入到Subscription构造函数内部,作用就是Subscription内部使用。
# 订阅
return connect(mapStateToProps, mapDispatchToProps)(Component)
// react-redux中,checkForUpdates函数,负责更新connect组件
subscription.onStateChange = checkForUpdates
// redux保存触发 通知函数
store.subscribe(subscription.notifyNestedSubs);
// react-redux保存 更新函数
subscription.listeners.subscribe(subscription.onStateChange)
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)
}
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]()
}
2
3
4
以上细节可能有所省略,但基本就是先订阅,将更新函数保存进入订阅数组,然后当dispatch函数被调用时,遍历调用订阅数组,完成connect组件的更新。
在最新的react-redxu库中 ,有大量的React Hooks出现,中间我木有过多的说明,有兴趣可以自行研究。(高阶组件、React Hooks、订阅发布模式)
ps: 微信公众号:Yopai,有兴趣的可以关注,每周不定期更新。不断分享,不断进步
男性,武汉工作,会点Web,擅长Javascript.
技术或工作问题交流,可联系微信:1169170165.