先看一下FiberNode在源码中的样子

FiberNode

// packages/react-reconciler/src/ReactFiber.old.js
function FiberNode(tag: WorkTag,  pendingProps: mixed,  key: null | string,  mode: TypeOfMode,
) {// Instancethis.tag = tag;this.key = key;this.elementType = null;this.type = null;this.stateNode = null;// Fiberthis.return = null;this.child = null;this.sibling = null;this.index = 0;this.ref = null;this.pendingProps = pendingProps;this.memoizedProps = null;this.updateQueue = null;this.memoizedState = null;this.dependencies = null;this.mode = mode;// Effectsthis.flags = NoFlags;this.nextEffect = null;this.firstEffect = null;this.lastEffect = null;this.lanes = NoLanes;this.childLanes = NoLanes;this.alternate = null;if (enableProfilerTimer) {// Note: The following is done to avoid a v8 performance cliff.//// Initializing the fields below to smis and later updating them with// double values will cause Fibers to end up having separate shapes.// This behavior/bug has something to do with Object.preventExtension().// Fortunately this only impacts DEV builds.// Unfortunately it makes React unusably slow for some applications.// To work around this, initialize the fields below with doubles.//// Learn more about this here:// https://github.com/facebook/react/issues/14365// https://bugs.chromium.org/p/v8/issues/detail?id=8538this.actualDuration = Number.NaN;this.actualStartTime = Number.NaN;this.selfBaseDuration = Number.NaN;this.treeBaseDuration = Number.NaN;// It's okay to replace the initial doubles with smis after initialization.// This won't trigger the performance cliff mentioned above,// and it simplifies other profiler code (including DevTools).this.actualDuration = 0;this.actualStartTime = -1;this.selfBaseDuration = 0;this.treeBaseDuration = 0;}if (__DEV__) {// This isn't directly used but is handy for debugging internals:...}
}
  • 我们看FiberNode这个构造函数里面只是赋值,我们再找一下链路上的Fiber,我们发现在函数createFiber的返回值类型里面出现了Fiber类型,所以
// packages/react-reconciler/src/ReactInternalTypes.js
export type Fiber = {|// These first fields are conceptually members of an Instance. This used to// be split into a separate type and intersected with the other Fiber fields,// but until Flow fixes its intersection bugs, we've merged them into a// single type.// An Instance is shared between all versions of a component. We can easily// break this out into a separate object to avoid copying so much to the// alternate versions of the tree. We put this on a single object for now to// minimize the number of objects created during the initial render.// dom节点的相关信息tag: WorkTag,// 组件的类型key: null | string, // 唯一值elementType: any,// 元素类型// 判定fiber节点的类型,用于difftype: any,// 真实 dom 节点stateNode: any,// Conceptual aliases// parent : Instance -> return The parent happens to be the same as the// return fiber since we've merged the fiber and instance.// Remaining fields belong to Fiber// fiber 链表树return: Fiber | null, // 父 fiberchild: Fiber | null, // 第一个子 fibersibling: Fiber | null, // 下一个兄弟 fiberindex: number, // 在父 fiber 下面的子 fiber 中的下标// The ref last used to attach this node.// I'll avoid adding an owner field for prod and model that as functions.ref:| null| (((handle: mixed) => void) & {_stringRef: ?string, ...})| RefObject,// 计算 state 和 props 渲染pendingProps: any, // 本次渲染需要使用的 propsmemoizedProps: any, // 上次渲染使用的 propsupdateQueue: mixed, // 用于状态更新、回调函数、DOM更新的队列memoizedState: any, // 上次渲染后的 state 状态dependencies: Dependencies | null, // contexts、events 等依赖// Bitfield that describes properties about the fiber and its subtree. E.g.// the ConcurrentMode flag indicates whether the subtree should be async-by-// default. When a fiber is created, it inherits the mode of its// parent. Additional flags can be set at creation time, but after that the// value should remain unchanged throughout the fiber's lifetime, particularly// before its child fibers are created.mode: TypeOfMode,// Effectflags: Flags, // 记录更新时当前 fiber 的副作用(删除、更新、替换等)状态subtreeFlags: Flags, // 当前子树的副作用状态deletions: Array<Fiber> | null, // 要删除的子 fibernextEffect: Fiber | null, // 下一个有副作用的 fiberfirstEffect: Fiber | null, // 指向第一个有副作用的 fiberlastEffect: Fiber | null, // 指向最后一个有副作用的 fiber // 渲染优先级lanes: Lanes,childLanes: Lanes,// This is a pooled version of a Fiber. Every fiber that gets updated will// eventually have a pair. There are cases when we can clean up pairs to save// memory if we need to.alternate: Fiber | null,// 指向 workInProgress fiber 树中对应的节点// Time spent rendering this Fiber and its descendants for the current update.// This tells us how well the tree makes use of sCU for memoization.// It is reset to 0 each time we render and only updated when we don't bailout.// This field is only set when the enableProfilerTimer flag is enabled.actualDuration?: number,// If the Fiber is currently active in the "render" phase,// This marks the time at which the work began.// This field is only set when the enableProfilerTimer flag is enabled.actualStartTime?: number,// Duration of the most recent render time for this Fiber.// This value is not updated when we bailout for memoization purposes.// This field is only set when the enableProfilerTimer flag is enabled.selfBaseDuration?: number,// Sum of base times for all descendants of this Fiber.// This value bubbles up during the "complete" phase.// This field is only set when the enableProfilerTimer flag is enabled.treeBaseDuration?: number,// Conceptual aliases// workInProgress : Fiber ->  alternate The alternate used for reuse happens// to be the same as work in progress.// __DEV__ only_debugID?: number,_debugSource?: Source | null,_debugOwner?: Fiber | null,_debugIsCurrentlyTiming?: boolean,_debugNeedsRemount?: boolean,// Used to verify that the order of hooks does not change between renders._debugHookTypes?: Array<HookType> | null,
|};

