vue3渲染器设计

利用响应系统的能力,自动调用渲染器完成页面的渲染和更新

const { ref, effect } = Vue;

function renderer(domString, container) {
	container.innerHTML = domString;
}

const count = ref(0);

effect(() => {
    renderer(`<h1>${count.value}</h1>`, document.getElementById('app'));
});

count.value++;

渲染器renderer: 把虚拟DOM渲染成为特定平台的真是元素。

render函数: 以container为挂载点,将vnode渲染为真实的DOM并添加到挂载点下。

render函数基本实现

function createRenderer() {
    // 挂载
    function mountElement(vnode, container) {
        const el = document.createElement(vnode.type);
        if (typeof vnode.children === 'string') {
            el.textContent = vnode.children
        }
        // 将元素添加到容器中
        conatiner.appendChild(el)
    }
    // 更新打补丁
    function patch(n1, n2, container) {
        if (!n1) {
            // 挂载
            mountElement(n2, container)
        }
        else {
            // 更新,打补丁,暂时忽略
        }
    }
    function render(vnode, container) {
        if (vnode) {
            patch(container._vnode, vnode, container);
        } else {
            if (container._vnode) {
                // 旧vnode存在,新vnode不存在 => 卸载
                container.innerHTML = '';
            }
        }
        container._vnode = vnode;
    }

    return {
        render,
    };
}

此函数中大量依赖了浏览器的API(document.*)如何修改成通用渲染器呢?

抽象通用渲染器

抽离浏览器特有的API

​ 将操作DOM的API作为配置项,作为createRenderer函数的参数传递。这样就可以在函数内部通过配置项来获取操作DOM的API。

const rener = createRenderer({
    // 用于创建元素
    createElement(tag) {
        return document.createElement(tag);
    },
    // 用于设置元素的文本节点
    setElementText(el, text) {
        el.textContent = text;
    },
    // 用于在给定parent下添加指定元素
    insert(el, parent, anchor = null) {
        parent.insertBefore(el, anchor);
    },
});

function createRenderer(options) {
    const { createElement, setElementText, insert } = options;

    function mountElement(vnode, container) {
        const el = createElement(vnode.type);
        if (typeof el.children === 'string') {
            setElementText(el, vnode.children);
        }
        insert(el, container);
    }
    
    // 更新打补丁
    function patch(){}
    function render(){}
    
    return {
        render
    }
}

自定义渲染器通过抽象的手段让核心代码不再依赖平台特有的API,再通过个性化配置的能力来实现跨平台。

挂载与更新

vnode.children定义:

// 虚拟节点: 版本vnode-1
const vnode = {
    type: 'div',
    children: [
        {
            type: 'p',
            children: 'hello'
        }
    ]
}

vnode.children是一个数组,每个元素都是一个独立的虚拟节点对象,这样就形成了树形结构,即: 虚拟DOM树。

// 挂载方法:版本mountElement-1
function mountElement(vnode, container) {
    const el = document.createElement(vnode.type);
    if (typeof vnode.children === 'string') {
        el.textContent = vnode.children
    }
    else if (Array.isArray(vnode.children)) {
        // 遍历每一个节点,调用patch挂载
        vnode.children.forEach(child => {
            paych(null, child, el)
        })
    }
    // 将元素添加到容器中
    insert(el, container);
}

版本mountElement-1中需要注意两点:

  • patch第一个参数为null,因为挂载阶段没有旧vnode。
  • path第三个参数为挂载点,因为正在挂载的子元素是div标签的子节点,需要吧刚创建的div元素作为挂载点。

完成挂载后,看一下vnode如何描述一个标签属性。虚拟节点增加vnode.props字段描述元素属性

// 虚拟节点: 版本vnode-2
const vnode = {
    type: 'div',
    props: {
      id: 'foo'  
    },
    children: [
        {
            type: 'p',
            children: 'hello'
        }
    ]
}

props是一个对象,可以通过遍历对象把属性渲染到对应元素上:

// 挂载方法:版本mountElement-2
function mountElement(vnode, container) {
    const el = document.createElement(vnode.type);
    // ...省略children部分代码
    
    if (vnode.props) {
        for(const key in vnode.props){
            el.setAtrriubute(key, vnode.peops[key])
            // 或者直接设置 el[key] = vnode.props[key]
        }
    }
    
    // 将元素添加到容器中
    insert(el, container);
}

HTML Attributes 与 DOM Properties

HTML Attributes的作用是设置与之对应的 DOM Properties的初始值,一旦值改变,那么 DOM Properties始终存储当前值,而通过getAttribute函数得到的仍然是初始值。

// case:
<input value="foo">
// 如果用户修改了输入框的值为bar,那么通过el.value获取的 DOM Properties值为bar,但是el.getAttribute('value')值还是foo
el.value // bar
el.getAttribute('value') // foo

setAttribute设置的值总会被字符化

el.setAttribute('disabled', false)
el.setAttribute('disabled', 'false')
// 二者等价

事件的处理

// 虚拟节点: 版本vnode-3
const vnode = {
    type: 'div',
    props: {
      id: 'foo',
      onClick: () => {
          alert('alert')
      }
    },
    children: [
        {
            type: 'p',
            children: 'hello'
        }
    ]
}

对事件的处理,通过调用addEventListener函数来绑定事件

// 版本patchProps-1
patchProps(el, key, prevValue, nextValue){
    // 匹配on开头的属性,视为事件
    if (/^on/.test(key)) {
        // onClick => click
        const name = key.slice(2).toLoweCase();
        el.addEventListener(name, nextValue)
    }
}

事件更新如何处理呢?

常规方法先调用removeEventListener移除之前的事件,再绑定新的事件,但这种方式性能不好,有中更优的方式处理:

