Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

RectDOM.render 发生了什么 #68

Open
MengZhaoFly opened this issue May 28, 2020 · 0 comments
Open

RectDOM.render 发生了什么 #68

MengZhaoFly opened this issue May 28, 2020 · 0 comments

Comments

@MengZhaoFly
Copy link
Owner

MengZhaoFly commented May 28, 2020

本文基于 React 16.8.6 版本创作

React element

jsx 都会被 babel 编译成 createElement 函数,该函数会创建一个 react element,一个 element 如下:

{
    // This tag allows us to uniquely identify this as a React Element
    $$typeof: REACT_ELEMENT_TYPE,
    // Built-in properties that belong on the element
    type: type,
    key: key,
    ref: ref,
    props: props,

    // Record the component responsible for creating this element.
    _owner: owner,
  };

上文中 element 提到的 $$typeof 字段,还有如下类型:

  • REACT_ELEMENT_TYPE
  • REACT_PORTAL_TYPE
  • REACT_FRAGMENT_TYPE
  • REACT_STRICT_MODE_TYPE
  • REACT_PROFILER_TYPE
  • REACT_PROVIDER_TYPE
  • REACT_CONTEXT_TYPE
  • REACT_ASYNC_MODE_TYPE
  • REACT_CONCURRENT_MODE_TYPE
  • REACT_FORWARD_REF_TYPE
  • REACT_SUSPENSE_TYPE

ReactDOM.render(<App />, document.getElementById('container')) 为例,
type 值就为 一个 function App()
type指代这个ReactElement的类型:

  • 比如div,p代表原生DOM,称为HostComponent

React 中的更新

让组件发生渲染,无非就是下列几种情况:

  • ReactDOM.render
  • setState
  • forceUpdate
    其他的一些 hook,在 React 内部对这种行为进行了抽象,为此 新起了一个机制,叫做 Update
    调用 setState,render 都会产生一个 Update ,一个 Update 如下所示
{
    // 过期时间
    expirationTime: expirationTime,
    // export const UpdateState = 0; 表示更新State
    // export const ReplaceState = 1; 表示替换State
    // export const ForceUpdate = 2; 强制更新
    // export const CaptureUpdate = 3; 捕获更新(发生异常错误的时候发生)
    // 指定更新的类型,值为以上几种
    tag: UpdateState,
    // 更新内容,比如`setState`接收的第一个参数
    payload: null,
    // 更新完成后的回调,比如 `setState`,`render`的回调
    callback: null,

    next: null,
    nextEffect: null,
  };

在上文中的 render(<App />), 就会创建一个更新,并且把 当前 App 组件当做 payload。
伪代码如下:

var update = createUpdate(expirationTime);
// being called "element".
update.payload = { element: element };

Fiber

在上文中,还会为 App 创建对应的 Fiber 节点,创建完 Update,就会将该 Fiber 节点调度更新。

enqueueUpdate(current, update);
scheduleWork(current, expirationTime);

enqueueUpdate 为 FIber 节点添加一个 更新队列(updateQueue)的属性,为一个队列,专门存储上文提到的 一个个 Update。

image

scheduleWork 先找到 fiber root,本文中即对应挂载节点,

{
  containerInfo: div#container
}

由于 第一次 render 会选取 performSyncWork,以同步的方式进行渲染,让用户以最快的时间看到内容。从顶层节点开始。不过,React 会略过已经处理过的 Fiber 节点,直到找到未完成工作的节点。例如,如果在组件树中的深层组件中调用 setState 方法,则 React 将从顶部开始,但会快速跳过各个父项,直到它到达调用了 setState 方法的组件。

performWorkOnRoot

设置 isRendering = true; 代表进入 render 阶段。
采用深度优先遍历,从上往下生成子 Fiber,这里也是大名鼎鼎的 work loop 在这进行循环单元更新。并且进行善后处理。

do {
    try {
      workLoop(isYieldy);
    } catch (thrownValue) {
   // 
   }
} while (true)

workLoop 如下,

function workLoop(isYieldy) {
  if (!isYieldy) {
    // Flush work without yielding
    // 不可中断
    while (nextUnitOfWork !== null) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  } else {
    while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
      nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    }
  }
}

这里这是一个循环处理的策略,
如果你不了解 workLoop,可以查看:https://stackblitz.com/edit/js-ntqfil?file=index.js,
此链接来源于译文:https://zhuanlan.zhihu.com/p/51483167。
beginWork 根据不同的组件执行不同的渲染,

向下遍历 JSX,为每个 JSX 节点的子 JSX 节点生成对应的 Fiber,并且记上一个 tag

Fiber.effectTag = Placement

commit 阶段

render 阶段有 2 棵树和副作用列表。第一个树表示当前在屏幕上渲染的状态,然后在 render 阶段会构建一个备用树。它在源代码中称为 finishedWork 或 workInProgress,表示需要映射到屏幕上的状态
我们已经知道哪些 Fiber 会打上 Fiber.effectTag, 插入、更新或删除的节点。
有了 effectList 就能渲染页面。
在 commit 阶段运行的主要函数是 commitRoot , 它会根据不同的 tag 执行不同的行为。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant