氧气柠檬 發表於 2023-3-22 17:39:00

在Vue3+TypeScript 前端项目中使用事件总线Mitt

<p>事件总线Mitt使用非常简单,本篇随笔介绍在Vue3+TypeScript 前端项目中使用的一些场景和思路。我们在Vue 的项目中,经常会通过emits&nbsp;触发事件来通知组件或者页面进行相应的处理,不过我们使用事件总线Mitt来操作一些事件的处理,也是非常方便的。</p>
<p>Mitt 的GitHub官网地址如下所示:https://github.com/developit/mitt, 它的安装和其他插件一样,我们不再赘述,只讲述它的如何使用。</p>
<p><code>Mitt&nbsp;</code>具有以下优点:</p>
<div>
<div>
<ul>
<li>零依赖、体积超小,压缩后只有<code>200b</code>。</li>
<li>提供了完整的<code>typescript</code>支持,能自动推导出参数类型。</li>
<li>基于闭包实现,没有烦人的<code>this</code>困扰。</li>
<li>为浏览器编写但也支持其它<code>javascript</code>运行时,浏览器支持<code>ie9+</code>(需要引入<code>Map</code>的<code>polyfill</code>)。</li>
<li>与框架无关,可以与任何框架搭配使用。</li>
</ul>
</div>
</div>
<div>Mitt 只是提供了几个简单的方法,如on,off, emit 等基础的几个函数。</div>
<div>在JS中我们使用的话,不需要类型化事件的类型,如下代码所示。</div>
<div>
<div class="cnblogs_code">
<pre>import mitt from 'mitt'<span style="color: rgba(0, 0, 0, 1)">
const emitter </span>=<strong><span style="color: rgba(255, 0, 0, 1)"> mitt()

</span></strong><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 订阅一个具体的事件</span>
emitter.on('foo', e =&gt; console.log('foo'<span style="color: rgba(0, 0, 0, 1)">, e) )

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 订阅所有事件</span>
emitter.on('*', (type, e) =&gt;<span style="color: rgba(0, 0, 0, 1)"> console.log(type, e) )

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 发布一个事件</span>
emitter.emit('foo', { a: 'b'<span style="color: rgba(0, 0, 0, 1)"> })

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 根据订阅的函数来取消订阅</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> onFoo() {}
emitter.on(</span>'foo', onFoo)   <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> listen</span>
emitter.off('foo', onFoo)<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> unlisten</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 只传一个参数,取消订阅同名事件</span>
emitter.off('foo')<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> unlisten</span>

<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 取消所有事件</span>
emitter.all.clear()</pre>
</div>
<p>而我们如果在Vue3 + TypeScript 环境中使用的话,就需要类型化事件的类型,已达到强类型的处理目的。</p>
<div class="cnblogs_code">
<pre>import mitt from "mitt"<span style="color: rgba(0, 0, 0, 1)">;

type Events </span>=<span style="color: rgba(0, 0, 0, 1)"> {
foo: string;
bar: number;
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提供泛型参数让 emitter 能自动推断参数类型</span>
const emitter = <span style="color: rgba(255, 0, 0, 1)"><strong>mitt&lt;Events&gt;</strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"><strong>()</strong></span>;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 'e' 被推断为string类型</span>
emitter.on("foo", (e) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
console.log(e);
});

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ts error: 类型 string 的参数不能赋值给类型 'number' 的参数</span>
emitter.emit("bar", "xx"<span style="color: rgba(0, 0, 0, 1)">);

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> ts error: otherEvent 不存在与 Events 的key中</span>
emitter.on("otherEvent", () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
</span><span style="color: rgba(0, 128, 0, 1)">//
</span>});</pre>
</div>
<p>在前端项目使用的时候,我们在utils/mitt.ts中定义默认导出的mitt对象,如下代码所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> utils/mitt.ts</span>
<span style="color: rgba(0, 0, 0, 1)">
import mitt, { Emitter } from </span>'mitt'<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 类型</span>
const emitter: Emitter&lt;MittType&gt; = mitt&lt;MittType&gt;<span style="color: rgba(0, 0, 0, 1)">();

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 导出</span>
export <span style="color: rgba(0, 0, 255, 1)">default</span> emitter;</pre>
</div>
<p>在其中的MittType类型,可以单独文件放置TypeScript的预定义文件目录中,如types/mitt.d.ts</p>
<p>而我们在使用的时候,直接导入该对象就可以了,如下代码所示。</p>
<div class="cnblogs_code">
<pre>declare type <strong><span style="color: rgba(255, 0, 0, 1)">MittType&lt;T = any&gt;</span></strong> =<span style="color: rgba(0, 0, 0, 1)"> {
    openSetingsDrawer</span>?<span style="color: rgba(0, 0, 0, 1)">: string;
    restoreDefault</span>?<span style="color: rgba(0, 0, 0, 1)">: string;
    setSendColumnsChildren: T;

    .................. </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">省略其他事件类型</span>
<span style="color: rgba(0, 0, 0, 1)">
    noticeRead: number; </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 消息已读事件</span>
    lastAddParentId?: string | number;<span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">新增记住最后的父信息</span>
};</pre>
</div>
<p>例如我们定义一个更新和记住父菜单的Mitt 事件,在页面加载完毕的时候监听事件,在页面退出的时候关闭事件即可,如下代码所示是在菜单列表页面中处理的。</p>
<div class="cnblogs_code">
<pre>&lt;script lang="ts" setup name="sysMenu"&gt;<span style="color: rgba(0, 0, 0, 1)">
import { onMounted, onUnmounted, reactive, ref } from </span>'vue'<span style="color: rgba(0, 0, 0, 1)">;
import mittBus from </span>'/@/utils/mitt'<span style="color: rgba(0, 0, 0, 1)">;
......
onMounted(async () </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    handleQuery();

<strong><span style="color: rgba(255, 0, 0, 1)">    mittBus.on(</span></strong></span><strong><span style="color: rgba(255, 0, 0, 1)">'submitRefresh', () =&gt; {
      handleQuery();
    });
    mittBus.on('lastAddParentId', (pid) =&gt; {
      state.lastAddParentId = pid as string;//记住最后的父菜单ID
</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="color: rgba(255, 0, 0, 1)">    });</span></strong>
});

