react源码渲染实现

react源码初次渲染

(1)帧
页面是一帧一帧绘制出来的,当每秒绘制的帧数(FPS)达到60时,页面是流畅的,小于这个值,用户会感觉到卡顿,所以每一帧分到的时间大概是16.6ms,这个和屏幕刷新率有关
javaScript执行javaScript引擎和页面渲染引擎在同一个渲染线程,GUI渲染和javaScript执行两者是互斥的。
在一帧里任务执行完处于空闲阶段,在空闲阶段去执行react任务
(2)fiber
react任务执行单元,将虚拟dom转成fiber
vue把每个更新任务分割的足够小,watch组件级更新
react任务还是很大,但是分割成多个小任务,可以中断和恢复,不阻塞主进程

实现

(1)页面效果

(2)代码实现
index.js

import React from "./react";
import ReactDOM from "./react-dom";

let style={border:'3px solid red',margin:'5px'}
let element = (
  <div id="A1" style={style}>
    <div id="B1" style={style}>
      <div id="C1" style={style}>C1</div>
      <div id="C2" style={style}>C2</div>
    </div>
    <div id="B2" style={style}>
      B2
    </div>
  </div>
);
ReactDOM.render(element, document.getElementById("root"));

react.js创建元素

import { ELEMENT_TEXT } from "./constant";
/**
 * 创建元素
 */
function createElement(type, config, ...children) {
  delete config._self;
  delete config._source;
  return {
    type,
    props: {
      ...config,//作兼容处理,如果是react元素就返回自己,如果是文本类型,如果是一个字符串的话,返回元素对象
      children: children.map((child) => {
        return typeof child === "object"
          ? child
          : {
              type: ELEMENT_TEXT,
              props: { text: child, children: [] },
            };
      }),
    },
  };
}
const React={
    createElement,
}
export default React;

react-dom.js 定义render


import {TAG_ROOT} from './constant'
import {scheduleRoot} from './schedule'
/**
 * render是要把一个元素渲染到一个容器内
 */
function render(element,container){
let rootFiber={
    tag:TAG_ROOT,//每个fiber会有一个tag标识比元素的类型
    stateNode:container,//一般情况下如果这个元素是一个原生节点的话,stateNode指向真实DOM元素
    // props.children是一个数组,里面放的是React元素 虚拟DOM 后面会根据每个React元素创建对应的fiber
    props:{
        children:[element]//这个fiber的属性对象children属性,里面放的是要渲染的元素
    }
}
scheduleRoot(rootFiber)
}
const ReactDOM={
    render
}
export default ReactDOM;

schedule.js 执行任务,当页面一帧的任务执行完且有空闲时间时,会调用requestIdleCallback方法去执行我们的react任务,workLoop有一个deadline参数,可判断是否有剩余时间,如果有剩余时间,则继续执行,如果没有,则中断任务交给浏览器去执行
performUnitOfWork()深度优先遍历树,创建fiber,当一个节点完成后,执行completeUnitOfWork()建立副作用链表,建立顺序和创建fiber一致,当render阶段结束,进入提交阶段commitRoot(),最后将链表清空

import {
  ELEMENT_TEXT,
  TAG_TEXT,
  TAG_HOST,
  PLACEMENT,
  TAG_ROOT,
} from "./constant";
import { setProps } from "./utils";
/**
 * 从根节点开始渲染和调度
 * 两个阶段
 * diff阶段 对比新旧的虚拟DOM 进行增量 更新或创建,
 * render阶段 这个阶段可能比较花时间,需要我们对任务进行拆分,拆分的维度虚拟DOM,此阶段可暂停
 * render阶段成果是effect list知道哪些节点更新哪些节点删除了哪些节点增加了
 * render阶段有两个任务,根据虚拟DOM生成fiber树,收集effectlist
 * commit阶段,进行DOM更新创建阶段,此阶段不可暂停,要一气呵成
 */
let nextUnitOfWork = null;
let workInProgressRoot = null;
export function scheduleRoot(rootFiber) {
  //{tag:TAG_ROOT,stateNode:container,props:{children:[element]}} 
  workInProgressRoot = rootFiber;
  nextUnitOfWork = rootFiber;
  
}

function performUnitOfWork(currentFiber) {
  beginWork(currentFiber);
  if (currentFiber.child) {
    return currentFiber.child;
  }
  while (currentFiber) {
    completeUnitOfWork(currentFiber); //没有儿子让自己完成
    if (currentFiber.sibling) {
      //看有没有弟弟
      return currentFiber.sibling; //有弟弟返回弟弟
    }
    currentFiber = currentFiber.return;
  }
}

