Web前端最全React16源码解读:揭秘ReactDOM(1),2024年最新腾讯+华为+阿里面试真题分享

总结一下

面试前要精心做好准备,简历上写的知识点和原理都需要准备好,项目上多想想难点和亮点,这是面试时能和别人不一样的地方。

还有就是表现出自己的谦虚好学,以及对于未来持续进阶的规划,企业招人更偏爱稳定的人。

万事开头难,但是程序员这一条路坚持几年后发展空间还是非常大的,一切重在坚持。

开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

为了帮助大家更好更高效的准备面试,特别整理了《前端工程师面试手册》电子稿文件。

前端面试题汇总

// 循环遍历每个子节点进行删除

while ((rootSibling = container.lastChild)) {

container.removeChild(rootSibling);

}

}

// Legacy roots are not batched.

// 返回一个ReactSyncRoot实例

// 该实例具有一个_internalRoot属性指向fiberRoot

return new ReactSyncRoot(

container,

LegacyRoot,

shouldHydrate

? {

hydrate: true,

}
undefined,

);

}

/**

  • 根据nodeType和attribute判断是否需要融合

  • @param container DOM容器

  • @returns {boolean}

*/

function shouldHydrateDueToLegacyHeuristic(container) {

const rootElement = getReactRootElementInContainer(container);

return !!(

rootElement &&

rootElement.nodeType === ELEMENT_NODE &&

rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)

);

}

/**

  • 根据container来获取DOM容器中的第一个子节点

  • @param container DOM容器

  • @returns {*}

*/

function getReactRootElementInContainer(container: any) {

if (!container) {

return null;

}

if (container.nodeType === DOCUMENT_NODE) {

return container.documentElement;

} else {

return container.firstChild;

}

}

其中在shouldHydrateDueToLegacyHeuristic方法中,首先根据container来获取 DOM 容器中的第一个子节点,获取该子节点的目的在于通过节点的nodeType和是否具有ROOT_ATTRIBUTE_NAME属性来区分是客户端渲染还是服务端渲染,ROOT_ATTRIBUTE_NAME位于packages/react-dom/src/shared/DOMProperty.js文件中,表示data-reactroot属性。我们知道,在服务端渲染中有别于客户端渲染的是,node服务会在后台先根据匹配到的路由生成完整的HTML字符串,然后再将HTML字符串发送到浏览器端,最终生成的HTML结构简化后如下:

在客户端渲染中是没有data-reactroot属性的,因此就可以区分出客户端渲染和服务端渲染。在 React 中的nodeType主要包含了五种,其对应的值和W3C中的nodeType标准是保持一致的,位于与DOMProperty.js同级的HTMLNodeType.js文件中:

// 代表元素节点

export const ELEMENT_NODE = 1;

// 代表文本节点

export const TEXT_NODE = 3;

// 代表注释节点

export const COMMENT_NODE = 8;

// 代表整个文档,即document

export const DOCUMENT_NODE = 9;

// 代表文档片段节点

export const DOCUMENT_FRAGMENT_NODE = 11;

经过以上分析,现在我们就可以很容易地区分出客户端渲染和服务端渲染,并且在面试中如果被问到两种渲染模式的区别,我们就可以很轻松地在源码级别上说出两者的实现差异,让面试官眼前一亮。怎么样,到目前为止,其实还是觉得挺简单的吧?

FiberRoot VS RootFiber


在这一小节中,我们将尝试去理解两个比较容易混淆的概念:FiberRootRootFiber。这两个概念在 React 的整个任务调度过程中起着关键性的作用,如果不理解这两个概念,后续的任务调度过程就是空谈,所以这里也是我们必须要去理解的部分。接下来接着上一小节的内容,继续分析legacyCreateRootFromDOMContainer方法中的剩余内容,在函数体的结尾返回了一个ReactSyncRoot实例,我们重新回到ReactDOM.js文件可以很容易找到ReactSyncRoot构造函数的具体内容:

/**

  • ReactSyncRoot构造函数

  • @param container DOM容器

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @param options 配置信息,只有在hydrate时才有值,否则为undefined

  • @constructor

*/

