React原理学习–Build your own React

React原理学习–Build your own React

这是一篇基于Rodrigo Pombo的博客Build your own React 简要了解React运作原理的学习笔记,React16.8版本。这篇笔记只是为了方便之后复习所作,整体基本是翻译原文,原文中代码会跟随设计思路渐近切换和高亮,有更好的交互体验,强烈建议看下原文。前端小小白,有问题请大家指正!!

Step 0 :了解React、JSX和DOM的运作关系

首先忽略React的一些相关细节代码,从最简单的以下3行代码切入:

//生成一个h1节点添加到root节点下
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)

JSX一种JavaScript语法扩展,在React中可以方便地用来显式定义UI。本质上,JSX为我们提供了创建React元素方法(React.createElement(component, props, ...children))的语法糖。

首先第一行用JSX语法生成了一个element(为了便于区分,React构建的元素称为element,DOM 元素称为node),经过Babel编译后变为JS。编译过程就是将JSX表达式中的tag name,props,children等作为参数调用React.creactElement函数生成React元素。

//JSX语法编译过程
const element = <h1 title="foo">Hello</h1>
            |
            |
            V
const element = React.createElement(
  "h1", //tag name
  {
    title: "foo" }, //props
  "Hello" //children
)
//最终函数生成的对象
const element = {
   
  type: "h1",
  props: {
   
    title: "foo",
    children: "Hello",
  },
}

最终生成的React element就是一个包含两个属性type,props的对象(还包含其他属性,但暂时只关心这两个属性)。其中type可以是一个string,对应DOM node名称以及我们使用传统js方法document.createElement时传入的tagName。type也可以是一个function,但到Step VIII再解释。

props是一个对象属性,包含了JSX语法传入的所有属性的键值对以及children。在这个例子中children是一个字符串“Hello”,但通常是一个array,里面包含其他的React Elements,由于这样的嵌套关系每个React element也是一棵树。

除了第一行的JSX语法,第3行也是React代码,render的过程是React改变DOM的过程,它的编译过程简化如下:

ReactDOM.render(element, container)
            |
            |
            V
const node = document.createElement(element.type) // 根据第一行中生成的element对象中的type属性创建DOM node,'h1'
node["title"] = element.props.title // 'foo' 绑定各个属性

//创建node的children
//这里使用creatTextNode的方式而不是innerText的方式,保证我们接下来对其他类型的孩子元素也可以用相同的方式创建
const text = document.createTextNode("")
text["nodeValue"] = element.props.children

//将构建的DOM nodes依次组装起来添加到定义的container中
node.appendChild(text)
container.appendChild(node)

到这里我们将所有React相关的语句都编译成了纯JS的操作。

Step I: creatElement实现

在前1步中我们了解了JSX语句编译过程中调用creatElement函数生成了一个包含type和props属性的JS对象。实现creatElement就是构建这个对象的过程。

const element = (
  <div id="foo">
    <a>bar</a>
    <b />
  </div>
)
            |
            |
            V
const element = React.createElement(
  "div",
  {
    id: "foo" },
  React.createElement("a", null, "bar"),
  React.createElement("b")
)

function createElement(type, props, ...children) {
   
  return {
   
    type,
    props: {
   
      ...props,
      children,
    },
  }
}

用扩展操作符…对props进行扩展,用rest运算符收集children元素,这样element元素中props属性的children属性就能对应一个array。

element的children可以是element或者string或者number,在上面的代码中我们只包含了element的情况,还需要为字符串和数字类型的children创建一种生成方法。

function createElement(type, props, ...children) {
   
  return {
   
    type,
    props: {
   
      ...props,
		children: children.map(child =>
        typeof child === "object"
          ? child
          : createTextElement(child)
      ),
    },
  }
}

function createTextElement(text) {
   
  return {
   
    type: "TEXT_ELEMENT",
    props: {
   
      nodeValue: text,
      children: [],
    },
  }
}

==注意:==React中并不会包装原始值或在没用children的情况下创建空数组,但在此篇教程中为了简化实现,便于理解原文做了上述功能。接下来我们将用我们自己实现的类React功能的库Didact进行讲解和进一步实现。

const Didact = {
   
  createElement,
}

const element = Didact.createElement(
  "div",
  {
    id: "foo" },
  Didact.createElement("a", null, "bar"),
  Didact.createElement("b")
)

Step II: render初步实现

这一步中将实现我们自己的ReactDOM.render函数,这里只关注DOM添加的操作,更新和删除的步骤下面的步骤中再介绍。

render函数中,用传进的element.type属性创建一个DOM node并添加这个新建节点到container中,对每个孩子元素将递归调用这个步骤。

function render(element, container) {
   
  //根据emlement.type创建DOM元素
  const dom =
    element.type == "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(element.type)
 
  //props赋值给新建的DOM node
  const isProperty = key => key !== "children"
  Object.keys(element.props)
    .filter(isProperty)
    .forEach(name => {
   
      dom[name] = element.props[name]
    })
  
  //对element的每个children递归调用render生成DOM元素并组装成树
  element.props.children.forEach(child =>
    render(child, dom)
  )

  container.appendChild(dom)
}

Step III:unit式render

在编写更多功能前,我们还需要对上一步中的render进行完善,因为render中children是递归生成的,我们无法中断这个过程,只能等到整棵树构建完成。

如果这棵树很大,那么它的构建将阻断主线程较长时间,而当主线程中有诸如处理用户输入,保证动画平滑等需要较高优先级的操作时这样的等待是不利的。

所以我们将这整个render过程分成许多小单元(unit),每个小单元执行完如果主线程有操作需要执行,则将会允许中断渲染。

let nextUnitOfWork = null

function workLoop(deadline) {
   
  let shouldYield = false
  //render整个过程被分为许多unit,每执行完一个unit判断是否到允许中断的时间
  //当render完毕(没有unit)或到系统中断时间跳出循环
  while (nextUnitOfWork && !shouldYield) {
   
      //操作当前unit并返回下一个work unit
    nextUnitOfWork = performUnitOfWork(
      nextUnitOfWork
    )
    shouldYield = deadline.timeRemaining() < 1
  }
    //control交给浏览器主线程,实现浏览器主线程同render操作间的交替循环
  requestIdleCallback(workLoop)
}

//可以看作是一个没有明确时延的setTimeout,当主线程空闲时它将会执行回调函数
requestIdleCallback
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值