阅读react-redux源码 - 一

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现

阅读react-redux源码零中准备了一些react、redux和react-redux的基础知识。从使用的例子中可以看出来顶层的代码中需要用一个来自react-redux的Provider组件提供redux的store,然后Provider的后代组件通过connect组件连接自己的业务组件就可以获取到通过Provider组件跨组件传递过来的store中的state值。

所以我们先从Provider开始看源码的实现。因为源码很短,直接先贴出来整个源码如下:

import React, { useMemo, useEffect } from 'react'
import PropTypes from 'prop-types'
import { ReactReduxContext } from './Context'
import Subscription from '../utils/Subscription'function Provider({ store, context, children }) {const contextValue = useMemo(() => {const subscription = new Subscription(store)subscription.onStateChange = subscription.notifyNestedSubsreturn {store,subscription}}, [store])const previousState = useMemo(() => store.getState(), [store])useEffect(() => {const { subscription } = contextValuesubscription.trySubscribe()if (previousState !== store.getState()) {subscription.notifyNestedSubs()}return () => {subscription.tryUnsubscribe()subscription.onStateChange = null}}, [contextValue, previousState])const Context = context || ReactReduxContextreturn <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

可以看到在顶部引入了 ReactReduxContext 这个Context也很简单就是一个React.createContext创建出来的context,用于跨层级向后代组件提供 contextValue。这个contextValue将在下面被定义。

再下面引人注意的就是 Subscription 这个函数,从名字上可以看出是一个实现发布订阅的类。这将是本文的重点。

再往下就定义了我们今天的主角 Provider组件。

Provider组件指接收三个props,分别为store、context和children。而它的返回值为:

<Context.Provider value={contextValue}>{children}
</Context.Provider>

这个组件是React的context的Provider,用于向后代组件跨层级传递值,这里的值就是contextValue。

获取该值的方法就是通过<Context.Consumer>{contextValue => null}</Context.Consumer>或者使用hooks的useContext也可以拿到contextValue

contextValue

const contextValue = useMemo(() => {const subscription = new Subscription(store)subscription.onStateChange = subscription.notifyNestedSubsreturn {store,subscription}}, [store])

contextValue的值只依赖store,如果store没变那么contextValue的值则不会变。

可以看出来contextValue是一个对象其中有store和一个subscription对象。subscription对象是一个监听器(监听到某些事件然后通知自己的监听者),监听store中的state的变化,只要state变化了那么subscription对象的onStateChange则会执行,由此可见onStateChange这个名字也是很能说明这个方法是做什么的,就是监听store的state改变事件。

subscription.onStateChange = subscription.notifyNestedSubs这样就表示state发生变化则subscription.notifyNestedSubs则会被调用,用来通知自身的监听者。

再下面得到 contextValue 的值为 {store, subscription}。

const previousState = useMemo(() => store.getState(), [store])

暂存一下首次进来的state。(暂时只知道这里做了什么,但是为什么这么做还不是很清楚)

useEffect(() => {const { subscription } = contextValuesubscription.trySubscribe()if (previousState !== store.getState()) {subscription.notifyNestedSubs()}return () => {subscription.tryUnsubscribe()subscription.onStateChange = null}}, [contextValue, previousState])

一个执行副作用的钩子,执行subscription.trySubscribe()尝试监听某个对象,当前上下文中是store中的state的改变。之后返回一个函数用于在卸载的时候做一些清理工作,例如卸载监听和去除onStateChange的关联。

const Context = context || ReactReduxContext

获取Context,这个Context可以不用react-redux提供的默认Context也可以自己提供context。

return <Context.Provider value={contextValue}>{children}</Context.Provider>

返回Context.Provider包裹的组件。被包裹的组件可以通过对应的Context来获取被传入的value(contextValue)。

小结

组件Provider中一共使用了三个生命周期,useMemo、useMemo和useEffect,这三个生命周期都是直接或者间接监听store的改变。所以可以看出来这些逻辑是为了处理在运行过程中Provider的父组件改变store的行为。在父组件改变store的时候可以及时卸载旧store上的监听设置新store的监听,并且通知后代组件有新的state产生。

./utils/Subscription.js

在组件Provider中使用Subscription类的实例来监听store中state的改变,并且通知改变给自己的监听者。

Subscription的源码如下:

import { getBatch } from './batch'// encapsulates the subscription logic for connecting a component to the redux store, as
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendantsconst nullListeners = { notify() {} }function createListenerCollection() {const batch = getBatch()let first = nulllet last = nullreturn {clear() {first = nulllast = null},notify() {batch(() => {let listener = firstwhile (listener) {listener.callback()listener = listener.next}})},get() {let listeners = []let listener = firstwhile (listener) {listeners.push(listener)listener = listener.next}return listeners},subscribe(callback) {let isSubscribed = truelet listener = (last = {callback,next: null,prev: last})if (listener.prev) {listener.prev.next = listener} else {first = listener}return function unsubscribe() {if (!isSubscribed || first === null) returnisSubscribed = falseif (listener.next) {listener.next.prev = listener.prev} else {last = listener.prev}if (listener.prev) {listener.prev.next = listener.next} else {first = listener.next}}}}
}export default class Subscription {constructor(store, parentSub) {this.store = storethis.parentSub = parentSubthis.unsubscribe = nullthis.listeners = nullListenersthis.handleChangeWrapper = this.handleChangeWrapper.bind(this)}addNestedSub(listener) {this.trySubscribe()return this.listeners.subscribe(listener)}notifyNestedSubs() {this.listeners.notify()}handleChangeWrapper() {if (this.onStateChange) {this.onStateChange()}}isSubscribed() {return Boolean(this.unsubscribe)}trySubscribe() {if (!this.unsubscribe) {this.unsubscribe = this.parentSub? this.parentSub.addNestedSub(this.handleChangeWrapper): this.store.subscribe(this.handleChangeWrapper)this.listeners = createListenerCollection()}}tryUnsubscribe() {if (this.unsubscribe) {this.unsubscribe()this.unsubscribe = nullthis.listeners.clear()this.listeners = nullListeners}}
}

整个Subscription实现了一个事件链:

------ subscriptionA ------ subscriptionB\ ------ subscriptionC
-------------- |---------------------|---------------------------- |
-------------- | - listenerA1 — | - listenerB1 ----------- | - listenerB1
-------------- | - listenerA2 — | - listenerB2 ----------- | - listenerC2
-------------- | - listenerA3 — | - listenerB3 ----------- | - listenerC3

​使用Subscription实现上面的事件链:

const eventOrigin = {listeners: [],subscribe(fn) {eventOrigin.listeners.push(fn)return function() {eventOrigin.listeners = eventOrigin.listeners.filter(item => item !== fn)}},notify() {let i = 0while (i < eventOrigin.listeners.length) eventOrigin.listeners[i++]()}
}const subscriptionA = new Subscription(eventOrigin)
subscriptionA.onStateChange = subscriptionA.notifyNestedSubs
subscriptionA.trySubscribe()
subscriptionA.addNestedSub(function listenerA1() {console.log('listenerA1')
})
subscriptionA.addNestedSub(function listenerA2() {console.log('listenerA2')
})
subscriptionA.addNestedSub(function listenerA3() {console.log('listenerA3')
})const subscriptionB = new Subscription(undefined, subscriptionA)
subscriptionB.onStateChange = subscriptionB.notifyNestedSubs
subscriptionB.trySubscribe()
subscriptionB.addNestedSub(function listenerA1() {console.log('listenerB1')
})
subscriptionB.addNestedSub(function listenerA2() {console.log('listenerB2')
})
subscriptionB.addNestedSub(function listenerA3() {console.log('listenerB3')
})const subscriptionC = new Subscription(undefined, subscriptionB)
subscriptionC.onStateChange = subscriptionC.notifyNestedSubs
subscriptionC.trySubscribe()
subscriptionC.addNestedSub(function listenerA1() {console.log('listenerC1')
})
subscriptionC.addNestedSub(function listenerA2() {console.log('listenerC2')
})
subscriptionC.addNestedSub(function listenerA3() {console.log('listenerC3')
})// 测试,触发事件源的notify
eventOrigin.notify()

打印出如下结果:

listenerA1
listenerA2
listenerA3
listenerB1
listenerB2
listenerB3
listenerC1
listenerC2
listenerC3

每个subscription实例就是一个事件的节点,每个节点上面有很多事件的监听器,事件沿着subscription实例组成的链传递,挨个通知每个subscription节点,然后subscription节点的事件监听器监听到事件之后挨个执行回调。

可以想象成DOM的原生事件,事件沿着DOM传递,每个DOM上可以addEventListener多个事件回调。

createListenerCollection

其中用到的函数createListenerCollection创建的对象也是一个监听器,监听某个事件发生通知自己的监听者。

createListenerCollection返回的是一个双向链表,这个数据结构方便修改,删除某一项十分快捷,不需要遍历链表中的每一个,直接将上一个的next指针指向下一个就完成了自身的删除。源码如下:

import { getBatch } from './batch'// encapsulates the subscription logic for connecting a component to the redux store, as
// well as nesting subscriptions of descendant components, so that we can ensure the
// ancestor components re-render before descendantsconst nullListeners = { notify() {} }function createListenerCollection() {const batch = getBatch()let first = nulllet last = nullreturn {clear() {first = nulllast = null},notify() {batch(() => {let listener = firstwhile (listener) {listener.callback()listener = listener.next}})},get() {let listeners = []let listener = firstwhile (listener) {listeners.push(listener)listener = listener.next}return listeners},subscribe(callback) {let isSubscribed = truelet listener = (last = {callback,next: null,prev: last})if (listener.prev) {listener.prev.next = listener} else {first = listener}return function unsubscribe() {if (!isSubscribed || first === null) returnisSubscribed = falseif (listener.next) {listener.next.prev = listener.prev} else {last = listener.prev}if (listener.prev) {listener.prev.next = listener.next} else {first = listener.next}}}}
}
const batch = getBatch()

