例子
实现思路
- 需要把写好
codegen
暴露到全局,在runtime
阶段使用 runtime
暴露对应函数, 让codegen
引用这个函数,这也是Vue
官网推荐的做法,这样做的好书
- 降低耦合
- 不会打包多余代码
- 在初始化阶段,判断用户是否写了
render
,若是写了使用render
,否则查询是否写了template
。两者同时存在,取render
实现
暴露 compiler
新建一个 compiler.ts
/*
* @Author: Lin ZeFan
* @Date: 2022-04-20 20:47:43
* @LastEditTime: 2022-04-20 20:49:25
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\src\compiler-core\src\compiler.ts
*
*/
import { generate } from "./codegen";
import { baseParse } from "./parse";
import { transform } from "./transform";
import { transformElement } from "./transforms/transformElement";
import { transformExpression } from "./transforms/transformExpression";
import { transformText } from "./transforms/transformText";
// 暴露编译方法
export function baseCompile(template: string) {
const ast = baseParse(template);
transform(ast, {
nodeTransforms: [transformExpression, transformElement, transformText],
});
const code = generate(ast);
return {
code,
};
}
暴露compiler
/*
* @Author: Lin ZeFan
* @Date: 2022-04-20 20:49:37
* @LastEditTime: 2022-04-20 20:49:37
* @LastEditors: Lin ZeFan
* @Description:
* @FilePath: \mini-vue3\src\compiler-core\src\index.ts
*
*/
// 暴露整个 compiler
export * from './compiler'
接收compiler
runtime-core
暴露接收函数
// src\runtime-core\component.ts
let compiler;
export function registerCompiler(_compiler) {
compiler = _compiler;
}
接收 compiler
/*
* @Author: Lin zefan
* @Date: 2022-03-22 15:40:00
* @LastEditTime: 2022-04-24 13:03:57
* @LastEditors: Lin ZeFan
* @Description: 打包入口文件
* @FilePath: \mini-vue3\src\index.ts
*
*/
export * from "./runtime-dom";
export * from "./reactivity";
import { baseCompile } from "./compiler-core/src";
import * as runtimeDom from "./runtime-dom";
import { registerCompiler } from "./runtime-dom";
// 创建一个 render 函数
function compileToFunction(template: string) {
const { code } = baseCompile(template);
const render = new Function("Vue", code)(runtimeDom);
return render;
}
// 在这里将 compiler 传入到 component 内部中
registerCompiler(compileToFunction);
模板编译判断
新增 template
判断
src\runtime-core\component.ts
function finishComponentSetup(instance) {
const component = instance.type;
// 挂载实例的render函数,取当前组件实例声明得render
if (component.render) {
instance.render = component.render;
+ } else if (component.template && compiler) {
+ // 没有写 render,但是写了template
+ // 使用了高阶函数,暴露了 codegen 方法
+ instance.render = compiler(component.template);
+ }
}
初始化
src\runtime-core\render.ts
function renderComponentInstance(instance) {
const { proxy } = instance;
return instance.render.call(proxy, proxy);
}
function setupRenderEffect(instance, container, anchor) {
instance.runner = effect(
() => {
// 初始化vnode
if (!instance.isMounted) {
let { vnode } = instance;
// 通过render函数,获取render返回虚拟节点,并绑定render的this,并把this传出去
+ const subTree = renderComponentInstance(instance);
/**
* 1. 调用组件render后把结果再次给到patch
* 2. 再把对应的dom节点append到container
* 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
*/
patch(null, subTree, container, instance, anchor);
/** 挂载当前的dom元素到$el
* 1. 当遍历完所有Component组件后,会调用processElement
* 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
* 3. 当前的dom元素也就是processElement中创建的dom元素
*/
vnode.el = subTree.el;
// 更新初始化状态
instance.isMounted = true;
// 保存当前vnode
instance.preTree = subTree;
} else {
const { next, vnode } = instance;
if (next) {
// 保存当前的dom节点,因为新vnode没有走创建流程,所以没有el
next.el = vnode.el;
updateComponentPreRender(instance, next);
}
// 通过render函数,获取render返回虚拟节点,并绑定render的this
+ const nowTree = renderComponentInstance(instance);
// 旧vnode
const preTree = instance.preTree;
// 更新旧的vnode
instance.preTree = nowTree;
// 对比新老vnode
patch(preTree, nowTree, container, instance, anchor);
}
},
{
scheduler() {
// 将本次 update 加入到任务队列中
queueJobs(instance.runner);
},
}
);
}