【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 其中 imperativeHandleEffectpushEffect在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

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

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

相关文章

类和对象的简述(c++篇)

开局之前&#xff0c;先来个小插曲&#xff0c;放松一下&#xff1a; 让我们的熊二来消灭所有bug 各位&#xff0c;在这祝我们&#xff1a; 放松过后&#xff0c;开始步入正轨吧。爱学习的铁子们&#xff1a; 目录&#xff1a; 一类的定义&#xff1a; 1.简述&#xff1a; 2…

【JavaScript 算法】贪心算法:局部最优解的构建

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、贪心算法的基本概念贪心算法的适用场景 二、经典问题及其 JavaScript 实现1. 零钱兑换问题2. 活动选择问题3. 分配问题 三、贪心算法的应用四、总结 贪心算法&#xff08;Greedy Algorithm&#xff09;是一种逐步构建解…

mybatisPlus和mybatis的版本冲突问题、若依换成MP、解决git无法推送、使用若依框架的swagger、以后再遇到团队项目应该怎么做。

20240716 一. mybatisPlus和mybatis的版本冲突问题1. 使用前的准备2. 我遇到了一个很严重的问题。3. 解决问题&#xff0c;好吧也没解决&#xff0c;发现问题&#xff01;&#xff01; 二、该死的git&#xff01;&#xff01;&#xff01;&#xff01;1. 解决无法在idea中使用g…

【Outlook】从Outlook新版回归经典版全攻略

引言 在微软宣布计划于2024年底淘汰邮件应用&#xff08;Mail app&#xff09;之后&#xff0c;许多用户发现新版Outlook应用&#xff08;Outlook (new)&#xff09;在他们的Windows 11/10系统上自动启动。如果您更倾向于使用经典版Outlook&#xff08;Outlook (classic)&…

webpack优化

