🔥Vue"非常规"技巧, 🤖99%的人是第一次看

6,156 阅读3分钟

让"原生Event"与"v-on"玩出花

一句话理解"Event": 类似vue中$emit, 使用new Event可以创建浏览器的原生事件,通过使用addEventListener监听事件.

简单使用:

// 监听
el.addEventListener('abc', onAbc);
// 创建事件
const event = new Event(type);
// 派发事件对象
el.dispatchEvent(event);

这和Vue有什么关系?

看, 这样给组件绑定事件很常见:

<my-scroll @scroll-reach-bottom="onScrollReachBottom"/>

但是如果不是组件是一个html元素呢?

<div @scroll-reach-bottom="onScrollReachBottom"/>

要实现上例就需要用到自定义事件(Event).

为什么不用"组件"?

以"拖拽组件"为例, 众所周知组件至少有一个元素(根), 那么如果我们使用"拖拽组件":

<my-drag>
    <span>待拖拽</span>
</my-drag>

<!--实际dom结构-->
<div>
    <span>待拖拽</span>
</div>

可以看到组件破坏了dom结构, 使用时可能会直接影响样式, 所以很多vue插件都使用了"vue指令"解决这个问题.

更好的"vue指令"

这个是饿了么ui的InfiniteScroll(无限滚动)指令:

<ul 
    v-infinite-scroll="load" 
    infinite-scroll-distance="10" 
    infinite-scroll-delay="10">
    <li v-for="i in count">{{ i }}</li>
</ul>
export default{
    directives:{
        infiniteScroll
    }
}

对比下如果用Event实现后:

<ul @infinite-scroll="load">
    <li v-for="i in count">{{ i }}</li>
</ul>
export default{
    mounted(){
        const is = new InfiniteScroll(this.$el,{
            distance:10,
            delay:10
        });
        this.$on('hook:destroy', is.destroy);
    }
}

说实话没对使用者差别不太大, 无非就是"@"语义看起来稍微好点, 但是销毁需要自己手动触发.

但是如果你编写过"vue指令", 你应该还记得vue对指令的"钩子函数"的设计并不是特别友好, 需要自己实现钩子间的数据管理, 不优雅.

但是用了Event方式, 你可以把自己的组件做成构造函数, 上面的"状态管理"问题就好解决了, 而且你这样写出的代码就不局限于是vue插件了, 他就是一个普通的js插件, 可以用在任何框架下.

当然指令的"修饰符"等概念还是很吸引人的, 如果需要可以结合Event和"vue指令"一起用, 如虎添翼.

如何使用Event?

定义事件'abc', 并给事件对象赋值:

// 注册监听
el.addEventListener('abc', event=>{
    'abc' === event.type // true
    1 === event.a // true
});
// 创建事件
const event = new Event(type, {
     // 事件是否可以冒泡, 默认false
    bubbles: false,
     // 事件是否可以取消(使用preventDefault时), 默认false
    cancelable: false,
    // 事件是否会在影子DOM根节点之外触发侦听器, 默认false, 
    // 此属性涉及原生web component, 欲深入了解web component请查阅mdn
    composed: false 
});

// 修改事件对象的值
Object.assign(event, {a:1,b:2});
// 派发事件对象
el.dispatchEvent(event);

cancelable实例

el.addEventListener('abc', event=>{
    // 受到cancelable控制
    // cancelable === false时,
    // preventDefault()无效
    event.preventDefault();
});

bubbles实例

parent.appendChild(el);

el.addEventListener('abc', event=>{
    // event.stopPropagation();
});

parent.addEventListener('abc', event=>{
    // 受到bubble控制
    // bubble === false时,
    // 子元素绑定的事件回调中, 
    // 有没有event.stopPropagation()都不触发
});

兼容

Event对低版本不兼容, 但是mdn上已经明确写出, 未来的浏览器都会以Event作为标准, 所以我们还要结合老语法做下兼容.

createEvent

const event = document.createEvent('HTMLEvents');
// 参数意义和new Event一样
event.initEvent(type, bubbles, cancelable);

"灰色"代表"未知", 但是可以放心使用, 因为vue2.6的源码中关于"v-model"部分多处使用了createEvent说明应该可以兼容到ie9.

兼容代码

function dispatchDOMEvent(el, payload, eventInit){
    let event;
    if (void 0 !== Event) {
        event = new Event(type, eventInit);
    } else {
        event = document.createEvent('HTMLEvents');
        event.initEvent(type, eventInit?.bubbles, eventInit?.cancelable);
    }
    return el.dispatchEvent(event);
}

源码: github.com/any86/any-t…

我的应用

其实这种方式和vue配合也是偶然发现, 不是什么复杂的东西, 只是一般不会往这上面想.

我做了一个✋手势库用这种方式实现在vue下和"v-on"配合:

✋手势库: github.com/any86/any-t…

🔥typescript系列课程

如果你对ts感兴趣了, 欢迎看看我的ts基础教程.

第一课, 体验typescript

第二课, 基础类型和入门高级类型

第三课, 泛型

第四课, 解读高级类型

第五课, 命名空间(namespace)是什么

特别篇, 在vue3🔥源码中学会typescript🦕 - "is"

第六课, 什么是声明文件(declare)? 🦕 - 全局声明篇

新手前端学🔥typescript - 实战篇, 实现浏览器全屏(59行)

微信群

感谢大家的阅读, 如有疑问可以加我微信, 我拉你进入微信群(由于腾讯对微信群的100人限制, 超过100人后必须由群成员拉入)