Vue组件的实现原理详细分析
知奕奕 人气:0渲染组件
一个组件内部必须要使用 render 进行渲染,且返回虚拟 DOM
这是一个最简组件实例
const MyComponent = { // 组件名称,可选 name: "MyComponent", // 组件的渲染函数,其返回值必须为虚拟 DOM render() { // 返回虚拟 DOM return { type: "div", children: `我是文本内容`, }; }, };
渲染器中的 mountComponent 函数完成组件的渲染
function mountComponent(vnode, container, anchor) { // 通过 vnode 获取组件的选项对象,即 vnode.type const componentOptions = vnode.type; // 获取组件的渲染函数 render const { render } = componentOptions; // 执行渲染函数,获取组件要渲染的内容,即 render 函数返回的虚拟 const subTree = render(); // 最后调用 patch 函数来挂载组件所描述的内容,即 subTree patch(null, subTree, container, anchor); }
组件更新
组件初始化步骤:
- 取得 data 函数后用 reactive 将其变成响应式的
- render 函数内将 this 指向 state,并将 state 作为第一个参数传入 render 函数
将渲染任务包装到一个副作用函数 effect 里面,即可实现响应式更新数据
若要使每次响应式数据修改后,effect 仅执行一次,则需要引入调度器概念
这是书中给出的最简调度器实例
即先把 effect 放入微任务队列,等执行栈清空再调出来执行
// 任务缓存队列,set可以自动去重 const queue = new Set(); // 一个标志,代表是否正在刷新任务队列 let isFlushing = false; // 创建一个立即 resolve 的 Promise 实例 const p = Promise.resolve(); // 调度器的主要函数,用来将一个任务添加到缓冲队列中,并开始刷新队列 function queueJob(job) { // 将 job 添加到任务队列 queue 中 queue.add(job); // 如果还没有开始刷新队列,则刷新之 if (!isFlushing) { // 将该标志设置为 true 以避免重复刷新 isFlushing = true; // 在微任务中刷新缓冲队列 p.then(() => { try { // 执行任务队列中的任务 queue.forEach((job) => job()); } finally { // 重置状态 isFlushing = false; queue.clear = 0; } }); } }
父子组件
这是一个简单的父子组件代码
// 子组件 <template> <MyComponent :title="title" /> </template> // 父组件 const vnode = { type: MyComponent, props: { title: 'A Big Title' } }
父组件更新导致子组件更新(被动更新)过程:
- 父组件自更新
- 渲染器检查 subTree 发现存在 vnode,则调用 patchComponent 实现子组件更新
setup函数
setup 函数为配合组合式 API 所引入的
他有如下两种返回值形式
// 返回函数 const comp = { setup() { return () => { return { type: "div", children: "give up for vuejs", }; }; }, }; // 返回对象 const comp = { setup() { const count = ref(0); return { count, }; }, render() { return { type: "div", children: `count is ${this.count}`, }; }, };
setup 接收两个参数,分别是 props 以及 setupContext
setupContext 包含以下四个主要对象
- slots 插槽
- emit 自定义事件
- attrs 自定义属性
- expose 暴露
emit 实现
只需要实现一个 emit 函数并将其添加到 setupContext 对象中
加载全部内容