优化方向 热更新 概念 /** hmr: hot module replacement 热模块替换 / 模块热更新作用&#xff1a; 一个模块发生改变&#xff0c;只会重新打包这一个模块&#xff08;而不是打包所有模块&#xff09;&#xff0c;极大的提升了构建速度样式文件&#xff1a; 可以使用hmr功能…

Facebook:数字时代的社交瑰宝

在当今数字化飞速发展的时代&#xff0c;社交媒体已经成为人们日常生活中不可或缺的一部分&#xff0c;而Facebook作为其中的领军者&#xff0c;不仅连接了全球数十亿的用户&#xff0c;更深刻地改变了人们的社交方式和生活方式。本文将探讨Facebook如何成为数字时代的社交瑰宝…

再谈有关JVM中的四种引用

1.强引用 强引用就是我们平时使用最多的那种引用&#xff0c;就比如以下的代码 //创建一个对象 Object obj new Object();//强引用 这个例子就是创建了一个对象并建立了强引用&#xff0c;强引用一般就是默认支持的当内存不足的时候&#xff0c;JVM开始垃圾回收&#xff0c…

防火墙的冗余基础知识+实验检测

将之前先理清需要注意的知识点&#xff1a; 1、注意防火墙冗余时的会话表必须保持一致&#xff0c;这里HRP技术已经做到 2、vrrp是自动开启抢占的&#xff0c;且是根据优先级进行抢占的 3、免费ARP的作用&#xff1a;告诉交换机的某个IP的mac地址变成了我的这个mac地址 4、HRP …

C++ | Leetcode C++题解之第231题2的幂

题目&#xff1a; 题解&#xff1a; class Solution { private:static constexpr int BIG 1 << 30;public:bool isPowerOfTwo(int n) {return n > 0 && BIG % n 0;} };

强化学习——多臂老虎机问题(MAB)【附python代码】

文章目录 一、问题描述1.1 问题定义1.2 形式化描述1.3 累积懊悔1.4 估计期望奖励 二、解决方法2.1 ϵ-贪婪算法2.2 上置信界算法2.3 汤普森采样算法2.4 小结 一、问题描述 1.1 问题定义 有一个用于 K 根拉杆的老虎机&#xff0c;每一根拉杆都对应一个关于奖励的概率分布 R 。每…

【C++题解】1154. 数组元素的查找

问题&#xff1a;1154. 数组元素的查找 类型&#xff1a;数组找数 题目描述&#xff1a; 给你 m 个整数&#xff0c;查找其中有无值为 n 的数&#xff0c;有则输出该数第一次出现的位置,没有则输出 −1 。 输入&#xff1a; 第一行一个整数 m 代表数的个数 ( 0≤m≤100 ) 。…

Qt基础 | Qt全局定义 | qglobal头文件中的数据类型、函数、宏定义

文章目录 一、数据类型定义二、函数三、宏定义 QtGlobal头文件包含了 Qt 类库的一些全局定义 &#xff0c;包括基本数据类型、函数和宏&#xff0c;一般的Qt类的头文件都会包含该文件。 详细内容可参考&#xff1a;https://doc.qt.io/qt-5/qtglobal.html 一、数据类型定义 为了…

数据可视化在智慧医疗中的重要应用

在现代智慧医疗的推动下&#xff0c;数据可视化技术正日益成为医疗领域的重要工具。通过将复杂的医疗数据转换为直观的图表和图形&#xff0c;数据可视化不仅提升了医疗服务的效率&#xff0c;还极大地改善了患者的就医体验。 在智慧医疗中&#xff0c;数据可视化首先在电子病历…

客流统计系统优化景区服务流程,增强游客满意度

在当今旅游业蓬勃发展的时代&#xff0c;景区面临着越来越多的挑战和机遇。如何提供更优质、更高效的服务&#xff0c;满足游客日益增长的需求&#xff0c;成为了景区管理者们关注的焦点。客流统计系统作为一种创新的技术手段&#xff0c;正逐渐成为优化景区服务流程、增强游客…

MySQL主从同步的原理与思考

摘要 分析主从同步出现的原因&#xff0c;MySQL实现主从同步的原理&#xff0c;思考实现原理的局限性和优点 背景 在实际应用中主从同步常用于实现备份、负载均衡和高可用。数据冗余的目的是提高数据的安全性&#xff0c;避免因磁盘损坏导致数据丢失的问题。读写分离的目的是…

基于CNN的MINIST手写数字识别项目代码以及原理详解

文章目录 项目简介项目下载地址项目开发软件环境项目开发硬件环境前言一、数据加载的作用二、Pytorch进行数据加载所需工具2.1 Dataset2.2 Dataloader2.3 Torchvision2.4 Torchtext2.5 加载项目需要使用的库 三、加载MINIST数据集3.1 数据集简介3.2 数据预处理3.3 加载数据集 四…

2.10、matlab中字符、数字、矩阵、字符串和元胞合并为字符串并将字符串以不同格式写入读出excel

1、前言 在 MATLAB 中&#xff0c;可以使用不同的数据类型&#xff08;字符、数字、矩阵、字符串和元胞&#xff09;合并为字符串&#xff0c;然后将字符串以不同格式写入 Excel 文件。 以下是一个示例代码&#xff0c;展示如何将不同数据类型合并为字符串&#xff0c;并以不…

重生奇迹mu魔法师瞬间移动技能

瞬间移动是勇士大陆魔法师所拥有的一项技能。一开始&#xff0c;许多玩家对这种技能的用处感到困惑。实际上&#xff0c;这种技能只能在游戏中不同的位置间进行移动&#xff0c;不能随机传送到地图的其他坐标位置。 一位重生奇迹mu魔法师在PK中不小心使用了一项技能&#xff0c…

labview使用斑马打印机打印标签

使用ZebraDesigner 3设计标签样式 设计完成后打印至文件&#xff0c;生成prn文件 用记事本打开prn文件 ^MMT 标签撕下 ^MMP 标签剥离 按照需求替换FD--------^FS中间内容

路由上传一个ui_control参数(uint32类型)控制页面UI显隐

前言&#xff1a;传一个uint32类型的值&#xff0c;通过 按位或操作符&#xff08;|&#xff09;来设置ui_control的值&#xff0c;通过按位与操作符&#xff08;&&#xff09;来检测是否显示或隐藏 简单介绍一下两个概念&#xff1a; 按位与操作符和按位或操作符都是二进…