组件的挂载流程
组件需要提供一个render函数,渲染函数需要返回虚拟DOM 类似effect
data 类似于reactive
const VueComponent = {
data(){ // 类似于reactive
return {name: 'lyp', age:18}
},
render(){ // 类似effect
setTimeout(()=>{
this.age++
},1000)
return h('p',`${this.name}今年${this.age}岁了`)
}
}
render(h(VueComponent), app)
添加组件类型
h方法中如果传入一个对象说明要渲染的是一个组件。(后续还有其他可能)
// packages/runtime-core/src/vnode.ts
export const createVNode = (type,props,children = null)=>{
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT:0;
// ... 稍后可以根据类型来进行组件的挂载
}
组件的渲染 processComponent
//packages/runtime-core/src/renderer.ts
const patch = (n1,n2,container,anchor?) => {
// 初始化和diff算法都在这里喲
if(n1 == n2){return }
if(n1 && !isSameVNodeType(n1,n2)){ // 有n1 是n1和n2不是同一个节点
unmount(n1)
n1 = null
}
const {type,shapeFlag} = n2;
switch(type){
// ...
default:
if(shapeFlag & ShapeFlags.ELEMENT){
processElement(n1,n2,container,anchor)
}else if(shapeFlag & ShapeFlags.COMPONENT){
processComponent(n1,n2,container,anchor)
}
}
}
const processComponent = (n1,n2,container,anchor)=>{ // 统一处理组件渲染 里边处理是函数式组件还是普通组件
if(n1 == null){
mountComponent(n2,container,anchor);
}else{
// 组件更新逻辑
}
}
const mountComponent = (n2,container,anchor)=>{
const {render,data=()=>({})} = n2.type; // 1、取出 data render
const state = reactive(data()) // 2、组件的状态
const instance = { // 3、组件的实例
state, // 组件的状态
isMounted:false, // 组件是否挂载
subTree:null, // 子树(子节点) 渲染组件的内容
update:null, //
vnode: n2 // 组件的虚拟节点
}
const componentUpdateFn = ()=>{ //4、组件更新函数 区分组件要更新还是初始化
if(!instance.isMounted){ // 初始化
const subTree = render.call(state,state); //作为this选项 后续this会更改
patch(null,subTree,container,anchor); // 创造了subTree的真实节点并且插入
instance.subTree = subTree
instance.isMounted = true;
}else{ // 组件内部更新
const subTree = render.call(state,state); // 拿到新的
patch(instance.subTree,subTree,container,anchor) // 和老的比对
instance.subTree = subTree // 更新
}
}
// 5、组件的渲染
const effect = new ReactiveEffect(componentUpdateFn)
// 6、调用 effect.run() 可以让组件强制更新
const update = instance.update = effect.run.bind(effect);
update();
}
组件异步渲染
组件的更新不能是同步的, 会出现叠加更新的情况
修改上边的mountComponent方法的调度函数,将更新方法压入到队列中
// 组件的异步渲染 批处理操作scheduler
const effect = new ReactiveEffect(componentUpdateFn,()=>queueJob(instance.update) )
const update = instance.update = effect.run.bind(effect);
批处理操作scheduler.js
const queue = []; // 队列
let isFlushing = false; // 是否正在刷新
const resolvedPromise = Promise.resolve() // 异步
export function queueJob(job){
if(!queue.includes(job)){ // 队列中没有这个任务就放进去
queue.push(job);
}
if(!isFlushing){ // 不是正在刷新 增加异步批处理逻辑
isFlushing = true;
resolvedPromise.then(()=>{
isFlushing = false;
let copy = queue.slice(0)
for(let i = 0; i < copy.length;i++){
let job = copy[i];
job();
}
queue.length = 0;
copy.length = 0;
})
}
}