function ReactSyncRoot(

container: DOMContainer,

tag: RootTag,

options: void | RootOptions,

) {

this._internalRoot = createRootImpl(container, tag, options);

}

/**

  • 创建并返回一个fiberRoot

  • @param container DOM容器

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @param options 配置信息,只有在hydrate时才有值,否则为undefined

  • @returns {*}

*/

function createRootImpl(

container: DOMContainer,

tag: RootTag,

options: void | RootOptions,

) {

// Tag is either LegacyRoot or Concurrent Root

// 判断是否是hydrate模式

const hydrate = options != null && options.hydrate === true;

const hydrationCallbacks =

(options != null && options.hydrationOptions) || null;

// 创建一个fiberRoot

const root = createContainer(container, tag, hydrate, hydrationCallbacks);

// 给container附加一个内部属性用于指向fiberRoot的current属性对应的rootFiber节点

markContainerAsRoot(root.current, container);

if (hydrate && tag !== LegacyRoot) {

const doc =

container.nodeType === DOCUMENT_NODE

? container
container.ownerDocument;

eagerlyTrapReplayableEvents(doc);

}

return root;

}

从上述源码中,我们可以看到createRootImpl方法通过调用createContainer方法来创建一个fiberRoot实例,并将该实例返回并赋值到ReactSyncRoot构造函数的内部成员_internalRoot属性上。我们继续深入createContainer方法去探究一下fiberRoot完整的创建过程,该方法被抽取到与react-dom包同级的另一个相关的依赖包react-reconciler包中,然后定位到react-reconciler/src/ReactFiberReconciler.js的第299行:

/**

  • 内部调用createFiberRoot方法返回一个fiberRoot实例

  • @param containerInfo DOM容器

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @param hydrate 判断是否是hydrate模式

  • @param hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted

  • @returns {FiberRoot}

*/

export function createContainer(

containerInfo: Container,

tag: RootTag,

hydrate: boolean,

hydrationCallbacks: null | SuspenseHydrationCallbacks,

): OpaqueRoot {

return createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks);

}

/**

  • 创建fiberRoot和rootFiber并相互引用

  • @param containerInfo DOM容器

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @param hydrate 判断是否是hydrate模式

  • @param hydrationCallbacks 只有在hydrate模式时才可能有值,该对象包含两个可选的方法:onHydrated和onDeleted

  • @returns {FiberRoot}

*/

export function createFiberRoot(

containerInfo: any,

tag: RootTag,

hydrate: boolean,

hydrationCallbacks: null | SuspenseHydrationCallbacks,

): FiberRoot {

// 通过FiberRootNode构造函数创建一个fiberRoot实例

const root: FiberRoot = (new FiberRootNode(containerInfo, tag, hydrate): any);

if (enableSuspenseCallback) {

root.hydrationCallbacks = hydrationCallbacks;

}

// Cyclic construction. This cheats the type system right now because

// stateNode is any.

// 通过createHostRootFiber方法创建fiber tree的根节点,即rootFiber

// 需要留意的是,fiber节点也会像DOM树结构一样形成一个fiber tree单链表树结构

// 每个DOM节点或者组件都会生成一个与之对应的fiber节点(生成的过程会在后续的文章中进行解读)

// 在后续的调和(reconciliation)阶段起着至关重要的作用

const uninitializedFiber = createHostRootFiber(tag);

// 创建完rootFiber之后,会将fiberRoot实例的current属性指向刚创建的rootFiber

root.current = uninitializedFiber;

// 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用

uninitializedFiber.stateNode = root;

// 最后将创建的fiberRoot实例返回

return root;

}

一个完整的FiberRootNode实例包含了很多有用的属性,这些属性在任务调度阶段都发挥着各自的作用,可以在ReactFiberRoot.js文件中看到完整的FiberRootNode构造函数的实现(这里只列举部分属性):

/**

  • FiberRootNode构造函数

  • @param containerInfo DOM容器

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @param hydrate 判断是否是hydrate模式

  • @constructor

*/

