前言
之前的vue3响应式核心原理也算是告一段落了,接下来开启的就是结合运行时来开发了,更贴切于框架式开发,业务开发和框架开发的区别就在于通用性和严谨程度,所以做框架开发,哪怕是模仿源码,也是有助于自身的开发能力提升
vue3中runtime-core 运行时的源码也能作为一个不错的学习目标,下面就围绕vue3对组件和元素的初始化展开话题
VNode
vue3的dom是围绕着VNode来实现的,所以我们传入组件或元素的时候都会被转为VNode之后再处理,,vue3为什么使用vnode这里不展开说,但一定不是提高效率啊,效率没有原生高,第一,降低心智负担,第二考虑通用性,当然还有别的原因,但不是必要充分条件
patch
patch处理虚拟dom,vnode是subtree结构,到页面上肯定是以真实dom元素展现,所以这些subtree肯定会被处理成真实dom
render
render呢是可以渲染我们的虚拟节点的,然后通过h函数生成虚拟dom,两种生成虚拟节点的方法都是通过render实现,可见render的重要性
初始化流程
下面就直接开始初始化流程了
其实有些写react的开发者更习惯用这种写法来写vue
篇幅展开讲太多了,所以大部分贴源码,初始化流程不用看在干嘛,就看看流程就行了,后续的文章会对源码做详细说明
下面就是创建一个app.js
export const App = {
render() {
return h("div",
{
id: 'root',
class: ["red", "layhead"]
}
, [h("p", { class: "red" }, "red"), h("p", { class: 'blue' }, "blue")])
},
setup() {
return {
msg: 'xin-vue'
}
}
}
App根组件引入到main.js,createApp传入App,组件的本质就是个对象嘛
const rootContainer = document.querySelector("#app")
createApp(App).mount(rootContainer)
一层套一层,俄罗斯套娃,只是更符合语义化和代码美观性,当然学习鱿鱼须的代码风格及规范也是很有必要的
createApp
import { createVNode } from "./vnode"
import { render } from "./render"
export function createApp(rootComponent) {
return {
mount(rootContainer) {
// 先转换成vNode
// component -> VNode
//所有components基于vNode操作
const vNode = createVNode(rootComponent)
render(vNode, rootContainer)
}
}
}
createVNode
export function createVNode(type, props?, children?) {
return {
type,
props,
children
}
}
h 实质就是套娃方便开发者调用
import { createVNode } from "./vnode";
export function h(type, props, children){
return createVNode(type, props, children)
}
render
render相比其他实现要稍微麻烦一些,组件和元素该怎么走其实就是在render函数内才决定的,组件本质是对象,所以再判断是不是组件上其实是很简单的,看看是不是对象就行了,如果是元素,那么type就会显示元素名称
export function render(vNode, container) {
//patch
patch(vNode, container)
}
function patch(vNode, container) {
//处理组件 判断是不是element类型
//是element走element逻辑
//可以log一下vNode看看类型 是object->组件 是string -> element
console.log(vNode.type);
if (typeof vNode.type === 'string') {
processElement(vNode, container)
} else if (isObject(vNode.type)) {
processComponent(vNode, container)
}
}
判断完就需要做分叉处理了,下面先处理组件
processComponent
function processComponent(vNode, container) {
//init 以及unpate
//init
mountComponent(vNode, container)
}
来京城只办三件事
- 组件实例化
- 处理setup
- 处理vnode
function mountComponent(vNode, container) {
const instance = createComponentInstance(vNode)
setupComponentInstance(instance)
setupRenderEffect(instance, container)
}
createComponentInstance
export function createComponentInstance(vNode) {
const component = {
vNode,
type: vNode.type
}
return component
}
setupComponentInstance
export function setupComponentInstance(instance) {
//todo
//initProps
//initSlots
setupStatefulComponent(instance)
}
setupStatefulComponent
function setupStatefulComponent(instance) {
const component = instance.type
const { setup } = component
if (setup) {
const setupResult = setup()
//判断返回值是Function还是Object
handlerSetupResult(instance, setupResult)
}
}
基于setup返回值类型,对象类型做不同处理,我们这里先实现Object类型的处理,将setup的返回值挂载到组件实例上,设置好返回之后的一个render函数,组件对象转换是为element元素做准备
function handlerSetupResult(instance, setupResult) {
//todo
//function
if (typeof setupResult === 'object') {
instance.setupState = setupResult
}
//判断是否有render
finishComponentSetup(instance)
}
如果我们没有了render说明已经到了element元素这一步了,如果还有我们就将组件身上的VNode给到它的实例对象身上,方便之后的调用
function finishComponentSetup(instance) {
const component = instance.type
instance.render = component.render
}
setupRenderEffect
这里其实就已经到递归环节了,再次调用patch
function setupRenderEffect(instance, container) {
const subTree = instance.render()
//vnode -> patch -> Mountelement
patch(subTree, container)
}
processElement
组件结束到element
function processElement(vNode, container) {
//init 以及unpate
//init
mountElement(vNode, container)
}
mountElement
function mountElement(vNode, container) {
//type就是元素类型
const el = document.createElement(vNode.type)
//children就是el的值如果是基本类型就这样处理, 如果children是Array代表有后代,就用另外一种方式
const { children } = vNode
if (typeof children === 'string') {
el.textContent = children
} else if (Array.isArray(children)) {
mountChildren(vNode, el)
}
//props就是属性
const { props } = vNode
for (const key in props) {
const val = props[key]
el.setAttribute(key, val)
}
container.append(el)
}
//挂载元素后代
function mountChildren(vNode, container) {
vNode.children.forEach((v) => {
patch(v, container)
})
}
初始化差不多走完了,后面看情况补充打包和测试吧,目前肯定跑不起来,没打包肯定跑不起来
补充
index.html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>xin-vue</title>
<style>
.bgc-red {
background-color: red;
}
.bgc-blue {
background-color: blue;
}
</style>
</head>
<body>
<div id="app"></div>
<script src="main.js" type="module"></script>
</body>
</html>
app.js
import { h } from '../../lib/xin-vue.esm.js'
export const App = {
render() {
return h("div",
{
id: 'root',
class: ["bgc-red"]
}
, [h("p", { class: "bgc-red" }, "red"), h("p", { class: 'bgc-blue' }, "blue")])
},
setup() {
return {
msg: 'xin-vue'
}
}
}
main.js
import { createApp } from '../../lib/xin-vue.esm.js'
import { App } from './app.js'
//vue3
const rootContainer = document.querySelector("#app")
createApp(App).mount(rootContainer)
ts肯定跑不起来,所以要打包
安装rollup
根目录创建rollup.config.js
这里的 assert { type: "json" } 是为了断言,不然打包报错
tsconfig.json也要改,把module改为ESNext
import typescript from '@rollup/plugin-typescript'
import pkg from './package.json' assert { type: "json" }
export default {
input: "./src/index.ts",
output: [
//1.cjs -> commonJs
//2.esm
{
format: "cjs",
file: pkg.main
},
{
format: "es",
file: pkg.module
},
],
plugins: [typescript()]
}
{
"name": "啪!你已死亡",
"version": "1.0.0",
"description": "",
"main": "lib/xin-vue.cjs.js",
"module": "lib/xin-vue.esm.js",
"type": "module",
"scripts": {
"build": "rollup -c rollup.config.js",
"test": "jest"
},
"keywords": [],
"author": "",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.9",
"@babel/preset-env": "^7.22.9",
"@babel/preset-typescript": "^7.22.5",
"@rollup/plugin-typescript": "^11.1.2",
"@types/jest": "^29.5.3",
"babel-jest": "^29.6.1",
"jest": "^29.6.1",
"rollup": "^3.27.0",
"tslib": "^2.6.1",
"typescript": "^5.1.6"
}
}
都配置好就可以打包了,用live server跑index.html,可以看下dom结构是不是与写的一致
结语
vue3的源码是更偏向于语义化和功能性严格划分的,学习有益身心健康,学习有益健康,不学有益心理健康🙂
如果有不对的地方还希望各位悉数指出,有什么疑问也可以私信我,感谢各位