/**
 *
 * 在完成的时候要收集有副作用的fiber,然后组成effect list
 * 每一个fiber有两个属性,firstEffect指向第一个有副作用的子fiber lastEffect指向最后一个有副作用的子fiber
 * 中间的用nextEffect做成一个单链表  firstEffect=直系儿子.nextEffect二儿子 lastEffect 最后一个儿子
 */
function completeUnitOfWork(currentFiber) {
  //第一个完成A1(TEXT)
  let returnFiber = currentFiber.return;
  if (returnFiber) {
    //把自己儿子的effect链挂到父亲身上
    if (!returnFiber.firstEffect) {
      returnFiber.firstEffect = currentFiber.firstEffect;
    }
    if (!!currentFiber.lastEffect) {
      if (!!returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = currentFiber.firstEffect;
      }
      returnFiber.lastEffect = currentFiber.lastEffect;
    }
    //把自己挂到父亲身上
    const effectTag = currentFiber.effectTag;
    if (effectTag) {
      //如果自己有副作用
      if (returnFiber.lastEffect) {
        returnFiber.lastEffect.nextEffect = currentFiber;
      } else {
        returnFiber.firstEffect = currentFiber;
      }
      returnFiber.lastEffect = currentFiber;
    }
  }
}

/**
 *
 * beginWork开始
 * completeUnitOfWork该节点完成
 * 1 创建真实DOM元素
 * 2 创建子fiber
 */

function beginWork(currentFiber) {
  if (currentFiber.tag === TAG_ROOT) {
    updateHostRoot(currentFiber);
  } else if (currentFiber.tag === TAG_TEXT) {
    updateHostText(currentFiber);
  } else if (currentFiber.tag === TAG_HOST) {
    updateHost(currentFiber);
  }
}
function updateHost(currentFiber) {
  if (!currentFiber.stateNode) {
    //如果比fiber没有创建DOM节点
    currentFiber.stateNode = createDOM(currentFiber);
  }
  const newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber, newChildren);
}
function createDOM(currentFiber) {
  if (currentFiber.tag === TAG_TEXT) {
    return document.createTextNode(currentFiber.props.text);
  } else if (currentFiber.tag === TAG_HOST) {
    //span div
    let stateNode = document.createElement(currentFiber.type);
    updateDOM(stateNode, {}, currentFiber.props);
    return stateNode;
  }
}
function updateDOM(stateNode, oldProps, newProps) {
  setProps(stateNode, oldProps, newProps);
}
function updateHostText(currentFiber) {
  if (!currentFiber.stateNode) {
    currentFiber.stateNode = createDOM(currentFiber);
  }
}

function updateHostRoot(currentFiber) {
  //先处理自己,如果是一个原生节点,创建真实DOM,2.创建子fiber
  //因为拿到的是根节点,所以直接取子节点
  let newChildren = currentFiber.props.children;
  reconcileChildren(currentFiber, newChildren);
}

function reconcileChildren(currentFiber, newChildren) {
  let newChildIndex = 0; //新子节点的索引
  let prevSibling; //上一个新的子fiber

  while (newChildIndex < newChildren.length) {
    let newChild = newChildren[newChildIndex]; //取出虚拟DOM节点
    let tag;
    if (newChild.type === ELEMENT_TEXT) {
      tag = TAG_TEXT; //这是一个文本节点
    } else if (typeof newChild.type === "string") {
      tag = TAG_HOST; //这是一个原生DOM节点
    }
    //beginWork创建fiber,在completeUnit
    let newFiber = {
      tag, //TAG_HOST
      type: newChild.type, //div
      props: newChild.props,
      stateNode: null, //div还没创建DOM元素
      return: currentFiber, //父fiber
      effectTag: PLACEMENT, //副作用标识 render我们要会收集副作用 增加 删除 更新
      nextEffect: null, //effect list也是一个单链表
      //effect list顺序和完成顺序是一样的,但是节点只放那些出钱的人的fiber节点,
    };
    if (newFiber) {
      if (newChildIndex === 0) {
        //如果索引为0,说明这是第一个儿子
        currentFiber.child = newFiber;
      } else {
        prevSibling.sibling = newFiber;
      }
      prevSibling = newFiber;
    }
    newChildIndex++;
  }
}

//循环执行工作 nextUnitWork
function workLoop(deadline) {
  let shouldYield = false; //是否要让出时间片或者控制权
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork); //执行完一个任务后
    shouldYield = deadline.timeRemaining() < 1; //没有时间的话就要让出控制权
  }
  if (!nextUnitOfWork && workInProgressRoot) {
    console.log("render阶段结束 ");
    commitRoot();
  }
  //不管有没有任务,都请求再次调度,每一帧都要执行一次workLoop
  requestIdleCallback(workLoop, { timeout: 500 });
}
function commitRoot() {
  let currentFiber = workInProgressRoot.firstEffect;
  while (currentFiber) {
    commitWork(currentFiber);
    currentFiber = currentFiber.nextEffect;
  }
  workInProgressRoot = null;
}
function commitWork(currentFiber) {
  if (!currentFiber) return;
  let returnFiber = currentFiber.return;
  let returnDOM = returnFiber.stateNode;
  if (currentFiber.effectTag === PLACEMENT) {
    returnDOM.appendChild(currentFiber.stateNode);
  }
  currentFiber.effectTag = null;
}