这个batch表示的是批量更新的意思。这个是react的知识点,简单描述已备忘。

react自身发起的更新是默认批量的。例如onClick函数里setState两次只会引起一次render,componentDidMount里面setState两次也只会引起一次render,但是setTimeout里面两次setState就会引起两次跟新,想要避免这个事情就可以用batch来规避。

原理就类似于react自己张开了一个袋子,需要更新的组件都会被收到这个袋子里,装完之后一起处理组件的更新。那么为什么setTimeout里面的装不到袋子里里呢?因为setTimeout是异步的并不归react管,不在一个调用栈内。或者说setTimeout的回调函数执行之前张开的袋子已经闭合了。

const batch = getBatch()
let first = null
let last = null

首先拿到batch函数,定义头指针和尾指针,这是一个双向链表,和单链表不同,不仅可以从first到last遍历,也可以从last到first遍历。

并且删除双向链表中的一个节点也十分方便,可以将当前节点的上一个节点的next指针指向当前节点的下一个节点就直接将当前节点删除了。如果是单链表需要从头开始遍历链表才可以。从空间上来说链表不需要连续的内存空间,相较于数组这方面也是更加灵活。

return {// 清理当前所有监听者clear() {},// 通知监听者事件发生notify() {},// 返回所有监听者组成的数组get() {},// 设置监听者subscribe(callback) {}
}
clear() {first = nulllast = null
}

