Vue 3 中移除了 $on、$off 和 $once 方法(附:Composables 组合式函数 使用详解)

Vue3移除了on、off和$once事件API,旨在简化API并更好地支持组合式API。


主要替代方案包括:

1)使用mitt等第三方事件库;

2)基于provide/inject和响应式状态模拟事件总线;

3)使用composable共享状态;

4)采用Pinia/Vuex进行状态管理;

5)父子组件通过props/emits通信。


Vue3鼓励使用更明确的组件通信方式,如状态管理工具和组合式API,替代传统事件总线模式,使代码更可维护。


推荐优先考虑Pinia配合provide/inject或composables方案。


组合式函数是Vue3的核心特性,支持逻辑复用、类型安全和清晰的作用域,适用于数据获取、表单处理等场景。


Vue 3 中移除了 $on$off$once 方法


这是 Vue 3 的一项重大变化。


为什么被移除?

Vue 3 的 事件 API 重构 旨在:

  1. 简化 API - 减少全局方法数量

  2. 更好地支持组合式 API - 传统的事件总线模式与组合式 API 理念不太契合

  3. 促进更明确的组件通信模式 - 鼓励使用更可维护的通信方式


替代方案

1. 使用第三方事件库(最接近的替代)

复制代码
// 安装:npm install mitt
import mitt from 'mitt';

// 创建事件总线
const emitter = mitt();

// 发送事件
emitter.emit('event-name', data);

// 监听事件
emitter.on('event-name', (data) => {
  console.log(data);
});

// 取消监听
emitter.off('event-name', handler);

2. 使用 Vue 3 的 provide/inject + 响应式状态

复制代码
// 事件总线模拟
import { reactive, provide, inject } from 'vue';

// 创建事件总线
const eventBusSymbol = Symbol();

export function createEventBus() {
  const listeners = reactive({});
  
  return {
    on(event, callback) {
      if (!listeners[event]) listeners[event] = [];
      listeners[event].push(callback);
    },
    off(event, callback) {
      if (listeners[event]) {
        const index = listeners[event].indexOf(callback);
        if (index > -1) listeners[event].splice(index, 1);
      }
    },
    emit(event, data) {
      if (listeners[event]) {
        listeners[event].forEach(callback => callback(data));
      }
    }
  };
}

// 使用
export function useEventBus() {
  return inject(eventBusSymbol);
}

3. 使用 composable 替代事件总线

复制代码
// counter.js - 共享状态
import { ref, readonly } from 'vue';

export function useCounter() {
  const count = ref(0);
  
  const increment = () => {
    count.value++;
    // 可以在这里触发其他逻辑
  };
  
  return {
    count: readonly(count),
    increment
  };
}

// 多个组件共享同一个实例

4. 使用 Vuex/Pinia 进行状态管理

复制代码
// 使用 Pinia(Vue 3 推荐状态管理)
import { defineStore } from 'pinia';

export const useAppStore = defineStore('app', {
  state: () => ({
    events: [],
    data: null
  }),
  actions: {
    triggerEvent(eventName, payload) {
      // 处理事件逻辑
    }
  }
});

5. 使用 v-model 和 emits(父子组件通信)

复制代码
<!-- 子组件 -->
<script setup>
defineProps(['modelValue']);
defineEmits(['update:modelValue']);

const updateValue = (newVal) => {
  emit('update:modelValue', newVal);
};
</script>

<!-- 父组件 -->
<ChildComponent v-model="value" />

迁移建议

  1. 父子组件通信 → 使用 propsemits

  2. 兄弟组件通信 → 使用共同的父组件或状态管理

  3. 远房组件通信 → 使用 Pinia/Vuex 或 provide/inject

  4. 全局事件 → 使用 mitt 等第三方库


总结

虽然 $on 被移除,但 Vue 3 提供了更现代化、类型安全的替代方案。


对于大多数应用,推荐使用 Pinia 进行状态管理,配合 provide/injectcomposables 来替代传统的事件总线模式。


