更新 element 流程搭建
- 我们初始化页面展示
js
复制代码
// index.html
<div id="root"></div>
<script type="module" src="./main.js"></script>
// App.js
import { h, ref } from "../../lib/guide-mini-vue.esm.js";
export const App = {
name: "App",
setup() {
const count = ref(0); // ✅ 声明 ref 值
const onClick = () => { count.value++; } // ✅ 对该值进行修改,引发页面更新
return { count, onClick }
},
render() {
return h("div",
{
id: "root"
},
[
h("div", {}, "count:" + this.count), // 依赖收集
h('button', {
onClick: this.onClick
},
"click"
)
]
)
}
}
// main.js
import { App } from './App.js'
import { createApp } from '../../lib/guide-mini-vue.esm.js'
const root = document.querySelector('#root')
createApp(App).mount(root)
- 实现功能
- 声明 ref 值
- 对该值进行修改,引发页面更新
- 我们之前已经实现了 ref 的功能, 在前面的目录中可以找到,现在我们进行导出和引入
js
复制代码
// reactivity.ts
export { ref, proxyRefs } from './ref'
// src/index.ts
import * from './reactivity'
// src/runtime-core/component.ts
import { proxyRefs } from '../reactivity'
function handleSetupResult(instance, setupResult: any) { // 将状态挂载到实例上
if(typeof setupResult === 'object') {
instance.setupState = proxyRefs(setupResult)
}
finishComponentSetup(instance)
}
- 进行上面的操作后,我们已经能够在页面上看到数字已经被展示出来,而且我们点击按钮实现数字 +1,不过会每点击一次,多渲染一次
- 原因是我们每点击一次,就会在 container 里面多渲染一次,而旧的 dom 没有进行删除,所以我们需要区分初始化和更新两个阶段来处理
- 代码
js
复制代码
// component.ts
export function createComponentInstance(vnode, parent) {
const component = {
vnode,
type: vnode.type,
setupState: {},
props: {},
emit:()=>{},
slots:{},
provides: parent ? parent.provides : {},
parent,
isMounted: false, // ✅
subTree: {}, // ✅
}
component.emit = emit.bind(null, component) as any
return component
}
// renderer.ts
function setupRenderEffect(instance, vnode, container) {
effect(()=>{
let { proxy } = instance
if(!instance.isMounted) { // ✅ 初始化渲染
const subTree = instance.subTree = instance.render.call(proxy)
patch(null, subTree, container, instance)
vnode.el = subTree.el
instance.isMounted = true
console.log('init');
} else { // ✅ 更新时渲染
console.log('update');
const { proxy } = instance
const subTree = instance.render.call(proxy)
const prevSubTree = instance.subTree
instance.subTree = subTree
console.log(subTree,'subTree');
console.log(prevSubTree,'prevSubTree');
patch(prevSubTree, subTree, container, instance)
}
})
}
- 除此之外,我们给 patch 的传参时,多加一个 n1,表示是旧虚拟dom,之前 vnode 参数为n2,表示新虚拟 dom
js
复制代码
// n1 老的虚拟节点
// n2 新的虚拟节点
function patch(n1, n2, container, parentComponent) {
const { type, shapeFlag } = n2
switch (type) {
case Fragment:
processFragment(n1, n2, container, parentComponent)
break;
case Text:
processText(n1, n2, container)
break;
default:
if (shapeFlag & shapeFlags.ELEMENT) {
processElement(n1, n2, container, parentComponent)
} else if (shapeFlag & shapeFlags.STATEFUL_COMPONENT) {
processComponent(n1, n2, container, parentComponent)
}
break;
}
}
- 当 n1 为空时,说明没有旧的虚拟dom,属于初始化加载
js
复制代码
function processElement(n1, n2, container, parentComponent) {
if(!n1) {
mountElement(n2, container, parentComponent)
} else {
patchElement(n1, n2, container)
}
}
function patchElement(n1, n2, container) {
// 我们在这里做更新 dom 的处理
}