mitt和script setup语法糖的使用

问题说明

现在的版本script setup直接使用mitt会报错

以下有多种解决方案,第一个是我使用的解决办法。之后是摘录于网络的方法,掘金用户:羽墨。自己留存学习

我使用的解决办法是局部引入使用

创建js文件 我是在hooks下创建了mittBus.js 以下是文件内容

import mitt from "mitt";

const mittBus = mitt();

export defau

接收数据的组件:

<script setup lang="ts">
    import {onUnmounted} from "vue";
    import mittBus from '../../hooks/mittBus.js'
    // 监听时间方法
    const callback = (test:string) => {
        console.log(test)
    }
    
    mittBus.on('form-item-created',callback)

    // 用完后要清理监听器
    onUnmounted(()=>{
        mittBus.off('form-item-created',callback)
    })
</script>

发送数据的组件:

<script setup lang="ts">
    import {onMounted} from "vue";
    import mittBus from '../../hooks/mittBus.js'

    onMounted(()=>{
        mittBus.emit('form-item-created',inputRef.val)
    })
</script>

ok,搞定!

以下是来源于网络:

前言

在vue3中 o n , on, onoff 和 $once 实例方法已被移除,组件实例不再实现事件触发接口,原因在于在短期内EventBus往往是最简单的解决方案,但从长期来看,它维护起来总是令人头疼,于是不再提供官方支持。vue3推荐使用props、emits、provide/inject、vuex、pinia等方案来取缔它,然而在一些小项目中我们仍希望使用EventBus,对于这种情况,vue3推荐使用第三方库来实现例如 mitt 或 tiny-emitter。本文就来分析分析mitt。
强烈建议: 不要在大型项目中使用EventBus,不要折磨你的队友!本文只做学习分析😂😂😂

简介

mitt具有以下优点:
  • 零依赖、体积超小,压缩后只有200b。
  • 提供了完整的typescript支持,能自动推导出参数类型。
  • 基于闭包实现,没有烦人的this困扰。
  • 为浏览器编写但也支持其它javascript运行时,浏览器支持ie9+(需要引入Map的polyfill)。
    与框架无关,可以与任何框架搭配使用。

基础使用

import mitt from 'mitt'

const emitter = mitt()

// 订阅一个具体的事件
emitter.on('foo', e => console.log('foo', e) )

// 订阅所有事件
emitter.on('*', (type, e) => console.log(type, e) )

// 发布一个事件
emitter.emit('foo', { a: 'b' })

// 根据订阅的函数来取消订阅
function onFoo() {}
emitter.on('foo', onFoo)   // listen
emitter.off('foo', onFoo)  // unlisten

// 只传一个参数,取消订阅同名事件
emitter.off('foo')  // unlisten

// 取消所有事件
emitter.all.clear()

mitt 一共只暴露了on、off、emit三个api,以及all这个实例(它是一个map,用来存储 eventName => handlers 的映射)。
基本的用法和vue2.x差不多,只不过取消订阅的方式采用了emitter.all.clear()这样的形式,而vue2.x采用的是 o f f ( ) ( 不 传 参 数 ) , 私 以 为 v u e 2. x 的 这 种 更 好 些 。 比 较 遗 憾 的 是 没 有 封 装 类 似 于 v u e 2 中 off()(不传参数),私以为vue2.x的这种更好些。 比较遗憾的是没有封装类似于vue2中 off()vue2.xvue2once这样的api,不过自己手动实现也非常简单。

在TS中使用

import mitt from "mitt";

type Events = {
  foo: string;
  bar: number;
};

// 提供泛型参数让 emitter 能自动推断参数类型
const emitter = mitt<Events>();

// 'e' 被推断为string类型
emitter.on("foo", (e) => {
  console.log(e);
});

// ts error: 类型 string 的参数不能赋值给类型 'number' 的参数
emitter.emit("bar", "xx");

// ts error: otherEvent 不存在与 Events 的key中
emitter.on("otherEvent", () => {
  //
});

mitt通过传入泛型参数Events,从而使得我们在写代码的时候能自动推断出回调函数中参数的类型,在敲事件名的时候也有响应的代码提示,typescript真香。

继承vue3

有很多方法让vue3和mitt集成,下面推荐几种并分析下各自的优缺点

