【React 原理(一)】实现 createElement 和 render 方法

前言

在 React 中,咱们都知道能够写 jsx 代码会被编译成真正的 DOM 插入到要显示的页面上。这具体是怎么实现的,今天咱们就本身动手作一下。javascript

实现 createElement 方法

这个方法平时开发咱们并不会用到,由于它是经 babel 编译后的代码,咱们新建一个 React 项目,index.js 最简单的代码结构以下:html

import React from 'react'
import ReactDOM from 'react-dom'
ReactDOM.render(<h1 className='title'>Hello React</h1>, document.getElementById('root'))
复制代码

这里就 jsx 会变编译成真正的 DOM ,把 html 代码拿到 babel 官网编译java

因而咱们就看到了 React.createElement() 方法,但这只是调用这个方法,它具体作了什么返回什么咱们还不知道,咱们能够打印这个函数运行的结果:react

console.log(
  React.createElement(
    'h1',
    {
      className: 'title',
    },
    'Hello React'
  )
)
复制代码

返回的这个对象就是虚拟 DOM 了。

咱们来分析它返回的对象参数,首先第一个是git

  • $$typeof: REACT_ELEMENT_TYPE

这个是 React 元素对象的标识属性github

REACT_ELEMENT_TYPE 的值是一个 Symbol 类型,表明了一个独一无二的值。若是浏览器不支持 Symbol类型,值就是一个二进制值。数组

为何是 Symbol?主要防止 XSS 攻击伪造一个假的 React 组件。由于 JSON 中是不会存在 Symbol 类型的。浏览器

  • key:这个好比循环中会用到这个key值
  • props:传入的属性值,好比 id, className, style, children 等
  • ref: DOM 的引用
  • 剩下的是私有属性(本篇不展开讨论)

在本篇咱们会用本身简单的方式实现这两个方法,而不是根据源码,因此实现上的方法只要能实现它的基本功能便可;有个基本概念在,之后再按部就班学习源码。babel

而 createElement 中有三个参数,更确切说是 n 个参数:app

  • type:表示要渲染的元素类型。这里能够传入一个元素 Tag 名称,也能够传入一个组件(如div span 等,也能够是是函数组件和类组件)
  • props:建立React元素所须要的props。
  • childrens(可选参数):要渲染元素的子元素,这里能够向后传入n个参数。能够为文本字符串,也能够为数组

初步 createElement 方法:

// 建立 JSX 对象
function createElement(type, props, ...childrens) {
    return {
        type,
        props: {
          ...props,
          children: childrens.length <= 1 ? childrens[0] || '' : childrens,
        },
}
复制代码

参数中 props 和 childrens 是并列关系,而后返回的 props 对象,里面包含了 children,因此咱们须要再 props 里面添加 children 参数,而后根据 children 参数为一个或多个的可能在进行取值处理。

调用该方法:

console.log(
  createElement(
    'h1',
    {
      className: 'title',
    },
    'Hello React'
  )
)
复制代码

除去其它本篇咱们不讨论的属性,目前算是实现了一半;咱们观察原来 React 自身方法输出的结果有 key, ref, 同输出的 props 也是并列关系,因而咱们进一步做出处理

function createElement(type, props, ...childrens) {
  let ref, key
  if ('ref' in props) {
    ref = props['ref']
    props['ref'] = undefined
  }
  if ('key' in props) {
    key = props['key']
    props['key'] = undefined
  }
  return {
    type,
    props: {
      ...props,
      children: childrens.length <= 1 ? childrens[0] || '' : childrens,
    },
    ref,
    key,
  }
}
复制代码

一样的方式调用结果以下:

若是添加多一些属性,咱们来看看结果

console.log(
  createElement(
    'div',
    { id: 'box', className: 'box', style: { color: 'red' }, key: '20' },
    'this is text',
    createElement('h2', { className: 'title' }, 'hello'),
    createElement('div', { className: 'content' }, 'Hi')
  )
)
复制代码

用了这种比较粗鲁的方式添加,设置为 undefined 在实现 render 方法的时候咱们会根据这个忽略props内部的 key 和 props 属性,这里就实现了最基本的 createElement 方法了。

实现 render 方法

render 方法的第一个参数接收的是 createElement 返回的对象,也就是虚拟DOM; 第二个参数则是挂载的目标DOM。一样的作法,咱们用 babel 编译来看:

执行后,就被挂在到页面了

实现代码以下:

/* * 功能:把建立的对象生成对应的DOM元素,最后插入到页面中 * objJSX: createElement 返回的 JSX 对象 * container:挂载的容器,如 document.getElementById('root') */
function render(objJSX, container) {
  let { type, props } = objJSX
  let newElement = document.createElement(type)
  for (let attr in props) {                 // 遍历传入的 props 属性
    if (!props.hasOwnProperty(attr)) break  // 不是私有的直接结束遍历
    let value = props[attr]                 // >若是当前属性没有值,直接不处理便可
    if (value == undefined) continue        // NULL OR UNDEFINED
    
    // 对几个特殊属性单独设置
    switch (attr.toUpperCase()) {
        case 'ID':
            newElement.setAttribute('id', value)
            break
      case 'CLASSNAME':
            newElement.setAttribute('class', value)
            break
      case 'STYLE': // 传入的行内样式 style 是个对象,故需遍历赋值
        for (let styleAttr in value) {
          if (value.hasOwnProperty(styleAttr)) {
            newElement['style'][styleAttr] = value[styleAttr]
          }
        }
        break
      case 'CHILDREN':
        /* * 多是一个值:多是字符串也多是一个JSX对象 * 多是一个数组:数组中的每一项多是字符串也多是JSX对象 */
        // 首先把一个值也变为数组,这样后期统一操做数组便可
        !(value instanceof Array) ? (value = [value]) : null
        value.forEach((item, index) => {
          // 验证ITEM是什么类型的:若是是字符串就是建立文本节点,若是是对象,咱们须要再次执行RENDER方法,把建立的元素放到最开始建立的大盒子中
          if (typeof item === 'string') {
            let text = document.createTextNode(item)
            newElement.appendChild(text)
          } else {
            render(item, newElement)
          }
        })
        break
      default:
        newElement.setAttribute(attr, value)
    }
  }
  container.appendChild(newElement)
}
复制代码

相关文章
相关标签/搜索