React16源码: React中event事件触发的源码实现

event 事件触发过程


1 )概述

  • 在之前事件绑定时,绑定的是两个方法
    • 一个是 dispatchInteractiveEvent
    • 另外一个 dispatchEvent
  • 其实它们调用的方法都是差不多的,一开始会有一点小的区别

2 )源码

定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L165

进入 trapCapturedEvent

export function trapCapturedEvent(topLevelType: DOMTopLevelEventType,element: Document | Element,
) {if (!element) {return null;}// 注意这里,根据是否是 Interactive 类型的事件,调用的不同的回调,最终赋值给 dispatchconst dispatch = isInteractiveTopLevelEventType(topLevelType)? dispatchInteractiveEvent: dispatchEvent;addEventCaptureListener(element,getRawEventName(topLevelType),// Check if interactive and wrap in interactiveUpdates// 这边的topleveltype呢,是我们在进行 dom 的事件绑定的时候已经通过 bind 给它绑定好了// 在绑定事件的时候,就已经确定了一个值, 比如说是onChange这类的toplevel的事件名称dispatch.bind(null, topLevelType),);
}

2.1 先看 dispatchInteractiveEvent

定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L184

// packages/react-dom/src/events/ReactDOMEventListener.js#L184
// nativeEvent,就是我们事件触发的时候,我们的domm的事件体系会给我们一个event对象
// 可以通过它来进入默认行为之类的这么一个事件对象
function dispatchInteractiveEvent(topLevelType, nativeEvent) {interactiveUpdates(dispatchEvent, topLevelType, nativeEvent);
}// packages/events/ReactGenericBatching.js#L55
export function interactiveUpdates(fn, a, b) {return _interactiveUpdatesImpl(fn, a, b);
}// packages/events/ReactGenericBatching.js#L23
let _interactiveUpdatesImpl = function(fn, a, b) {return fn(a, b);
};
  • 可见 dispatchInteractiveEvent 最终还是要调用 dispatchEvent, 只是多包了一层

2.2 进入 dispatchEvent

定位到 packages/react-dom/src/events/ReactDOMEventListener.js#L188

