为了账号安全,请及时绑定邮箱和手机立即绑定

从 0 到 1 实现 React 系列 —— 1.JSX 和 Virtual DOM

标签:
React.JS

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/组件/生命周期/diff算法/setState/ref/...)

前置准备

看源码一个痛处是会陷进理不顺主干的困局中,本系列文章在实现一个 (x)react 的同时理顺 React 框架的主干内容(JSX/虚拟DOM/...)

环境准备

项目打包工具选择了 parcel,使用其可以快速地进入项目开发的状态。快速开始

此外需要安装以下 babel 插件:

"babel-core": "^6.26.0","babel-preset-env": "^1.6.1","babel-plugin-transform-react-jsx": "^6.24.1"

同时 .babelrc 配置如下:

{    "presets": ["env"],    "plugins": [
        // 插件如其名:转化 JSX 语法为定义的形式
        ["transform-react-jsx", {            "pragma": "React.createElement"
        }]
    ]
}

JSX 和 虚拟 DOM

const element = (  <div className="title">
    hello<span className="content">world!</span>
  </div>)

JSX 是一种语法糖,经过 babel 转换结果如下,可以发现实际上转化成 React.createElement() 的形式:

var element = React.createElement(  "div",
  { className: "title" },  "hello",
  React.createElement(    "span",
    { className: "content" },    "world!"
  )
);

打印 element, 结果如下:

{  attributes: {className: "title"}
  children: ["hello", t] // t 和外层对象相同
  key: undefined
  nodeName: "div"}

因此,我们得出结论:JSX 语法糖经过 Babel 编译后转换成一种对象,该对象即所谓的虚拟 DOM,使用虚拟 DOM 能让页面进行更为高效的渲染。

我们按照这种思路进行函数的构造:

const React = {
  createElement
}function createElement(tag, attr, ...child) {  return {    attributes: attr,    children: child,    key: undefined,    nodeName: tag,
  }
}// 测试const element = (  <div className="title">
    hello<span className="content">world!</span>
  </div>)console.log(element) // 打印结果符合预期// {//   attributes: {className: "title"}//   children: ["hello", t] // t 和外层对象相同//   key: undefined//   nodeName: "div"// }

虚拟 DOM 转化为真实 DOM

上个小节介绍了 JSX 转化为虚拟 DOM 的过程,这个小节接着来实现将虚拟 DOM 转化为真实 DOM (页面上渲染的是真实 DOM)。

我们知道在 React 中,将虚拟 DOM 转化为真实 DOM 是使用 ReactDOM.render 实现的,使用如下:

ReactDOM.render(
  element, // 上文的 element,即虚拟 dom
  document.getElementById('root')
)

接着来实现 ReactDOM.render 的逻辑:

const ReactDOM = {
  render
}/**
 * 将虚拟 DOM 转化为真实 DOM
 * @param {*} vdom      虚拟 DOM
 * @param {*} container 需要插入的位置
 */function render(vdom, container) {  if (typeof(vdom) === 'string') {
    container.innerText = vdom    return
  }  const dom = document.createElement(vdom.nodeName)  for (let attr in vdom.attributes) {
    setAttribute(dom, attr, vdom.attributes[attr])
  }
  vdom.children.forEach(vdomChild => render(vdomChild, dom))
  container.appendChild(dom)
}/**
 * 给节点设置属性
 * @param {*} dom   操作元素
 * @param {*} attr  操作元素属性
 * @param {*} value 操作元素值
 */function setAttribute(dom, attr, value) {  if (attr === 'className') {
    attr = 'class'
  }  if (attr.match('/on\w+/')) {   // 处理事件的属性:
    const eventName = attr.toLowerCase().splice(1)
    dom.addEventListener(eventName, value)
  } else if (attr === 'style') { // 处理样式的属性:
    let styleStr = ''
    let standardCss    for (let klass in value) {
      standardCss = humpToStandard(klass) // 处理驼峰样式为标准样式
      styleStr += `${standardCss}: ${value[klass]};`
    }
    dom.setAttribute(attr, styleStr)
  } else {                       // 其它属性
    dom.setAttribute(attr, value)
  }
}

至此,我们成功将虚拟 DOM 复原为真实 DOM,展示如下:

https://img1.sycdn.imooc.com//5d5d55b400013c3805840665.png

image

另外配合热更新,在热更新的时候清空之前的 dom 元素,改动如下:

const ReactDOM = {
  render(vdom, container) {
    container.innerHTML = null
    render(vdom, container)
  }
}

总结

JSX 经过 babel 编译为 React.createElement() 的形式,其返回结果就是 Virtual DOM,最后通过 ReactDOM.render() 将 Virtual DOM 转化为真实的 DOM 展现在界面上。流程图如下:

https://img1.sycdn.imooc.com//5d5d55b900012e3709100277.png

image

思考题

如下是一个 react/preact 的常用组件的写法,那么为什么要 import 一个 React 或者 h 呢?

import React, { Component } from 'react' // react// import { h, Component } from 'preact' // preactclass A extends Component {
  render() {    return <div>I'm componentA</div>
  }
}

render(<A />, document.body) // 组件的挂载

项目说明

该系列文章会尽可能的分析项目细节,具体的还是以项目实际代码为准。



作者:牧云云
链接:https://www.jianshu.com/p/c7bf12fae053


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消