前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >简单实现虚拟 dom 和渲染

简单实现虚拟 dom 和渲染

作者头像
用户4793865
发布2023-01-12 13:37:54
1.2K0
发布2023-01-12 13:37:54
举报
文章被收录于专栏:前端小菜鸡yym前端小菜鸡yym

前言

我们打算实现一下jsx语法的转换过程。但是在此之前要说一下react17之后的一个变化。

react17 之前

在v17之前,我们即使没有直接使用React,也需要引入React。这是因为babel在转译之后会触发React.createElement,所以不引入会报错。

代码语言:javascript
复制
import React from 'react';

function App() {
  return <h1>Hello World</h1>;
}
=============编译为=======================
import React from 'react';

function App() {
  return React.createElement('h1', null, 'Hello world');
}

react17之后

使用了全新的转换,所以可以单独引入jsx(禁止自己引入),而无需引入React。

代码语言:javascript
复制
function App() {
  return <h1>Hello World</h1>;
}
==================================
// 由编译器引入(禁止自己引入!)
import {jsx as _jsx} from 'react/jsx-runtime';

function App() {
  return _jsx('h1', { children: 'Hello world' });
}

因此,如果我们自己想要实现 React.createElement 就需要修改package.json的运行配置。

修改package.json配置

安装 cross-env

cross-env是运行跨平台设置和使用环境变量的脚本。这里我就不详细说了,如果想了解可以看这篇文章

代码语言:javascript
复制
npm install --save-dev cross-env

package.json

代码语言:javascript
复制
// cross-env 需要自己安装
scripts": {
  "start": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts start",
  "build": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts build",
  "test": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts test",
  "eject": "cross-env DISABLE_NEW_JSX_TRANSFORM=true react-scripts eject"
},

虚拟DOM元素

render/createRoot

这里再提一下react18版本的改变。ReactDOM中废弃了render(),用createRoot进行了替代render

image.png
image.png

这里是createRoot的使用,创建了一个root后,再用render()去渲染。更详细的介绍请看这篇文章

代码语言:javascript
复制
// v18
import * as ReactDOM from 'react-dom';
import App from 'App';

const container = document.getElementById('app');

// Create a root.
const root = ReactDOM.createRoot(container);

// Initial render: Render an element to the root.
root.render(<App tab="home" />);

// During an update, there's no need to pass the container again.
root.render(<App tab="profile" />);

打印一个虚拟DOM

代码语言:javascript
复制
import React from 'react';
import ReactDOM from 'react-dom';

let element1 = (
  <div className='title' style={{ color: 'red' }}>
    <span>hello</span> world
  </div>
)
console.log(JSON.stringify(element1, null, 2))
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(element1)  
image.png
image.png

实现元素渲染

我们要实现react.jsreact-dom.js的源码。 在src文件夹下新建 react.js react-dom.js

image.png
image.png

我们需要做的:

  • 虚拟DOM的创建-也就是createElement()
  • 渲染 render()

react.js

先创建一个函数,然后把函数放到 React对象中,最后导出。

代码语言:javascript
复制
function createElemet() {

}
const React = { createElemet }
export default React
复制代码

babel处理后

经过babel处理,将Es6的内容转换为了Es5,因为这样;浏览器才识别。

代码语言:javascript
复制
let element1 = (
  <div className='title' style={{ color: 'red' }}>
    <span>hello</span> world
  </div>
)

如下的元素,是上面的element1元素 处理后得到的。

代码语言:javascript
复制
// 转换为Es5
let element2 = React.createElement('div', {
  "className": "title",
  "style": { "color": "red" }
},
// 子节点 所以又使用 React.createElement 创建节点
  React.createElement("span", null, "hello"),
  "world"
)

得到虚拟DOM

根据Es5的内容完善react.js

image.png
image.png
代码语言:javascript
复制
/**
 * 
 * @param {*} type  元素类型
 * @param {*} config 配置对象
 * @param {*} children  
 */
// 需要实现 虚拟DOM的创建方法 和 render渲染的方法
function createElemet(type,config,children) { 
    // 如果config存在 删除以下两个属性(官方react使用的我们不需要就删掉了)
    if(config){
        delete config.__source
        delete config.__selef
    }
    let props = {...config};
    // arguments是函数内置的存储实参的容器 这里>3说明children不止一个,还有其他
    if(arguments.length>3){
        children = Array.prototype.slice.call(arguments,2)
    }
    props.children = children
    // 在这里得到虚拟DOM元素
    return {
        type,
        props
    }
}
const React = { createElemet }
export default React

最后将虚拟DOM暴露出去。然后就需要对虚拟DOM转为真实DOM的处理

转为真实DOM

思路

  1. 把虚拟DOM变为真实DOM
  2. 把虚拟DOM上的属性更新/同步到DOM上
  3. 把此虚拟DOM的儿子也都变成真实DOM并插入到这个容器中 dom.appendChild
  4. 把自己挂载到容器上