清除的时候直接将first和last设置为null这个链表没有被引用,自然就会被垃圾回收机制回收掉。

notify() {batch(() => {let listener = firstwhile (listener) {listener.callback()listener = listener.next}})
}

从第一个节点开始遍历这个链表,执行每个节点上的存储的回调函数。

get() {let listeners = []let listener = firstwhile (listener) {listeners.push(listener)listener = listener.next}return listeners
}

将双向链表转换成数组返回出去。

subscribe

设置事件发生的回调函数,notify通知的就是在这里subscribe的回调函数,这个方法相对复杂点,整体来说做了两件事情:

一件是将入参callback作为一个节点添加到双向链表中,以便notify的时候可以通知到

一件是返回一个函数用于在链表中删除该节点

subscribe(callback) {let isSubscribed = truelet listener = (last = {callback,next: null,prev: last})if (listener.prev) {listener.prev.next = listener} else {first = listener}return function unsubscribe() {if (!isSubscribed || first === null) returnisSubscribed = falseif (listener.next) {listener.next.prev = listener.prev} else {last = listener.prev}if (listener.prev) {listener.prev.next = listener.next} else {first = listener.next}}
}

一个标识符表示callback是否在链表中,为了防止返回的卸载监听的函数多次被调用:

let isSubscribed = true
let listener = (last = {callback,next: null,prev: last
})

