本文原文连接javascript
在以前的一篇文章中已讲过redux 原理解析,我将redux
返回的store对象挂载在window中,不是太懂的同窗能够看看以前的redux 原理解析。html
const reducer = combineReducers({ home: homeNumber, number: addNumber }) const store = createStore(reducer) window.$reduxStore = store // 使用 window.$reduxStore.dispatch(action); let { state } = window.$reduxStore.getState()
但在平时的开发中,通常是将redux+react-redux+react
配合使用,那么,下面就一块儿来看看react-redux
中的常见方法,它具体作了什么。<span style="color: red;font-weight: 900;">【下面是以最新版本react-redux@7.1.3库解析】</span>(下面的源码部分省略)java
react-redux
提供了<Provider />
组件用来挂载redux返回的store对象,同时将整个应用做为Provider的子组件。react
ReactDOM.render( <Provider store={store}> <App /> </Provider>, rootElement )
下面看看<Provider />
组件作了什么:git
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
Provider接收三个参数,store参数接收store对象,context参数接收上下文对象,children参数接收ReactElement元素;
在原应用组件上包裹一层,使原来整个应用成为Provider的子组件,Context.Provider API:只有当 Provider 的 value 值发生变化时,它内部的全部消费组件都会从新渲染。github
将contextValue
挂载在Provider上,contextValue包含store对象和订阅发布subscription对象,以备connect
组件使用获取。Subscription主要负责connect、Provider组件的更新,属于核心内容,这些将在下面讲到。redux
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)
hoistStatics
函数的做用就是相似于Object.assign,能够这样理解hoistStatics(targetComponent, sourceComponent)
,hoist-non-react-statics库。上面代码中ConnectFunction
是核心函数,虽然中间部分代码省略,不过留下的都是精华。app
在ConnectFunction
函数中,经过hooks useContext
获取Provider组件的contextValue
对象;renderedWrappedComponent
将actualChildProps做为props传入,actualChildProps是已经处理过的props属性,上面已经挂载了dispatch
函数和state
状态等属性;而renderedChild
组件,其实connect函数最后返回的实际内容。(中间部分代码省略了源码连接跳转)
以上就是Provice组件和connect组件初次调用时,所通过的实际代码,固然在其中有一些删减,不过基本都有说到。
当dispatch被调用时,会发生什么呢?上面部分彻底没有说到,下面咱们就来看看当dispatch(action)
调用后,react-redux内部,是如何更新connect组件。
当在React应用中调用dispatch函数时,redux中实际调用的就是reducer函数,同时返回新的state。那么redux-react中的connect组件如何更新呢,下面咱们来一块儿看看更新的流程:
dispatch(action)
下面内容不是精读代码,只是聊一下基本的流程。
当Provider
组件被调用注册时,订阅更新Subscription函数被注册:
const subscription = new Subscription(store)
在redux-react中,订阅发布函数Subscription是其中的核心函数(订阅发布模式是其核心),它有效的保证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)
在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) }
checkForUpdates
函数负责connect组件的内部的状态更新。
notifyNestedSubs
函数是做用是触发react-redux中的subscription对象的更新函数,而它被保存在nextListeners数组中,是为了当redux的dispatch函数被触发时,调用notifyNestedSubs通知函数,进而触发react-redux的connect组件的checkForUpdates更新函数。
react-redux:checkForUpdates 函数源码
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]() }
以上细节可能有所省略,但基本就是先订阅,将更新函数保存进入订阅数组,而后当dispatch函数被调用时,遍历调用订阅数组,完成connect组件的更新。
在最新的react-redxu库中 ,有大量的React Hooks出现,中间我木有过多的说明,有兴趣能够自行研究。(高阶组件、React Hooks、订阅发布模式)
ps: 微信公众号:Yopai,有兴趣的能够关注,每周不按期更新。不断分享,不断进步