相关参考视频讲解:进入学习

  • 整个fiber架构看起来可以分为dom信息、副作用、优先级、链表树等几个模块,那我们依次来拆分一下

dom信息节点

tag: WorkTag
我们看到这个`tag`为`WorkTag`类型,用来区分`React`组件的类型
// packages/react-reconciler/src/ReactWorkTags.js
export type WorkTag =| 0| 1| 2| 3| 4| 5| 6| 7| 8| 9| 10| 11| 12| 13| 14| 15| 16| 17| 18| 19| 20| 21| 22| 23| 24;export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const FundamentalComponent = 20;
export const ScopeComponent = 21;
export const Block = 22;
export const OffscreenComponent = 23;
export const LegacyHiddenComponent = 24;

上述代码,区分了组件的类型,在后期协调阶段beginWorkcompleteWork的流程里根据不同的类型组件去做不同的fiber节点的处理

key: null | string、type: any
key为唯一值,type为与fiber关联的节点类型,都用于beginWork流程里面的reconcileChildren流程
elementType
元素类型
stateNode
- stateNode 用于记录当前 fiber 所对应的真实 dom 节点或者当前虚拟组件的实例。
- 便于实现Ref
- 便于追踪Rdom

fiber 链表树

fiber链表树里面有四个字段returnchildsiblingindex

  • return:指向父节点,没有父节点则为null。
  • child:指向下一个子节点,没有下一个子节点则为null。
  • sibling:指向兄弟节点,没有下一个兄弟节点则为null。
  • index:父fiber下面的子fiber下标
    通过这些字段那么我们可以形成一个闭环链表,举个栗子。
<div className='box'><h1 className='title' style={{'color':'red'}}>React源码解析</h1><ul><li>第一章</li><li>第二章</li><li>第三章</li><li>第四章</li></ul>
</div>

根据上面的代码所对应的fiber链表树结构就是:

副作用相关

所谓副作用就是一套流程中我们不期望发生的情况。举个通俗的例子就是我们生活中去学游泳,在学会游泳的过程中呛了几口水,这个呛了几口水相对于成功学会游泳来说就是副作用,回归到react代码中,我们通过某些手段去修改propsstate等数据,数据修改完毕之后,但是同时引起了dom不必要的变化,那么这个变化就是副作用,当然这个副作用是必然存在的,就像游泳一样,必然会呛几口水,哈哈。

flags: Flags
记录当前节点通过`reconcileChildren`之后的的副作用,如插入,删除等

例如Placement,表示插入,也叫新增。Deletion表示删除,Update表示更新

