Vue3源码解析(四)

Vue3源码解析(四)

一、事件系统

  1. 事件绑定原理
    • 在 Vue3 中,事件绑定是通过模板语法(如 @click)实现的。当模板被编译时,这些事件绑定表达式会被转化为 JavaScript 代码来处理事件。例如,对于模板 <button @click="handleClick">Click me</button>,编译后大致会生成如下代码:

function render() {
    return h('button', {
        onClick: this.handleClick
    }, 'Click me');
}
  • 这里 h 函数创建按钮虚拟节点时,将 onClick 属性指向组件实例的 handleClick 方法。当按钮在页面上被点击时,就会调用 handleClick 方法。
  • 在底层,Vue3 使用了 DOM 的原生事件机制。当组件挂载时,会根据虚拟节点上的事件绑定信息,通过 addEventListener 为真实 DOM 元素添加相应的事件监听器。例如:

function patch(n1, n2, container) {
    if (!n1) {
        // 新增节点
        const el = (n2.el = document.createElement(n2.type));
        if (n2.props) {
            for (const key in n2.props) {
                if (key.startsWith('on')) {
                    const eventName = key.slice(2).toLowerCase();
                    el.addEventListener(eventName, n2.props[key]);
                }
            }
        }
        container.appendChild(el);
    } else {
        // 更新节点逻辑...
    }
}
  • 上述代码展示了在 patch 过程中,当创建新节点时,如果节点属性中有以 on 开头的属性(即事件绑定),会提取事件名并为 DOM 元素添加相应的事件监听器。
  1. 自定义事件与事件冒泡
    • 自定义事件是 Vue 组件间通信的重要方式。在子组件中,可以通过 this.$emit('eventName', payload) 触发自定义事件,父组件通过 @eventName="handleEvent" 监听该事件。例如:

<!-- 子组件 -->
<template>
    <button @click="sendCustomEvent">Send Event</button>
</template>
<script>
export default {
    methods: {
        sendCustomEvent() {
            this.$emit('custom - event', 'Hello from child');
        }
    }
};
</script>

<!-- 父组件 -->
<template>
    <child - component @custom - event="handleCustomEvent"></child - component>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
    components: {
        ChildComponent
    },
    methods: {
        handleCustomEvent(payload) {
            console.log('Received payload:', payload);
        }
    }
};
</script>
  • 从源码角度看,$emit 函数在组件实例上被定义,它会在组件的 _events 对象中查找对应的事件监听器数组,并依次调用这些监听器函数。对于组件树中的事件冒泡,它并非基于 DOM 的事件冒泡机制,而是 Vue 自定义的事件传播机制。当子组件触发自定义事件时,Vue 会沿着组件树向上查找父组件中是否有对应的事件监听器,并依次调用,从而实现类似冒泡的效果。

二、计算属性与侦听器

  1. 计算属性的实现机制
    • 计算属性在 Vue3 中通过 computed 函数创建。例如:

import { computed, reactive } from 'vue';

const state = reactive({
    count: 0
});

const doubleCount = computed(() => state.count * 2);
  • 在源码层面,computed 函数返回一个 ComputedRefImpl 实例。这个实例内部维护了一个 effect,用于追踪计算属性依赖的响应式数据(如上述例子中的 state.count)。ComputedRefImpl 有一个 _dirty 标志,用于判断计算属性的值是否过期。当依赖的数据发生变化时,_dirty 会被设为 true,表示计算属性需要重新计算。当访问计算属性时,如果 _dirty 为 true,会重新执行 effect 中的计算逻辑,并更新 _dirty 为 false,返回计算后的值。例如:

class ComputedRefImpl {
    constructor(getter) {
        this.getter = getter;
        this._dirty = true;
        this._value = undefined;
        this.effect = new ReactiveEffect(getter, () => {
            if (!this._dirty) {
                this._dirty = true;
            }
        });
    }
    get value() {
        if (this._dirty) {
            this._value = this.effect.run();
            this._dirty = false;
        }
        return this._value;
    }
}
  1. 侦听器的工作原理
    • 侦听器在 Vue3 中通过 watch 函数实现。例如:

import { reactive, watch } from 'vue';

const state = reactive({
    message: 'Initial'
});

watch(() => state.message, (newValue, oldValue) => {
    console.log('Message changed from', oldValue, 'to', newValue);
});

state.message = 'Updated';
  • watch 函数接受一个源(可以是一个函数返回响应式数据,如上述例子中的 () => state.message)和一个回调函数。在源码中,watch 会创建一个 WatchEffect 实例,它内部同样基于 ReactiveEffect 来追踪源的变化。当源依赖的响应式数据发生变化时,会触发 WatchEffect 实例的回调函数,并传入新值和旧值。此外,watch 还支持一些选项,如 deep 选项用于深度监听对象或数组的变化,immediate 选项用于在初始化时立即执行回调函数。例如,当设置 deep: true 时,WatchEffect 会递归地为对象或数组的所有属性添加依赖追踪,确保深层数据变化时也能触发回调。

三、错误处理

  1. 全局错误处理
    • Vue3 提供了全局错误处理机制。可以通过 app.config.errorHandler 来设置全局的错误处理函数。例如:

import { createApp } from 'vue';

const app = createApp({
    // 组件选项
});

app.config.errorHandler = (err, vm, info) => {
    console.error('Global error caught:', err);
    console.log('Component instance:', vm);
    console.log('Error info:', info);
};

app.mount('#app');
  • 在源码中,当 Vue 在组件渲染、事件处理、生命周期钩子函数等过程中捕获到错误时,会调用 app.config.errorHandler 设置的函数。err 是错误对象,vm 是发生错误的组件实例(如果有的话),info 提供了关于错误发生位置的额外信息,如错误发生在哪个生命周期钩子函数或指令等。
  1. 组件内错误处理
    • 组件内也可以通过 errorCaptured 生命周期钩子函数来捕获子组件传递上来的错误。例如:

<template>
    <child - component></child - component>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
    components: {
        ChildComponent
    },
    errorCaptured(err, childVm, errorInfo) {
        console.error('Error captured in parent:', err);
        console.log('Child component instance:', childVm);
        console.log('Error info:', errorInfo);
        return true; // 阻止错误继续向上传播
    }
};
</script>
  • 当子组件抛出错误时,会触发父组件的 errorCaptured 钩子函数。可以在这个钩子函数中进行错误处理,并且通过返回 true 或 false 来决定是否阻止错误继续向上传播到祖先组件的 errorCaptured 钩子函数。这种机制使得 Vue 应用在错误处理方面具有较好的层次结构和灵活性。
© 版权声明
THE END
喜欢就支持一下吧
点赞19 分享
评论 抢沙发

    暂无评论内容