//react告诉浏览器,我现在有任务请你在闲的时候
//有一个优先级的概念 expirationTime
requestIdleCallback(workLoop, { timeout: 500 });

constant.js

export const ELEMENT_TEXT=Symbol.for("ELEMENT_TEXT");  //文本元素
export const TAG_ROOT=Symbol.for("TAG_ROOT"); //根fiber
export const TAG_HOST=Symbol.for("TAG_HOST"); //原生节点,span div 函数组件 ,类组件
export const TAG_TEXT=Symbol.for("TAG_TEXT"); //文本节点
export const PLACEMENT=Symbol.for("PLACEMENT"); //插入节点
export const UPDATE=Symbol.for("UPDATE"); //更新节点
export const DELETION=Symbol.for("DELETION"); //删除节点

utils.js

export function setProps(dom,oldProps,newProps){
    // for(let key in oldProps){

    // }
    for(let key in newProps){
        if(key!=='children'){
            setProp(dom,key,newProps[key]);
        }
    }
}
function setProp(dom,key,value){
    if(/^on/.test(key)){
        dom[key.toLowerCase()]=value;
    }else if(key==='style'){
        if(value){
            for(let styleName in value){
                dom.style[styleName]=value[styleName];
            }

        }else{
            dom.setAttribute(key,value)
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
### 回答1: java mysql react 是三种不同的技术或工具。 Java 是一种广泛使用的编程语言,具有跨平台性和面向对象的特点。它是一种静态类型的编程语言,用于开发各种类型的应用程序,包括桌面应用程序、移动应用程序和服务端应用程序。Java 码是开发人员使用 Java 编写的代码,用于创建和构建应用程序。 MySQL 是一种关系型数据库管理系统,被广泛应用于网站和应用程序的后端数据存储和管理。它是一个开的数据库系统,具有高性能和可靠性。MySQL 码是用 C 和 C++ 编程语言编写的,并实现了 SQL 语言的标准。 React 是一个用于构建用户界面的 JavaScript 库。它由 Facebook 开发,用于构建单页应用程序和可重用的 UI 组件。React码是使用 JavaScript 编写的,它实现了虚拟 DOM 和组件化的思想,以提供高效的页面渲染和可维护的代码。 Java、MySQL 和 React 都具有广泛的应用和广泛的社区支持。开发人员可以使用 Java 编写后端代码,使用 MySQL 存储和管理数据,在前端使用 React 构建用户界面。这三种技术或工具的码都是公开的,任何人都可以查看和学习,以满足不同的开发需求。 ### 回答2: Java是一种广泛应用于开发各类应用程序的编程语言,具有跨平台特性和面向对象的编程风格。通过Java,开发者可以编写服务器端程序、移动应用、桌面应用等。 MySQL是一种流行的开关系型数据库管理系统,它支持多线程处理和事务处理,并提供了可靠的数据存储、备份和恢复功能。通过MySQL,开发者可以使用结构化查询语言(SQL)来管理和操作数据。 React是一种用于构建用户界面的JavaScript库,它采用了组件化的开发模式,使得开发者能够通过创建可复用的UI组件来构建复杂的Web应用程序。React还提供了虚拟DOM(虚拟文档对象模型)概念,可以有效地更新和渲染视图,提升应用程序的性能。 码是指软件或程序的原始代码,是开发者编写程序的基础。Java、MySQL和React都有对应的码供开发者参考和学习。 Java的码包含了标准库、API和核心类库等,开发者可以通过查看Java码来理解Java的底层实现原理,掌握编程技巧和最佳实践。 MySQL的码是开的,开发者可以自由访问和修改。通过阅读MySQL码,开发者可以深入了解数据库管理系统的设计思路、底层实现和优化策略,从而提升对数据库性能和扩展性的理解和控制。 React码也是开的,通过查看React码,开发者可以学习到React的内部工作原理、虚拟DOM的实现、组件化开发模式等。同时,React码也是一个很好的学习案例,可以帮助开发者提升自己的代码质量和设计能力。 总之,Java、MySQL和React码对于开发者来说是非常宝贵的资,通过学习和理解码,开发者能够深入了解这些技术背后的原理和机制,并能够更好地应用和优化这些技术。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值