第二章初始化Component
前言
上一篇文章讲了整个runtime-core
包的核心逻辑,以及patch
算法流程图
流程图: render
函数内patch
算法初始化组件和Element的流程,这一章就开始实现整个流程,我们只要按着流程去创建函数就可以了。
案例
今天项目的结构目录为以下图所示:
需要新建的文件比较多
我们首先实现一个案例
\example\helloworld\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="app"></div>
<script src="main.js" type="moudle"></script>
</body>
</html>
\example\helloworld\main.js
import App from './App.js'
// vue3
createApp(App).mount("#app")
\example\helloworld\App.js
export const App = {
render() {
return h('div','hi,'+this.msg)
},
setup() {
return {
msg:"mini-vue"
}
}
}
上文已经说过纯运行时的框架:
用户在使用它渲染内容时,直接为Render
函数提供了一个树型结构的数据对象。这里面不涉及任何额外的步骤,用户也不需要学习额外的知识。
创建好案例文件,我们希望编写完runtime
初始化组件模块,再去打开html
文件时,浏览器可以识别并且渲染出"hi,mini-vue"
的字符串。
createApp 、 creatVNode 、 render
createApp API
createApp API
主要接受一个树形结构rootComponent
参数
import { creatVNode } from './vnode'
import { render } from './renderer'
export function creatApp(rootComponent) {
return {
mount(rootContainer) {
// 先转换成vnode
// Component转换成vnode
// 所有逻辑操作都基于vnode
const vnode = creatVNode(rootComponent)
render(vnode, rootContainer)
}
}
}
参数
rootComponent
接受一个树形结构的内容,也就是App.js
中的内容
然后
creatApp
返回一个mount
函数
mount
函数接受一个参数rootContainer
,表示根节点
也就是index.html
中的<div id="app"></div>
根节点,所有VNode
到时候都基于根节点开始依次往深层挂载
creatVNode API
生成VNode
export function creatVNode(type,props?,children?) {
const vnode = {
type,
props,
children
}
return vnode
}
props
,children
两个参数是可选的
初始化的时候只接收到了一个type
也就是我们的 App
常量
render API
这时候我们就需要新建一个renderer.ts
的文件夹了
导出我们的render
函数,上一章已经详细说了runtime-core
的核心流程主要对是render
函数的调用。在render
函数中最重要的就是patch
算法
export function render(vnode, container) {
// patch
// 递归处理
patch(vnode, container)
}
这样写是因为抽离出
patch
函数后,可以方便我们递归调用patch
Patch
算法
好了接下来是比较简单的一些封装,迅速过一遍,根据注释和函数名就可以知道他们的功能作用了
\src\runtime-core\renderer.js
import { createComponentInstance,setupComponent } from "./component"
export function render(vnode, container) {
patch(vnode, container)
}
// 抽离出patch是为了方便后续的递归处理
function patch(vnode, container) {
// 处理组件
processComponent(vnode, container)
}
// 处理组件的进程
function processComponent(vnode, container) {
mountComponent(vnode,container)
}
// 挂载组件
function mountComponent(vnode,container) {
// 获得组件实例
const instance = createComponentInstance(vnode)
// setup组件
setupComponent(instance)
// 给组件绑定effect
setupRenderEffect(instance,container)
}
// 这里主要是为将来更新组件diff算法做铺垫的
// setup渲染并且绑定effect副作用
function setupRenderEffect(instance,container) {
const subTree = instance.render()
// vnode-> patch
// vnode -> element -> mountElement
patch(subTree,container)
}
\src\runtime-core\component.js
// 创建组件实例
export function createComponentInstance(vnode) {
const component = {
vnode,
type:vnode.type
}
return component
}
// 初始化组件
export function setupComponent(instance) {
// TODO
// initProps()
// initSlots()
setupStatefulComponent(instance)
}
// 设置组件状态
function setupStatefulComponent(instance) {
const Component = instance.type
const { setup } = Component
if (setup) {
const setupResult = setup()
handleSetupResult(instance, setupResult)
}
}
// 处理setup的结果
function handleSetupResult(instance, setupResult) {
// function object
// TODO funciton
if (typeof setupResult === "object") {
instance.setupState = setupResult
}
finishComponentSetup(instance)
}
// 完成组件设置
function finishComponentSetup(instance) {
const Component = instance.render
if(!Component.render) {
instance.render = Component.render
}
}
完成源码初始化组件的逻辑,可以对照着这张流程图看一看代码的运行过程:
流程图建议大家多看看,这样理解程序运行逻辑时会更加的透彻!!!!后面文章也会反复提到这张图。
当然第一章中我们已经说了patch
算法会识别VNode
类型并且执行相应的渲染逻辑,那么下一章要对代码进行正式打包,并且识别VNode
类型了!