react-dom.js

  • render方法接收 虚拟DOM 和 一个容器。内部调用创建DOM方法:createDOM,然后将其添加到容器中
  • createDOM方法接收虚拟DOM,如果是文本元素(包括字符串和数值的),就像我们上面的element1中的world没有标签包着的这种文本,使用document.createTextNode将其添加到节点上。
  • 否则 他就是一个虚拟DOM对象了,也就是React元素。然后解构出 type(字符串 如'<div>')和props(属性对象),通过 document.createElement将其添加到节点。
代码语言:javascript
复制
/**
 * @param {*} vdom  要渲染的虚拟DOM
 * @param {*} container  要把虚拟DOM转换为真实DOM并插入到xx容器内
 */
function render(vdom, container) {
    const dom = createDOM(vdom)
    container.appendChild(dom)
}
/**
 * 把虚拟DOM变成真实DOM
 * @param {*} vdom 
 */
function createDOM(vdom) {
    // 处理vdom是数字或者字符串  就好比我们刚才element中的字符串 返回一个文本节点
    if (typeof vdom === 'string' || typeof vdom === 'number') {
        return document.createTextNode(vdom)
    }
    //否则 他是一个虚拟DOM对象了,也就是React元素  
    // type 是一个字符串 如:'<div>' '<span>'   props是一个属性对象
    let {type,props} = vdom;
    // 创建一个真实DOM
    let dom = document.createElement(type)
    return dom
}
const ReactDOM = { render }
export default ReactDOM
复制代码

测试一下

在我们的index.js中 引入我们写好的 react.jsreact-dom.js

代码语言:javascript
复制
import React from './react';
import ReactDOM from './react-dom';

let element1 = (
  <div className='title' style={{ color: 'red' }}>
    <span>hello</span> world
  </div>
)
ReactDOM.render(element1,document.getElementById('root'))
复制代码

可以看到虚拟DOM已经打印了,但是页面上并没有内容

image.png
image.png

看一下元素 发现只有div其属性和子元素都没有添加上。

image.png
image.png

渲染属性和子元素

在我们刚才写好的方法中去调用 updateProps方法

代码语言:javascript
复制
/**
 * 把虚拟DOM变成真实DOM
 * @param {*} vdom 
 */
function createDOM(vdom) {
    // 处理vdom是数字或者字符串  就好比我们刚才element中的字符串 返回一个文本节点
    if (typeof vdom === 'string' || typeof vdom === 'number') {
        return document.createTextNode(vdom)
    }
    //否则 他是一个虚拟DOM对象了,也就是React元素  
    // type 是一个字符串 如:'<div>' '<span>'   props是一个属性对象
    let { type, props } = vdom;
    // 创建一个真实DOM
    let dom = document.createElement(type)
    // 使用虚拟DOM的属性更新刚创建出来的真实DOM的属性
    updateProps(dom, props)
    // 在这里处理props.children属性
    // 如果只有一个儿子,并且这个儿子是一个文本
    if (typeof props.children == 'string' || typeof props.children == 'number') {
        dom.textContent = props.children
        // 如果只有一个儿子,并且这个儿子是一个虚拟DOM元素
    } else if (typeof props.children === 'object' &amp;&amp; props.children.type) {
        // 递归 把自己的儿子变成真实DOM插到自己身上
        render(props.children, dom)
        // 如果儿子是一个数组,说明,儿子有多个
    } else if (Array.isArray(props.children)) {
        console.log(44)
        reconcileChildren(props.children, dom)
    } else {
        document.textContent = props.children ? props.children.toString() : ''
    }
    // 把真实DOM作为一个DOM属性放在虚拟DOM,为以后更新做准备
    // vdom.dom = dom
    return dom
}
代码语言:javascript
复制
/**
 * 
 * @param {*} childrenVdom 儿子们的虚拟DOM
 * @param {*} parentDOM  父亲的真实DOM
 */
function reconcileChildren(childrenVdom, parentDOM) {
    for (let i = 0; i < childrenVdom.length; i++) {
        let childVdom = childrenVdom[i]
        // 将儿子挂载到父亲身上
        render(childVdom, parentDOM)
    }
}
/**
 * 
 * @param {*} dom  真实DOM
 * @param {*} newProps  新属性对象
 */
function updateProps(dom, newProps) {
    for (let key in newProps) {
        if (key === 'children') continue; //不在此处处理
        // 处理样式
        if (key === 'style') {
            let styleObj = newProps.style;
            for (let attr in styleObj) {
                dom.style[attr] = styleObj[attr]
            }
        } else {
            dom[key] = newProps[key]
        }
        // className不需要处理成class吗? 不需要 className是真实DOM的写法

    }
}
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-04-29,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
    • react17 之前
      • react17之后
        • 修改package.json配置
          • 安装 cross-env
          • package.json
      • 虚拟DOM元素
        • render/createRoot
          • 打印一个虚拟DOM
          • 实现元素渲染
            • react.js
              • babel处理后
              • 得到虚拟DOM
            • 转为真实DOM
              • 思路
              • react-dom.js
            • 测试一下
              • 渲染属性和子元素
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档