定义链表中当前节点的值。从右往左看,新加进去的节点一定是最后一个节点,所以新加入节点的上一个节点一定是当前的last节点,所以prev的值是last。

而新加入节点的next值就是null了。

if (listener.prev) {listener.prev.next = listener
} else {first = listener
}

如果有上一个节点,表示新加入的节点不是第一个,也就是加入前的last节点不是null。新加入的节点是第一个就需要将当前节点作为头结点赋值给first变量,以便随时可以从头开始遍历链表。

return function unsubscribe() {if (!isSubscribed || first === null) returnisSubscribed = falseif (listener.next) {listener.next.prev = listener.prev} else {last = listener.prev}if (listener.prev) {listener.prev.next = listener.next} else {first = listener.next}
}

可以改变链表的不仅仅是被返回的函数,还有一个clear方法,会删除整个链表,所以在删除节点的时候首先要检查下这个链表是否还存在,然后看下当前节点是否还存在,如果链表存在并且还在监听中,那么可以执行卸载流程了。

首先修改刚才的订阅标识符,修改标识符为未订阅,因为马上要卸载了。

如果当前节点有下一个节点,就将当前节点的下一个节点的prev指针指向当前节点的prev。

如果当前节点没有下一个几点,直接将闭包中的last指向当前节点的prev,就删除了当前节点。

还需要改下节点的next指针,因为是双向链表。

注:subscribe函数设置监听的同时还会返回一个卸载监听的函数,这种风格和redux的store的subscribe的风格如出一辙。

小结

createListenerCollection()返回一个对象,这个对象有方法subscribe和notify,一个用来订阅事件的发生,一个用来通知订阅的回调事件发生了,内部的实现则是通过双向链表来完成的。

class Subscription

export default class Subscription {constructor(store, parentSub) {this.store = storethis.parentSub = parentSubthis.unsubscribe = nullthis.listeners = nullListenersthis.handleChangeWrapper = this.handleChangeWrapper.bind(this)}addNestedSub(listener) {this.trySubscribe()return this.listeners.subscribe(listener)}notifyNestedSubs() {this.listeners.notify()}handleChangeWrapper() {if (this.onStateChange) {this.onStateChange()}}isSubscribed() {return Boolean(this.unsubscribe)}trySubscribe() {if (!this.unsubscribe) {this.unsubscribe = this.parentSub? this.parentSub.addNestedSub(this.handleChangeWrapper): this.store.subscribe(this.handleChangeWrapper)this.listeners = createListenerCollection()}}tryUnsubscribe() {if (this.unsubscribe) {this.unsubscribe()this.unsubscribe = nullthis.listeners.clear()this.listeners = nullListeners}}
}

使用案例:


const subscriptionB = new Subscription(undefined, subscriptionA)
subscriptionB.onStateChange = subscriptionB.notifyNestedSubs
subscriptionB.trySubscribe()
subscriptionB.addNestedSub(function listenerA1() {console.log('listenerB1')
})
subscriptionB.addNestedSub(function listenerA2() {console.log('listenerB2')
})
subscriptionB.addNestedSub(function listenerA3() {console.log('listenerB3')
})

这个类做的事情在上面大体上说过,类似于浏览器的时间冒泡,现在看下具体是怎么实现的。


constructor(store, parentSub) {this.store = storethis.parentSub = parentSubthis.unsubscribe = nullthis.listeners = nullListenersthis.handleChangeWrapper = this.handleChangeWrapper.bind(this)
}

Subscription的构造函数有两个入参,一个是store,一个是parentSub是同一个类的不同实例。

上面在使用这个类的时候,new了之后紧接着会关联实例的onStateChange到notifyNestedSubs中去,表示onStateChange执行的时候,实际上执行的是notifyNestedSubs。

紧接着调用实例trySubscribe方法,尝试订阅:

trySubscribe() {if (!this.unsubscribe) {this.unsubscribe = this.parentSub? this.parentSub.addNestedSub(this.handleChangeWrapper): this.store.subscribe(this.handleChangeWrapper)this.listeners = createListenerCollection()}
}

如果没有订阅,那么就去订阅事件。订阅的时候以parentSub优先,如果没有提供parentSub,那么就订阅store的事件。

parentSub和subscribe方法有同样的签名,需要一个入参函数,会返回一个取消订阅的函数。