关联阅读推荐


Vue 3 中使用 Mitt 事件总线


Pinia 使用详解(附:如何查看或区分项目使用的是 Pinia 还是 Vuex 4)


Vue3 provide/inject 使用详解


Composables 是什么?


Composables(组合式函数) 是 Vue 3 组合式 API 的核心概念,是一种利用 Vue 组合式 API 来封装和重用有状态逻辑的函数。


核心特点

1. 逻辑复用

将组件逻辑提取到可复用的函数中:

javascript

复制代码
// 使用 composable
import { useMouse } from './mouse';

export default {
  setup() {
    const { x, y } = useMouse();
    return { x, y };
  }
}

2. 有状态逻辑

不同于工具函数,composables 可以包含响应式状态:

javascript

复制代码
// useCounter.js
import { ref } from 'vue';

export function useCounter(initialValue = 0) {
  const count = ref(initialValue);
  
  const increment = () => count.value++;
  const decrement = () => count.value--;
  const reset = () => count.value = initialValue;
  
  return {
    count,
    increment,
    decrement,
    reset
  };
}

完整示例

基础示例:鼠标位置跟踪

javascript

复制代码
// useMouse.js
import { ref, onMounted, onUnmounted } from 'vue';

export function useMouse() {
  const x = ref(0);
  const y = ref(0);
  
  const update = (event) => {
    x.value = event.pageX;
    y.value = event.pageY;
  };
  
  onMounted(() => window.addEventListener('mousemove', update));
  onUnmounted(() => window.removeEventListener('mousemove', update));
  
  return { x, y };
}

在组件中使用

vue

复制代码
<script setup>
import { useMouse } from './useMouse';

const { x, y } = useMouse();
</script>

<template>
  <div>鼠标位置: {{ x }}, {{ y }}</div>
</template>

实际应用场景

1. 数据获取

javascript

复制代码
// useFetch.js
import { ref, onMounted } from 'vue';

export function useFetch(url) {
  const data = ref(null);
  const error = ref(null);
  const loading = ref(false);
  
  const fetchData = async () => {
    loading.value = true;
    try {
      const response = await fetch(url);
      data.value = await response.json();
    } catch (err) {
      error.value = err;
    } finally {
      loading.value = false;
    }
  };
  
  onMounted(fetchData);
  
  return {
    data,
    error,
    loading,
    refetch: fetchData
  };
}

// 使用
const { data: users, loading } = useFetch('/api/users');

2. 本地存储

javascript

复制代码
// useLocalStorage.js
import { ref, watch } from 'vue';

export function useLocalStorage(key, defaultValue) {
  const data = ref(JSON.parse(localStorage.getItem(key)) || defaultValue);
  
  watch(data, (newValue) => {
    localStorage.setItem(key, JSON.stringify(newValue));
  }, { deep: true });
  
  const clear = () => {
    localStorage.removeItem(key);
    data.value = defaultValue;
  };
  
  return { data, clear };
}

// 使用
const { data: theme, clear } = useLocalStorage('theme', 'light');

3. 表单处理

javascript

复制代码
// useForm.js
import { ref, computed } from 'vue';

export function useForm(initialValues = {}) {
  const form = ref({ ...initialValues });
  const errors = ref({});
  
  const validate = () => {
    // 验证逻辑
    const newErrors = {};
    if (!form.value.name) newErrors.name = '姓名必填';
    if (!form.value.email.includes('@')) newErrors.email = '邮箱格式错误';
    errors.value = newErrors;
    return Object.keys(newErrors).length === 0;
  };
  
  const reset = () => {
    form.value = { ...initialValues };
    errors.value = {};
  };
  
  const isDirty = computed(() => 
    JSON.stringify(form.value) !== JSON.stringify(initialValues)
  );
  
  return {
    form,
    errors,
    validate,
    reset,
    isDirty
  };
}

Composables vs 其他模式

