【React Hooks原理 - forwardRef、useImperativeHandle】
概述
上文我们聊了useRef
的使用和实现,主要两个用途:1、用于持久化保存 2、用于绑定dom
。 但是有时候我们需要在父组件中访问子组件的dom或者属性/方法,而React中默认是不允许父组件直接访问子组件的dom的
,这时候就可以通过forwardRef将ref传入子组件,并暴露子组件的dom给父组件使用,但是这种方式直接暴露了子组件的dom,处于安全性能考虑,我们希望子组件只暴露我们所希望的属性,由子组件自己决定暴露什么,这个就需要使用到useImperativeHandle来处理。 基于这些使用场景,所以本文主要从基本使用和源码实现两方面来介绍下forwardRef、useImperativeHandle
这两个API。
基本使用
本小节主要介绍这两个API在Function Component中的使用,已经熟悉APi的同学可以跳过该部分,直接查看源码解析部分。
forwardRef
forwardRef主要解决的是从父组件传递ref到子组件的问题,定义如下:
const SomeComponent = forwardRef(render)
- render:组件的渲染函数。React 会调用该函数并传入父组件传递的 props 和 ref。返回的 JSX 将作为组件的输出。
- 返回一个可以在 JSX 中渲染的 React 组件。与作为纯函数定义的 React 组件不同,forwardRef 返回的组件还能够接收 ref 属性。
forwardRef接收一个渲染函数,然后返回一个可在JSX中渲染的组件。一般用于包裹子组件,用于ref传递,将子组件绑定的ref通过第二个参数传入,并绑定到子组件dom节点暴露。可以这样理解forwardRef 是一个接收render函数作为参数的高阶函数
如下demo在子组件中暴露了input
组件,使父组件可以访问并进行例如获取焦点等dom操作
const MyInput = forwardRef(function MyInput(props, ref) {return (<label>{props.label}<input ref={ref} /></label>);
});
进过forwardRef包裹之后,父组件就可以通过ref来访问子组件中暴露的dom节点,但是有时候我们希望自己控制暴露哪些属性,尤其是当作为公共组件被多方调用的时候,这时候就需要通过useImperativeHandle
来实现自定义暴露属性
forwardRef并不是一个Hook,这里主要是作为介绍useImperativeHandle的媒介,通常是将这两个Api连用,所以这里一起简单介绍下。所有的Hook都是用use开头命名的
useImperativeHandle
useImperativeHandle 是 React 中的一个 Hook,它能让你自定义由 ref 暴露出来的句柄。
useImperativeHandle(ref, createHandle, dependencies?)
- ref:该 ref 是你从 forwardRef 渲染函数 中获得的第二个参数。
- createHandle:该函数无需参数,它返回你想要暴露的 ref 的句柄。该句柄可以包含任何类型。通常,你会返回一个包含你想暴露的方法的对象。
- dependencies:可选参数,作为函数 createHandle的依赖收集。React 会使用 Object.is 来比较每一个依赖项与其对应的之前值。如果该数组值发生改变或者数组为空数组,则会重新执行createHandle并将新的对象绑定到ref。
一般是将forwardRef和useImperativeHandle一起用,通过useImperativeHandle暴露指定属性,然后父组件可以通过forwardRef注入的ref来进行访问
。举例来说,假设你不想暴露出整个 DOM 节点,但你想要它其中两个方法:focus 和 scrollIntoView。为此,用单独额外的 ref 来指向真实的浏览器 DOM。然后使用 useImperativeHandle 来暴露一个句柄,它只返回你想要父组件去调用的方法:
import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) {const inputRef = useRef(null);useImperativeHandle(ref, () => {return {focus() {inputRef.current.focus();},scrollIntoView() {inputRef.current.scrollIntoView();},};}, []);return <input {...props} ref={inputRef} />;
});
在上述代码中,该 ref 已不再被转发到<input>
中,而是传入到了useImperativeHandle,可以理解为useImperativeHandle将ref进行了劫持并将暴露的属性绑定到ref.current上
。而且我们也可以自定义返回的数据,比如正常在父组件是通过ref.current
访问,这个可以自定义为ref.xxx
import { forwardRef, useRef, useImperativeHandle } from 'react';const MyInput = forwardRef(function MyInput(props, ref) {const inputRef = useRef(null);useImperativeHandle((createResult) => {ref['xxx'] = createResult;return ref}, () => {return {focus() {inputRef.current.focus();},scrollIntoView() {inputRef.current.scrollIntoView();},};}, []);return <input {...props} ref={inputRef} />;
});
这是由于在源码实现中对于传递对象类型和函数类型的ref处理是不一样的,详情可以查看下面源码解析。
源码解析
上面我们介绍了这两个API的基本语法和使用场景,下面我们将从源码的角度,一步一步分析其内部是如何实现的。
主要涉及文件如下:
- 代码入口文件路径:
https://github.com/facebook/react/blob/main/packages/react/src/ReactHooks.js
- 执行代码文件路径:
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberHooks.js
- forwardRef文件路径:
https://github.com/facebook/react/blob/main/packages/react/src/ReactForwardRef.js
- beginWork函数路径:
https://github.com/facebook/react/blob/main/packages/react-reconciler/src/ReactFiberBeginWork.js
下面的都是基于生产环境下的代码分析,以及会省略与本次解释无关的代码,完整代码可以根据以上路径前往官网github查看。
forwardRef
从代码中能看出,在生产环境下,调用forwardRef包裹render函数之后,会将该render函数打上标签$$typeof
用于React区分当前组件是什么类型并进行不同的处理。
$$typeof: 这个属性是一个符号常量,用来标识这是一个 forwardRef 组件类型。在 React 内部,这个符号被用来区分不同类型的 React 元素,例如函数组件、类组件、片段(fragment)等。REACT_FORWARD_REF_TYPE 的值是一个独特的符号,确保了它在 React 内部可以被正确识别。
export function forwardRef<Props, ElementType: React$ElementType>(render: (props: Props, ref: React$Ref<ElementType>) => React$Node
) {const elementType = {$$typeof: REACT_FORWARD_REF_TYPE,render,};return elementType;
}
当完成标记之后,后续会进入到Recondiler协调器中进行fiber构造,其中会经历beginWork阶段对JSX代码进行处理并生成Fiber节点。在这个阶段会根据tag
来对不同组件进行处理,这里就是ForwardRef
类型
function beginWork(current, workInProgress, renderLanes) {// ...switch (workInProgress.tag) {// ...case ForwardRef:const type = workInProgress.type;const unresolvedProps = workInProgress.pendingProps;const resolvedProps =disableDefaultPropsExceptForClasses ||workInProgress.elementType === type? unresolvedProps: resolveDefaultPropsOnNonClassComponent(type, unresolvedProps);return updateForwardRef(current, // 当前页面显示的fiber树workInProgress, // 内存中构建的fiber树type, // fiber类型,即ForwardRef返回的elementTyperesolvedProps, // 传递给组件的属性集合renderLanes // 优先级);// ...}
}
参数介绍如下:
- current: 当前页面显示的旧的fiber节点
- workInProgress: 内存中构建的新的fiber节点
- type: fiber类型,即通过ForwardRef创建的elementType对象,包含
$$typeof、render
- resolvedProps: 传递给组件的属性集合
- renderLanes: 渲染优先级
调用renderWithHooks
处理 hooks 逻辑,并调用实际的 render 函数,传递 nextProps 和 ref
function updateForwardRef(current, workInProgress, Component, nextProps, renderLanes) {const render = Component.render; // 获取 render 函数const ref = workInProgress.ref; // 获取 reflet nextChildren;// 调用 renderWithHooks 处理 hooks 逻辑并调用 render 函数nextChildren = renderWithHooks(current, workInProgress, render, nextProps, ref, renderLanes);workInProgress.flags |= PerformedWork;// 调用 reconcileChildren 处理子节点协调reconcileChildren(current, workInProgress, nextChildren, renderLanes);return workInProgress.child;
}
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {// ...const children = Component(props, secondArg);// ...return children;
}
从以上代码能看出,forWardRef原理就是: 给传递的render函数打上ForWardRef($$typeof
)的标签,让React知道当前组件的类型,然后在beginWork阶段会根据这个类型处理,将render和ref解析处理之后将ref作为render的第二个参数传入即const children = Component(props, secondArg);
useImperativeHandle
该Hook提供了子组件自定义暴露的属性方法的能力,同其他Hook一样,本文也从首次渲染、更新渲染两个方便来说明其实现。
由于本文分成了Mount、Update两个部分介绍,所以这里简单介绍下两者区别:
- Mount 阶段:在组件的初始挂载(第一次渲染)阶段。主要负责初始化 Hook 的状态和队列,并建立 Hook 之间的链式关系。
- Update 阶段:组件在其 props 或 state 改变后被重新渲染。主要负责处理状态更新,将新的状态应用到 memoizedState 中,以便下一次渲染使用,并更新 Hook 链表(复用/克隆现有Hook)及其更新队列updateQueue。
mount首次渲染
虽然我们在使用时只是useImperativeHandle
函数,但是在React内部通过dispatcher
进行了派发,在mount阶段执行的mountImperativeHandle
函数
function mountImperativeHandle<T>(ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,create: () => T,deps: Array<mixed> | void | null
): void {// TODO: If deps are provided, should we skip comparing the ref itself?const effectDeps =deps !== null && deps !== undefined ? deps.concat([ref]) : null;let fiberFlags: Flags = UpdateEffect | LayoutStaticEffect;mountEffectImpl(fiberFlags,HookLayout,imperativeHandleEffect.bind(null, create, ref),effectDeps);
}
mountImperativeHandle
函数作为入口函数,主要就是调用mountEffectImpl
创建副作用:
- 获取依赖,并将ref(第一个参数)作为依赖添加,以便后面对比判断是否更新
- 获取Flag标识当前副作用
- 通过bind绑定
imperativeHandleEffect
,然后调用mountEffectImpl
创建副作用
上面知道我们传入的第二个参数即create函数,在调用时实际执行的imperativeHandleEffect
函数来对ref进行处理,其中该函数逻辑如下:
function imperativeHandleEffect<T>(create: () => T,ref: { current: T | null } | ((inst: T | null) => mixed) | null | void
): void | (() => void) {if (typeof ref === "function") {const refCallback = ref;const inst = create(); // 创建实例const refCleanup = refCallback(inst); // 执行 refCallback 并返回结果return () => {if (typeof refCleanup === "function") {refCleanup(); // 如果 refCleanup 是函数,则调用它} else {refCallback(null); // 否则调用 refCallback(null) 清除引用}};} else if (ref !== null && ref !== undefined) {const refObject = ref;const inst = create();refObject.current = inst;return () => {refObject.current = null;};}
}
从代码能看出来,由于ref可以是对象、或者函数,所以这里进行了差别处理。当为对象时,会将暴露的对象绑定在current中,即可以通过ref.current来访问暴露的属性,然后会返回一个清除函数在组件卸载时会调用,来清除引用便于垃圾收回。当ref是函数时,会讲create执行结果作为入参传递给ref函数,然后自行处理(通过ref.current不能访问),根据返回的值是否是函数判断进一步处理清除函数,方便垃圾回收。
mountEffectImpl
函数如下:
function mountEffectImpl(fiberFlags: Flags,hookFlags: HookFlags,create: () => (() => void) | void,deps: Array<mixed> | void | null
): void {const hook = mountWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;currentlyRenderingFiber.flags |= fiberFlags;hook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,createEffectInstance(),nextDeps);
}
主要就是通过mountWorkInProgressHook
基于当前fiber创建一个初始化hook,然后将依赖和create传入pushEffect处理副作用列表。
pushEffect
函数如下:
function pushEffect(tag: HookFlags,create: () => (() => void) | void,inst: EffectInstance,deps: Array<mixed> | null
): Effect {const effect: Effect = {tag,create,inst,deps,// Circularnext: (null: any),};let componentUpdateQueue: null | FunctionComponentUpdateQueue =(currentlyRenderingFiber.updateQueue: any);// 首次渲染时 为nullif (componentUpdateQueue === null) {componentUpdateQueue = createFunctionComponentUpdateQueue();currentlyRenderingFiber.updateQueue = (componentUpdateQueue: any);componentUpdateQueue.lastEffect = effect.next = effect;} else {const lastEffect = componentUpdateQueue.lastEffect;if (lastEffect === null) {componentUpdateQueue.lastEffect = effect.next = effect;} else {const firstEffect = lastEffect.next;lastEffect.next = effect;effect.next = firstEffect;componentUpdateQueue.lastEffect = effect;}}return effect;
}createFunctionComponentUpdateQueue = () => {return {lastEffect: null,events: null,stores: null,memoCache: null,};
};
主要逻辑就是根据当前配置创建effect副作用,并将其添加到更新队列updateQueue中。在代码中通过判断 currentlyRenderingFiber.updateQueue是否为null来判断当前是否有其他的更新任务,如果没有则通过createFunctionComponentUpdateQueue
创建初始更新队列,反之则直接添加到链表尾部。
updateQueue更新队列也是是通过lastEffect指向尾节点的循环链表,可以更好的进行插入和快速找到头节点
至此我们介绍了在mount阶段依次调用的函数链, mountImperativeHandle - mountEffectImpl - mountWorkInProgressHook - pushEffect
最终初始化构建了从fiber到更新的链式关系。其中本次需要更新的状态保存在updateQueue中,而memoizedState中保存的是上一次渲染更新的状态,为了方便状态的追踪和新状态的基准值。
Update更新渲染
在这里先介绍下函数调用关系,然后再针对该调用链以此介绍。通过dispatcher派发之后函数调用如下:updateImperativeHandle - updateEffectImpl - imperativeHandleEffect - updateWorkInProgressHook - pushEffect
其中 imperativeHandleEffect
和pushEffect
在Mount阶段已经讲过,所以这里就跳过,主要介绍其他函数。
updateImperativeHandle
函数
function updateImperativeHandle<T>(ref: { current: T | null } | ((inst: T | null) => mixed) | null | void,create: () => T,deps: Array<mixed> | void | null
): void {// TODO: If deps are provided, should we skip comparing the ref itself?const effectDeps =deps !== null && deps !== undefined ? deps.concat([ref]) : null;updateEffectImpl(UpdateEffect,HookLayout,imperativeHandleEffect.bind(null, create, ref),effectDeps);
}
从代码能看出该函数主要工作就是调用updateEffectImpl来处理副作用:
- 获取deps依赖数组,并将ref(第一个参数)作为依赖添加
- 绑定
imperativeHandleEffect
处理ref,并调用updateEffectImpl
更新副作用列表
updateEffectImpl
函数:
function updateEffectImpl(fiberFlags: Flags,hookFlags: HookFlags,create: () => (() => void) | void,deps: Array<mixed> | void | null
): void {const hook = updateWorkInProgressHook();const nextDeps = deps === undefined ? null : deps;const effect: Effect = hook.memoizedState;const inst = effect.inst;// currentHook is null on initial mount when rerendering after a render phase// state update or for strict mode.// 如果 currentHook 存在,表示这是一个更新操作,否则是一个初始化操作。if (currentHook !== null) {if (nextDeps !== null) {const prevEffect: Effect = currentHook.memoizedState;const prevDeps = prevEffect.deps;if (areHookInputsEqual(nextDeps, prevDeps)) {// 依赖没有变化,则传入hookFlags,不需要更新hook.memoizedState = pushEffect(hookFlags, create, inst, nextDeps);return;}}}// 通过位或运算,更新flag表示当前fiber需要更新currentlyRenderingFiber.flags |= fiberFlags;hook.memoizedState = pushEffect(HookHasEffect | hookFlags,create,inst,nextDeps);
}// 通过for循环遍历依赖数组,然后通过Object.is判断是否变化
function areHookInputsEqual(nextDeps: Array<mixed>,prevDeps: Array<mixed> | null,): boolean {if (prevDeps === null) {return false;}// $FlowFixMe[incompatible-use] found when upgrading Flowfor (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {// $FlowFixMe[incompatible-use] found when upgrading Flowif (is(nextDeps[i], prevDeps[i])) {continue;}return false;}return true;}
该函数主要功能就是创建更新任务然后添加到Hook中,并对比deps是否变化来决定是否触发更新,最后更新memoizedState缓存状态。
- 通过
updateWorkInProgressHook
函数复用Hook并添加更新任务 - 通过
areHookInputsEqual
对比依赖变化,通过传入Flag来判断是否跳过更新 - 通过
pushEffect
添加副作用,并更新memoizedState
缓存值
updateWorkInProgressHook函数在前面文章已经介绍过,主要就是复用Hook链表,优先复用workInProgress中的Hook,没有则克隆当前页面显示的current Hook 详情可以查看这篇文章:【React Hooks原理 - useState】
Hook数据结构中和fiber数据结构中都有memoizedState字段,但是表达的意义不同,Hook中是作为缓存的state值,但是fiber中是指向的当前fiber下的hooks队列的首个hook(hook是链表结构,指向首个,就意味着可以访问整个hooks队列)
至此Mount阶段和Update阶段就介绍完了,总的来说就是在Mount阶段进行初始化,在Update阶段创建更新任务添加到更新列表,等待Scheduler调度更新。
总结
总的来说React默认不允许父组件访问子组件中的DOM,所以需要通过forwardRef来将ref注入到子组件中,通过在子组件中绑定dom来让父组件访问。但是我们又想自定义暴露哪些属性,所以需要useImperativeHandle
这个Hook来帮助完成。
forwardRef的本质就是返回一个带有特定标识符$$typeof
的对象,React根据这个表示知道当前组件是ForWardRef类型,则会在执行函数组件渲染时将ref作为第二个参数传入即Component(props, ref)
useImperativeHandle可以理解为这个Hook是对forwardRef传入的ref进行了拦截,根据不同数据类型的ref做了不同处理,对于对象类型,直接将暴露的对象绑定到ref.current中(因为ref是通过useRef创建,默认会带有current属性),而函数类型则将暴露的对象作为ref函数的入参由开发者自行控制。所以ref是对象时,父组件可以通过ref.curren访问,而ref是函数时则需要根据设置访问,此时ref.current === null
。
相关文章:

【React Hooks原理 - forwardRef、useImperativeHandle】
概述 上文我们聊了useRef的使用和实现,主要两个用途:1、用于持久化保存 2、用于绑定dom。 但是有时候我们需要在父组件中访问子组件的dom或者属性/方法,而React中默认是不允许父组件直接访问子组件的dom的,这时候就可以通过forwa…...

用于可穿戴传感器的人类活动识别、健康监测和行为建模的大型语言模型
这篇论文题为《用于可穿戴传感器的人类活动识别、健康监测和行为建模的大型语言模型:早期趋势、数据集和挑战的综述》,由埃米利奥费拉拉(Emilio Ferrara)撰写。论文主要内容如下: 摘要 可穿戴技术的普及使得传感器数…...

react事件绑定
react基础事件绑定 function passwordChange(e){console.log(e.target.value); } function usernameChange(e){console.log(e.target.value); }function App() {return (<div><input type"text" placeholder请输入用户名onChange{usernameChange}/><i…...

spring框架之AOP注解方式(java代码实例)
目录 半注解形式: 业务层接口实现类: 编写切面类: 在配置文件里面唯一需要加的: 测试类: 全注解形式: 不要配置文件,改为配置类: 同样的业务层接口实现类: 同样的…...

windows下gcc编译C、C++程序 MinGW编译器
文章目录 1、概要2、MinGW安装2.1 编译器下载2.2 编译器安装2.3 设置环境变量2.4 查看gcc版本信息 3、编译C、C程序3.1 编写Hello World.c3.2 编译C程序3.3 运行程序3.4 编译C程序 1、概要 GCC原名为GNU C语言编译器(GNU C Compiler),只能处…...

uniapp启动图延时效果,启动图的配置
今天阐述uniapp开发中给启动图做延迟效果,不然启动图太快了,一闪就过去了; 一:修改配置文件:manifest.json "app-plus" : {"splashscreen" : {"alwaysShowBeforeRender" : false,"…...

SQL,python,knime将数据混合的文字数字拆出来,合并计算(学习笔记)
将下面将数据混合的文字数字拆出来,合并计算 一、SQL解决: ---创建表插入数据 CREATE TABLE original_data (id INT AUTO_INCREMENT PRIMARY KEY,city VARCHAR(255),value DECIMAL(10, 2) );INSERT INTO original_data (city, value) VALUES (上海0.5…...

【算法】LRU缓存
难度:中等 题目: 请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。 实现 LRUCache 类: LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存int get(int key) 如果关键字 key 存在于缓存中,…...

解决elementUI列表的疑难杂症,排序显示错乱的问题
大家好,在使用elementUI表格时,有时会出现一些意料之外的问题,比如数据排序正常但表格显示、排序错乱等。在网上搜索后一般有2种解决方法:1.给表格每一项的el-table-column添加唯一的id用于区分。2.给表格每一项的el-table-column…...

重大消息:手机车机互联投屏专题发布-千里马带你学框架
背景: android投屏的使用场景以前在新能源车机还没火爆时候,大部分停留在手机小屏幕投屏到大屏幕的情况及整个多端设备的互动,整体需求和技术发展其实也就是比较有限,但是新能源车机火爆后,那么这种手机和车机互联互动…...

jail子系统里升级Ubuntu focal到jammy
Ubuntu focal是20.04 ,jammy版本是22.04,本次的目的就是将FreeBSD jail子系统里的Ubuntu 从20.04升级到22.04 。这个focal 子系统是通过cbsd克隆得到的。使用CBSD克隆复制Ubuntu jail子系统环境-CSDN博客 do-release-upgrade升级没成功,用de…...

2024年7月20日(星期六)骑行支里山
2024年7月20日 (星期六)骑行支里山,早8:00到8:30,大观公园门口集合,9:00准时出发【因迟到者,骑行速度快者,可自行追赶偶遇。】 偶遇地点:大观公园门口集合 ,家住东,南,北…...

Python:正则表达式相关整理
最近因为一些原因频繁使用正则表达式,因为以前系统整理过关于正则表达式的相关知识,所以这里仅记录使用期间遇到的问题。 本文内容基于re包 1. match和search方法的区别 在Python中,re.search和re.match都是用于匹配字符串的正则表达式函数&a…...

ChatGPT对话:有关花卉数据集
【编者按】编者准备研究基于深度学习的花卉识别,首先需要花卉数据集。 后续,编者不断会记录研究花卉识别过程中的技术知识,敬请围观 1问:推荐一下用于深度学习的花卉数据集 ChatGPT 以下是一些用于深度学习的优秀花卉数据集&am…...

特征向量及算法
数据挖掘流程 加载数据 把需要的模型数据先计算出来 特征工程 提取数据特征,对特征数据进行清洗转化 数据的筛选和清洗数据转化 类型转为 性别 男,女 ----> 1,0特征交叉 性别/职业/收入 —> 新特这 优质男性程序员 将多个特征值组合在一起特征筛选…...

cpp 强制转换
一、static_cast static_cast 是 C 中的一个类型转换操作符,用于在类的层次结构中进行安全的向上转换(从派生类到基类)或进行不需要运行时类型检查的转换。它主要用于基本数据类型之间的转换、对象指针或引用的向上转换(即从派生…...

MySQL字符串魔法:拼接、截取、替换与定位的艺术
在数据的世界里,MySQL作为一把强大的数据处理利剑,其字符串处理功能犹如魔术师手中的魔法棒,让数据变换自如。今天,我们就来一场关于MySQL字符串拼接、截取、替换以及查找位置的奇幻之旅,揭开这些操作的神秘面纱。 介绍…...

在 Windows 上开发.NET MAUI 应用_1.安装开发环境
开发跨平台的本机 .NET Multi-platform App UI (.NET MAUI) 应用需要 Visual Studio 2022 17.8 或更高版本,或者具有 .NET MAUI 扩展的最新 Visual Studio Code。要开始在 Windows 上开发本机跨平台 .NET MAUI 应用,请按照安装步骤安装 Visual Studio 20…...

深度学习驱动智能超材料设计与应用
在深度学习与超材料融合的背景下,不仅提高了设计的效率和质量,还为实现定制化和精准化的治疗提供了可能,展现了在材料科学领域的巨大潜力。深度学习可以帮助实现超材料结构参数的优化、电磁响应的预测、拓扑结构的自动设计、相位的预测及结构…...

Netty UDP
Netty在UDP(用户数据报协议,User Datagram Protocol)通信中的应用非常广泛,特别是在对实时性要求较高、对数据准确性要求相对较低的场景中,如视频传输、语音通信等。以下是对Netty在UDP通信中的详细解析: …...

Spring Framework各种jar包官网下载2024年最新下载官方渠道。
Spring其实就是一个大家族,它包含了Spring Framework,Spring Boot等一系列技术,它其实就是由许许多多的jar包构成,我们要使用Spring的框架,就要去下载支持这个框架的jar包即可。 1.官网下载Spring Framework的jar包 官…...

【Unity】RPG2D龙城纷争(十三)升级系统
更新日期:2024年7月16日。 项目源码:第五章发布(正式开始游戏逻辑的章节) 索引 简介一、升级系统数据集1.升级公式2.获得经验值公式3.预览所有等级经验值二、为关卡配置升级系统三、玩家角色获得经验事件四、玩家角色升级事件五、计算玩家角色获得经验值六、计算玩家角色是…...

保障低压设备安全!中国星坤连接器精密工艺解析!
在现代电子设备中,连接器扮演着至关重要的角色,它们是电子系统之间沟通的桥梁。随着技术的发展,对连接器的需求也在不断提升,特别是在低电压应用领域。中国星坤最新推出的低压连接器,以其精密性和安全性,为…...

中国星坤X0800HI系列线对板连接器:创新技术连接,引领智能家居未来!
近日,中国星坤推出的X0800HI系列线对板连接器,凭借其独特的设计和卓越的性能,引起了业界的广泛关注。 X0800HI系列线对板连接器在极小空间内实现了线对板的W-B连接,这不仅解决了传统连接方式中剥线和焊接的繁琐步骤,还…...

SPring Boot整合第三方框架
springboot整合第三方框架 1. 整合mybatis 1.1引入依赖——pom.xml配置文件 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instanc…...

读取sqlserver数据库中varbinary(max)类型的内容,并将图片信息显示在前端页面
目录 1.varbinary(max)的说明 2.图片显示 3.总结 1.varbinary(max)的说明 varbinary(max) 是一种SQL Server数据库字段类型,用于存储二进制数据,可以存储最大长度的二进制数据。以下是关于 varbinary(max) 的说明: 存储容量: 可以存储最大…...

orcad导出pdf 缺少title block
在OrCAD中导出PDF时没有Title Block 最后确认问题在这里: 要勾选上Title Block Visible下面的print...

XML 验证器:确保数据完整性和准确性的关键工具
XML 验证器:确保数据完整性和准确性的关键工具 引言 在当今数字化时代,数据的有效管理和交换至关重要。XML(可扩展标记语言)作为一种用于存储和传输数据的语言,广泛用于各种应用程序和系统之间。为确保XML数据的完整…...

opencv学习:图像视频的读取截取部分图像数据颜色通道提取合并颜色通道边界填充数值计算图像融合
一、计算机眼中的图像 1.图像操作 构成像素点的数字在0~255之间 RGB叫做图像的颜色通道 h500,w500 2.灰度图像 3. 彩色图像 4.图像的读取 5.视频的读取 cv2.VideoCapture()--在OpenCV中,可以使用VideoCapture来读取视频文件,或是摄像头数…...

数据结构——单链表详解(超详细)(2)
前言: 上一篇文章小编简单的介绍了单链表的概念和一些函数的实现,不过为了保证文章的简洁,小编把它分成了两篇来写,这一篇小编紧接上一篇文章继续写单链表函数功能的实现: 目录: 1.单链表剩余函数的编写 1.…...