一. 实现jsx到真实dom的挂载
react 中jsx到真实dom的挂载大致可分为三步:
jsx模板语法 —— babel模板语法拆解使用React.createElement() 生成虚拟dom —— 虚拟dom通过render函数挂载到真实dom上。
其实很多的细节和vue很相似可以看前面我关于vue源码的记载。
1. 首先是模拟 React.createElement() 生成虚拟dom
看一下原来版本的虚拟节点
这里我们主要来模仿其中的核心属性:typeoof这个就是节点类型(是节点元素还是文本),key用于diff算法,props里面保存三类配置:
(1)children 子元素
(2)样式属性
(3)其他属性值
ref用于指向生成的真实dom,type其实就是dom节点类型(div,span等等)。
首先是模拟生成vdom
import { REACT_ELEMENT } from "./stants"
import { toObject } from "./utils"
/**
* @name 创建虚拟节点
* @param {*} type dom类型
* @param {*} config 配置项
* @param {*} children 子元素
* @returns 返回虚拟节点对象
*/
function createElement(type, config, children) {
let props = { ...config }
let key = null, ref = null //用于diffing算法和构建真实dom
if (config) {
key = config.key ? config.key : null
ref = config.ref ? config.ref : null
delete config.key
delete config.ref
}
if (config) {
//有多个children
if (arguments.length > 3) {
props.children = Array.prototype.slice.call(arguments, 2).map(toObject) //获取所有的子元素
} else if (arguments.length === 3) {
props.children = toObject(children)
}
}
//返回虚拟节点
return {
$$typeof: REACT_ELEMENT,
type,//类型
key,//diff算法优化
ref,//获取真实dom
props,
}
}
const React = {
createElement
}
export default React
然后引入的js一个symbol的枚举类,一个是负责检验dom的类型是节点元素还是文本
stants.js
//标所元素
export const REACT_ELEMENT = Symbol("react.element")
export const REACT_TEXT = Symbol("react.TEXT")
utlis
import { REACT_TEXT } from "./stants"
export function toObject(element) {
return typeof element == 'string' || typeof element == 'number' ?
{ type: REACT_TEXT, content: element } : element
}
通过以上步骤就可以实现jsx到虚拟dom的转变
2. 将虚拟dom挂载为真实dom,这个就没什么细说的直接看代码就好
react-dom.js
import { REACT_ELEMENT, REACT_TEXT } from "./stants"
/**
*
* @param {*} vdom 虚拟节点
* @param {*} container 容器
*/
function render(vdom, container) {
let newDom = createDom(vdom)
container.appendChild(newDom)
}
/**
* @name 生成真实dom
* @param {*} vdom 虚拟dom
* @returns
*/
function createDom(vdom) {
//vDom变成真实dom 和vue相差不大
let dom = null
let { type, props } = vdom
//判断是元素还是文本
if (type === REACT_TEXT) {
dom = document.createTextNode(vdom.content)
} else {
dom = document.createElement(type)
}
//处理属性
if (props) {
updateProps(dom, {}, props) //三个参数分别为 真实dom 旧的属性 新的属性
}
//处理子元素
if (props) {
let children = props.children
if (children) {
if (Array.isArray(children)) {
for (let child of children) {
render(child, dom)
}
}
else {
render(children, dom)
}
}
}
return dom
}
/**
* @name 更新dom属性
* @param {*} dom 真实dom
* @param {*} oldProps 旧的props
* @param {*} newProps 新的props
*/
function updateProps(dom, oldProps, newProps) {
//处理新属性
for (let key in newProps) {
if (key === "children") {
continue
} else if (key === "style") {
let styleObj = newProps[key]
for (let styleKey in styleObj) {
dom.style[styleKey] = styleObj[styleKey]
}
} else {
dom[key] = newProps[key]
}
}
//处理就属性
if (oldProps) {
//旧的属性在新的中没有就删除
for (let key in oldProps) {
if (!newProps[key]) {
dom[key] = null
}
}
}
}
const ReactDOM = {
render
}
export default ReactDOM
然后可以进行测试
index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import myReact from "./实现jsx/react"
import myReactDom from "./实现jsx/react-dom"
let element = <h1>5555</h1>
console.log(element); //实际上是一个虚拟dom 通过babel的react.createElement实现
/**
* 例如 一共三个参数 type是dom节点类型 config配置属性 children子元素 可以接收多个
*/
let element2 = myReact.createElement("h1", {
className: "test",
style: {
color: "red"
}
}, "55555", myReact.createElement("span", { style: { color: "yellow" } }, 666, 5555))
console.log(element2);
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render( //创建组件进行渲染 核心其实是使用了babel将jsx语法转换
// // <React.StrictMode>
// // <App />
// // </React.StrictMode>
// element2
// );
myReactDom.render(element2, document.getElementById('root'))
// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();
可以看到页面的效果