问题说明
现在的版本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,
on,off 和 $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.x的这种更好些。比较遗憾的是没有封装类似于vue2中once这样的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。
缺点:
- 在setup-script语法中,需要两个script标签来配合使用。
- 需要 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菜鸟而言是极佳的学习资料,在分析的过程中我也学到很多。码字不易,如果各位看官觉得对你有帮助的话,那就给我点个赞????????????吧,先行谢过!
最后
以上就是冷傲鸵鸟最近收集整理的关于mitt和script setup语法糖的使用的全部内容,更多相关mitt和script内容请搜索靠谱客的其他文章。
发表评论 取消回复