思考
- 如何触发侦听收集?
- 如何判断是初始化或者是更新?
基于这两点,逐步实现element更新流程
先来个例子
/*
* @Author: Lin zefan
* @Date: 2022-03-21 21:46:14
* @LastEditTime: 2022-04-02 10:42:02
* @LastEditors: Lin zefan
* @Description:
* @FilePath: \mini-vue3\example\updateComponent\App.js
*
*/
import { h, ref } from "../../lib/mini-vue.esm.js";
export default {
setup() {
const counter = ref(1);
function inc() {
counter.value += 1;
}
return {
counter,
inc,
};
},
render() {
return h("div", {}, [
h("div", {}, "" + this.counter),
h("button", { onClick: this.inc }, "inc"),
]);
},
};
记得把相关函数暴露出来
/*
* @Author: Lin zefan
* @Date: 2022-03-22 15:40:00
* @LastEditTime: 2022-04-02 10:40:45
* @LastEditors: Lin zefan
* @Description: 打包入口文件
* @FilePath: \mini-vue3\src\index.ts
*
*/
// 暴露runtime-core模块
export * from "./runtime-dom/index";
export * from "./reactivity/ref";
export * from "./reactivity/effect";
依赖收集
在写ref、reative的时候,已经用effect做依赖收集了,vue的双向绑定也是借助这些api来的,所以在初始化阶段做依赖收集。
解构ref对象
利用 proxyRefs 解构setup中的ref对象
// component.ts
function handleSetupResult(instance, setupResult) {
/** 这里有setup返回值有两种情况
* 1. 是一个函数
* 2. 是一个对象
*/
// 如果是对象,将对象注入上下文
if (typeof setupResult === "object") {
// 利用proxyRefs解构ref对象
instance.setupState = proxyRefs(setupResult);
} else {
// 如果是函数,那么当作render处理TODO
}
// other code
}
依赖收集
render.ts
function setupRenderEffect(instance, container) {
const { proxy, vnode, isMounted } = instance;
effect(() => {
// 通过render函数,获取render返回虚拟节点,并绑定render的this
const subTree = instance.render.call(proxy);
/**
* 1. 调用组件render后把结果再次给到patch
* 2. 再把对应的dom节点append到container
* 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
*/
patch(subTree, container, instance);
/** 挂载当前的dom元素到$el
* 1. 当遍历完所有Component组件后,会调用processElement
* 2. 在processElement中,会创建dom元素,把创建的dom元素挂载到传入的vnode里面
* 3. 当前的dom元素也就是processElement中创建的dom元素
*/
vnode.el = subTree.$el;
});
}
借助effect侦听数据变化,重新触发patch
更新element判断
- 在实例上加一个flag来判断是否初始化
- 在实例上加一个key来保存老的vnode
- 更新element时,会比对新老vnode的差别,只做变动部分的更新
新增实例属性
component.ts
export function createComponentInstance(initVNode, parent) {
const component = {
// other code
isMounted: false, // 是否初始化
preTree: {}, // 旧的vnode
};
// other code
}
初始化判断
render.ts
function setupRenderEffect(instance, container) {
effect(() => {
// 初始化vnode
if (!instance.isMounted) {
let { proxy, vnode } = instance;
// 通过render函数,获取render返回虚拟节点,并绑定render的this
const subTree = instance.render.call(proxy);
/**
* 1. 调用组件render后把结果再次给到patch
* 2. 再把对应的dom节点append到container
* 3. 把当前实例传过去,让子组件可以通过parent获取父组件实例
*/
patch(null, subTree, container, instance);
/** 挂载当前的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 {
let { proxy } = instance;
// 通过render函数,获取render返回虚拟节点,并绑定render的this
const nowTree = instance.render.call(proxy);
// 旧vnode
const preTree = instance.preTree;
// 对比新老vnode
patch(preTree, nowTree, container, instance);
// 更新旧的vnode
instance.preTree = nowTree;
}
});
}
function patch(n1, n2, container, parentComponent) {
if (!n2) return;
const { type } = n2;
switch (type) {
case Fragment:
processFragment(n1, n2, container, parentComponent);
break;
case TextNode:
processTextNode(n2, container);
break;
default:
const shapeFlags = getShapeFlags(type);
if (shapeFlags === ShapeFlags.COMPONENT) {
// 是一个Component
processComponent(n1, n2, container, parentComponent);
} else if (shapeFlags === ShapeFlags.ELEMENT) {
// 是一个element
processElement(n1, n2, container, parentComponent);
}
break;
}
}
新老vnode判断
render.ts
- n1为旧的vnode,初始化是空的,所以是创建组件阶段
- n1不为空即非初始化,即走更新dom操作
function processComponent(n1, n2, container, parentComponent) {
if (!n1) {
// 创建组件
mountComponent(n2, container, parentComponent);
} else {
// 更新组件
updateComponent(n1, n2, container, parentComponent);
}
}
function updateComponent(n1, n2, container, parentComponent) {
console.log("updateComponent更新");
console.log("旧vnode", n1);
console.log("新vnode", n2);
}
function processElement(n1, n2, container, parentComponent) {
if (!n1) {
mountElement(n2, container, parentComponent);
} else {
updateElement(n1, n2, container, parentComponent);
}
}
function updateElement(n1, n2, container, parentComponent) {
console.log("updateElement更新");
console.log("旧vnode", n1);
console.log("新vnode", n2);
}