阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
  • 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理

在阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates中介绍了函数connectAdvanced是如何关联到store变动的,其实这个函数做的事情不仅仅有关联变动,还有对于store改变的事件转发、ref的处理和pure模式的处理。

首先来看store改变的事件的转发。

store改变的事件转发

function connectAdvanced (selectorFactory,{...context = ReactReduxContext,...}
) {...const Context = context...return function wrapWithConnect (WrappedComponent) {...function ConnectFunction (props) {const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {// Distinguish between actual "data" props that were passed to the wrapper component,// and values needed to control behavior (forwarded refs, alternate context instances).// To maintain the wrapperProps object reference, memoize this destructuring.const { forwardedRef, ...wrapperProps } = propsreturn [props.context, forwardedRef, wrapperProps]}, [props])const ContextToUse = useMemo(() => {// Users may optionally pass in a custom context instance to use instead of our ReactReduxContext.// Memoize the check that determines which context instance we should use.return propsContext &&propsContext.Consumer &&isContextConsumer(<propsContext.Consumer />)? propsContext: Context}, [propsContext, Context])// Retrieve the store and ancestor subscription via context, if availableconst contextValue = useContext(ContextToUse)// The store _must_ exist as either a prop or in context.// We'll check to see if it _looks_ like a Redux store first.// This allows us to pass through a `store` prop that is just a plain value.const didStoreComeFromProps =Boolean(props.store) &&Boolean(props.store.getState) &&Boolean(props.store.dispatch)const didStoreComeFromContext =Boolean(contextValue) && Boolean(contextValue.store)if (process.env.NODE_ENV !== 'production' &&!didStoreComeFromProps &&!didStoreComeFromContext) {throw new Error(`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.`)}// Based on the previous check, one of these must be trueconst store = didStoreComeFromProps ? props.store : contextValue.storeconst [subscription, notifyNestedSubs] = useMemo(() => {if (!shouldHandleStateChanges) return NO_SUBSCRIPTION_ARRAY// This Subscription's source should match where store came from: props vs. context. A component// connected to the store via props shouldn't use subscription from context, or vice versa.const subscription = new Subscription(store,didStoreComeFromProps ? null : contextValue.subscription)// `notifyNestedSubs` is duplicated to handle the case where the component is unmounted in// the middle of the notification loop, where `subscription` will then be null. This can// probably be avoided if Subscription's listeners logic is changed to not call listeners// that have been unsubscribed in the  middle of the notification loop.const notifyNestedSubs = subscription.notifyNestedSubs.bind(subscription)return [subscription, notifyNestedSubs]}, [store, didStoreComeFromProps, contextValue])// Determine what {store, subscription} value should be put into nested context, if necessary,// and memoize that value to avoid unnecessary context updates.const overriddenContextValue = useMemo(() => {if (didStoreComeFromProps) {// This component is directly subscribed to a store from props.// We don't want descendants reading from this store - pass down whatever// the existing context value is from the nearest connected ancestor.return contextValue}// Otherwise, put this component's subscription instance into context, so that// connected descendants won't update until after this component is donereturn {...contextValue,subscription}}, [didStoreComeFromProps, contextValue, subscription])}...// If React sees the exact same element reference as last time, it bails out of re-rendering// that child, same as if it was wrapped in React.memo() or returned false from shouldComponentUpdate.const renderedChild = useMemo(() => {if (shouldHandleStateChanges) {// If this component is subscribed to store updates, we need to pass its own// subscription instance down to our descendants. That means rendering the same// Context instance, and putting a different value into the context.return (<ContextToUse.Provider value={overriddenContextValue}>{renderedWrappedComponent}</ContextToUse.Provider>)}return renderedWrappedComponent}, [ContextToUse, renderedWrappedComponent, overriddenContextValue])return renderedChild}const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction...return hoistStatics(Connect, WrappedComponent)}

可以看出来ConnectFunction组件中如果props传入了合法的context那么ContextToUse就是props.context如果不是则使用默认的ReactReduxContext。

到这里拿到了ContextToUse,后面通过useContext(ContextToUse)得到context中传过来的值contextValue。通过前面的解读我们知道contextValue需要一个对象中包含两个值,一个是store一个是subscription(Subscription的实例)。

除了context可以使用来自props外store也可以来自props。如果props中有store那么就放弃contextValue中的store而使用props中传入的store。

如果contextValue中也没有store,毕竟contextValue可能来自用户传入,props中也没有store,没有store监听啥,继续不下去了,直接报错。

根据store和contextValue和didStoreComeFromProps三个值来生成新的subscription(不明白为啥要生成新的,直接订阅传入的subscription不是更加简单吗?如果是props.store再生成新的)。得到新的subscription和subscription的通知函数notifyNestedSubs,来通知subscription的订阅者。

重写contextValue使用新生成的subscription来覆盖contextValue中的subscription,然后通过Provider提供给后代组件。

小结:整个react-redux构建的上下文中,并不只能有位移的store和context,还可以通过props的方式传入别的store和context让Connect组件响应这个传入的store和context的变动。

获取业务组件的实例(透传ref)

function createConnect() {return function connect() {return function connectHOC()}
}export default createConnect()
// 就是上面的connectHOC
function connectAdvanced () {return function wrapWithConnect(WrappedComponent) {function ConnectFunction () {return <WrappedComponent />}return ConnectFunction}
}

基本上connect(mapStateToProps, mapDispatchToProps)(wrappedComponent)就是这个结构,所以最后被返回的出去的是ConnectFunction这个组件,如果在connectFunction上设置props ref也拿不到业务组件WrappedComponent的实例。

首先在connectAdvanced的配置项中有forwardRef = false如果是true,那么connectAdvanced就会帮你透传ref给业务组件。

看看如何做的:

function connectAdvanced(selectorFactory,{forwardRef = false}
) {return function wrapWithConnect(WrappedComponent) {function ConnectFunction () {const [propsContext, forwardedRef, wrapperProps] = useMemo(() => {const { forwardedRef, ...wrapperProps } = propsreturn [forwardedRef, wrapperProps]}, [props])return <WrappedComponent ref={forwardedRef} />}return ConnectFunctionif (forwardRef) {const forwarded = React.forwardRef(function forwardConnectRef(props,ref) {return <ConnectFunction {...props} forwardedRef={ref} />})return forwarded}return ConnectFunction}
}

上面的代码基本上就是透传ref的所有实现了,和源码有些出入,主要是删除了一些干扰因素。

如果配置项中forwardRef是true那么wrapWithConnect返回的就是:

React.forwardRef(function forwardConnectRef(props, ref) {return <ConnectFunction {...props} forwardedRef={ref} />}
)

使用React.forwardRef提取了ref通过keyforwardedRef属性传递给了ConnectFunction组件。connectFunction组件内部拿到forwardedRef只有再给到业务组件<ConnectFunction ref={forwardedRef} />。这样就完成了透传ref的任务,让父组件可以拿到被包裹的业务组件的实例。

pure模式

对于PureComponent我们有所了解,当props变动的时候这个Component会帮助我们自动做一个props的浅比较,如果浅比较相等则不会update组件,如果不相等才会update组件。

对于类组件可以继承PureComponent而函数式组件可以使用React.memo来做同样的事情,当然钩子函数useMemo也可以达到同样的效果。

业务组件的props有两个来源,一个是父组件,一个是store。store又被分为两个,一个是store的state,一个是store的dispatch。

想要让整个业务组件是pure那么就要考虑两个方面的变动,一个是父组件重新渲染的时候需要浅比较当前业务子组件的props来阻止不必要的更新,另一个是store中state更新的时候,需要对当前组件监听的state做浅比较防止不必要的更新。

return function wrapWithConnect(WrappedComponent) {...const { pure } = connectOptions...const usePureOnlyMemo = pure ? useMemo : callback => callback()...function ConnectFunction(props) {...const actualChildProps = usePureOnlyMemo(() => {if (childPropsFromStoreUpdate.current &&wrapperProps === lastWrapperProps.current) {return childPropsFromStoreUpdate.current}return childPropsSelector(store.getState(), wrapperProps)}, [store, previousStateUpdateResult, wrapperProps])...}...const Connect = pure ? React.memo(ConnectFunction) : ConnectFunction...
}

首先拿到配置中的pure,根据pure得到usePureOnlyMemo,如果是pure模式那么actualChildProps的计算就会被memo,如果依赖没变则不会重新计算。

如果是pure模式的话Connect组件则等于React.memo(ConnectFunction),这样会阻止掉父组件更新导致的子组件不必要的更新,作用和PureComponent一样,会进行一次属性的浅对比,相等则不更新当前组件。

接下来看store更新是如何避免不必要的更新的。


function subscribeUpdates () {...const checkForUpdates = () => {...newChildProps = childPropsSelector(latestStoreState,lastWrapperProps.current)if (newChildProps === lastChildProps.current) {...} else {...forceComponentUpdateDispatch({type: 'STORE_UPDATED',payload: {error}})...}}...
}

上面的代码可以看出来如果store更新新计算出来的newChildProps和lastChildProps.current(上一次更新的childProps)一样的话就不会主动触发当前组件更新(调用方法forceComponentUpdateDispatch)。

所以现在组要看newChildProps的计算过程,也就是childPropsSelector(latestStoreState, lastWrapperProps.current)的调用,它是怎么在多次调用的时候返回的引用是相同的(对象是相同的可以通过===严格等于)。

const selectorFactoryOptions = {...connectOptions,getDisplayName,methodName,renderCountProp,shouldHandleStateChanges,storeKey,displayName,wrappedComponentName,WrappedComponent
}function createChildSelector(store) {return selectorFactory(store.dispatch, selectorFactoryOptions)
}const childPropsSelector = useMemo(() => {// The child props selector needs the store reference as an input.// Re-create this selector whenever the store changes.return createChildSelector(store)
}, [store])

这里面的东西有点多而且很巧妙,所以下一章继续解读。

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现
  • 阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories
  • 阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates
  • 阅读react-redux源码(五) - connectAdvanced中store改变的事件转发、ref的处理和pure模式的处理

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361993.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Servlet编程API

一、基本的servlet APIJavaEE关于Servlet的API主要有两个包&#xff1a;javax.servlet和javax.servlet.http。前者主要提供了Web容器能够使用的servlet基本类和接口&#xff0c;后者主要包括和HTTP协议相关的servlet类和接口。对servlet的编程&#xff0c;主要是根据需要&#…

初级开发人员在编写单元测试时常犯的错误

自从我编写第一个单元测试以来已经有10年了。 从那时起&#xff0c;我不记得我已经编写了成千上万的单元测试。 老实说&#xff0c;我在源代码和测试代码之间没有任何区别。 对我来说是同一回事。 测试代码是源代码的一部分。 在过去的3-4年中&#xff0c;我与多个开发团队合作…

OpenDaylight开发hello-world项目之开发工具安装

OpenDaylight开发hello-world项目之开发环境搭建 OpenDaylight开发hello-world项目之开发工具安装 OpenDaylight开发hello-world项目之代码框架搭建 在ODL开发之前&#xff0c;要安装好开发环境。ODL使用java语言开发&#xff0c;所以要安装好java。ODL的代码框架是有maven这个…

Google Chrome 扩展程序开发

根据公司的规定&#xff0c;每月八小时&#xff0c;弹性工作制。所以大家平时来的不太准时&#xff0c;如果有事&#xff0c;下班也就早些回去了。所以一个月下来工作时间可能不够&#xff0c;但是公司的考勤日历是这样的&#xff1a; 除了请假和法定节假日外&#xff0c;其他样…

阅读react-redux源码(六) - selectorFactory处理store更新

阅读react-redux源码 - 零阅读react-redux源码 - 一阅读react-redux源码(二) - createConnect、match函数的实现阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories阅读react-redux源码(四) - connectAdvanced、wrapWithC…

[Silverlight入门系列]使用MVVM模式(7):ViewModel的INotifyPropertyChanged接口实现

本文说说ViewModel的这个INotifyPropertyChanged接口可以用来做啥&#xff1f; 举例1&#xff1a;我有个TabControl&#xff0c;里面放了很多View&#xff0c;每个由ViewModel控制&#xff0c;我想是想TabSelectionChanged就打开相应的ViewModel&#xff0c;怎么做&#xff1f;…

无状态Spring安全性第1部分:无状态CSRF保护

如今&#xff0c;随着RESTful架构变得越来越标准&#xff0c;可能值得花一些时间重新考虑当前的安全方法。 在这个小系列的博客文章中&#xff0c;我们将探索一些以无状态方式解决与Web相关的安全问题的相对较新的方法。 这第一篇文章是关于保护您的网站免受跨站请求伪造&#…

window.Event参数详解

原文地址&#xff1a;window.Event参数详解作者&#xff1a;cz0090704window.evet 说明 event代表事件的状态&#xff0c;例如触发event对象的元素、鼠标的位置及状态、按下的键等等。 event对象只在事件发生的过程中才有效。 event的某些属性只对特定的事件有意义。比如&…

微信群运营之设计运营思路

商家要想运营好微信群&#xff0c;那么首要做的工作就是设计运营思路。如果做事毫无章法思路&#xff0c;那么很有可能会让自己的工作陷入僵局。运营微信群并不简单&#xff0c;需要考虑多方面社群鸭因素。卖什么产品&#xff0c;群管理体系的设立&#xff0c;规则的制定&#…

阅读react-redux源码(七) - 实现一个react-redux

阅读react-redux源码 - 零阅读react-redux源码 - 一阅读react-redux源码(二) - createConnect、match函数的实现阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories阅读react-redux源码(四) - connectAdvanced、wrapWithC…

[读书笔记]TCP/IP详解V1读书笔记-4 5

IP地址与以太网地址之间的关系 R P发送一份称作A R P请求的以太网数据帧给以太网上的每个主机。这个过程称作广播&#xff0c;在32 bit的I P地址和采用不同网络技术的硬件地址之间提供动态映射 ----------------------------------------- arp以太网帧的类型字段为x 0 8 0 6&am…

未来是Apache Karaf上的微服务架构

这是Jamie Goodyear的客座博客文章&#xff08; 博客 &#xff0c; icbts &#xff09;。 他是Savoir Technologies的开源倡导者&#xff0c;Apache开发人员和计算机系统分析师&#xff1b; 他为全球大型组织设计&#xff0c;批判和支持了体系结构。 他拥有纽芬兰纪念大学的计…

springcloud微服务多节点高性能、高可用、高并发部署

1. 共有三个服务 discovery服务&#xff0c;domain服务&#xff0c;gateway服务。 discovery服务是用来注册其他服务的&#xff0c;作为服务治理用。 domain服务是主业务服务。 gateway服务是所有服务的一个入口&#xff0c;用来做一些服务的判断和过滤用。 2. 有三台机器分别为…

只能是数字、字母、-和_

在文本框的keypress事件调用下面函数。 如 <input disabled"disabled" type"text" iduserNameToEdit οnkeypress"TextValidate()" /> 如果在文本框中按下特殊字符键&#xff0c;则显示警告信息&#xff0c;或者输入框不接受非法输入。 …

代码风格之Prettier简介

多人协作中统一的代码风格有利于项目的发展这是共识&#xff0c;但是采用什么标准来统一代码这选择就相对纷杂。项目刚开始使用了ESLint来规范代码&#xff0c;但是ESLint默认是支持JavaScript&#xff0c;加上配置可以支持TypeScript&#xff0c;而样式的支持则需要再配置Styl…

带有Swagger的Spring Rest API –集成和配置

如今&#xff0c;公开的API终于获得了应有的关注&#xff0c;公司也开始意识到其战略价值。 但是&#xff0c;使用第三方API确实是一项繁琐的工作&#xff0c;尤其是当这些API维护不当&#xff0c;设计不当或缺少任何文档时。 这就是为什么我决定四处寻找可以为集成编程人员和其…

A customized combobox with JQuery

要求实现一个轻量级的在客户端筛选的combobox&#xff0c;支持大数据量&#xff08;超过1000个items&#xff09;&#xff0c;能快速检索内容&#xff0c;并支持数据的设置和活动等基本操作。在这之前尝试过使用Jquery UI的Autocomplete&#xff0c;但是当数据量太大时客户端检…

使用内存回流的方法来实现将image的内容转换为 byte[]

在今天的开发中老大不知道怎么突发奇想&#xff0c;要使用Image的Byte数据。当时使用老几种方式效果均不理想&#xff0c;最后发现其实可以使用内存回流的方式来实现。多的不说老&#xff0c;马上贴上代码&#xff1a;/**//// <summary> /// 将byte[]转换为Image…

TypeScript中的class声明了什么

在初看TypeScript的时候在这里卡住的时间难以估计&#xff0c;并不能很好的理解”换个角度说&#xff0c;我们可以认为类具有 实例部分与 静态部分这两个部分。“这句话。今天再回头看这部分文档&#xff0c;在同事的帮助下突然有了比较通透的理解。 class Greeter {static st…

CentOS 6下搭建Apache+MySQL+PHP+SSL

网上的一些文章都已经比较老了&#xff0c;现在版本高了之后&#xff0c;其实配置是很省力的&#xff08;不考虑什么负载的话&#xff09; 分享全过程&#xff0c;出了文中提到的安装epel rpmfushion 源指令不同外&#xff0c;其他的过程也适用与Centos 5 1.安装CentOS 6 ,可以…