[export/import]
// @/utils/emitter.ts
import mitt from "mitt";

type Events = {
  foo: string;
  bar: number;
};

const emitter = mitt<Events>();
export default emitter;

// main.ts
import emitter from "@/utils/emitter";

emitter.on('foo',(e) => {
    console.log(e)
})

emitter.emit('foo',"hello");

这是最经典的集成方式,适合用于全局的事件总线。

优点: 不需要做其它处理就能获得typescript,在非组件中亦可使用。
缺点: 使用时需要import,只适合全局级别,组件级别不适合

[挂载在 globalProperties 上使用]
// main.ts
import emitter from "@/utils/emitter";

// 挂载在全局上
app.config.globalProperties.$emitter = emitter

// 由于必须要拓展ComponentCustomProperties类型才能获得类型提示
declare module "vue" {
  export interface ComponentCustomProperties {
    $emitter: typeof emitter;
  }
}

// App.vue 中使用, 非 setup script 语法糖
<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  ...
  mounted() {
    this.$emitter.on("foo", () => {
      //do something
    });
  },
  ...
});
</script>

// App.vue 中使用, setup script 语法糖
<script lang="ts" setup>
import { getCurrentInstance } from "vue";
const instance = getCurrentInstance();
instance?.proxy?.$emitter.on("foo", () => {
  // dosomething
});

不同于在vue2中通过拓展原型prototype来使其全局可用,在vue3中需要设置在app.config.globalProperties中。

优点: 在组件使用时不用import/export
缺点:

需要用declare module "vue"拓展ComponentCustomProperties才能得到ts类型提示。
setup script 语法糖中需要用getCurrentInstance().proxy才能获取到$emitter,较为繁琐。
只适合全局级别,组件级别不适合。

[provide/inject]

// 父组件 Parent.vue
<script lang="ts">
import { InjectionKey } from "vue";
import mitt, { Emitter } from "mitt";
type Events = {
  foo: string;
  bar: number;
};
// 为了拥有类型化提示,必须要导出一个类型化的key
export const emitterKey: InjectionKey<Emitter<Events>> = Symbol("emitterKey");
</script>

<script lang="ts" setup>
import { provide } from "vue";
const emitter = mitt<Events>();
provide(emitterKey, emitter);
</script>

// 子组件 Child.vue
<script lang="ts" setup>
import { inject } from "vue";
import { emitterKey } from "./Parent.vue";

// 通过类型化的key使得ts推断出emitter的类型
const emitter = inject(emitterKey)!;
emitter.on("foo", (e) => {
  // dosomething
});
</script>

为了使得我们的emitter在inject的时候拥有类型,需要借用InjectionKey导出一个类型化的key,而setup-script语法不是一个标准模块,所以只能再添加一个普通的模块来做导出。

优点: 支持组件级别的eventbus。
缺点:

  1. 在setup-script语法中,需要两个script标签来配合使用。
  2. 需要 export/import 导出导入一个类型化的key才能支持类型化。

小结

三种集成方式各有千秋,如果只是全局的可以考虑只使用import/export方式,不管在组件中和普通文件都可以使用。懒得写import的同学可以用globalProperties拓展。provide/inject写法虽然较为复杂,但是支持组件级别的event-bus,在某些场景是必选方案。

源码欣赏

js版本
function mitt(all) {
  all = all || new Map();
  return {
    all: all,
    on: function (type, handler) {
      var handlers = all.get(type);
      if (handlers) {
        handlers.push(handler);
      } else {
        all.set(type, [handler]);
      }
    },
    off: function (type, handler) {
      var handlers = all.get(type);
      if (handlers) {
        if (handler) {
          handlers.splice(handlers.indexOf(handler) >>> 0, 1);
        } else {
          all.set(type, []);
        }
      }
    },
    emit: function (type, evt) {
      var handlers = all.get(type);
      if (handlers) {
        handlers.slice().map(function (handler) {
          handler(evt);
        });
      }
      handlers = all.get("*");
      if (handlers) {
        handlers.slice().map(function (handler) {
          handler(type, evt);
        });
      }
    },
  };
}

以上的是我编译过后留下的代码,可以发现实现非常的精简,总共50行代码不到,也非常利于理解。核心逻辑相比大家都能写出来,相比而言,我觉得ts版本更适合大家学习。

