【React Hooks原理 - forwardRef、useImperativeHandle】

news2024/11/28 4:24:20

概述

上文我们聊了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返回的elementType
        resolvedProps, // 传递给组件的属性集合
        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;  // 获取 ref

  let 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,
    // Circular
    next: (null: any),
  };
  let componentUpdateQueue: null | FunctionComponentUpdateQueue =
    (currentlyRenderingFiber.updateQueue: any);
  // 首次渲染时 为null
  if (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 Flow
    for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) {
      // $FlowFixMe[incompatible-use] found when upgrading Flow
      if (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.coloradmin.cn/o/1930768.html

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

相关文章

进程通信(3): System V IPC

System IPC包括System V消息队列&#xff0c;System V信号量&#xff0c;System V共享内存区。 System V IPC使用一个key&#xff08;key_t&#xff09;作为他们名字&#xff0c;这个值由ftok函数通过路径名和一个id生成。 客户和服务器通过在路径名和id达成一致&#xff0c;双…

解决网页中的 video 标签在移动端浏览器(如百度访问网页)视频脱离文档流播放问题

问题现象 部分浏览器视频脱离文档流&#xff0c;滚动时&#xff0c;视频是悬浮出来&#xff0c;在顶部播放 解决方案 添加下列属性&#xff0c;可解决大部分浏览器的脱离文档流的问题 <videowebkit-playsinline""playsInlinex5-playsinlinet7-video-player-t…

pico+unity3d开启彩色透视

1、点击游戏对象、点击XR、点击添加XR Origin&#xff0c;并把自带的摄像对象删除 2、添加脚本 using System.Collections; using System.Collections.Generic; using UnityEngine; using Unity.XR.PXR;//引入xr对象 public class toushi : MonoBehaviour {// Start is called…

Python项目部署到Linux生产环境(uwsgi+python+flask+nginx服务器)

1.安装python 我这里是3.9.5版本 安装依赖&#xff1a; yum install zlib-devel bzip2-devel openssl-devel ncurses-devel sqlite-devel readline-devel tk-devel gcc make -y 根据自己的需要下载对应的python版本&#xff1a; cd local wget https://www.python.org/ftp…

ELK日志管理

目录 一.ELK简介 1.Elasticsearch 2.Logstash 4.ELK 组件解决的主要问题 5.ELK应用的好处 6.ELK的工作原理 7.ELK的主要应用场景 二.安装部署ELK 1.前期准备 2.安装部署ElasticseaErch 软件 3. 在apache节点上 部署 Logstash 4.安装 kibana 三.实现nginx日志管理 …

modbus slave 设备通过 网关thingsboard-gateway 将数据上传到thingsboard云平台

搭建thingsboard物联网云平台花了大量时间&#xff0c;从小白到最后搭建成功&#xff0c;折磨了好几天&#xff0c;也感谢网友的帮助&#xff0c;提供了思路最终成功搞定&#xff0c;特此记录。 一、thingsboard环境搭建&#xff08;Ubuntu20.04LTS&#xff09; 参考官方文档&a…

景区导航导览系统:基于AR技术+VR技术的功能效益全面解析

在数字化时代背景下&#xff0c;游客对旅游体验的期望不断提升。游客们更倾向于使用手机作为旅行的贴身助手&#xff0c;不仅因为它能提供实时、精准的导航服务&#xff0c;更在于其融合AR&#xff08;增强现实&#xff09;、VR&#xff08;虚拟现实&#xff09;等前沿技术&…

Template execution failed: ReferenceError: name is not defined

问题 我们使用了html-webpack-plugin&#xff08;webpack&#xff09;进行编译html&#xff0c;导致的错误。 排查结果 连接地址 html-webpack-plugin版本低(2.30.1)&#xff0c;html模板里面不能有符号&#xff0c;注释都不行 // var reg new RegExp((^|&)${name}([^&…

【ROS2】高级:使用 Fast DDS 发现服务器作为发现协议 [社区贡献]

目标&#xff1a;本教程将展示如何使用 Fast DDS Discovery Server 发现协议启动 ROS 2 节点。 教程级别&#xff1a;高级 时间&#xff1a;20 分钟 目录 背景 快速 DDS 发现服务器 v2 先决条件 运行此教程 设置发现服务器 启动监听节点 启动对话节点展示发现服务器执行 可视化…

【15】Android基础知识之Window(一)

概述 这篇文章纠结了很久&#xff0c;在想需要怎么写&#xff1f;因为window有关的篇幅&#xff0c;如果需要讲起来那可太多了。从层级&#xff0c;或是从关联&#xff0c;总之不是很好开口。这次也下定决心&#xff0c;决定从浅入深的讲讲window这个东西。 Window Window是…

django报错(三):No crontab program或got an unexpected keyword argument ‘user’

Crontab是linux系统上的定时管理模块&#xff0c;简单配置&#xff0c;灵活使用。但是要在windows使用必须借助Cygwin等虚拟工具&#xff0c;否则会报错“No crontab program”。如下图&#xff1a; python-crontab是其提供了python模块对crontab的访问&#xff0c;即可以通过p…

【简历】惠州某二本学院:前端简历指导,秋招面试通过率为0

注&#xff1a;为保证用户信息安全&#xff0c;姓名和学校等信息已经进行同层次变更&#xff0c;内容部分细节也进行了部分隐藏 简历说明 这是一份25届二本同学&#xff0c;投递前端职位的简历&#xff0c;那么在校招环节二本同学主要针对的还是小公司&#xff0c;这个学校因为…

怎么关闭Windows安全中心?

Windows安全中心是Windows操作系统中的一项重要功能&#xff0c;系统提供这个功能的目的是保护电脑免受各种安全威胁。尽管如此&#xff0c;有时候我们可能出于某些原因需要关闭它。本文将详细介绍如何关闭Windows安全中心&#xff0c;以及需要注意的事项。 重要提醒&#xff1…

minIO集成springboot

问题 minIO与spring集成。 步骤 创建桶 创建key 找到创建账号页面&#xff0c;如下图&#xff1a; 点击创建&#xff0c;如下图&#xff1a; 设置如下权限&#xff1a; {"Version": "2012-10-17","Statement": [{"Effect": &q…

生成式AI、3D模型交易、模型轻量化、模型格式转换、3D可视化、数字孪生引擎等

老子云3D可视化快速开发平台&#xff0c;集云压缩、云烘焙、云存储云展示于一体&#xff0c;使3D模型资源自动输出至移动端PC端、Web端&#xff0c;能在多设备、全平台进行展示和交互&#xff0c;是全球领先、自主可控的自动化3D云引擎。 平台架构 平台特性 1、基于 HTML5 和 …

django报错(一):python manage.py makemigrations,显示“No changes detected”

执行python manage.py makemigrations命令无任何文件生成&#xff0c;结果显示“No changes detected”。 解决方案一&#xff1a; 1、执行命令&#xff1a;python manage.py makemigrations –empty appname 2、删除其中的0001_initial.py文件&#xff08;因为这个文件内容是…

《昇思25天学习打卡营第25天|第10天》

今天是打卡的第十天&#xff0c;今天开始学应用实践中的LLM原理和实践&#xff0c;今天学的是基于MindSpore实现BERT对话情绪识别。最先了解的是BERT模型的简介&#xff08;来自变换器的双向编码器表征量&#xff08;Bidirectional Encoder Representations from Transformers&…

【Java】:浅克隆和深克隆

克隆 克隆和赋值 克隆的结果是有多个相同的实体&#xff0c;各个对象指向不同的实体而多个不同对象指向一个相同的实体不是克隆&#xff0c;而是赋值 克隆的过程 首先实例化一个 student1 对象 在堆里开辟了一块内存用来存储 age 10 这个数据 调用 clone 方法 在堆中又开辟了一…

Python数据结构:实现自定义栈与队列

更多Python学习内容&#xff1a;ipengtao.com 在计算机科学中&#xff0c;栈&#xff08;Stack&#xff09;和队列&#xff08;Queue&#xff09;是两种常见的数据结构。它们在算法和数据处理方面有着广泛的应用。本文将详细介绍如何在Python中实现自定义的栈与队列&#xff0c…

stm32精密控制步进电机(升级篇)

这一篇文章里会深入的对步进电机控制方法进行论述 如何避免步进电机丢转的问题 1.机械结构&#xff1a;排查一下传动的问题&#xff0c;举个例子&#xff0c;我的毕设里大臂机械臂的步进电机有时会有丢转问题&#xff0c;造成无法运动到指定位置&#xff0c;后面发现是因为皮带…