前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >react 学习(一) 实现简版虚拟 dom 和挂载

react 学习(一) 实现简版虚拟 dom 和挂载

原创
作者头像
测不准
发布2022-04-05 18:10:58
4910
发布2022-04-05 18:10:58
举报
文章被收录于专栏:与前端沾边与前端沾边

楼主最近入职新单位了,恰好新单位使用的技术栈是 react,因为之前一直进行的是 vue2/vue3 和小程序开发,对于这些技术栈实现机制也有一些了解,最少面试的也都能答出来。但对于 react 只是有一定的了解,没有真实的学习过实现,虽然之前也看过一些文章,但是只停留在表面,因为别人这么写了,也就下意识的认为是这样。本次正好配合工作的契机,我们从零开始学习一下,使用的话呢就简单一过,相信大家也都用过或者看完官网也都了解了。如果您是大佬,欢迎批评指正;如果您是初级选手,希望能够一起学习。

初始化项目

我们借助脚手架实现开发环境,内部使用的库用自己开发的。

  1. npx create-react-app react-dome1 (当然也可以全局安装脚手架)

public 目录只留下 index.htmlsrc 目录下只留下 index.js

  1. 修改 scripts 命令

我们需要使用旧的转换方式,这样我们可以自己实现 createElement 方法

代码语言:txt
复制
// 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"
},

react 17 引入了新的 jsx 转换特性,因为之前的开发,即使页面中未直接使用 React,但是也要引入,这是因为 babel 在转译之后会触发 React.createElement,如果不引入会报错,但是引入了可能也会触发 eslint 的报错,引入但是未使用。新特性可以单独使用 JSX 而无需引入 React

新特性一些好处
  1. 使用全新的转换,你可以单独使用 JSX 而无需引入 React
  2. 根据你的配置,JSX 的编译输出可能会略微改善 bundle 的大小。
  3. 它将减少你需要学习 React 概念的数量,以备未来之需

之前的转换方式

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

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

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

新特性转换方式

代码语言:txt
复制
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

我们先看下原生 createElement 的返回结果

代码语言:txt
复制
// src/index.js
import React from 'react'
const jsx = <h1 className='title' style={{color: 'red'}}>hello</h1>
console.log(jsx)

我们看到返回了对象,几个重要属性,$$typeof, props, type。我们实现下自己的

createElement 函数。

定义类型常量

代码语言:txt
复制
// src/constants.js

// react 内的元素都是这个类型
export const REACT_ELEMENT = Symbol("react.element");
// react 文本类型
export const REACT_TEXT = Symbol("react.text");

实现 createElement

代码语言:txt
复制
// src/react.js
// 这三个参数是 babel 解析完,调用React.createElement 传入的,从第三个参数开始都是儿子
function createElement(type, config, children) {
  if(config) {
    // 这里可写可不写,就是为了简化下我们自己写的,只把必要的返回,没用的参数越少越清晰嘛
    delete config.__source
    delete config.__self
  }
  const props = {...config}
  if (arguments.length > 3) {
    // 有多个儿子
    props.chidlren = Array.prototype.slice.call(arguments, 2)
  } else if (argument.length === 3) {
    // 只有一个子,直接赋值
    props.children = children
  }
  
  return {
    $$typeof: REACT_ELEMENT,
    type,
    props
  }
}

const React = {
  createElement
}
export default React

这里也可以 ...children 形式,判断只要判断 children 长度就可以了,但是属于 es6 的用法,我们按照源码实现

实现 toVdom 辅助函数

我们这里还要进行一下处理,因为如果是文本类型的话,直接就是字符串了,没有类型这种标识了,所以我们要对 children 进行一下包裹,也为了后面的 diff

代码语言:txt
复制
// src/utils.js

// 统一规范,方便  diff
export function toVdom(element) {
  return typeof element === "string" || typeof element === "number"
    ? { // 字符串包裹
        $$typeof: REACT_ELEMENT,
        type: REACT_TEXT,
        props: element,
      }
    : element;
}

修改 createElement 函数,包裹儿子节点

