这是我参与8月更文挑战的第4天,活动详情查看:8月更文挑战”
前言
React是用于构建用户界面的
JavaScript
库, 起源于jsx
渲染的原理
介绍
本次是一边写代码一边进行逻辑讲解,大量的注释会写在代码中可能全部操作下来要耗费一些时间,所以我尽量把展示的效果也一起贴出来本次的渲染组件是没有
fiber
的逻辑也就是React16
版本之前的逻辑,之后下一篇会使用16版本以后的fiber
在写一篇
开始
使用
create-react-app
创建一个应用,重新构建index.js
文件,这是我们的基础结构包含ClassComponen
t和FunctionConponent
组件,目的是全部正常渲染到页面
import React from 'react';
import ReactDOM from './react-dom'
import Component from './Component'
import './index.css' //样式只有一个boder颜色就不用贴了
class ClassComponent extends Component {
render() {
return (
<div className="box">
<p>{this.props.name}</p>
</div>
);
}
}
function FunctionComponent(props) {
return (
<div className="box">
<p>{props.name}</p>
</div>
);
}
const JSX = (
<div className='box'>
<h1>飞雪连天射白鹿,笑书神侠倚碧鸳</h1>
<a href='https://juejin.cn/post/6995918802546343973' >my world</a>
<FunctionComponent name='函数组件' />
<ClassComponent name='类组件'/>
</div>
)
ReactDOM.render(
JSX,
document.getElementById('root')
);
Component.js
function Component(props) {
this.props = props; //将props挂载到实例上
}
Component.prototype.isReactComponent = {}; //主要区分函数组件还是类组件,源码中也是这么干的
export default Component;
创建
react-dom.js
,之后我们会按照函数调用顺序编写我们的渲染逻辑
实现render函数
//vnode 虚拟dom节点
//node 真实dom节点
/**
* @vnode 虚拟节点
* @container 真实容器节点
*/
function render(vnode, container) {
//step1 将vnode转为node
let node = createNode(vnode)
//step2 将节点插入到容器中
container.appendChild(node)
}
export default { render }
createNode创建真实dom
/**
* @vnode 虚拟节点
*/
function createNode(vnode) {
let node
const { type } = vnode
if (typeof type === 'string') {
//生成标签节点 div h1 等
node = updateHostComponent(vnode)
} else if (isStringOrNumber(vnode)) {
//生成文本节点只判断数字或者字符串
node = updateTextComponent(vnode)
} else if (typeof type === 'function') {
//区分渲染函数组件还是类组件
node = type.prototype.isReactComponent ?
updateClassComponent(vnode) :
updateFunComponent(vnode)
}
return node
}
updateHostComponent更新标签节点
/**
* @vnode 虚拟节点
*/
function updateHostComponent(vnode) {
const { type, props } = vnode
//step1 创建标签节点
let node = document.createElement(type)
//step2 更新节点上的属性 props传的参数等
updateNode(node, props)
//step3 遍历渲染子节点
reconcilChildren(node, props.children)
return node
}
updateTextComponent更新文本节点
/**
* @vnode 虚拟节点
*/
function updateTextComponent(vnode) {
//创建文本节点
const node = document.createTextNode(vnode)
return node
}
updateClassComponent更新类组件
/**
* @vnode 虚拟节点
*/
function updateClassComponent(vnode) {
const { type, props } = vnode
//step1 实例化类组件传递props
const instance = new type(props)
//step2 执行类组件中的render函数获取vnode
const child = instance.render()
//step3 创建真实节点
const node = createNode(child)
return node
}
updateFunComponent更新函数组件
/**
* @vnode 虚拟节点
*/
function updateFunComponent(vnode) {
const { type, props } = vnode
//step1 执行函数组件传递props
const child = type(props)
//step2 创建真实节点
const node = createNode(child)
return node
}
updateNode更新节点上的属性
/**
* @node 真实节点
* @value props值
*/
function updateNode(node, nextVal) {
//step1 遍历props,需要筛选出children因为这里是将props值放到节点上不需要children
//step2 将值存储到节点上
Object.keys(nextVal).filter(v => {
return v !== 'children'
}).forEach(key => {
node[key] = nextVal[key]
})
return node
}
reconcilChildren遍历子节点
/**
* @container 真实容器节点
* @children 节点元素
*/
function reconcilChildren(container, children) {
//step1 整合一下children,方便遍历
const newChildren = Array.isArray(children) ? children : [children]
//step2 将节点插入到真实dom中,直接调用我们写好的render即可
for (let i = 0; i < newChildren.length; i++) {
let child = newChildren[i]
render(child, container)
}
}
流程图
我们画个流程图串一下可以更加方便大家理解整个渲染流程
结语
本次渲染流程到这里就结束了之后我会继续编写下一篇使用
fiber
的渲染逻辑,感谢观看☕️