返回的取消订阅的函数在Subscription会被用作是否订阅事件的标识符。

调用createListenerCollection初始化字段listeners,后面Subscription实例的监听函数都会被委托到listeners上。

handleChangeWrapper() {if (this.onStateChange) {this.onStateChange()}
}

毕竟onStateChange到notifyNestedSubs的关联是调用方手动关联的,如果没有关联的话直接调用会报错,为了不报错,做一次检查也是有必要的。

为什么要onStateChange = notifyNestedSubs?做一次关联?主观感觉应该是语义上的考虑。

这个类实例化出来的对象主要是监听store中state的改变的,所以对外onStateChange这个名字一听就懂。但是对内的话,其实响应事件之后是要通知自身的监听者,所以是notifyNestedSubs。

addNestedSub(listener) {this.trySubscribe()return this.listeners.subscribe(listener)
}

添加Subscription实例的监听函数,被委托给listeners。

notifyNestedSubs() {this.listeners.notify()
}

通知自身的订阅者们,事件发生了,你们要做点什么了。

总结

Provider组件跨组件向后代组件(主要是后面要提到的connect)提供了一个contextValue对象,其中包括了Subscription类的实例和store自身,其中Subscription的实例在监听到store中state的变化的时候就会通知自身的监听者,store的state变化了你们需要重新store.getState()重新渲染组件了。

  • 阅读react-redux源码 - 零
  • 阅读react-redux源码 - 一
  • 阅读react-redux源码(二) - createConnect、match函数的实现

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

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

相关文章

【K8S in Action】服务:让客户端发现pod 并与之通信(2)