ts版本
export type EventType = string | symbol;

// An event handler can take an optional event argument
// and should not return a value
export type Handler<T = unknown> = (event: T) => void;
export type WildcardHandler<T = Record<string, unknown>> = (
  type: keyof T,
  event: T[keyof T]
) => void;

// An array of all currently registered event handlers for a type
export type EventHandlerList<T = unknown> = Array<Handler<T>>;
export type WildCardEventHandlerList<T = Record<string, unknown>> = Array<
  WildcardHandler<T>
>;

// A map of event types and their corresponding event handlers.
export type EventHandlerMap<Events extends Record<EventType, unknown>> = Map<
  keyof Events | "*",
  EventHandlerList<Events[keyof Events]> | WildCardEventHandlerList<Events>
>;

export interface Emitter<Events extends Record<EventType, unknown>> {
  all: EventHandlerMap<Events>;

  on<Key extends keyof Events>(type: Key, handler: Handler<Events[Key]>): void;
  on(type: "*", handler: WildcardHandler<Events>): void;

  off<Key extends keyof Events>(
    type: Key,
    handler?: Handler<Events[Key]>
  ): void;
  off(type: "*", handler: WildcardHandler<Events>): void;

  emit<Key extends keyof Events>(type: Key, event: Events[Key]): void;
  emit<Key extends keyof Events>(
    type: undefined extends Events[Key] ? Key : never
  ): void;
}

/**
 * Mitt: Tiny (~200b) functional event emitter / pubsub.
 * @name mitt
 * @returns {Mitt}
 */
export default function mitt<Events extends Record<EventType, unknown>>(
  all?: EventHandlerMap<Events>
): Emitter<Events> {
  type GenericEventHandler =
    | Handler<Events[keyof Events]>
    | WildcardHandler<Events>;
  all = all || new Map();

  return {
    /**
     * A Map of event names to registered handler functions.
     */
    all,

    /**
     * Register an event handler for the given type.
     * @param {string|symbol} type Type of event to listen for, or `'*'` for all events
     * @param {Function} handler Function to call in response to given event
     * @memberOf mitt
     */
    on<Key extends keyof Events>(type: Key, handler: GenericEventHandler) {
      const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
      if (handlers) {
        handlers.push(handler);
      } else {
        all!.set(type, [handler] as EventHandlerList<Events[keyof Events]>);
      }
    },

    /**
     * Remove an event handler for the given type.
     * If `handler` is omitted, all handlers of the given type are removed.
     * @param {string|symbol} type Type of event to unregister `handler` from, or `'*'`
     * @param {Function} [handler] Handler function to remove
     * @memberOf mitt
     */
    off<Key extends keyof Events>(type: Key, handler?: GenericEventHandler) {
      const handlers: Array<GenericEventHandler> | undefined = all!.get(type);
      if (handlers) {
        if (handler) {
          handlers.splice(handlers.indexOf(handler) >>> 0, 1);
        } else {
          all!.set(type, []);
        }
      }
    },

    /**
     * Invoke all handlers for the given type.
     * If present, `'*'` handlers are invoked after type-matched handlers.
     *
     * Note: Manually firing '*' handlers is not supported.
     *
     * @param {string|symbol} type The event type to invoke
     * @param {Any} [evt] Any value (object is recommended and powerful), passed to each handler
     * @memberOf mitt
     */
    emit<Key extends keyof Events>(type: Key, evt?: Events[Key]) {
      let handlers = all!.get(type);
      if (handlers) {
        (handlers as EventHandlerList<Events[keyof Events]>)
          .slice()
          .map((handler) => {
            handler(evt!);
          });
      }

      handlers = all!.get("*");
      if (handlers) {
        (handlers as WildCardEventHandlerList<Events>)
          .slice()
          .map((handler) => {
            handler(type, evt!);
          });
      }
    },
  };
}

ts版本的代码很简单,从源码中可以学到如何编写一个类型安全的小型库,非常值得学习。
总结
不得不感慨大神们写的代码就像诗一样,麻雀虽小五脏俱全,特别是源码ts的实现,对于我等typescript菜鸟而言是极佳的学习资料,在分析的过程中我也学到很多。码字不易,如果各位看官觉得对你有帮助的话,那就给我点个赞👍👍👍吧,先行谢过!

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值