onUnmounted(() </span>=&gt;<span style="color: rgba(0, 0, 0, 1)"> {
<span style="color: rgba(255, 0, 0, 1)"><strong>    mittBus.off(</strong></span></span><span style="color: rgba(255, 0, 0, 1)"><strong>'submitRefresh');
    mittBus.off('lastAddParentId'</strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"><strong>);</strong></span>
});

</span>&lt;/script&gt;</pre>
</div>
<p>在新增菜单的时候我们触发对应刷新事件&nbsp;<strong>submitRefresh</strong>,以及触发选择的父记录ID的事件&nbsp;<strong>lastAddParentId</strong>,这样就可以做相应的处理了。</p>
<p>例如在菜单的编辑子控件页面中,我们触发对应的事件逻辑代码如下所示。</p>
<div class="cnblogs_code">
<pre><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 关闭弹窗</span>
const closeDialog = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    <strong><span style="color: rgba(255, 0, 0, 1)">mittBus.emit(</span></strong></span><strong><span style="color: rgba(255, 0, 0, 1)">'submitRefresh'</span></strong><span style="color: rgba(0, 0, 0, 1)"><strong><span style="color: rgba(255, 0, 0, 1)">);</span></strong>
    state.isShowDialog </span>= <span style="color: rgba(0, 0, 255, 1)">false</span><span style="color: rgba(0, 0, 0, 1)">;
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 提交</span>
const submit = () =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
    ruleFormRef.value.validate(async (valid: </span><span style="color: rgba(0, 0, 255, 1)">boolean</span>) =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (!valid) <span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)">;
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (state.ruleForm.id != undefined &amp;&amp; state.ruleForm.id &gt; 0<span style="color: rgba(0, 0, 0, 1)">) {
            await menuApi.update(state.ruleForm);
      } </span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
            await menuApi.add(state.ruleForm);
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">记住最后的菜单</span>
            <span style="color: rgba(255, 0, 0, 1)"><strong>mittBus.emit('lastAddParentId'</strong></span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"><strong>, state.ruleForm.pid);</strong></span>
      }
      closeDialog();
    });
};</span></pre>
</div>
<p>&nbsp;如果为了减少每次重复的导入mitt,也可以把它全局挂载到变量中,统一入口进行访问,详细可以参考随笔《在基于vue-next-admin的Vue3+TypeScript前端项目中,为了使用方便全局挂载的对象接口》处理即可。</p>
<div class="cnblogs_code">
<pre>const $u: $u_interface =<span style="color: rgba(0, 0, 0, 1)"> {
message,
test,
util,
date,
crypto,
base64,
$t: i18n.global.t,
fun: commonFunction(),

cloneDeep,
debounce,
throttle,
<span style="color: rgba(255, 0, 0, 1)"><strong>mitt</strong></span>
};

</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">安装$u组件到app上</span>
import type { App } from 'vue'<span style="color: rgba(0, 0, 0, 1)">;
export </span><span style="color: rgba(0, 0, 255, 1)">default</span><span style="color: rgba(0, 0, 0, 1)"> {
install(app: App</span>&lt;Element&gt;<span style="color: rgba(0, 0, 0, 1)">) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 挂载全局</span>
    app.config.globalProperties.$u =<span style="color: rgba(0, 0, 0, 1)"> $u;
}
};</span></pre>
</div>
<p>&nbsp;<img src="https://img2023.cnblogs.com/blog/8867/202303/8867-20230322174210400-236524827.png" alt="" width="807" height="188" loading="lazy"></p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div>

</div>
<div id="MySignature" role="contentinfo">
    <div style="border-right-color: #cccccc; border-right-width: 1px; border-right-style: solid; padding-right: 5px; border-top-color: #cccccc; border-top-width: 1px; border-top-style: solid; padding-left: 4px; font-size: 13px; padding-bottom: 4px; border-left-color: #cccccc; border-left-width: 1px; border-left-style: solid; width: 98%; padding-top: 4px; border-bottom-color: #cccccc; border-bottom-width: 1px; border-bottom-style: solid; background-color: #eeeeee;">
    <img src="http://www.cnblogs.com/Images/OutliningIndicators/None.gif" align="top" alt>
    <span style="color: #000000"><span class="Apple-tab-span" style="white-space: pre"></span>
   专注于代码生成工具、.Net/Python 框架架构及软件开发,以及各种Vue.js的前端技术应用。著有Winform开发框架/混合式开发框架、微信开发框架、Bootstrap开发框架、ABP开发框架、SqlSugar开发框架、Python开发框架等框架产品。
   <br>  转载请注明出处:撰写人:伍华聪  http://www.iqidi.com <br>    </span></div><br><br>
来源:https://www.cnblogs.com/wuhuacong/p/17244470.html
頁: [1]
查看完整版本: 在Vue3+TypeScript 前端项目中使用事件总线Mitt