特性 Composables Mixins 高阶组件 工具函数
状态支持 ✅ 响应式状态 ✅ 有状态 ⚠️ 复杂 ❌ 无状态
逻辑隔离 ✅ 明确来源 ❌ 命名冲突 ✅ 较好 ✅ 明确
TypeScript ✅ 完美支持 ⚠️ 一般 ⚠️ 一般 ✅ 支持
可组合性 ✅ 易于组合 ❌ 易冲突 ⚠️ 嵌套深 ✅ 可组合

最佳实践

1. 命名约定

javascript

复制代码
// 以 "use" 开头
useUser()
useFetch()
useLocalStorage()
useMousePosition()

2. 参数灵活性

javascript

复制代码
// 支持配置选项
export function useFetch(url, options = {}) {
  const {
    immediate = true,
    retry = 3,
    onSuccess = null
  } = options;
  // ...
}

3. 返回值一致性

javascript

复制代码
// 返回响应式引用和函数
export function useTimer() {
  const seconds = ref(0);
  const isRunning = ref(false);
  
  const start = () => { /* ... */ };
  const stop = () => { /* ... */ };
  
  return {
    seconds,
    isRunning,
    start,
    stop
  };
}

4. 副作用管理

javascript

复制代码
import { onUnmounted } from 'vue';

export function useEventListener(target, event, callback) {
  onMounted(() => target.addEventListener(event, callback));
  onUnmounted(() => target.removeEventListener(event, callback));
}

与事件总线的结合

javascript

复制代码
// useEventBus.js - 用 composable 包装事件总线
import { onUnmounted } from 'vue';
import mitt from 'mitt';

const emitter = mitt();

export function useEventBus() {
  const eventHandlers = [];
  
  const emit = (event, data) => emitter.emit(event, data);
  
  const on = (event, handler) => {
    emitter.on(event, handler);
    eventHandlers.push({ event, handler });
  };
  
  onUnmounted(() => {
    eventHandlers.forEach(({ event, handler }) => {
      emitter.off(event, handler);
    });
  });
  
  return { emit, on };
}

// 使用
const { emit, on } = useEventBus();
on('user-login', (user) => console.log(user));
emit('user-login', { name: 'John' });

总结

Composables 的本质:将 Vue 3 的响应式系统(ref、reactive)和生命周期钩子组合成可复用的逻辑单元。


优势

  • 逻辑复用 - 提取和共享状态逻辑

  • 类型安全 - 完整的 TypeScript 支持

  • 可组合 - 多个 composables 可以组合使用

  • 作用域清晰 - 避免 mixin 的命名冲突问题

  • 灵活 - 可以接受参数,返回需要的内容


适用场景

  • 数据获取和缓存

  • 表单处理和验证

  • 浏览器 API 交互

  • 第三方库集成

  • 复杂业务逻辑封装


这是 Vue 3 组合式 API 最强大的特性之一,极大地改善了代码的组织和复用方式。

相关推荐
Sapphire~1 天前
Vue3-15 html标签和组件上的ref属性 + 接口泛型
vue3
Irene19911 天前
Vue 3 中使用 Mitt 事件总线
vue3·mitt
咸甜适中2 天前
双色球、大乐透兑奖分析小程序(rust_Tauri + Vue3 + sqlite)
爬虫·rust·sqlite·vue3·tauri2
Sapphire~3 天前
Vue3-012 vue2与vue3中的computed
vue3
Sapphire~6 天前
Vue3-11 toRefs 和 toRef
vue3
华玥作者7 天前
uni-app + Vite 项目中使用 @uni-helper/vite-plugin-uni-pages 实现自动路由配置(超详细)
前端·uni-app·vue·vue3·vite
独立开发者阿乐9 天前
Vue3中Markdown解析与渲染的完整解决方案:从安全到性能优化
web安全·性能优化·vue3·前端开发·语法高亮·markdown解析·markdown-it
Sapphire~10 天前
Vue3-10 ref与reactive创建响应式数据的区别
vue3
Irene199110 天前
Vue3 TypeScript 项目中,Emits 验证的使用场景
typescript·vue3·验证