export type Flags = number;// Don't change these two values. They're used by React Dev Tools.
export const NoFlags = /*                      */ 0b000000000000000000;
export const PerformedWork = /*                */ 0b000000000000000001;// You can change the rest (and add more).
export const Placement = /*                    */ 0b000000000000000010;
export const Update = /*                       */ 0b000000000000000100;
export const PlacementAndUpdate = /*           */ 0b000000000000000110;
export const Deletion = /*                     */ 0b000000000000001000;
export const ContentReset = /*                 */ 0b000000000000010000;
export const Callback = /*                     */ 0b000000000000100000;
export const DidCapture = /*                   */ 0b000000000001000000;
export const Ref = /*                          */ 0b000000000010000000;
export const Snapshot = /*                     */ 0b000000000100000000;
export const Passive = /*                      */ 0b000000001000000000;
// TODO (effects) Remove this bit once the new reconciler is synced to the old.
export const PassiveUnmountPendingDev = /*     */ 0b000010000000000000;
export const Hydrating = /*                    */ 0b000000010000000000;
export const HydratingAndUpdate = /*           */ 0b000000010000000100;// Passive & Update & Callback & Ref & Snapshot
export const LifecycleEffectMask = /*          */ 0b000000001110100100;// Union of all host effects
export const HostEffectMask = /*               */ 0b000000011111111111;// These are not really side effects, but we still reuse this field.
export const Incomplete = /*                   */ 0b000000100000000000;
export const ShouldCapture = /*                */ 0b000001000000000000;
export const ForceUpdateForLegacySuspense = /* */ 0b000100000000000000;// Static tags describe aspects of a fiber that are not specific to a render,
// e.g. a fiber uses a passive effect (even if there are no updates on this particular render).
// This enables us to defer more work in the unmount case,
// since we can defer traversing the tree during layout to look for Passive effects,
// and instead rely on the static flag as a signal that there may be cleanup work.
export const PassiveStatic = /*                */ 0b001000000000000000;// Union of side effect groupings as pertains to subtreeFlags
export const BeforeMutationMask = /*           */ 0b000000001100001010;
export const MutationMask = /*                 */ 0b000000010010011110;
export const LayoutMask = /*                   */ 0b000000000010100100;
export const PassiveMask = /*                  */ 0b000000001000001000;// Union of tags that don't get reset on clones.
// This allows certain concepts to persist without recalculting them,
// e.g. whether a subtree contains passive effects or portals.
export const StaticMask = /*                   */ 0b001000000000000000;// These flags allow us to traverse to fibers that have effects on mount
// without traversing the entire tree after every commit for
// double invoking
export const MountLayoutDev = /*               */ 0b010000000000000000;
export const MountPassiveDev = /*              */ 0b100000000000000000;

当然副作用不仅仅只是一个,所以React中在render阶段中采用的是深度遍历的策略去找出当前fiber树中所有的副作用,并维护一个副作用链表EffectList,与链表相关的字段还有firstEffectnextEffectlastEffect 我们来画一张图来简略示意一下。

解读一下就是,fristEffect指向第一个有副作用的fiber节点,lastEffect指向最后一个具有副作用的fiber节点,中间都是用nextEffect链接,这样组成了一个单向链表。
render阶段里面这一段处理就完了,在后面的commit阶段里面,React会根据EffectList里面fiber节点的副作用,会对应的处理相应的DOM,然后生成无副作用的虚拟节点,进行真实dom的创建。

优先级相关

当然React作为一个庞大的框架,肯定有自己的一套关于渲染的优先级机制,不然全都是一股脑按部就班的走,那肯定不行哒。

那么优先级我们就要关注一下lanealternateReact中每个fiber任务都有自己的lane(执行优先级),这样在render阶段react才知道,应该优先把哪个fiber任务提交到commit阶段去执行。而alternate是在render阶段中用来做为指针的,什么意思?React在状态发生改变的时候,就会根据当前的页面结构,生成两棵fiber树,一棵老的称之为current Fiber,而另一棵将要生成新的页面的树叫做workInProgress Fiber,而alternate作为指针,就是把current Fiber中的每一个节点指向workInProgress Fiber中的每一个节点。同样的他也会从workInProgress Fiber中指向 current Fiber

我们了解到了alternate,那就来说一说这个lane吧。

