preface
In VUe3, the $ON, $OFF, and $once instance methods have been removed, and component instances no longer implement event-triggering interfaces, because EventBus is often the easiest solution in the short term, but in the long term it becomes a headache to maintain and is no longer officially supported. Vue3 recommends props, emits, provide/ Inject, vuex, and Pinia to suppress it. However, for small projects we still want to use EventBus, for which vue3 recommends using third-party libraries such as Mitt or Tiny-Emitter. This paper will analyze mitt.
Strongly recommended: Do not use it in large projectsEventBus
Don’t torture your teammates! This article only does study analysis 😂😂😂
Introduction to the
Mitt has the following advantages:
- Zero dependence, super small size, only after compression
200b
. - Provides the complete
typescript
Support, can automatically derive the parameter type. - Implementation based on closures, nothing annoying
this
Obsession. - Written for browsers but also supports others
javascript
Runtime, browser supportie9+
(Need to introduceMap
thepolyfill
). - Framework-independent and can be used with any framework.
Based on using
import mitt from 'mitt'
const emitter = mitt()
// Subscribe to a specific event
emitter.on('foo'.e= > console.log('foo', e) )
// Subscribe to all events
emitter.on(The '*'.(type, e) = > console.log(type, e) )
// Publish an event
emitter.emit('foo', { a: 'b' })
// Unsubscribe according to the subscription function
function onFoo() {}
emitter.on('foo', onFoo) // listen
emitter.off('foo', onFoo) // unlisten
// Unsubscribe from events with the same name
emitter.off('foo') // unlisten
// Cancel all events
emitter.all.clear()
Copy the code
Mitt only exposes the ON, OFF, and EMIT apis, as well as the All instance (which is a map that stores the mapping of eventName => Handlers).
The basic usage is similar to vue2. X, except that unsubscribing is emitter.all.clear(), while vue2. X uses $off(), which is better.
Unfortunately, there is no API wrapper like $once in VUe2, but it is easy to implement manually.
Used in TS
import mitt from "mitt";
type Events = {
foo: string;
bar: number;
};
// Provide generic parameters for Emitter to automatically infer parameter types
const emitter = mitt<Events>();
// 'e' is inferred to be a string
emitter.on("foo".(e) = > {
console.log(e);
});
// ts error: Arguments of type string cannot be assigned to arguments of type 'number'
emitter.emit("bar"."xx");
// ts error: otherEvent does not exist in the key with Events
emitter.on("otherEvent".() = > {
//
});
Copy the code
Mitt passes in the generic Events parameter so that we can automatically infer the type of the parameter in the callback function when writing code, and also have the code prompt for the response when typing the event name.
Integrated vue3
There are many ways to integrate VUe3 and MITT. The following are recommended and their advantages and disadvantages are analyzed.
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");
Copy the code
This is the most classic integration approach and is suitable for a global event bus.
- Advantages: Do not need to do other processing can be obtained
typescript
, can also be used in non-components. - Cons: Needed in use
import
, only at the global level, not the component level.
Mount for use on globalProperties
// main.ts
import emitter from "@/utils/emitter";
// Mount globally
app.config.globalProperties.$emitter = emitter
/ / because must expand ComponentCustomProperties type to get tips
declare module "vue" {
export interface ComponentCustomProperties {
$emitter: typeofemitter; }}// Non-setup script syntax sugar used in app.vue
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
...
mounted() {
this.$emitter.on("foo".() = > {
//do something}); },... }); </script>// Setup script syntax sugar used in app. vue
<script lang="ts" setup>
import { getCurrentInstance } from "vue";
constinstance = getCurrentInstance(); instance? .proxy? .$emitter.on("foo".() = > {
// dosomething
});
Copy the code
Unlike in vue2 by expanding the prototype prototype to make its globally available, in vue3 need to set up the app. Config. GlobalProperties.
- Advantage: Not used when the component is in use
import/export
- Disadvantages:
- Need to use
declare module "vue"
expandComponentCustomProperties
To getts
Type hint. setup script
Grammar sugar needs to be usedgetCurrentInstance().proxy
In order to obtain$emitter
, more complicated.- Only at the global level, not at the component level.
- Need to use
provide/inject
// Parent component parent.vue
<script lang="ts">
import { InjectionKey } from "vue";
import mitt, { Emitter } from "mitt";
type Events = {
foo: string;
bar: number;
};
// To have typed hints, you must export a typed 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 component child.vue<script lang="ts" setup>
import { inject } from "vue";
import { emitterKey } from "./Parent.vue";
// The typed key allows ts to infer the type of emitter
constemitter = inject(emitterKey)! ; emitter.on("foo".(e) = > {
// dosomething
});
</script>
Copy the code
In order to make our Emitter have type when injecting, we need to borrow InjectionKey to export a typed key. Setup-script syntax is not a standard module, so we can only add a common module to export.
- Benefits: Component-level support
eventbus
. - Disadvantages:
- in
setup-script
In syntax, you need twoscript
Tag to work with. - Need to be
export/import
Export imports a typedkey
To support typing.
- in
summary
Each of the three integration methods has its own strengths. If it is global, you can use import/export only, both in components and ordinary files. If you are too lazy to write import, you can extend it with globalProperties. Although the provide/inject notation is complex, it supports component-level Event-bus and is mandatory in some scenarios.
The source code to appreciate
Js version
function mitt(all) {
all = all || new Map(a);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); }); }}}; }Copy the code
The above is the code I left behind after compiling, and you can see that the implementation is very concise, with less than 50 lines of code, and very easy to understand. Compared with the core logic that everyone can write out, I think the TS version is more suitable for everyone to learn.
Ts version
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
) :Emitter<Events> {
type GenericEventHandler =
| Handler<Events[keyof Events]>
| WildcardHandler<Events>;
all = all || new Map(a);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] asEventHandlerList<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]) {lethandlers = 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!) ; }); }}}; }Copy the code
Ts version of the code is very simple, from the source can learn how to write a type safe small library, is worth learning.
conclusion
I can’t help but feel that the code written by the masters is like poetry. It has all the guts in it, especially the implementation of source CODE TS, which is a great learning material for me and other typescript novices. Code word is not easy, if you see the officer feel helpful to you, then give me a thumbs-up 👍👍👍, the first thanks!