function FiberRootNode(containerInfo, tag, hydrate) {

// 用于标记fiberRoot的类型

this.tag = tag;

// 指向当前激活的与之对应的rootFiber节点

this.current = null;

// 和fiberRoot关联的DOM容器的相关信息

this.containerInfo = containerInfo;

// 当前的fiberRoot是否处于hydrate模式

this.hydrate = hydrate;

// 每个fiberRoot实例上都只会维护一个任务,该任务保存在callbackNode属性中

this.callbackNode = null;

// 当前任务的优先级

this.callbackPriority = NoPriority;

}

部分属性信息如上所示,由于属性过多并且在本文中暂时还用不到,这里就先不一一列举出来了,剩余的属性及其注释信息已经上传至Github(https://github.com/qq591468061/react-16.10.2),感兴趣的朋友可以自行查看。在了解完了fiberRoot的属性结构之后,接下来继续探究createFiberRoot方法的后半部分内容:

// 以下代码来自上文中的createFiberRoot方法

// 通过createHostRootFiber方法创建fiber tree的根节点,即rootFiber

const uninitializedFiber = createHostRootFiber(tag);

// 创建完rootFiber之后,会将fiberRoot实例的current属性指向刚创建的rootFiber

root.current = uninitializedFiber;

// 同时rootFiber的stateNode属性会指向fiberRoot实例,形成相互引用

uninitializedFiber.stateNode = root;

// 以下代码来自ReactFiber.js文件

/**

  • 内部调用createFiber方法创建一个FiberNode实例

  • @param tag fiberRoot节点的标记(LegacyRoot、BatchedRoot、ConcurrentRoot)

  • @returns {Fiber}

*/

export function createHostRootFiber(tag: RootTag): Fiber {

let mode;

// 以下代码根据fiberRoot的标记类型来动态设置rootFiber的mode属性

// export const NoMode = 0b0000; => 0

// export const StrictMode = 0b0001; => 1

// export const BatchedMode = 0b0010; => 2

// export const ConcurrentMode = 0b0100; => 4

// export const ProfileMode = 0b1000; => 8

if (tag === ConcurrentRoot) {

mode = ConcurrentMode | BatchedMode | StrictMode;

} else if (tag === BatchedRoot) {

mode = BatchedMode | StrictMode;

} else {

mode = NoMode;

}

// 调用createFiber方法创建并返回一个FiberNode实例

// HostRoot表示fiber tree的根节点

// 其他标记类型可以在shared/ReactWorkTags.js文件中找到

return createFiber(HostRoot, null, null, mode);

}

/**

  • 创建并返回一个FiberNode实例

  • @param tag 用于标记fiber节点的类型(所有的类型存放在shared/ReactWorkTags.js文件中)

  • @param pendingProps 表示待处理的props数据

  • @param key 用于唯一标识一个fiber节点(特别在一些列表数据结构中,一般会要求为每个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场)

  • @param mode 表示fiber节点的模式

  • @returns {FiberNode}

*/

const createFiber = function(

tag: WorkTag,

pendingProps: mixed,

key: null | string,

mode: TypeOfMode,

): Fiber {

// $FlowFixMe: the shapes are exact here but Flow doesn’t like constructors

// FiberNode构造函数用于创建一个FiberNode实例,即一个fiber节点

return new FiberNode(tag, pendingProps, key, mode);

};

至此我们就成功地创建了一个fiber节点,上文中我们提到过,和 DOM 树结构类似,fiber节点也会形成一个与 DOM 树结构对应的fiber tree,并且是基于单链表的树结构,我们在上面刚创建的fiber节点可作为整个fiber tree的根节点,即RootFiber节点。在目前阶段,我们暂时不用关心一个fiber节点所包含的所有属性,但可以稍微留意一下以下相关属性:

/**

  • FiberNode构造函数

  • @param tag 用于标记fiber节点的类型

  • @param pendingProps 表示待处理的props数据

  • @param key 用于唯一标识一个fiber节点(特别在一些列表数据结构中,一般会要求为每个DOM节点或组件加上额外的key属性,在后续的调和阶段会派上用场)

  • @param mode 表示fiber节点的模式

  • @constructor

*/

function FiberNode(

tag: WorkTag,

pendingProps: mixed,

key: null | string,

mode: TypeOfMode,

) {

// Instance

// 用于标记fiber节点的类型

this.tag = tag;

// 用于唯一标识一个fiber节点

this.key = key;

// 对于rootFiber节点而言,stateNode属性指向对应的fiberRoot节点

// 对于child fiber节点而言,stateNode属性指向对应的组件实例

this.stateNode = null;

// Fiber

// 以下属性创建单链表树结构

// return属性始终指向父节点

// child属性始终指向第一个子节点

// sibling属性始终指向第一个兄弟节点

this.return = null;

this.child = null;

this.sibling = null;

// index属性表示当前fiber节点的索引

this.index = 0;

// 表示待处理的props数据

this.pendingProps = pendingProps;

// 表示之前已经存储的props数据

this.memoizedProps = null;

// 表示更新队列

// 例如在常见的setState操作中

// 其实会先将需要更新的数据存放到这里的updateQueue队列中用于后续调度

this.updateQueue = null;

// 表示之前已经存储的state数据

this.memoizedState = null;

// 表示fiber节点的模式

this.mode = mode;

// 表示当前更新任务的过期时间,即在该时间之后更新任务将会被完成

this.expirationTime = NoWork;

// 表示当前fiber节点的子fiber节点中具有最高优先级的任务的过期时间

// 该属性的值会根据子fiber节点中的任务优先级进行动态调整

this.childExpirationTime = NoWork;

// 用于指向另一个fiber节点

// 这两个fiber节点使用alternate属性相互引用,形成双缓冲

// alternate属性指向的fiber节点在任务调度中又称为workInProgress节点

this.alternate = null;

}

其他有用的属性笔者已经在源码中写好相关注释,感兴趣的朋友可以在Github上查看完整的注释信息帮助理解。当然在现阶段,其中的一些属性还暂时难以理解,不过没有关系,在后续的内容和系列文章中将会逐个击破。在本小节中我们主要是为了理解FiberRootRootFiber这两个容易混淆的概念以及两者之间的联系。同时在这里我们需要特别注意的是,多个fiber节点可形成基于单链表的树形结构,通过自身的returnchildsibling属性可以在多个fiber节点之间建立联系。为了更加容易理解多个fiber节点及其属性之间的关系,这里先回顾一下在上一篇文章中的简单示例,我们在src/App.js文件中将create-react-app脚手架生成的默认根组件App修改为如下形式:

import React, {Component} from ‘react’;

function List({data}) {

return (

    {

    data.map(item => {

    return

    • {item}
    • })

      }

      最后

      开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

      其他有用的属性笔者已经在源码中写好相关注释,感兴趣的朋友可以在Github上查看完整的注释信息帮助理解。当然在现阶段,其中的一些属性还暂时难以理解,不过没有关系,在后续的内容和系列文章中将会逐个击破。在本小节中我们主要是为了理解FiberRootRootFiber这两个容易混淆的概念以及两者之间的联系。同时在这里我们需要特别注意的是,多个fiber节点可形成基于单链表的树形结构,通过自身的returnchildsibling属性可以在多个fiber节点之间建立联系。为了更加容易理解多个fiber节点及其属性之间的关系,这里先回顾一下在上一篇文章中的简单示例,我们在src/App.js文件中将create-react-app脚手架生成的默认根组件App修改为如下形式:

      import React, {Component} from ‘react’;

      function List({data}) {

      return (

        {

        data.map(item => {

        return

      • {item}
      • })

        }

        最后

        开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】

        [外链图片转存中…(img-yJCh6dTW-1715860745920)]

      • 30
        点赞
      • 17
        收藏
        觉得还不错? 一键收藏
      • 0
        评论

      “相关推荐”对你有帮助么?

      • 非常没帮助
      • 没帮助
      • 一般
      • 有帮助
      • 非常有帮助
      提交
      评论
      添加红包

      请填写红包祝福语或标题

      红包个数最小为10个

      红包金额最低5元

      当前余额3.43前往充值 >
      需支付:10.00
      成就一亿技术人!
      领取后你会自动成为博主和红包主的粉丝 规则
      hope_wisdom
      发出的红包
      实付
      使用余额支付
      点击重新获取
      扫码支付
      钱包余额 0

      抵扣说明:

      1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
      2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

      余额充值