前置知识jsx本质链接:https://www.yuque.com/linhe-8mnf5/fxyxkm/xekk7b
1、React渲染过程
- 把虚拟DOM变为真实DOM;
- 把虚拟DOM属性同步到真实DOM上;
- 把虚拟DOM上的children也变成真实DOM挂载到自己的DOM上;
- 把最终DOM挂载到容器上;
2、实现步骤一:将子节点挂载到根节点上
2.1 初始化项目文件
2.2 index内容初始化
import React from './react'; // 注意:使用的是自己写的react
import ReactDOM from './react-dom'; // 注意:使用的是自己写的react-dom
const element1 = (<div className="title" style={{ color: 'red' }}><span>hello word</span></div>)
ReactDOM.render(element1,
document.getElementById('root')
);
2.3 实现react.js文件内容
/**
* 实现createElement
* @param {*} type 元素类型
* @param {*} config 配对对象
* @param {*} children 子元素
*/
function createElement(type, config, children) {
if (config) {
delete config.__source;
delete config.__self;
}
let props = { ...config }
if (arguments?.length > 3) {
children = [].slice.call(arguments, 2)
}
props.children = children
return {
type,
props,
children
}
}
const React = { createElement }
export default React
2.4 实现react-dom.js文件内容
/**
* 1.把虚拟DOM变为真实DOM;
* 2.把虚拟DOM属性同步到真实DOM上;
* 3.把虚拟DOM上的children也变成真实DOM挂载到自己的DOM上;
* 4.把最终DOM挂载到容器上;
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 虚拟DOM转换成真实DOM,并插入到容器中
*/
function render(vdom, container) {
console.log(`vdom`, vdom)
const dom = createDom(vdom)
container.appendChild(dom)
}
/**
* 虚拟dom变为真实dom
* @param {*} vdom 虚拟dom
* {"type":"div","key":null,"ref":null,
* "props":{"className":"title","style":{"color":"red"},
* "children":{"type":"span","key":null,
* "ref":null,"props":{"children":"hello word"}
*/
function createDom(vdom) {
// 如果是字符串或者数字,直接返回真实节点
if (['number', 'string'].includes(typeof vdom)) {
console.log(`vdom`, vdom)
return document.createTextNode(vdom)
}
// 否则它就是一个react元素了
const { type, props } = vdom
let dom = document.createElement(type)
return dom
}
const ReactDom = { render }
export default ReactDom
2.5 实现效果
3、实现步骤二:将虚拟dom的属性更新到真实dom
3.1 加入处理属性函数
实现代码:
// react-dom文件
function createDom(vdom) {
// 如果是字符串或者数字,直接返回真实节点
if (['number', 'string'].includes(typeof vdom)) {
return document.createTextNode(vdom)
}
// 否则它就是一个react元素了
const { type, props } = vdom
let dom = document.createElement(type)
// 将虚拟dom属性更新到真实dom上
updateProps(dom, props)
return dom
}
/**
* 将虚拟dom属性更新到真实dom上
* @param {*} dom 真实dom
* @param {*} newProps 新属性
*/
function updateProps(dom, newProps) {
for (let key in newProps) {
if (key === 'children') continue
// style单独处理,因为dom不支持
if (key === 'style') {
const styleObj = newProps.style
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
}else{
dom[key] = newProps[key]
}
}
}
3.2 实现效果
4 实现步骤三:将子节点dom挂载到当前节点
4.1 处理子节点文本节点
4.2 处理子节是虚拟dom
4.3 处理子节点为数组
4.4 不满足以上条件处理
4.5 实现效果
5、React-dom完整代码实现
/**
* createElement编译出来的结果
* {"type":"div","key":null,"ref":null,
* "props":{"className":"title","style":{"color":"red"},
* "children":{"type":"span","key":null,
* "ref":null,"props":{"children":"hello word"}
*/
/**
* 1.把虚拟DOM变为真实DOM;
* 2.把虚拟DOM属性同步到真实DOM上;
* 3.把虚拟DOM上的children也变成真实DOM挂载到自己的DOM上;
* 4.把最终DOM挂载到容器上;
* @param {*} vdom 要渲染的虚拟DOM
* @param {*} container 虚拟DOM转换成真实DOM,并插入到容器中
*/
function render(vdom, container) {
const dom = createDom(vdom)
container.appendChild(dom)
}
/**
* 虚拟dom变为真实dom
* @param {*} vdom 虚拟dom
*/
function createDom(vdom) {
// 如果是字符串或者数字,直接返回真实节点
if (['number', 'string'].includes(typeof vdom)) {
return document.createTextNode(vdom)
}
// 否则它就是一个react元素了
const { type, props } = vdom
const dom = document.createElement(type)
// 将虚拟dom属性更新到真实dom上
updateProps(dom, props)
// 如果儿子只有一个儿子,并且儿子是文本节点
if (['number', 'string'].includes(typeof props.children)) {
dom.textContent = props.children
// 如果儿子是一个对象,并且是一个虚拟dom,递归调用render
} else if (typeof props.children === 'object' && props.children.type) {
// 把儿子变成真实dom,插入到自己身上
render(props.children, dom)
// 如果儿子是一个数组,说明儿子不止一个
} else if (Array.isArray) {
reconcileChildren(props.children, dom)
} else {
document.textContent = props.children ? props.children.toString() : ""
}
// // 把真实dom作为一个虚拟属性放在虚拟dom上,为以后更新做准备
// vdom.dom = dom
return dom
}
/**
* @param {*} childrenVdom 儿子们的虚拟dom
* @param {*} parentDom 父的真实dom
*/
function reconcileChildren(childrenVdom, parentDom) {
for (const childVdom of childrenVdom) {
render(childVdom, parentDom)
}
}
/**
* 将虚拟dom属性更新到真实dom上
* @param {*} dom 真实dom
* @param {*} newProps 新属性
*/
function updateProps(dom, newProps) {
for (const key in newProps) {
if (key === 'children') continue
// style单独处理,因为dom不支持
if (key === 'style') {
const styleObj = newProps.style
for (let attr in styleObj) {
dom.style[attr] = styleObj[attr]
}
} else {
dom[key] = newProps[key]
}
}
}
const ReactDom = { render }
export default ReactDom
6、源代码
本文代码:https://gitee.com/linhexs/react-write/tree/1.react-render/