react-redux 源码解析(1) -- Provider
react-redux 源码解析(2) -- Connect(上)
react-redux 源码解析(3) -- Connect(下)
---------------------------------------分割线-----------------------------------------
写了很多react代码,然鹅还没看过react-redux源码,只知道大概流程,就寻思着看看源码真正的了解其中原理。然后就去网上先看看别人的解析,看了很多篇文章后,大多都是只点到几个关键点,还是一脸懵逼。没办法自己建个工程慢慢调试吧。下面附上源码理解之后的解析,都在代码上标了注释,也会都解释一下。看源码的应该对react-redux大致流程都了解了,咱们就尽量细细的分析每个API,当做个分享也当个自己的存档。
Provider应用
在使用react-redux 的过程中,首先是使用的肯定就是 Provider。那么我们先来看看怎么使用它。
index.tsx
import AppContainer from './container/AppContainer';
import createStore from './store/createStore';
// redux 创建 store
const store = createStore();
// 这里将store传入Provider
// 创建一个Provider,将store传入Provider,作为 Provider 组件的第一个参数
ReactDOM.render(<AppContainer store={store} />, document.getElementById('root'));
AppContainer.tsx
render() {
const { store } = this.props;
return (
<Provider store={store}>
<Router>
<AppLayout />
</Router>
</Provider>
);
}
在代码中看到咱们使用Provider 还是很简单的,传入store, 作为当前context的值,便于组件通过context获取Redux store。
store
我们继续来看,上代码:
createStore.tx
import { applyMiddleware, compose, createStore } from 'redux';
import createSagaMiddleware from 'redux-saga';
import rootReducer from '../reducers/index';
import rootSaga from '../saga';
export default function configureStore(preloadedState?:any) {
// 创建saga中间件
const sagaMiddleware = createSagaMiddleware();
const middleWares = [sagaMiddleware];
// middleWares 传入 applyMiddleware, 拓展 store 的 dispatch,返回一个函数,给 createStore 调用
const middlewareEnhancer = applyMiddleware(...middleWares);
// 创建存储容器
// rootReducer 自定义的 reducer
// preloadedState 默认的 state
// 包装过的 enhancers
// middlewareEnhancer参数有值,则 createStore执行的是 middlewareEnhancer 函数,返回 store 和 包装过的 dispatch
const store = createStore(rootReducer, preloadedState, middlewareEnhancer);
sagaMiddleware.run(rootSaga);
return store;
}
代码里的注释大致能看出来干了什么,那么我们再来一步步分析。
首先createSagaMiddleware创建了redux-saga中间件来扩展dispatch,那么为什么要扩展dispatch? 这里稍微说下,原因就是我们需要处理异步。
在原始的redux,dispatch的action必须是一个纯对象(plain object),而且必须是同步的。但是我们的应用里面经常会有定时器,网络请求等等异步操作,扩展dispatch让它能接受函数,这个函数会传入dispatch本身作为参数,这就是一种异步解决方案(Redux-Thunk)。有兴趣的可以再深入了解redux以及redux-thunk/redux-saga。
继续往下看,我们看到它把生成的中间件传入了applyMiddleware,那么这个API又干了什么?
applyMiddleware 和 compose
从名称上就能看出来是使用中间件的意思,那么它是怎么直接使用的呢?参数可是一个数组(不止一个中间件)。我们来分析下代码:
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
// 创建store
const store = createStore(...args)
// 新建变量 dispatch 函数
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)
}
// 把 store.getState 和 dispatch 注入到每个中间件
const chain = middlewares.map(middleware => middleware(middlewareAPI))
// 调用compose, 重新赋值经过中间件扩展后的dispatch
dispatch = compose(...chain)(store.dispatch)
// 返回 store, 扩展后的dispatch
return {
...store,
dispatch
}
}
}
这里看到 applyMiddleware 关键部分就是compose函数调用后重新赋值dispatch。我们再来看看 compose
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)))
}
关键代码就在最后一句,说白了就是循环调用函数,就像下面一样
const func = [f1, f2, f3];
compose(...func) //return f1(f2(f3(...args)))
//注意函数调用的顺序是从左到右,即:f1 -> f2 -> f3
那么这里有个问题,applyMiddleware返回的store和dispatch又在什么地方用到了?留下这问题我们继续看
基本上面的细节我们已经分析完毕。这边我们到下一行,
const store = createStore(rootReducer, preloadedState, middlewareEnhancer);
这一行很清楚,调用createStore生成store。
这里提一下上面的问题,其实在传入middlewareEnhancer的情况下,这里生成的store 其实就是 applyMiddleware返回的值。这样我们的疑问也得到了解答。
到这里我们终于分析完了store的生成过程,对store有了大致的了解。说白了store就是createStore内置函数subscribe、getState等以及包装过的dispatch。
Provider干了什么
前面说了这么多,终于进入主题,哈哈。各位客官不要急,咱们慢慢道来。先上代码,
import React, { useMemo } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
import Subscription from '../utils/Subscription'
import { useIsomorphicLayoutEffect } from '../utils/useIsomorphicLayoutEffect'
// store 为 redux 创建的 store
function Provider({ store, context, children }) {
// 定义 contextValue
const contextValue = useMemo(() => {
// 新建订阅类
const subscription = new Subscription(store)
// 通知类 createListenerCollection 的通知方法赋值到订阅类的回调方法上,以便后面回调通知
subscription.onStateChange = subscription.notifyNestedSubs
return {
store,
subscription,
}
}, [store])
const previousState = useMemo(() => store.getState(), [store])
// useIsomorphicLayoutEffect 其实就是 useLayoutEffect 或者 useEffect, 这里使用的是 useLayoutEffect,为了在渲染之前调用
useIsomorphicLayoutEffect(() => {
const { subscription } = contextValue
// 订阅类增加订阅
subscription.trySubscribe()
//前后的state不一样,那么就去通知订阅者更新,onStateChange就会执行
if (previousState !== store.getState()) {
subscription.notifyNestedSubs()
}
return () => {
// 卸载订阅
subscription.tryUnsubscribe()
subscription.onStateChange = null
}
}, [contextValue, previousState])
// 默认使用 ReactReduxContext, 其实就是 React.createContext(null)
const Context = context || ReactReduxContext
// 返回react的 Context.Provider,说白了 react-redux其实是在 react Provider Consumer 上的拓展,
// 当然得升级到16.3 才是用的 react Context,要是项目小,react 版本高的话 不必用 redux, react Context 够用了
return <Context.Provider value={contextValue}>{children}</Context.Provider>
}
if (process.env.NODE_ENV !== 'production') {
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
首先看到定义了contentValue,使用useMemo包装监听store变化返回store和subscription,我们看倒了一个新东西Subscription,这又是个什么玩意,老规矩上源码,看看里面究竟是什么:
Subscription
export default class Subscription {
constructor(store, parentSub) {
// 初始化store
this.store = store
// 获取来自父级的Subscription实例,connect会用到
this.parentSub = parentSub
this.unsubscribe = null
this.listeners = nullListeners
this.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}
addNestedSub(listener) {
this.trySubscribe()
// 这里是被parentSub调用的,所以listener也会被订阅到parentSub上
return this.listeners.subscribe(listener)
}
notifyNestedSubs() {
// 通知listeners 去执行
this.listeners.notify()
}
handleChangeWrapper() {
if (this.onStateChange) {
// onStateChange会在外部的被实例化成subcription实例的时候,被赋值为不同的更新函数,被赋值的地方分别的Provider和connect中
// 一般就是这行 notifyNestedSubs方法 this.listeners.notify()
this.onStateChange()
}
}
isSubscribed() {
return Boolean(this.unsubscribe)
}
trySubscribe() {
if (!this.unsubscribe) {
// 如果parentSub没传,那么使用store订阅,否则,调用context中获取的subscrption来订阅,保证都订阅到一个地方
this.unsubscribe = this.parentSub
? this.parentSub.addNestedSub(this.handleChangeWrapper)
: this.store.subscribe(this.handleChangeWrapper)
// 创建listener集合
this.listeners = createListenerCollection()
}
}
// 取消订阅
tryUnsubscribe() {
if (this.unsubscribe) {
this.unsubscribe()
this.unsubscribe = null
this.listeners.clear()
this.listeners = nullListeners
}
}
}
Subscription就是将页面的更新工作和状态的变化联系起来。通过trySubscribe方法,根据情况被分别订阅到store或者Subscription内部。放入到listeners数组,当state变化的时候,listeners循环执行每一个监听器,触发页面更新。
trySubscribe中根据不同情况判断直接使用store订阅,还是调用addNestedSub来实现订阅的原因。者的场景是Provider将listener订阅到store中。后者是connect内部将checkForUpdates放到listeners数组中,实际上是利用Provider中传过来的Subscrption实例来订阅,保证所有被connect的组件都订阅到一个Subscrption实例上。
总结
到此Provider基本分析完毕。Provider最主要的功能是从props中获取我们传入的store,并将store作为context的其中一个值,向下层组件下发。一旦store变化,Provider要有所反应,以此保证将始终将最新的store放入context中。
那么如何向组件中注入state和dispatch,就要再分析分析connect。