React16源码: React中的reconcileChildren的源码实现

reconcileChildren


1 )概述

  • 在更新了一个节点之后,拿到它的props.children
  • 要根据这个children里面的 ReactElement 来去创建子树的所有的 fiber 对象
  • 要根据 props.children 来生成 fiber 子树,然后判断 fiber 对象它是否是可以复用的
    • 因为我们在第一次渲染的时候,就已经渲染了整个 fiber 子树
    • 再有一个更新进来之后,state 变化可能会导致一些子节点产生一个新的变化
    • 可能就不能复用之前的 fiber 节点了,它里面的很多东西都变得不一样
    • 大部分情况下所有 fiber 节点都是可以可以重复利用的
    • 这个时候我们根据什么进行判断,是这里面的一个非常重要的一个点
  • 在这里就会拿出react当中的非常重要的key,列表根据key来优化

2 )源码

在 packages/react-reconciler/src/ReactFiberBeginWork.js#L137 中

export function reconcileChildren(current: Fiber | null,workInProgress: Fiber,nextChildren: any,renderExpirationTime: ExpirationTime,
) {if (current === null) {// If this is a fresh new component that hasn't been rendered yet, we// won't update its child set by applying minimal side-effects. Instead,// we will add them all to the child before it gets rendered. That means// we can optimize this reconciliation pass by not tracking side-effects.workInProgress.child = mountChildFibers(workInProgress,null,nextChildren,renderExpirationTime,);} else {// If the current child is the same as the work in progress, it means that// we haven't yet started any work on these children. Therefore, we use// the clone algorithm to create a copy of all the current children.// If we had any progressed work already, that is invalid at this point so// let's throw it out.workInProgress.child = reconcileChildFibers(workInProgress,current.child,nextChildren,renderExpirationTime,);}
}
  • 可见,根据 current 是否为 null 调用两个方法: mountChildFibers, reconcileChildFibers
    • 两个方法的区别,在第二个参数
    • 在前者方法中传递的是 null, 因为 第一次渲染,不存在 current.child
    • 因为第一次渲染的时候,都是父亲节点先渲染,子节点后渲染
    • 只有在后续的渲染过程当中,已经过第一次渲染了我们的父节点
    • 这个时候才有一个子节点存在,所以这是一个区别
    • 在 react-reconciler/src/ReactFiberBeginWork.js 中 的 reconcileChildren 调用这个 reconcileChildFibers
  • 看一下这两个方法,它们来自于 ReactChildFiber.js
    export const reconcileChildFibers = ChildReconciler(true);
    export const mountChildFibers = ChildReconciler(false);
    
  • 可见,上述两个方法的区别是 参数的 true 和 false
    • 这个参数的意思是: 表示是否要跟踪副作用
  • 进入 ChildReconciler,这个方法 1k 多行,这里不去全部摘抄
  • 用到哪里,逐个分析

整体框架

// 参数 shouldTrackSideEffects 表示是否要跟踪副作用
function ChildReconciler(shouldTrackSideEffects) {// ... 跳过很多代码// This API will tag the children with the side-effect of the reconciliation// itself. They will be added to the side-effect list as we pass through the// children and the parent.function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,expirationTime: ExpirationTime,): Fiber | null {// 省略很多代码// Remaining cases are all treated as empty.return deleteRemainingChildren(returnFiber, currentFirstChild);}return reconcileChildFibers;
}
  • ChildReconciler 方法的最后, return reconcileChildFibers;,先看下这个方法

    // This API will tag the children with the side-effect of the reconciliation
    // itself. They will be added to the side-effect list as we pass through the
    // children and the parent.
    function reconcileChildFibers(returnFiber: Fiber,currentFirstChild: Fiber | null,newChild: any,expirationTime: ExpirationTime,
    ): Fiber | null {// This function is not recursive.// If the top level item is an array, we treat it as a set of children,// not as a fragment. Nested arrays on the other hand will be treated as// fragment nodes. Recursion happens at the normal flow.// Handle top level unkeyed fragments as if they were arrays.// This leads to an ambiguity between <>{[...]}</> and <>...</>.// We treat the ambiguous cases above the same.// newChild 是计算出来的 新children// 这个节点是一个 top level 的节点,因为 <></> 这种类型会匹配 REACT_FRAGMENT_TYPE// 符合以下就是没有key的 topLevel节点const isUnkeyedTopLevelFragment =typeof newChild === 'object' &&newChild !== null &&newChild.type === REACT_FRAGMENT_TYPE &&newChild.key === null;// 如果匹配了,那么 Fragment 是没有任何的操作更新的, 本身并没有什么实际的意义// 如果匹配了,则将newChild赋值if (isUnkeyedTopLevelFragment) {newChild = newChild.props.children;}// Handle object typesconst isObject = typeof newChild === 'object' && newChild !== null;// 如果是对象,说明有下面两种情况// 下面两种方法调用相似,都是基于 placeSingleChild 传递不同参数// 一个是 reconcileSingleElement// 另一个是 reconcileSinglePortalif (isObject) {switch (newChild.$$typeof) {// 匹配 REACT_ELEMENT,是通过 React.createElement 产生的case REACT_ELEMENT_TYPE:return placeSingleChild(reconcileSingleElement(returnFiber,currentFirstChild,newChild,expirationTime,),);// 匹配 REACT_PORTAL, 是通过 ReactDOM.createPortalcase REACT_PORTAL_TYPE:return placeSingleChild(reconcileSinglePortal(returnFiber,currentFirstChild,newChild,expirationTime,),);}}// 匹配到 string 和 number 类就是 text类型的Nodeif (typeof newChild === 'string' || typeof newChild === 'number') {return placeSingleChild(reconcileSingleTextNode(returnFiber,currentFirstChild,'' + newChild,expirationTime,),);}// 匹配到数组if (isArray(newChild)) {return reconcileChildrenArray(returnFiber,currentFirstChild,newChild,expirationTime,);}// 匹配到可迭代的对象if (getIteratorFn(newChild)) {return reconcileChildrenIterator(returnFiber,currentFirstChild,newChild,expirationTime,);}// 以上都没有匹配,但仍然是对象,则 throw errorif (isObject) {throwOnInvalidObjectType(returnFiber, newChild);}// 忽略 DEVif (__DEV__) {if (typeof newChild === 'function') {warnOnFunctionType();}}// 匹配到 undefined 的对象if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {// If the new child is undefined, and the return fiber is a composite// component, throw an error. If Fiber return types are disabled,// we already threw above.switch (returnFiber.tag) {// 匹配到 ClassComponent 忽略case ClassComponent: {if (__DEV__) {const instance = returnFiber.stateNode;if (instance.render._isMockFunction) {// We allow auto-mocks to proceed as if they're returning null.break;}}}// Intentionally fall through to the next case, which handles both// functions and classes// eslint-disable-next-lined no-fallthrough// 匹配到 FunctionComponent 提醒case FunctionComponent: {const Component = returnFiber.type;invariant(false,'%s(...): Nothing was returned from render. This usually means a ' +'return statement is missing. Or, to render nothing, ' +'return null.',Component.displayName || Component.name || 'Component',);}}}// Remaining cases are all treated as empty.// 以上都不符合,则 是 null, 删除现有所有 children// 新的 props.children 都是 null, 老的 props 节点都应该被删除return deleteRemainingChildren(returnFiber, currentFirstChild);
    }
    
  • 接下来看不同节点的更新 reconcileSingleElement

    // 从当前已有的所有子节点中,找到一个可以复用新的子节点的Fiber对象
    // 复用它之后,把剩下兄弟节点全部删掉
    function reconcileSingleElement(returnFiber: Fiber,currentFirstChild: Fiber | null, // 是当前已有的fiber节点,初次渲染没有,后续渲染后很可能存在element: ReactElement,expirationTime: ExpirationTime,
    ): Fiber {// 获取 key 和 childconst key = element.key;let child = currentFirstChild;// child 存在while (child !== null) {// TODO: If key === null and child.key === null, then this only applies to// the first item in the list.if (child.key === key) {// 注意这个判断if (child.tag === Fragment? element.type === REACT_FRAGMENT_TYPE // 是否是 frament: child.elementType === element.type // 新老 element type) {// 删除 已存在当前节点的兄弟节点// 为何要删除兄弟节点,因为我们这次渲染只有一个节点,老的节点有兄弟节点,新的节点只有一个,删除之deleteRemainingChildren(returnFiber, child.sibling);const existing = useFiber(child,element.type === REACT_FRAGMENT_TYPE? element.props.children: element.props,expirationTime,);// 挂载属性existing.ref = coerceRef(returnFiber, child, element);existing.return = returnFiber;if (__DEV__) {existing._debugSource = element._source;existing._debugOwner = element._owner;}// 找到可复用节点,直接returnreturn existing;} else {// 条件不符合,删除节点并退出 deleteRemainingChildren(returnFiber, child);break;}} else {// key 不同,则删除deleteChild(returnFiber, child);}// 当前兄弟节点 等于 child 再次进入while循环child = child.sibling;}// 创建新的节点, 基于各种不同的类型,调用不同的创建 Fiber的方式if (element.type === REACT_FRAGMENT_TYPE) {const created = createFiberFromFragment(element.props.children, // 注意这个参数returnFiber.mode,expirationTime,element.key,);created.return = returnFiber;return created;} else {const created = createFiberFromElement(element,returnFiber.mode,expirationTime,);created.ref = coerceRef(returnFiber, currentFirstChild, element);created.return = returnFiber;return created;}
    }
    
    • 进入 createFiberFromFragment
      export function createFiberFromFragment(elements: ReactFragment,mode: TypeOfMode,expirationTime: ExpirationTime,key: null | string,
      ): Fiber {const fiber = createFiber(Fragment, elements, key, mode);fiber.expirationTime = expirationTime;return fiber;
      }// This is a constructor function, rather than a POJO constructor, still
      // please ensure we do the following:
      // 1) Nobody should add any instance methods on this. Instance methods can be
      //    more difficult to predict when they get optimized and they are almost
      //    never inlined properly in static compilers.
      // 2) Nobody should rely on `instanceof Fiber` for type testing. We should
      //    always know when it is a fiber.
      // 3) We might want to experiment with using numeric keys since they are easier
      //    to optimize in a non-JIT environment.
      // 4) We can easily go from a constructor to a createFiber object literal if that
      //    is faster.
      // 5) It should be easy to port this to a C struct and keep a C implementation
      //    compatible.
      const createFiber = function(tag: WorkTag,pendingProps: mixed, // 对于 fragment来说,props只有children, 直接把children作为propskey: null | string,mode: TypeOfMode,
      ): Fiber {// $FlowFixMe: the shapes are exact here but Flow doesn't like constructorsreturn new FiberNode(tag, pendingProps, key, mode);
      };
      
    • 以上的 fragment 可以在 ReactFiberBeginWork.js 中的 updateFragment 方法中找到验证
      function updateFragment(current: Fiber | null,workInProgress: Fiber,renderExpirationTime: ExpirationTime,
      ) {// 看这里,直接通过 .pendingProps 而非 props.children 获取,参考上述 createFiber 写明原因const nextChildren = workInProgress.pendingProps;reconcileChildren(current,workInProgress,nextChildren,renderExpirationTime,);return workInProgress.child;
      }
      
    • 进入 createFiberFromElement
      export function createFiberFromElement(element: ReactElement,mode: TypeOfMode,expirationTime: ExpirationTime,
      ): Fiber {let owner = null;if (__DEV__) {owner = element._owner;}// 获取各类属性const type = element.type;const key = element.key;const pendingProps = element.props;// 创建 Fiberconst fiber = createFiberFromTypeAndProps(type,key,pendingProps,owner,mode,expirationTime,);if (__DEV__) {fiber._debugSource = element._source;fiber._debugOwner = element._owner;}return fiber;
      }
      
    • 进入 createFiberFromTypeAndProps
      // 这个方法比较复杂,要判断不同的 type 给 fiber 对象增加 不同的 tag
      // 主要是去匹配 fiberTag
      export function createFiberFromTypeAndProps(type: any, // React$ElementTypekey: null | string,pendingProps: any,owner: null | Fiber,mode: TypeOfMode,expirationTime: ExpirationTime,
      ): Fiber {let fiber;// 组件tag未知时的初始化配置 未指定状态let fiberTag = IndeterminateComponent;// The resolved type is set if we know what the final type will be. I.e. it's not lazy.let resolvedType = type;// 根据不同类型来处理 fiberTagif (typeof type === 'function') {// 判断是否有 constructor 方法/*function shouldConstruct(Component: Function) {const prototype = Component.prototype;// 注意,这里 isReactComponent 是一个 {}return !!(prototype && prototype.isReactComponent);}*/if (shouldConstruct(type)) {fiberTag = ClassComponent;}} else if (typeof type === 'string') {fiberTag = HostComponent;} else {getTag: switch (type) {case REACT_FRAGMENT_TYPE:return createFiberFromFragment(pendingProps.children,mode,expirationTime,key,);case REACT_CONCURRENT_MODE_TYPE:return createFiberFromMode(pendingProps,mode | ConcurrentMode | StrictMode,expirationTime,key,);case REACT_STRICT_MODE_TYPE:return createFiberFromMode(pendingProps,mode | StrictMode,expirationTime,key,);case REACT_PROFILER_TYPE:return createFiberFromProfiler(pendingProps, mode, expirationTime, key);case REACT_SUSPENSE_TYPE:return createFiberFromSuspense(pendingProps, mode, expirationTime, key);default: {if (typeof type === 'object' && type !== null) {switch (type.$$typeof) {case REACT_PROVIDER_TYPE:fiberTag = ContextProvider;break getTag;case REACT_CONTEXT_TYPE:// This is a consumerfiberTag = ContextConsumer;break getTag;case REACT_FORWARD_REF_TYPE:fiberTag = ForwardRef;break getTag;case REACT_MEMO_TYPE:fiberTag = MemoComponent;break getTag;case REACT_LAZY_TYPE:fiberTag = LazyComponent;resolvedType = null;break getTag;}}let info = '';if (__DEV__) {if (type === undefined ||(typeof type === 'object' &&type !== null &&Object.keys(type).length === 0)) {info +=' You likely forgot to export your component from the file ' +"it's defined in, or you might have mixed up default and " +'named imports.';}const ownerName = owner ? getComponentName(owner.type) : null;if (ownerName) {info += '\n\nCheck the render method of `' + ownerName + '`.';}}invariant(false,'Element type is invalid: expected a string (for built-in ' +'components) or a class/function (for composite components) ' +'but got: %s.%s',type == null ? type : typeof type,info,);}}}// 最终创建 Fiberfiber = createFiber(fiberTag, pendingProps, key, mode);fiber.elementType = type;fiber.type = resolvedType;fiber.expirationTime = expirationTime;return fiber;
      }
      
    • 以上,reconcileSingleElement 就完了
  • 进入 reconcileSingleTextNode

    function reconcileSingleTextNode(returnFiber: Fiber,currentFirstChild: Fiber | null,textContent: string,expirationTime: ExpirationTime,
    ): Fiber {// There's no need to check for keys on text nodes since we don't have a// way to define them.// 处理 原生 标签if (currentFirstChild !== null && currentFirstChild.tag === HostText) {// We already have an existing node so let's just update it and delete// the rest.// 删除兄弟节点,新节点只是一个纯的文本节点deleteRemainingChildren(returnFiber, currentFirstChild.sibling);// 基于 useFiber 复用当前节点const existing = useFiber(currentFirstChild, textContent, expirationTime);existing.return = returnFiber;return existing;}// The existing first child is not a text node so we need to create one// and delete the existing ones.deleteRemainingChildren(returnFiber, currentFirstChild);const created = createFiberFromText(textContent,returnFiber.mode,expirationTime,);created.return = returnFiber;return created;
    }
    
    • 这个流程就比较简单了
  • 现在我们主要看下 deleteRemainingChildren 这个api

    function deleteRemainingChildren(returnFiber: Fiber, // 当前正在更新的节点currentFirstChild: Fiber | null, // 子节点
    ): null {// 这是第一次渲染的时候,没有子节点// 直接 returnif (!shouldTrackSideEffects) {// Noop.return null;}// TODO: For the shouldClone case, this could be micro-optimized a bit by// assuming that after the first child we've already added everything.// 一个个的删除let childToDelete = currentFirstChild;while (childToDelete !== null) {deleteChild(returnFiber, childToDelete);childToDelete = childToDelete.sibling;}return null;
    }
    
    • 进入 deleteChild
      // 可以看到这里没有实施删除的操作,只是改变了节点上的 effectTag
      // 这里我们只更新 Fiber Tree, 如果真的要 delete 就会影响到 dom节点
      // 所以删除的流程不在这里做,只有在下个阶段,也就是 commit 阶段来做
      // 就是把Fiber Tree 需要更新的流程都过一遍之后,把整个需要更新的属性
      // 通过 firstEffect,lastEffect 这种链条,最终链到根节点上面
      // 最终要执行更新 只需要在 commit 阶段拿到 根节点上面的 这种链条把每个节点去执行即可
      // 在处理更新的时候任务是可以中断的,但是在 commit 阶段任务是不可中断的
      function deleteChild(returnFiber: Fiber, childToDelete: Fiber): void {if (!shouldTrackSideEffects) {// Noop.return;}// Deletions are added in reversed order so we add it to the front.// At this point, the return fiber's effect list is empty except for// deletions, so we can just append the deletion to the list. The remaining// effects aren't added until the complete phase. Once we implement// resuming, this may not be true.const last = returnFiber.lastEffect;// 存在 lastif (last !== null) {last.nextEffect = childToDelete;returnFiber.lastEffect = childToDelete;} else {// 不存在 lastreturnFiber.firstEffect = returnFiber.lastEffect = childToDelete;}childToDelete.nextEffect = null; // 已经是要删除的节点,上面的其他副作用都是没有任何意义的// effectTag 就是告诉后续的 commit 阶段,对于这个节点需要执行什么操作,可以看到,这里是删除childToDelete.effectTag = Deletion; // 只需要设置这个即可
      }
      
  • reconcileChildrenArrayreconcileChildrenIterator 这两个api比较复杂,先跳过

  • 其他的 API 都是比较简单了

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

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

相关文章

QT实现USB通讯

一.概述 QT实现USB通讯这里主要介绍两种方法&#xff0c;一种是通过libusb库来实现usb通讯&#xff0c;一种是通过hidapi库实现通信。 1.介绍libusb库 libusb 是一个 C 库&#xff0c;提供对 USB 设备的通用访问。 可移植的&#xff1a;使用单个跨平台API&#xff0c;它可以…

一、防御保护---信息安全概述

一、网络安全防御---信息安全概述 1.信息安全现状及挑战1.1 网络空间安全市场在中国&#xff0c;潜力无穷1.2 数字化时代威胁升级1.3 传统安全防护逐步失效1.4 安全风险能见度不足1.5 缺乏自动化防御手段1.6 网络安全监管标准愈发严苛 2.信息安全概述2.1 简介2.2 常见的网络安全…

Java 设计者模式以及与Spring关系(四) 代理模式

目录 简介: 23设计者模式以及重点模式 代理模式&#xff08;Proxy Pattern&#xff09; 静态代理示例 spring中应用 动态代理 1.基于JDK的动态代理 target.getClass().getInterfaces()作用 内名内部类写法(更简洁&#xff0c;但不推荐) 2.基于CGLIB实现 spring中应用 …

uniapp使用自定义组件

tt.vue中使用video-player组件 用到的目录如下&#xff1a; pages.json {"path": "pages/Tabbar/tt/tt","style": {"navigationBarTitleText": "","enablePullDownRefresh": false,// 使用自定义组件"using…

C++ 类定义

C 类定义 定义一个类需要使用关键字 class&#xff0c;然后指定类的名称&#xff0c;并类的主体是包含在一对花括号中&#xff0c;主体包含类的成员变量和成员函数。 定义一个类&#xff0c;本质上是定义一个数据类型的蓝图&#xff0c;它定义了类的对象包括了什么&#xff0…

【论文阅读笔记】Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation

1.介绍 Swin-Unet: Unet-like Pure Transformer for Medical Image Segmentation Swin-Unet&#xff1a;用于医学图像分割的类Unet纯Transformer 2022年发表在 Computer Vision – ECCV 2022 Workshops Paper Code 2.摘要 在过去的几年里&#xff0c;卷积神经网络&#xff…

14.任务管理系统

功能描述 角色 管理员、项目经理、员工 管理员功能 录入基础信息、用户信息。 项目经理功能 登陆系统录入项目信息、需求信息、拆解任务、分配任务 员工功能 登陆系统&#xff0c;查看任务、记录任务工作日志、完成任务。 数据模型 项目表 t_jd_project 字段名 类型 …

x-cmd pkg | dasel - JSON、YAML、TOML、XML、CSV 数据的查询和修改工具

目录 简介首次用户快速实验指南基本功能性能特点竞品进一步探索 简介 dasel&#xff0c;是数据&#xff08;data&#xff09;和 选择器&#xff08;selector&#xff09;的简写&#xff0c;该工具使用选择器查询和修改数据结构。 支持 JSON&#xff0c;YAML&#xff0c;TOML&…

CentOS 7 安装配置MySQL

目录 一、安装MySQL​编辑​编辑 1、检查MySQL是否安装及版本信息​编辑 2、卸载 2.1 rpm格式安装的mysql卸载方式 2.2 二进制包格式安装的mysql卸载 3、安装 二、配置MySQL 1、修改MySQL临时密码 2、允许远程访问 2.1 修改MySQL允许任何人连接 2.2 防火墙的问题 2…

大哈的变换迷宫的题解

目录 原题描述&#xff1a; 【题目描述】 【输入格式】 【输出格式】 【样例输入】 【样例输出】 【数据范围】 题目大意&#xff1a; 主要思路&#xff1a; 代码code&#xff08;附有注释&#xff09; 时间限制: 1000ms 空间限制: 524288kB 原题描述&#xff1a; …

【webrtc】跟webrtc学时间戳、序号类型转换

间隔ms src\modules\congestion_controller\remb_throttler.ccnamespace {constexpr TimeDelta kRembSendInterval = TimeDelta::Millis(200); } // namespace百分比的处理 src\modules\congestion_controller\remb_throttler.ccvoid RembT

行业-职业-大全-JSON

数据来源&#xff1a; https://blog.csdn.net/wjwABCDEFG/article/details/115669504 已将JSON进行格式处理 [{"Industry": "人事/行政/后勤","Occupations": ["人事专员/助理","人事信息系统(HRIS)管理","人事总监…

kotlin 简单实现实体类的Parcelable序列化接口

以前用Java代码实现Parcelable序列化接口&#xff0c;需要在实体类里面写一堆代码&#xff0c;麻烦得很&#xff0c;现在用kotlin开发安卓APP&#xff0c;只需2步就可以实现&#xff0c;这里记录下&#xff1b; 1. 在模块的build.gradle文件如下配置&#xff1a; apply plugi…

TCP三握四挥(面试需要)

TCP建立连接需要三次握手过程&#xff0c;关闭连接需要四次挥手过程 三次握手 从图中可以看出&#xff0c;客户端在发起connect时&#xff0c;会发起第一次和第三次握手。服务端在接收客户端连接时&#xff0c;会发起第二次握手。 这三次握手&#xff0c;都会通过SYNACK的方式…

基于ChatGPT4+Python近红外光谱数据分析及机器学习与深度学习建模教程

详情点击链接&#xff1a;基于ChatGPT4Python近红外光谱数据分析及机器学习与深度学习建模教程 第一&#xff1a;GPT4 1、ChatGPT&#xff08;GPT-1、GPT-2、GPT-3、GPT-3.5、GPT-4模型的演变&#xff09; 2、ChatGPT对话初体验 3、GPT-4与GPT-3.5的区别&#xff0c;以及与…

20240122在WIN10+GTX1080下使用字幕小工具V1.2的使用总结(whisper)

20240122在WIN10GTX1080下使用字幕小工具V1.2的使用总结 2024/1/22 19:52 结论&#xff1a;这个软件如果是习作&#xff0c;可以打101分&#xff0c;功能都实现了。 如果作为商业软件/共享软件&#xff0c;在易用性等方面&#xff0c;可能就只能有70分了。 【百分制】 可选的改…

2017年认证杯SPSSPRO杯数学建模A题(第二阶段)安全的后视镜全过程文档及程序

2017年认证杯SPSSPRO杯数学建模 A题 安全的后视镜 原题再现&#xff1a; 汽车后视镜的视野对行车安全非常重要。一般来说&#xff0c;汽车的后视镜需要有良好的视野范围&#xff0c;以便驾驶员能够全面地了解车后方的道路情况。同时&#xff0c;后视镜也要使图像的畸变尽可能…

idea编译打包前端vue项目

网上download了一个前端vue项目 第一次接触前端记录一下编译打包遇到的问题 1、idea前端项目打包一般是依赖 <groupId>org.codehaus.mojo</groupId> <artifactId>exec-maven-plugin</artifactId> <version>3.0…

GitLab备份与恢复测试(基于Docker)

GitLab环境准备 docker run --name gitlab \ -p 2022:22 -p 2080:80 -p 2443:443 -d \ -v /opt/gitlab/config:/etc/gitlab \ -v /opt/gitlab/gitlab/logs:/var/log/gitlab \ -v /opt/gitlab/gitlab/data:/var/opt/gitlab \ gitlab/gitlab-ce:16.2.1-ce.0备份 1.修改配置文件…

Centos升级gcc版本

步骤1&#xff1a;查看当前服务器gcc版本 gcc –version 步骤2&#xff1a;查看当前gcc安装目录 find / -name gcc cd /usr/bin ll gcc* 因为gcc&#xff0c;g&#xff0c;c都是配套的,查找出 g和c的原版本位置 步骤3&#xff1a;安装下载依赖包 yum install glibc-heade…