// packages/react-reconciler/src/ReactFiberLane.js
export const NoLanes: Lanes = /*                        */ 0b0000000000000000000000000000000;
export const NoLane: Lane = /*                          */ 0b0000000000000000000000000000000;export const SyncLane: Lane = /*                        */ 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /*                 */ 0b0000000000000000000000000000010;export const InputDiscreteHydrationLane: Lane = /*      */ 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /*                    */ 0b0000000000000000000000000011000;const InputContinuousHydrationLane: Lane = /*           */ 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /*                  */ 0b0000000000000000000000011000000;export const DefaultHydrationLane: Lane = /*            */ 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /*                   */ 0b0000000000000000000111000000000;const TransitionHydrationLane: Lane = /*                */ 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /*                       */ 0b0000000001111111110000000000000;const RetryLanes: Lanes = /*                            */ 0b0000011110000000000000000000000;export const SomeRetryLane: Lanes = /*                  */ 0b0000010000000000000000000000000;export const SelectiveHydrationLane: Lane = /*          */ 0b0000100000000000000000000000000;const NonIdleLanes = /*                                 */ 0b0000111111111111111111111111111;export const IdleHydrationLane: Lane = /*               */ 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /*                             */ 0b0110000000000000000000000000000;export const OffscreenLane: Lane = /*                   */ 0b1000000000000000000000000000000;

可见这个lane也是用31位二进制表示的唯一值,来进行优先级的判定的,并且位数越低,则优先级越大。

Props && State相关

pendingProps: any
本次渲染需要使用的 props
memoizedProps: any
上次渲染使用的 props
updateQueue: mixed
用于状态更新、回调函数、DOM更新的队列
memoizedState: any
上次渲染后的 state 状态
dependencies: Dependencies | null
contexts、events 等依赖

Fiber树的创建与更新的流程

上面一部分讲了React Fiber的基本架构,从真实dom信息副作用优先级等方面看了一下,为后面的render阶段协调调度以及commit阶段打下基础,那么接下来我们去探讨一下new FiberNode之后得到的什么样的rootFiber
我们在第二章节里面提到了整个的创建过程# React源码解析系列(二) – 初始化组件的创建更新流程,那么这里深入探讨一下createFiber,在这个函数里面new FiberNode,创建了rootFiber,他也就是整个React应用的的根fiber。并且在createFiberRoot里面new FiberRootNode,创建了fiberRoot,它便是指向真实dom的根节点。所以在# React源码解析系列(二) – 初始化组件的创建更新流程中我强调了root.currentuninitializedFiber.stateNode这两个东西,也就是这里说的rootFiberstateNode字段指向了 fiberRoot,并且fiberRoot的current指向了rootFiber,具体的示例图如下:

所以这里就完成了fiber树根节点的创建了。

拿到了上面创建完成的rootFiberfiberRoot之后那么我们接下来就是去根据我们的组件jsx去创建详细的dom树了,举个例子:

<div className='box'><h1 className='title' style={{'color':'red'}}>React源码解析</h1><ul><li>第一章</li><li>第二章</li><li>第三章</li><li>第四章</li></ul>
</div>

现有上面的jsx,那么我们创建dom树的形式是深度优先遍历,已beginworkcompletework表示一个节点的创建过程,流程如下:

上面的图说明了,在初始化的时候我们的dom树是怎么被创建出来的,那么在状态发生改变的时候,我们会根据当前新的jsx内容创建新的workInProgress fiber,我们以新的jsx为例:

<div className='box'>
<h1 className='title' style={{'color':'red'}}>React源码解析</h1>
+  <h1 className='title' style={{'color':'red'}}>React源码解析系列</h1><ul><li>第一章</li><li>第二章</li><li>第三章</li>
-    <li>第四章</li></ul>
+  <p>总结</p>
</div>

上面的jsx表示,更改了h1的内容,删除了第四章,增加了总结这几个操作,那么react根据当前新的jsx调用createWorkInProgress方法创建workInProgress fiber,那么我们先去看一下createWorkInProgress的源码实现。