一 通过Ingress暴露服务 Ingress (名词&#xff09; 一一进入或进入的行为&#xff1b;进入的权利&#xff1b;进入的手段或地点&#xff1b;入口。一个重要的原因是每个 LoadBalancer 服务都需要自己的负载均衡器&#xff0c; 以及 独有的公有 IP 地址&#xff0c; 而 Ingres…

事件绑定on与hover事件

今天项目中UI设计了一个鼠标划入和划出的效果&#xff0c;本来这个小效果是非常简单的&#xff01;可是在实际的生产环境中就出现了一点点问题&#xff01;因为在实际的环境中&#xff0c;数据全部是用ajax异步加载进去的&#xff0c;这样就造成了hover方法不能用了。先看一下原…

Java EE + MongoDb与Apache TomEE和Jongo Starter项目

知道MongoDB和Java EE &#xff0c;但是您不知道如何将两者集成在一起&#xff1f; 您是否阅读了很多有关该主题的内容&#xff0c;但没有找到适合该目的的解决方案&#xff1f; 这个入门项目适合您&#xff1a; 您将学习如何以一种时尚的方式使用MongoDB和Java EE &#xff0…

hdu 3831

神题&#xff0c;经典dp 关键是状态的表示。 f[i][j][k] 原串后i个字符&#xff0c;与目标后j个字符做匹配&#xff0c;在这之前最近一次发生的后缀操作为“置k”&#xff0c;k52时表示不置后缀 转载于:https://www.cnblogs.com/zhaozhe/archive/2011/08/26/2154684.html

1017 A除以B (20 分)

本题要求计算 /&#xff0c;其中 A 是不超过 1000 位的正整数&#xff0c;B 是 1 位正整数。你需要输出商数 Q 和余数 R&#xff0c;使得 ABQR 成立。 输入格式&#xff1a; 输入在一行中依次给出 A 和 B&#xff0c;中间以 1 空格分隔。 输出格式&#xff1a; 在一行中依次输出…

阅读react-redux源码(二) - createConnect、match函数的实现

阅读react-redux源码 - 零阅读react-redux源码 - 一阅读react-redux源码(二) - createConnect、match函数的实现 上一节看了Provider组件的实现&#xff0c;主要做的事情就是通过Context透传了来自redux的store和监听store变化的事件对象Subscription的实例。 本节会深入到co…

一个罐子统治一切:Apache TomEE + Shrinkwrap == JavaEE引导

警告&#xff1a;我不是Spring Boot的专家。 我发现很多事情对此非常有趣&#xff0c;并且当然可以真正改善您的日常工作。 而且&#xff0c;我对Spring Boot没有任何反对&#xff0c;也没有开发或使用它的人。 但是我认为社区高估了该产品。 一年前&#xff0c;我开始收到很多…

iview-admin框架运行步骤

第一步&#xff1a; 前往github下载整个iview-admin框架的全部源码 github地址&#xff1a; https://github.com/iview/iview-admin 第二步&#xff1a; 点击Clone or download绿色按钮。下载整个压缩包 第三步&#xff1a; 解压至D盘&#xff0c;在根目录中按 1、前往github下…

阅读react-redux源码(三) - mapStateToPropsFactories、mapDispatchToPropsFactories和mergePropsFactories

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

Xcode 升级后,常常遇到的遇到的警告、错误,解决方法(转)

从sdk3.2.5升级到sdk 7.1中间废弃了很多的方法&#xff0c;还有一些逻辑关系更加严谨了。1&#xff0c;警告&#xff1a;“xoxoxoxo” is deprecated解决办法&#xff1a;查看xoxoxoxo的这个方法的文档&#xff0c;替换掉这个方法即可。2&#xff0c;警告&#xff1a;Declarat…

.net 垃圾回收学习[How To: Use CLR Profiler][翻译学习]【2】

http://msdn.microsoft.com/zh-cn/library/ms979205 注意&#xff1a;内容可能已经过期了。 注意&#xff1a;CLR Profiler最新版本&#xff1a;http://www.microsoft.com/download/en/details.aspx?id16273 Identifying Common Garbage Collection Issues 可以使用CLR Profil…

JavaOne 2014:会议与合同利益冲突

杜克街咖啡馆&#xff0c;工程师可以在街上进行走廊交谈 。 与签约不兼容 我的第11届JavaOne会议&#xff08;2004年至2014年为11 10 1&#xff09;非常出色。 值得参加此活动并结识社区中所有参与的人。 现在&#xff0c;这里是绅士的&#xff0c;但 。 除了经济上的明显优…

JQuery(三)-- AJAX的深入理解以及JQuery的使用

HTTP HTTP http: 超文本传输协议。特点&#xff1a; 简单、快速、灵活、无状态、无连接 URL&#xff1a; 统一资源定位符。 组成&#xff1a;协议名://主机IP&#xff1a;端口号/项目资源地址&#xff1f;传递参数的键值对#锚点 ①ip地址在同一个网段是唯一的。如果是在公…

阅读react-redux源码(四) - connectAdvanced、wrapWithConnect、ConnectFunction和checkForUpdates

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

(转)模拟鼠标/键盘

鼠标操作类 using System;namespace Edobnet.Net.Lib{/// <summary>/// Mouse 的摘要说明。/// </summary>public class Mouse{public Mouse(){//// TODO: 在此处添加构造函数逻辑//}internal const byte SM_MOUSEPRESENT 19;internal const byte SM_CMOUSEBUTTON…

c++ 返回 char*

一段在C里经常犯错误的代码 一个类&#xff1a; class C{public:C(){}~C(){}public:string a;string funa(){string tmp "1234";return tmp;}};外部调用类C并使用其成员&#xff1a; C classc;char *test1 classc.a.c_str();printf("%s\n", test1);上述正…

JSF的工作方式和调试方式–可以使用polyglot吗?

JSF不是我们通常认为的那样。 这也是一个调试起来可能有些棘手的框架&#xff0c;尤其是在初次遇到时。 在这篇文章中&#xff0c;让我们继续探讨为什么会出现这种情况&#xff0c;并提供一些JSF调试技术。 我们将讨论以下主题&#xff1a; JSF不是我们经常想到的 JSF调试的难…

React组件实现越级传递属性

如果有这样一个结构&#xff1a;三级嵌套&#xff0c;分别是&#xff1a;一级父组件、二级子组件、三级孙子组件&#xff0c;且前者包含后者&#xff0c;结构如图&#xff1a; 如果把一个属性&#xff0c;比如color&#xff0c;从一级传递给三级&#xff0c;一般做法是使用prop…

尝试Office 2003 VSTO的开发、部署

背景&#xff1a;一年前&#xff0c;某项目需要使用到Excel进行数据录入&#xff0c;考虑到很多用户还是使用XPOffice 2003&#xff0c;所以开发的时候直接使用Excel 2003版本进行VBA开发。也许很多人都会说&#xff0c;Win10都出了&#xff0c;微软的Office都要免费了&#xf…

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

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