从插件组件说到vue的slot上监听

最开始写网页的时候我很崩溃,为什么写的代码很快就不能看了,一坨一坨的,对自己写的东西觉得非常没有安全感,以至于很长一段时间里我都在做重复而难以维护的事情。我感觉前端开发有两件事儿是特别需要重视的,一个就是可维护性,一个就是复用性。

可维护性有很多因素,我觉得最重要的就是模块化,遥想现在的老系统的代码都是要么js一坨,要么script标签引入一坨而且顺序还得小心翼翼,真是太惨了。。。

而可复用性就是今天要记录的一些东西,虽然说的是vue里具体的解决方案。可是很多东西都是相通的。

比如最开始的时候:

第一天,我有一个table页面,好的,我html一画,js数据一拿,事件一监听完工;

第二天,我又有一个这样的table页面,差不多,好的,我把昨天写的copy过来,改改,完工;

第三天,我又有一个这样的table页面,也差不多,好的个鬼。。。。我要疯了,难道我还要copy吗?好吧,忍了。。。copy去

第四天,好了我没有同样的页面,总算松了一口气,可是需求让我把每个table都加上排序功能,好的,啊。。不对,我要加在哪里。。。第四天我总于崩溃了。。。

后来,我发现了有jq插件这种好东西,尼玛啊我怎么这么傻,用用用起来,最开始觉得还挺好用呢?那为什么好用呢?研究一下吧,发现就是一个道理,把常用的东西封装起来!就如同我想用锤子,不再是每次拿个铁,然后段成平的,然后按上把手,用完扔掉,而是直接买一把万能的锤子不就得了。扯远了,jq插件它大致的思路是什么呢?

html写个模板 =》js里读取模板 =》处理配置参数 =》处理事件监听 =》渲染到页面里 =》暴露参数和方法给用户使用

这下好了,我每次写table的时候不再copy了,html里把模板一写,js钩子一挂;js里传好参数和添加需要的回调,最后初始化运行;完事儿,好的,这样开发好像比之前爽歪歪了。

又n天过去了。。。每天写table啊,真是table跟我有愁啊,每次页面都要先去写html,然后再切换到js啊。然后和其他插件混合在一起,有些插件要删除?好吧,仔细的寻找html和js,才能小心的把两处都删除掉。。。感受到了么,我是不是应该把html模板和js搞在一起啊,可是大家不都说逻辑和样式分离么,哎。。。管他呢,我觉得html不算样式啊,css才是哇,html和js写一起多好维护啊,好吧。。然后就开始了在js里拼接字符串。。。

好的,这下引入也好办了,html只用写个钩子就行了,嗯嗯。。感觉好一些了。。。可是js里每次都要写初始化语句啊,真是。。好吧,那就在钩子上做手脚,加data-*来特殊标识插件,然后js插件里自动初始化,好的。。反正又少了一句。。

可是插件慢慢多了起来,这种方式好像也有问题了,比如:

  • 命名冲突
  • js里拼字符串很酸爽啊
  • 各个插件代码调用风格差异挺大,很别扭有木有
  • 页面js钩子太多,看一眼页面不知道用了什么有木有
  • 不同插件想套嵌,什么?没错想套嵌。。。
  • 。。。

郁闷之时,这时候听说了组件化,什么什么啊?好的试试vue吧。。。矣,上面的问题好像都解决的不错啊,哎。。不要为了学框架而学啊,能解决问题就是好技术是不?

其实是想记录这么一种情况的,可是竟然扯了这么多。。。。

就是我有一个组件,我想其中一些内容是定制的而不是写死的,如果是jq插件呢,就是把它弄成一个可配参数,然后用户传一堆html字符串过去。。。然后我想监听自己传的内容,jq插件就再给一个可配参数,把监听函数传个它(html字符串中预设好js钩子,在可配函数绑在这个钩子上)。vue里呢?组件世界里提供了slot的概念,其实内部也是编译自定义的slot到组件的里,可是怎么监听这个slot的事件呢?先看看作者是怎么说的:(上英文。。。)

You cannot listen to events on . It can end up rendering anything: text, plain element, multiple nodes... the behavior will be unpredictable.

It seems you are trying to make a slot container communicate with a slot child - in most cases this means the two components are coupled by-design, so you can do something like this.$parent.$emit(...) from the child, and listen to that event in the parent with this.$on(...).

I am still open to ideas on improving the ways slot parent and child can communicate, but events on doesn't really sound like a right direction to me.

大致意思是:你不能直接在上监听。slot会渲染的东西太多了,例如文字、单个元素、多个节点等,这些都是不可预测的行为。如果你想让slot和slot的子组件通信,在大多数情况下意味着使用slot的组件和slot包裹的组件是coupled by-design的,就是说是需要一起规划的组件并不是独立的,所以你可以这么做:

this.$parent.$emit(...) // 在slot的组件里直接触发父组件的事件
this.$on(...) // 在父组件上注册好事件等待slot的组件来通信

作者说他也在思考如何提高slot的父组件和子组件的通信方式,但是直接<slot @="">这种方式应该不是一个对的方向。

例如:我们要开发一个平铺的单选筛选组件:

东 | 南 | 西 |北 | 中

我们希望我们调用的代码是这个样的:

<x-select-text class="ml-10" @x-select-item-clicked="changeStatus" :activeId="typeId">
            <x-select-text-item id="0">东</x-select-text-item>
            <x-select-text-item id="4">南</x-select-text-item>
            <x-select-text-item id="1">西</x-select-text-item>
            <x-select-text-item id="3">北</x-select-text-item>
            <x-select-text-item id="2">中</x-select-text-item>
          </x-select-text>

我们的组件该怎么写呢?是的,我们靠slot来完成:

<template>
  <div class="x-select-text">
    <slot></slot>
  </div>
</template>
<script>
  export default {
    name: 'XSelectText',
    data () {
      return {}
    },
    props: {
      activeId: {
        type: [String, Number],
        default: ''
      },
      seprator: {
        type: String,
        default: '|'
      }
    }
  }
</script>
<style lang="scss" scoped>
  .x-select-text {
    display: inline-block;
    vertical-align: middle;
    &:before,&:after{
      display: table;
      content: "";
      clear: both;
    }
  }
</style>
<template>
  <span class="x-select-text-item">
    <span class="x-select-text-item__inner" @click="clicked" :class="{highlight: activeId === id}">
      <slot></slot>
    </span><span class="x-select-text__separator"> {{ seprator }} </span>
  </span>
</template>
<script>
  export default {
    name: 'XSelectTextItem',
    props: {
      id: {
        type: [String, Number],
        default: ''
      }
    },
    computed: {
      activeId () {
        return this.$parent.activeId
      },
      seprator () {
        return this.$parent.seprator
      }
    },
    methods: {
      clicked () {
        this.$parent.$emit('x-select-item-clicked', {
          id: this.id,
          content: this.$slots.default[0].text
        })
      }
    }
  }
</script>
<style lang="scss" scoped>
  .x-select-text-item {
    float: left;
    font-size: 16px;
    line-height: 1;

    &:last-child .x-select-text__separator {
      display: none;
    }
  }
  .x-select-text-item__inner {
    cursor: pointer;
  }
  .x-select-text__separator {
    color: rgb(191, 203, 217);
    margin: 0px 8px;
  }
</style>

恩,是的,就是靠this.$parent建立起slot子组件和父组件的通信关系的,使用起来比只写一个组件,例如传给一个数组[{name:'东', id="1"},...]清爽的多,参数的格式也更灵活多变,易于扩展吧。

哎~