V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
Harldwell
V2EX  ›  推广

Vue 一个案例引发的动态组件与全局事件绑定总结

  •  
  •   Harldwell · 2018-11-11 16:13:35 +08:00 · 1279 次点击
    这是一个创建于 2188 天前的主题,其中的信息可能已经有所发展或是发生改变。

    技术这东西真的不能光靠看,看是没有的,你必须要动手实践,只有在实战项目中才能发现问题,才能发现我们没有掌握的知识点,然后发现问题解决问题,我们的能力才能得以提升,要不然就有点眼高手低了。

    基于这个想法于是就开始自己去撸了一个旅游网站,旅游网站嘛避免不了城市的选择,所以在实现城市选择列表的时候碰到的一些问题,以及解决办法今天就记录下来做一个总结。

    城市列表选择组件

    首先说说我们要实现一个什么样的城市选择组件:

    1. 输入框获取焦点时,显示组件
    2. 点击城市列表更新输入框的城市显示
    3. 点击其他空白处组件隐藏
    4. 在切换到其他组件时,选择的城市保留而不是被重置

    下面我们就一步一步的来拆解

    第一步

    输入框获取焦点后显示组件很简单,我们给输入框绑定焦点事件然后给组件传入一个显示的状态即可,我们把 isShowCityList 传递给城市选择组件控制行为。

    <el-input 
        @focus="isShowCityList=true"
        placeholder="请输入目的地">
    </el-input>
    

    第二步

    我们也不做过多的表述本文想更多的是介绍动态组件与全局事件的绑定,利用的是子组件给父组件利用自定义事件 $emit 传给父组件。

    第三步

    需要我们去点击其他地方城市组件被隐藏,有些同学的第一印象可能是利用 input 的 blur 事件(就是失去焦点事件),只要我们的 input 失去焦点时,我们就隐藏。

    其实我的第一印象也是如此,但是我们绑定的是 input 的失去焦点事件以后,当我们选择城市列表的时候也是 input 失去焦点的时候,所以我们就无法选取城市。显然这种思路是不行的。

    所以这里我们只能去用到 Vue 的全局事件的绑定,然后去进行一个判断我们点击的节点是哪里,如果是城市组件以外我们就进行隐藏操作。

    我们在 mounted 钩子函数中,进行如下操作。

    mounted() {
        document.addEventListener("click", e => {
            console.log('全局事件被触发');
            if (!this.$refs.searchCity.contains(e.target)) {
                this.isLoadCityList = false;
            }
        });
    }
    

    OK,进行这一步之后,我们的问题得到了解决,只要我们点击这个容器以外的地方就会隐藏城市列表组件,我以为算是结束了,不过那是不可能的,还是我太年轻了,这样做的后果就是不管我们点击任何一个地方它都会触发这个事件,即使是我们切换到其他组件时,事件照样会被触发,显然这个不是我们想要的,因为当前事件会被无限触发,无疑会给我们带来不可预见的问题。

    我们需要的最好效果肯定是当前的全局事件就在当前的组件下产生作用,当我们切换到其他组件时,事件自动删除,于是我可能想到的就是利用 beforeDestroy 钩子函数去删除这个全局事件。也就是当我们切换到其他组件时,去删除这个全局事件。

    beforeDestroy() {
        document.removeEventListener("click", () => {
          //...
        });
    }
    

    你以为这样我还就能解决问题了吗?显然还是不能,还是太年轻,只是这样我们是解除不了绑定的事件,那我们该怎么办呢?其实这里面有一个坑,大坑,因为这个大坑自己不知道,差了许多资料也没查出来,因为差的思路错了,最后在一个群里问了一个大佬,才得出答案,不得不说与前辈交流很重要啊,能帮你少踩很多坑。

    这里如果想要解除绑定,解除和绑定的两个回调函数必须一致,什么意思呢?看代码你就明白。如果不这么操作,你是解除不掉事件的,至于更深的原因我也不怎么明白了,以后再去查阅一些资料。

    methods: {
      isSearchCityNode(e) {
        if (!this.$refs.searchCity.contains(e.target)) {
          console.log("全局事件被触发");
          this.isLoadCityList = false;
        }
      }  
    },
    mounted() {
        document.addEventListener("click", this.isSearchCityNode);
    },
    beforeDestroy() {
        document.removeEventListener("click", this.isSearchCityNode);
    }
    

    第四步

    需要我们在切换组件的时候保留我们选择的城市,如果不保留我们每次切换到其他组件时,我们选择的城市都会被重置为默认值,这个体验肯定是肯差的,也不是我们想要的。

    被重置的原因则是我们在每次在不同的组件进行切换的时候,组件都会进行新建与销毁,这也会导致重复渲染问题对性能也是不友好的。

    那么我们该如何去处理这个问题呢? 我这里使用了 keep-alive 去解决这个问题,那么 keep-alive 该如何使用以及作用是什么呢?

    <keep-alive>
      <component v-bind:is="currentTabComponent"></component>
    </keep-alive>
    

    包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,它自身不会渲染一个 DOM 元素,也不会出现在父组件链中。

    但是当我们使用 的时候,我们的 beforeDestroy 钩子函数就会失效,导致我们第三步的全局事件的解绑就不能执行了,原因是我们的组件是被缓存起来,并没有被销毁。自然会失效,但是我们并不慌,当我们使用 时,activated 和 deactivated 两个钩子函数被触发。

    activated:keep-alive 组件激活时调用。

    deactivated:keep-alive 组件停用时调用。

    所以我们不难发现,我们完全可以使用这两个钩子去实现我们全局事件的绑定与解绑,简直完美。

    activated() {
        document.addEventListener("click", this.isSearchCityNode);
    },
    deactivated() {
        document.removeEventListener("click", this.isSearchCityNode);
    }
    

    总结

    通过一个城市列表组件的案例,介绍了我们在 Vue 中如何绑定全局事件以及进行优化,一定要记住事件的绑定与解除哪里有一个大坑。

    我们通过 可以创建一个可以缓存的组件,而且会新增两个钩子函数提供我们使用

    文中如有不足之处,欢迎大神拍砖!

    2 条回复    2018-11-13 15:23:47 +08:00
    Lax
        1
    Lax  
       2018-11-11 19:51:25 +08:00
    很有意思。我最近在用 quill 的时候也遇到个类似的问题
    Stlin
        2
    Stlin  
       2018-11-13 15:23:47 +08:00
    试试写成指令呢?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   1138 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 23ms · UTC 18:50 · PVG 02:50 · LAX 10:50 · JFK 13:50
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.