绑定一个伪造的事件处理函数invoker,然后把真正的事件处理函数设置为invoker.value属性的值。这样当更新事件的时候,我们不再需要调用removeEventListener函数移除上一次的绑定事件,只需要更新invoker.value的值即可。

// 版本patchProps-2
patchProps(el, key, prevValue, nextValue){
    // 匹配on开头的属性,视为事件
    if (/^on/.test(key)) {
        // 获取为该元素伪造的事件处理函数
        let invoker = el._vei;
        const name = key.slice(2).toLoweCase();
        if (nextValue) {
        	if (!invoker) {
                invoker = el._vei = e => {
                    // 当伪造的事件处理函数执行时,会执行真正的事件处理函数
                    invoker.value(e);
                }
                invoker.value = nextValue;
                el.addEventListener(name, invoker)}
            else {
                invoker.value = nextValue;
            }  
        }
         else if (invoker) {
             // 无新的绑定,移除之前的绑定
            el.removeEventListener(name, invoker)}
    }
}
<think>好的,我现在需要帮助用户理解Vue3的渲染机制和过程。根据用户的问题,他们想了解Vue3的渲染流程及实现原理。首先,我需要回忆Vue3的相关知识,并结合提供的引用内容来构建回答。 首先,Vue3的渲染机制主要涉及响应式系统、虚拟DOM和渲染器这几个部分。用户的引用内容提到了初始化阶段、响应式注入、异步更新机制以及渲染器的工作原理,这些都需要整合到回答中。 接下来,我应该按照流程分步骤解释。比如,从组件初始化开始,然后是响应式数据如何触发更新,接着是虚拟DOM的生成和对比,最后是渲染器如何将变化应用到真实DOM上。同时,需要提到异步更新和nextTick的实现,因为引用里提到了微任务队列。 在引用中,引用[1]提到了beforeCreate钩子和初始化注入响应式,这部分应该对应初始化阶段。引用[2]关于nextTick的异步更新机制,需要在渲染流程中说明数据变化后如何批量处理更新。引用[3]和[4]解释了渲染器的作用,如何精确更新DOM,这部分要详细说明虚拟DOM和patch过程。 需要注意用户提到的系统级指令,比如使用正确的中文回答,生成相关问题,引用标识放在句末。同时,确保LaTeX格式正确,但问题中可能不需要数学公式,所以可能不需要使用。但如果有涉及变量或术语,可能需要用行内格式。 可能还需要检查是否有遗漏的关键点,比如依赖收集、副作用函数、调度器的作用等。同时,结合用户提供的引用内容,确保每个步骤都有对应的引用标记。 最后,生成相关问题时要围绕Vue3的渲染机制,比如响应式系统原理、虚拟DOM的作用、异步更新的优势、nextTick的使用场景,以及渲染器跨平台的应用等。 现在需要组织语言,确保结构清晰,分步骤解释每个阶段,并自然地插入引用标记。同时,保持回答真实可靠,符合Vue3的官方文档和实现原理。</think>### Vue3 渲染流程及实现原理 #### 1. **初始化阶段** - **组件实例创建**:Vue3 在创建组件实例时,会初始化事件系统、生命周期钩子,并调用 `beforeCreate` 钩子。此时数据观测和事件配置尚未开始[^1]。 - **响应式系统注入**:通过 `setup` 函数建立响应式数据,依赖 `reactive` 或 `ref` 将数据转换为代理对象,实现数据变化的追踪。 #### 2. **模板编译** - **模板解析**:将模板(`.vue` 文件或模板字符串)转换为抽象语法树(AST)。 - **生成渲染函数**:将 AST 转换为可执行的渲染函数(`render` 函数),用于生成虚拟 DOM(vnode)。 #### 3. **响应式更新触发** - **依赖收集**:渲染函数执行时,会访问响应式数据,触发 `getter` 并收集依赖(即当前组件的副作用函数)。 - **数据变更通知**:当响应式数据变化时,触发 `setter`,通知关联的副作用函数重新执行。 #### 4. **异步更新队列** - **批量更新**:Vue3 通过调度器将多次数据变更合并为一次更新任务,避免重复渲染。更新任务会被推入微任务队列(如 `Promise.then`)[^2]。 - **`nextTick` 机制**:调用 `nextTick(callback)` 时,回调函数会被追加到微任务队列,确保在 DOM 更新后执行。 #### 5. **虚拟 DOM 与 Diff 算法** - **生成新 vnode**:重新执行渲染函数,生成新的虚拟 DOM 树。 - **Patch 过程**:通过 Diff 算法对比新旧 vnode,精确找到变更点(如文本修改、属性更新),并调用 DOM API 局部更新[^3]。 #### 6. **渲染器执行** - **跨平台渲染**:渲染器根据平台调用对应的原生 API(如浏览器 DOM 操作)。其核心逻辑是递归遍历 vnode,创建或更新真实节点[^4]。 - **高效更新**:仅对变化的节点进行操作(如 `textContent` 修改或 `setAttribute` 调用),避免全量渲染。 --- ### 关键代码示例 ```javascript // 简化的渲染流程示例 const { createApp, reactive } = Vue; const app = createApp({ setup() { const state = reactive({ count: 0 }); return { state }; }, render() { return h(&#39;div&#39;, this.state.count); // 生成 vnode } }); app.mount(&#39;#app&#39;); // 渲染器将 vnode 转为真实 DOM ``` --- ### 相关技术点 - **响应式原理**:基于 Proxy 代理对象,实现细粒度依赖追踪。 - **虚拟 DOM 优势**:减少直接操作 DOM 的开销,支持跨平台渲染(如小程序、Native)。 - **微任务队列**:利用浏览器事件循环机制,确保更新和用户代码的执行顺序可控。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值