export function dispatchEvent(topLevelType: DOMTopLevelEventType,nativeEvent: AnyNativeEvent,
) {if (!_enabled) {return;}// 注意这里const nativeEventTarget = getEventTarget(nativeEvent);let targetInst = getClosestInstanceFromNode(nativeEventTarget);// 存在 符合条件的,并且没有被挂载if (targetInst !== null &&typeof targetInst.tag === 'number' &&!isFiberMounted(targetInst)) {// If we get an event (ex: img onload) before committing that// component's mount, ignore it for now (that is, treat it as if it was an// event on a non-React tree). We might also consider queueing events and// dispatching them after the mount.targetInst = null; // 这个 target 置空}// 这里只是一个对象,用于携带信息const bookKeeping = getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst,);try {// Event queue being processed in the same cycle allows// `preventDefault`.batchedUpdates(handleTopLevel, bookKeeping);} finally {releaseTopLevelCallbackBookKeeping(bookKeeping);}
}
  • 首先它要获取 nativeEventTarget,这个 target 就是我们 event 对象上面的 target

  • 它是出于对各种系统的一个兼容调用的一个方法来进行一个 Polyfill

  • 进入这个 getEventTarget 方法

    import {TEXT_NODE} from '../shared/HTMLNodeType';/*** Gets the target node from a native browser event by accounting for* inconsistencies in browser DOM APIs.** @param {object} nativeEvent Native browser event.* @return {DOMEventTarget} Target node.*/
    function getEventTarget(nativeEvent) {// 这个是对 IE9 的兼容// Fallback to nativeEvent.srcElement for IE9// https://github.com/facebook/react/issues/12506let target = nativeEvent.target || nativeEvent.srcElement || window;// Normalize SVG <use> element events #4963if (target.correspondingUseElement) {target = target.correspondingUseElement;}// Safari may fire events on text nodes (Node.TEXT_NODE is 3).// @see http://www.quirksmode.org/js/events_properties.htmlreturn target.nodeType === TEXT_NODE ? target.parentNode : target;
    }
    
    • 这个方法主要是为了兼容浏览器API获取 target 对象, 最终根据是否是 TEXT_NODE 返回 target.parentNode 或 target
  • 进入 getClosestInstanceFromNode

    export function getClosestInstanceFromNode(node) {// 存在,直接 returnif (node[internalInstanceKey]) {return node[internalInstanceKey]; // 这个就是 初始化dom节点的时候,在node上插入这么一个key, 来指定对应的 fiber 对象}// 不存在,一直找 parentNodewhile (!node[internalInstanceKey]) {if (node.parentNode) {node = node.parentNode;} else {// Top of the tree. This node must not be part of a React tree (or is// unmounted, potentially).return null;}}// 这个 inst 就是一个 fiber 对象, 找到是 HostComponent 或 HostText 类型的let inst = node[internalInstanceKey];if (inst.tag === HostComponent || inst.tag === HostText) {// In Fiber, this will always be the deepest root.return inst;}return null;
    }
    
  • 进入 isFiberMounted

    const MOUNTING = 1;
    const MOUNTED = 2;
    const UNMOUNTED = 3;function isFiberMountedImpl(fiber: Fiber): number {let node = fiber;// 不存在,说明是即将插入或没有插入的节点if (!fiber.alternate) {// If there is no alternate, this might be a new tree that isn't inserted// yet. If it is, then it will have a pending insertion effect on it.// 这种是即将要插入的if ((node.effectTag & Placement) !== NoEffect) {return MOUNTING;}// 如果当前不是,向上找父节点,也是即将插入的while (node.return) {node = node.return;if ((node.effectTag & Placement) !== NoEffect) {return MOUNTING;}}} else {// 存在 alternate, 继续向上找while (node.return) {node = node.return;}}// 如果上级存在,并且是 HostRoot 说明已经被挂载了if (node.tag === HostRoot) {// TODO: Check if this was a nested HostRoot when used with// renderContainerIntoSubtree.return MOUNTED;}// If we didn't hit the root, that means that we're in an disconnected tree// that has been unmounted.// 其他情况都是未挂载的return UNMOUNTED;
    }export function isFiberMounted(fiber: Fiber): boolean {return isFiberMountedImpl(fiber) === MOUNTED;
    }
    
  • 进入 getTopLevelCallbackBookKeeping

    // packages/react-dom/src/events/ReactDOMEventListener.js#L50
    const CALLBACK_BOOKKEEPING_POOL_SIZE = 10;
    const callbackBookkeepingPool = [];// Used to store ancestor hierarchy in top level callback
    function getTopLevelCallbackBookKeeping(topLevelType,nativeEvent,targetInst,
    ): {topLevelType: ?DOMTopLevelEventType,nativeEvent: ?AnyNativeEvent,targetInst: Fiber | null,ancestors: Array<Fiber>,
    } {if (callbackBookkeepingPool.length) {const instance = callbackBookkeepingPool.pop();instance.topLevelType = topLevelType;instance.nativeEvent = nativeEvent;instance.targetInst = targetInst;return instance;}return {topLevelType,nativeEvent,targetInst,ancestors: [],};
    }
    
    • 这个函数就是一缓存机制
    • 最终我们在执行完整个react事件之后,会把这个对象再归还到这个pool里面来进行一个存储
    • 让浏览器的js运行环境不去删除这个对象,也让我们在后期不需要去重新创建一个对象,
    • 以减少这个对象声明和对象垃圾回收的一个性能开销
  • 进入 batchedUpdates

    // packages/events/ReactGenericBatching.js#L29
    let _batchedUpdatesImpl = function(fn, bookkeeping) {return fn(bookkeeping);
    };
    let isBatching = false;
    export function batchedUpdates(fn, bookkeeping) {if (isBatching) {// If we are currently inside another batch, we need to wait until it// fully completes before restoring state.return fn(bookkeeping);}isBatching = true;try {return _batchedUpdatesImpl(fn, bookkeeping);} finally {// Here we wait until all updates have propagated, which is important// when using controlled components within layers:// https://github.com/facebook/react/issues/1698// Then we restore state of any controlled component.isBatching = false;// 下面的代码其实就跟 input 控制输入 有关的// 我们知道在 react 当中我们通过 value 给 input 标签上面去绑定了值,直接在外部输入的值// 如果没有 onChange 事件来处理这个 state,它这个值输不进去的,这就是 input的控制输入 的一个概念// 其控制就是在这里实现的const controlledComponentsHavePendingUpdates = needsStateRestore();if (controlledComponentsHavePendingUpdates) {// If a controlled event was fired, we may need to restore the state of// the DOM node back to the controlled value. This is necessary when React// bails out of the update without touching the DOM._flushInteractiveUpdatesImpl();restoreStateIfNeeded();}}
    }// packages/events/ReactControlledComponent.js#L58
    export function needsStateRestore(): boolean {return restoreTarget !== null || restoreQueue !== null;
    }export function restoreStateIfNeeded() {if (!restoreTarget) {return;}const target = restoreTarget;const queuedTargets = restoreQueue;restoreTarget = null;restoreQueue = null;restoreStateOfTarget(target);if (queuedTargets) {for (let i = 0; i < queuedTargets.length; i++) {restoreStateOfTarget(queuedTargets[i]);}}
    }
    
  • batchedUpdates 里面调用的方法是 handleTopLevel

    function handleTopLevel(bookKeeping) {let targetInst = bookKeeping.targetInst;// Loop through the hierarchy, in case there's any nested components.// It's important that we build the array of ancestors before calling any// event handlers, because event handlers can modify the DOM, leading to// inconsistencies with ReactMount's node cache. See #1105.let ancestor = targetInst;do {// 如果不存在,就push,并且跳出循环if (!ancestor) {bookKeeping.ancestors.push(ancestor);break;}// 如果存在了,找到 Root 挂载节点const root = findRootContainerNode(ancestor);// 不存在挂载节点,则跳出if (!root) {break;}// 存在挂载节点bookKeeping.ancestors.push(ancestor);// 这个方法之前已经分析了,找到存储的 fiber 对象// 在这里, 这个时候传入了是这个root,对于大部分情况来讲, HostRoot 已经没有上级的节点,会是处于react的一个应用当中// 也可能是会有这种情况的, 比如通过一些比较 hack 的一些方式, 我们在react应用里面再去渲染了一个新的react应用// 这种方法也可能是存在的,所以在这边就尝试了这么去做// 因为它们两个如果确实出现这种情况,那么它们的root节点是不一样的// 事件正常来讲是要冒泡到最外层的那个root树的最顶上的// 所以这种情况需要去调用这个方法,去把所有的 ancestor 给找到,并且推到这个 bookKeeping.ancestors 里面ancestor = getClosestInstanceFromNode(root); } while (ancestor);// 对于每一个 concest,去获取它的 targetInst 对大部分情况, 其实就是我们这个 targetInst // 就是我们这个事件触发的那一个节点 event.target 对应的那个fiber对象for (let i = 0; i < bookKeeping.ancestors.length; i++) {targetInst = bookKeeping.ancestors[i];runExtractedEventsInBatch(bookKeeping.topLevelType,targetInst,bookKeeping.nativeEvent,getEventTarget(bookKeeping.nativeEvent),);}
    }
    
    • 进入 findRootContainerNode
      function findRootContainerNode(inst) {// TODO: It may be a good idea to cache this to prevent unnecessary DOM// traversal, but caching is difficult to do correctly without using a// mutation observer to listen for all DOM changes.// 向上找while (inst.return) {inst = inst.return;}// 如果不是 HostRoot 则失败if (inst.tag !== HostRoot) {// This can happen if we're in a detached tree.return null;}// 找到 react 应用挂载的 dom节点return inst.stateNode.containerInfo;
      }
      
    • 进入 runExtractedEventsInBatch
      export function runExtractedEventsInBatch(topLevelType: TopLevelType,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: EventTarget,
      ) {// 获取所有事件const events = extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget,);runEventsInBatch(events);
      }// 生成事件对象
      function extractEvents(topLevelType: TopLevelType,targetInst: null | Fiber,nativeEvent: AnyNativeEvent,nativeEventTarget: EventTarget,
      ): Array<ReactSyntheticEvent> | ReactSyntheticEvent | null {let events = null;// 调用每一个plugin, 在内部,调用 possiblePlugin.extractEventsfor (let i = 0; i < plugins.length; i++) {// Not every plugin in the ordering may be loaded at runtime.const possiblePlugin: PluginModule<AnyNativeEvent> = plugins[i];if (possiblePlugin) {const extractedEvents = possiblePlugin.extractEvents(topLevelType,targetInst,nativeEvent,nativeEventTarget,);// 如果存在,则插入到对象中if (extractedEvents) {events = accumulateInto(events, extractedEvents);}}}return events;
      }let eventQueue: ?(Array<ReactSyntheticEvent> | ReactSyntheticEvent) = null;
      export function runEventsInBatch(events: Array<ReactSyntheticEvent> | ReactSyntheticEvent | null,
      ) {if (events !== null) {eventQueue = accumulateInto(eventQueue, events);}// Set `eventQueue` to null before processing it so that we can tell if more// events get enqueued while processing.const processingEventQueue = eventQueue;eventQueue = null;if (!processingEventQueue) {return;}// 对这个 processingEventQueue, 可能是数组,也可能只有一个event的这个内容// 对它调用了这个 executeDispatchesAndReleaseTopLevel 方法forEachAccumulated(processingEventQueue, executeDispatchesAndReleaseTopLevel);invariant(!eventQueue,'processEventQueue(): Additional events were enqueued while processing ' +'an event queue. Support for this has not yet been implemented.',);// This would be a good time to rethrow if any of the event handlers threw.rethrowCaughtError();
      }
      
      • 进入 accumulateInto
        // 把两个值(数组)合并,形成一个数组
        function accumulateInto<T>(current: ?(Array<T> | T),next: T | Array<T>,
        ): T | Array<T> {invariant(next != null,'accumulateInto(...): Accumulated items must not be null or undefined.',);if (current == null) {return next;}// Both are not empty. Warning: Never call x.concat(y) when you are not// certain that x is an Array (x could be a string with concat method).// 合并两个数组if (Array.isArray(current)) {if (Array.isArray(next)) {current.push.apply(current, next);return current;}current.push(next);return current;}if (Array.isArray(next)) {// A bit too dangerous to mutate `next`.return [current].concat(next);}return [current, next];
        }
        
      • 进入 forEachAccumulated
        // 对于传进来的一个数组,判断它是否是一个数组
        // 如果是数组就会去对每一项调用这个 callback
        // 如果它不是一个数组,就直接调用这个callback传入这个值就可以了
        function forEachAccumulated<T>(arr: ?(Array<T> | T),cb: (elem: T) => void,scope: ?any,
        ) {if (Array.isArray(arr)) {arr.forEach(cb, scope);} else if (arr) {cb.call(scope, arr);}
        }
        
      • 进入 executeDispatchesAndReleaseTopLevel
        const executeDispatchesAndReleaseTopLevel = function(e) {return executeDispatchesAndRelease(e);
        };const executeDispatchesAndRelease = function(event: ReactSyntheticEvent) {if (event) {executeDispatchesInOrder(event);if (!event.isPersistent()) {event.constructor.release(event);}}
        };// packages/events/EventPluginUtils.js#L76
        // 最后真正调用这个事件的地方,其实它里面整个过程会非常的复杂,有各种各样的函数的嵌套调用
        // 其实里面可能有将近一半的函数都是工具类型的函数,注意阅读代码时,别被绕进去
        export function executeDispatchesInOrder(event) {// 这边获取了 dispatchListeners 以及 dispatchInstances 这两个数据// 这两个数据都来自 event 对象,上面会挂载了一个叫 _dispatchListeners 和 _dispatchInstances// 这两个东西都是数组,并且是一一对应的关系const dispatchListeners = event._dispatchListeners;const dispatchInstances = event._dispatchInstances;// 忽略if (__DEV__) {validateEventDispatches(event);}// 然后,它去判断一下是否是一个数组, 如果是一个数组对它进行一个遍历if (Array.isArray(dispatchListeners)) {for (let i = 0; i < dispatchListeners.length; i++) {// 并且判断一下 event 是否已经 isPropagationStopped,就是我们已经停止冒泡了// 如果是的话,我们就直接 breakif (event.isPropagationStopped()) {break;}// Listeners and Instances are two parallel arrays that are always in sync.executeDispatch(event, dispatchListeners[i], dispatchInstances[i]);}// 不是数组,但是存在} else if (dispatchListeners) {// 调用这个方法executeDispatch(event, dispatchListeners, dispatchInstances);}// 重置event._dispatchListeners = null;event._dispatchInstances = null;
        }
        
        • 进入 executeDispatch
          function executeDispatch(event, listener, inst) {const type = event.type || 'unknown-event';event.currentTarget = getNodeFromInstance(inst); // 从 inst 上获取 node 节点invokeGuardedCallbackAndCatchFirstError(type, listener, undefined, event); // 这里回调最终被触发调用event.currentTarget = null;
          }
          
          • 进入 invokeGuardedCallbackAndCatchFirstError
            // packages/shared/ReactErrorUtils.js#L67
            export function invokeGuardedCallbackAndCatchFirstError<A,B,C,D,E,F,Context,
            >(name: string | null,func: (a: A, b: B, c: C, d: D, e: E, f: F) => void,context: Context,a: A,b: B,c: C,d: D,e: E,f: F,
            ): void {// 这个函数之前遇到过,目前不再展开invokeGuardedCallback.apply(this, arguments);if (hasError) {const error = clearCaughtError();if (!hasRethrowError) {hasRethrowError = true;rethrowError = error;}}
            }
            
  • 到这里为止,这边的 listener 就已经被调用了

  • 我们真正的每一个节点上面,如果有绑定这个事件,它就会调用它的一个回调

  • 这就是事件触发的整个流程, 非常的繁琐,有各种各样的方法,嵌套的调用

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

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

相关文章

MySQL 函数参考手册(MySQL 高级函数)

目录 MySQL BIN() 函数 MySQL BINARY 函数 MySQL CASE 函数 MySQL CAST() 函数 MySQL COALESCE() 函数 MySQL CONNECTION_ID() 函数 MySQL CONV() 函数 MySQL CONVERT() 函数 MySQL CURRENT_USER() 函数 MySQL DATABASE() 函数 MySQL IF() 函数 MySQL IFNULL() 函数…

在PostgreSQL中不开归档?恭喜你!锅你背定了

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

k8s中deployment模板

一、清单文件 apiVersion: apps/v1 kind: Deployment metadata:annotations:version: "1.1.6"labels:k8s-app: deployment-nginxname: deployment-nginxnamespace: test spec:minReadySeconds: 30progressDeadlineSeconds: 600replicas: 2revisionHistoryLimit: 5sel…

智能汽车竞赛摄像头处理——摄像头入门(2)

前言 在上一节中&#xff0c;我们学习了如何将总钻风摄像头的图像显示在1.8寸TFT显示屏上&#xff0c;其实我建议大家显示屏还是要选用ips200&#xff0c;像素点多一些&#xff0c;显示的图像更加清晰。 二值化处理 &#xff08;1&#xff09;对原始的灰度图像进行二值化&am…

【操作宝典】IntelliJ IDEA新建maven项目详细教程

目录 &#x1f33c;1. 配置maven环境 &#x1f33c;2. 创建maven项目 &#x1f33c;3. 创建maven项目完整示例 a. 导入spring boot环境 b. 修改maven配置 c. 下载jar包 d. 创建Java类 &#x1f33c;1. 配置maven环境 【安装指南】maven下载、安装与配置详细教程-CSDN博客…

反物质(anti matter)和湮灭反应(Annihilation)浅读

反物质 反物质是正常物质的反状态。当正反物质相遇时&#xff0c;双方就会相互湮灭抵消&#xff0c;发生爆炸并产生巨大能量。 概念 正电子、负质子都是反粒子&#xff0c;它们跟通常所说的电子、质子相比较&#xff0c;电量相等但电性相反。科学家设想在宇宙中可能存在完全由…

【GitHub项目推荐--一个 C++ 实现快速存储的库】【转载】

一个提供可嵌入、持久键值存储以实现快速存储的库。 github地址 https://github.com/facebook/rocksdb 国内镜像 http://www.gitpp.com/ag/rocksdb RocksDB 是一个开源的嵌入式键值存储库&#xff0c;由 Facebook 开发&#xff0c;用于处理大量的数据&#xff0c;特别适合于…

【Eclipse插件开发】2运行时环境Runtime总览-【中篇】

系列文章目录 文章目录 系列文章目录三、并发基础架构并发基础运行作业Job常用作业操作作业的状态作业改变监听器作业管理器工作族关闭前完成作业3.1 报告进度进度报告简述进度监视器和UI系统作业用户作业进度组3.2 作业调度

sql注入绕过原理

了解SQL注入攻击的绕过技术对于加强网络安全具有重要意义。以下是对常见的绕过技术的整理,以及如何防御这些攻击策略。 1. 大小写绕过 原理:如果过滤机制仅识别特定大小写的关键字,攻击者可以通过改变关键字的大小写来尝试绕过过滤。 实验示例: SELECT * FROM users oR…

2024牛客寒假算法基础集训营1

H&#xff1a;01背包&#xff0c;但是bit 这题一看数据范围很大&#xff0c;重量和价值都是1e8级别的&#xff0c;当时还在想是不是背包&#xff0c;原来就是位运算 具体来说&#xff0c;我们枚举m的每一位为1的1&#xff0c;强制这一位为0&#xff0c;这样m被分为前后两部分…

力扣202-快乐数

快乐数 题目链接 解题思路&#xff1a; 两个指针&#xff0c;一快一慢&#xff0c;如果相遇&#xff0c;就会生成环如果环内元素为1,那么就可以返回 class Solution { public:int get(int n){int res 0;while(n){res (n%10) * (n%10);n / 10;}return res;}bool isHappy(int …

docker面试问题二

如何防止Docker容器中的漏洞和攻击&#xff1f; 防止Docker容器中的漏洞和攻击是一个多层次、多方面的任务&#xff0c;涉及从镜像构建、容器运行到网络安全的整个生命周期。以下是一些关键措施&#xff1a; 使用官方和受信任的镜像&#xff1a; 总是从官方源或受信任的第三方…

基于SSM的个性化旅游攻略定制系统设计与实现(有报告)。Javaee项目。ssm项目。

演示视频&#xff1a; 基于SSM的个性化旅游攻略定制系统设计与实现&#xff08;有报告&#xff09;。Javaee项目。ssm项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三层体系结构&#xf…

故障诊断 | 一文解决,RF随机森林的故障诊断(Matlab)

效果一览 文章概述 故障诊断 | 一文解决,RF随机森林的故障诊断(Matlab) 模型描述 随机森林(Random Forest)是一种集成学习(Ensemble Learning)方法,常用于解决分类和回归问题。它由多个决策树组成,每个决策树都独立地对数据进行训练,并且最终的预测结果是由所有决策…

为什么SSL会握手失败?SSL握手失败原因及解决方案

随着网络安全技术的发展&#xff0c;SSL证书作为网站数据安全的第一道防线&#xff0c;被越来越多的企业选择。SSL证书使用的是SSL协议&#xff0c;而SSL握手是SSL协议当中最重要的一部分。当部署SSL证书时&#xff0c;如果服务器和客户端之间无法建立安全连接&#xff0c;就会…

C++学习Day01之using声明以及using编译指令

目录 一、程序1.1 using声明1.2 using声明与就近原则1.3 using编译指令与就近原则1.4 多个using编译指令 二、分析与总结 一、程序 1.1 using声明 #include<iostream> using namespace std;namespace KingGlory {int sunwukongId 1; } void test01() {//1、using声明u…

由数据插入超长引起的问题——了解GaussDB和openGauss的字符集

前言 故事是这样开始的。我们的小DEMO项目的数据库版本从openGauss 2.1.0升级到了5.0.0版本。升级后进行功能验证的时候&#xff0c;测试同学发现个BUG&#xff0c;原来通过gs_restore导出来的数据再导入时报超长&#xff0c;插入失败了&#xff0c;如下图所示&#xff0c;nva…

2024年第十五届电子商务、管理与经济国际会议(ICEME 2024)即将召开!

2024年第十五届电子商务、管理与经济国际会议(ICEME 2024) 将于2024年7月19-21日在北京召开。本次会议将由北京工业大学主办&#xff0c;中国澳门圣若瑟大学提供学术支持。ICEME 2024旨在为来自世界各地的电子商务、管理与经济的研究人员提供一个展示最新研究成果的高质量交流平…

市场复盘总结 20240202

仅用于记录当天的市场情况&#xff0c;用于统计交易策略的适用情况&#xff0c;以便程序回测 短线核心&#xff1a;不参与任何级别的调整&#xff0c;采用龙空龙模式 一支股票 10%的时候可以操作&#xff0c; 90%的时间适合空仓等待 昨日主题投资 连板进级率 6/30 20% 二进三…