export function createWorkInProgress(current: Fiber, pendingProps: any): Fiber {let workInProgress = current.alternate;if (workInProgress === null) { // null为初始化,否为update// We use a double buffering pooling technique because we know that we'll// only ever need at most two versions of a tree. We pool the "other" unused// node that we're free to reuse. This is lazily created to avoid allocating// extra objects for things that are never updated. It also allow us to// reclaim the extra memory if needed.workInProgress = createFiber(current.tag,pendingProps,current.key,current.mode,);workInProgress.elementType = current.elementType;workInProgress.type = current.type;workInProgress.stateNode = current.stateNode;if (__DEV__) {// DEV-only fieldsworkInProgress._debugID = current._debugID;workInProgress._debugSource = current._debugSource;workInProgress._debugOwner = current._debugOwner;workInProgress._debugHookTypes = current._debugHookTypes;}// current 指向 workInProgressworkInProgress.alternate = current;// workInProgress 指向 currentcurrent.alternate = workInProgress;} else {// 上一次的propsworkInProgress.pendingProps = pendingProps;// Needed because Blocks store data on type.workInProgress.type = current.type;// We already have an alternate.// Reset the effect tag.//清除flagsworkInProgress.flags = NoFlags;// The effect list is no longer valid.workInProgress.nextEffect = null;workInProgress.firstEffect = null;workInProgress.lastEffect = null;if (enableProfilerTimer) {// We intentionally reset, rather than copy, actualDuration & actualStartTime.// This prevents time from endlessly accumulating in new commits.// This has the downside of resetting values for different priority renders,// But works for yielding (the common case) and should support resuming.workInProgress.actualDuration = 0;workInProgress.actualStartTime = -1;}}// 绑定挂载的子fiber节点优先级、状态、propsworkInProgress.childLanes = current.childLanes;workInProgress.lanes = current.lanes;workInProgress.child = current.child;workInProgress.memoizedProps = current.memoizedProps;workInProgress.memoizedState = current.memoizedState;workInProgress.updateQueue = current.updateQueue;// Clone the dependencies object. This is mutated during the render phase, so// it cannot be shared with the current fiber.const currentDependencies = current.dependencies;workInProgress.dependencies =currentDependencies === null? null: {lanes: currentDependencies.lanes,firstContext: currentDependencies.firstContext,};// These will be overridden during the parent's reconciliationworkInProgress.sibling = current.sibling;workInProgress.index = current.index;workInProgress.ref = current.ref;if (enableProfilerTimer) {workInProgress.selfBaseDuration = current.selfBaseDuration;workInProgress.treeBaseDuration = current.treeBaseDuration;}if (__DEV__) {workInProgress._debugNeedsRemount = current._debugNeedsRemount;switch (workInProgress.tag) {case IndeterminateComponent:case FunctionComponent:case SimpleMemoComponent:workInProgress.type = resolveFunctionForHotReloading(current.type);break;case ClassComponent:workInProgress.type = resolveClassForHotReloading(current.type);break;case ForwardRef:workInProgress.type = resolveForwardRefForHotReloading(current.type);break;default:break;}}return workInProgress;
}

并且为其标记副作用,具体如下:

而前面所说的alternate在这里相互指向,其实也就是在reconciler阶段起到了复用节点的作用,因为我们所说的current fiber或者是workInProgress fiber都是视图的产物,是可以在"新"与"老"之间转换的。

为什么会出现Fiber架构呢?

相信在座的各位写React的同学出去面试,面试官总会问:”请问你知道React Fiber架构吗?请你说说Fiber架构吧“

为什么会出现?通过上面的React Fiber架构的讲解,我们可以get到几个点,那就是fiber针对每一个fiber节点都会有一套自己的独立的beginworkcompletework,并且能够在每一个具有副作用的节点上进行打标处理,而不是直接变更。而且生成的current fiberworkIProgress fiber可以相互转换,这里间接地可以称之为缓存吧。对比与以前的React应用来讲,以前的React应用是根据执行生命周期diffdom的更新一套流程同步走的,一套流程下来,不能中断,而且每一次的更新都是从根节点出发向下遍历的,我们可以设想一下处理庞大的结构的时候,那将是不可想象的性能开销,处理长时间任务耗时更长,更重要的是用户的交互,事件得不到及时响应,用户体验非常的差。

但是fiber这种结构,我们说的是一种时间分片的概念,通过时间分片把长任务,分成一个个独立的小单元去执行,返回。这样子就不会让js线程React应用独占,能有有空余去处理其他优先级较高的任务,任务得到了相应并且执行,当然了这种情况下页面就不会显得卡顿了。

所以总结来说就是React Fiber给我们提供了一种协调调度暂停中止调优的方式去更好的处理React应用浏览器的工作,保证了页面的性能与流畅度

总结

这一章讲述了整个的fiber架构与fiber树的创建与更新,那么这里从React应用的初始化挂载到React更新就形成了一部分的闭环完结,之后我们便是沿着流程走到了updateContainer更新这里

react源码中的fiber架构相关推荐

  1. # React源码解析之fiber的初次渲染与更新(下)

    React源码解析之fiber的初次渲染与更新(下) 经历一个月的学习整理,站在前人的肩膀上,对React有了一些浅薄的理解,希望记录自己的学习过程的同时也可以给大家带来一点小帮助.如果此系列文章对您 ...

  2. React 源码中的 Object.seal

    本文源码基于 React V18.8.6 源码 在 React 和 ReactROM 源码中,xx.development.js 代码都会使用到 Object.seal,主要是对 ref 做属性扩展限 ...

  3. 细说react源码中的合成事件

    React 的鲜活生命起源于 ReactDOM.render ,这个过程会为它的一生储备好很多必需品,我们顺着这个线索,一探婴儿般 React 应用诞生之初的悦然. 更新创建的操作我们总结为以下两种场 ...

  4. React源码解读之React Fiber

    开始之前,先讲一下该文章能帮你解决哪些问题? 开始之前,先讲一下该文章能帮你解决哪些问题? facebook为什么要使用重构React React Fiber是什么 React Fiber的核心算法 ...

  5. webuploader 怎么在react中_另辟蹊径搭建阅读React源码调试环境支持所有React版本细分文件断点调试...

    引言(为什么写这篇文章) 若要高效阅读和理解React源码,搭建调试环境是必不可少的一步.而常规方法:使用react.development.js和react-dom.development.js调试 ...

  6. React 源码系列 | React Context 详解

    目前来看 Context 是一个非常强大但是很多时候不会直接使用的 api.大多数项目不会直接使用 createContext 然后向下面传递数据,而是采用第三方库(react-redux). 想想项 ...

  7. React源码解毒 - render方法解析

    render方法解析 要将react元素渲染到页面当中,分为了两个阶段: render阶段 和 commit阶段. render阶段:由协调层负责的阶段 在这个阶段当中要为每一个react元素构建对应 ...

  8. react map循环生成的button_【第1945期】彻底搞懂React源码调度原理(Concurrent模式)...

    前言 估计会懵逼.今日早读文章由成都@苏溪云投稿分享. 正文从这开始~~ 最早之前,React还没有用fiber重写,那个时候对React调度模块就有好奇.而现在的调度模块对于之前没研究过它的我来说更 ...

  9. React 源码剖析系列 - 解密 setState

    this.setState() 方法应该是每一位使用 React 的同学最先熟悉的 API.然而,你真的了解 setState 么?先看看下面这个小问题,你能否正确回答. 引子 class Examp ...

最新文章

  1. 学习笔记 ACCESS 延迟注入
  2. leetcode:Minimum Depth of Binary Tree【Python版】
  3. SAP GUI和Windows注册表
  4. Java 9 新特性概述
  5. mysql中locat函数,MySQL中的LOCATE和POSITION函数使用方法 | 很文博客
  6. 小学计算机教师交流计划,2023年小学计算机教师工作计划
  7. spring学习8之JDBC的CRUD
  8. js怎么获取当前日期
  9. api文档 luci_开发OpenWrt路由器上LuCI的模块
  10. 什么是SUM函数以及如何在Python,Excel和Mathlab中使用?
  11. 片上总线Wishbone 学习(九)总线周期之单次读操作
  12. 线程,Java8,10个lambda表达式
  13. rac archiving
  14. 最通俗易懂---多分类学习之OvO、OvR、MvM
  15. 基于Cortex M0+的STM32L0系列简介
  16. Bootstrap 4中使用BootstrapTable时需要导入popper.js
  17. 数据字典在web中的简单应用
  18. 滴滴小程序框架Mpx2.0
  19. [开发技巧]·TopN指标计算方法
  20. 根据轨道根数来计算卫星位置

热门文章

  1. 计算机内存条能装几个,笔记本电脑可以装几个内存条_笔记本电脑最多可以插多少条内存-win7之家...
  2. 文献阅读 - Poisson Image Editing
  3. VMware虚拟机配置文件(.vmx)损坏修复
  4. linux PCIe api
  5. 给大伙儿盘一盘,那些提效/创意的 vscode 插件
  6. Excel 控件Spire.XLS 7.12.130发布 | 附下载
  7. 计算机网络速度怎么改,如何手动设置网卡的速度
  8. python汉字转拼音首字母_python获取一组汉字拼音首字母的方法
  9. python通过Tkinter库实现的一个简单的文本编辑器源码
  10. 华为鸿蒙系统开发语言,华为鸿蒙系统采用什么语言进行开发的