代码语言:txt
复制
...
props.children = Array.prototype.slice.call(arguments, 2).map(toVdom);
...
props.children = toVdom(children);

调用我们自己的实现,我们可以得到如下结果

页面挂载

我们引入 react-dom,看下原生渲染

代码语言:txt
复制
import React from "react";
import ReactDOM from "react-dom";

let jsx = (
  <h1 className="title" style={{ color: "red", backgroundColor: "pink" }}>
    hello
    <span>111</span>
  </h1>
);
ReactDOM.render(jsx, document.getElementById("root"));

实现 reactDOM.render

大家可以按我写的第几步阅读,基本都做了注视

代码语言:txt
复制
// 做了两件事
// 1. 虚拟dom变真实dom
// 2. 挂载
function render(vdom, container) {            //。 第二步
    //1 
    const newDOM = createDOM(vdom) // 不同功能写在不同函数里,清晰          // 第三步
    //2
    container.appendChild(newDOM)
}

// 创建真实 dom
function createDOM(vdom) {
  let {type, props} = vdom // 我们知道虚拟dom就是我们生成的那个对象
  let dom // 最后要返回的
  
  if (type === REACT_TEXT) {
    // 如果是个文本
    dom = document.createTextNode(props)
  } else {
    // 标签节点
    dom = document.createElement(type)
  }
    
  // 需要对props 中的 style 和 children 和其他进行处理
  if(props) {
    // 单独处理属性
    updateProps(dom, {}, props)         // 第四步
    // 单独处理 chidlren
    if(props.chidlren && typeof props.children === 'object' && props.chidlren.$$typeof) {
      // 文本
      render(props.chidlren, dom)
    } else if (Array.isArray(props.children)) {
      // 子为数组,把子挂载到当前的父 dom
      reconcileChildren(props.children, dom)            // 第五步
    } 
  }
  
  return dom
}

// 子虚拟节点,父真实节点
function reconcileChildren(chidlrenVdom, parentDom) {
  // 循环递归处理, 算法题里非二叉树的多子树节点,也是 for 循环遍历处理
  for (let i = 0; i < childrenVdom.length; i++) {
    render(childrenVdom[i], parentDOM);
  }
}


// 对 dom 进行新属性赋值,旧属性没有的删除, vue中也是类型的操作,遍历新属性,旧属性
function updateProps(dom, oldProps, newProps) {
  for(let key in newProps) {
    if (key === 'children') {
      continue // 单独处理
    } else if (key === 'style') {
      let styleObj = newProps[key]
      for(let attr in styleObj) {
        dom.style[attr] = styleObj[attr] 
      }
    } else {
      dom[key] = newProps[key]
    }
  }
  // 老的有,新的没有 删除
  for(let const key in oldProps) {
    if (!newProps.hasOwnProperty(key)) {
      delete dom[key]
    }
  }
}

// 根据调用,返回的一定是对象       第一步
const ReactDOM = {
  render
}
export default ReactDOm

在入口文件使用我们自己的方法

代码语言:txt
复制
// src/index.js
import React from "./react";
import ReactDOM from "./react-dom";

可以看到,也实现了渲染

本篇就介绍到这里,我们了解了虚拟 dom 的对象形式,了解了如果挂载到页面上,下一节我们学习下类组件和函数组件的实现,如果有不对,欢迎指正!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

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

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 初始化项目
    • 新特性一些好处
    • 实现 React.createElement
      • 定义类型常量
        • 实现 createElement
          • 实现 toVdom 辅助函数
          • 页面挂载
            • 实现 reactDOM.render
            相关产品与服务
            云开发 CloudBase
            云开发(Tencent CloudBase,TCB)是腾讯云提供的云原生一体化开发环境和工具平台,为200万+企业和开发者提供高可用、自动弹性扩缩的后端云服务,可用于云端一体化开发多种端应用(小程序、公众号、Web 应用等),避免了应用开发过程中繁琐的服务器搭建及运维,开发者可以专注于业务逻辑的实现,开发门槛更低,效率更高。
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档