你知道compositionstart和compositionend吗

14,204 阅读2分钟

可能你会对compositionstart和compositionend感到陌生,甚至重来没听说过。但在特定场景下,这两个事件十分有用。

需求

最近接到一个需求,需要根据用户输入的文字来过滤列表选项。easy,一顿操作之后,代码如下:

<template>
  <div id="app">
    <input type="text" :value="filterText" @input="onInput" />
    <ul>
      <li v-for="item in filteredList" :key="item">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      filterText: "",
      list: [
        "The Wealth of Nations",
        "战争与和平",
        "海底两万里",
        "三国演义",
        "嫌疑人X的献身"
      ]
    };
  },
  computed: {
    filteredList() {
      if (!this.filterText) {
        return this.list;
      }
      return this.list.filter(item => item.indexOf(this.filterText) > -1);
    }
  },
  methods: {
    onInput(e) {
      this.filterText = e.target.value;
    }
  }
};
</script>

效果如下图:

这时,我想过滤出带有“三国”两字的书名。咦,不对劲,我刚输入拼音的s,就触发了过滤。可是我想输入中文呀,这效果显然不是我们想要的。

分析

导致以上效果的原因是input事件没办法知道我们在使用中文输入法,一旦输入就修改filterText。那怎么办呢?

compositionstart和compositionend

MDN是这么解释的:

The compositionstart event is fired when a text composition system such as an input method editor starts a new composition session.For example, this event could be fired after a user starts entering a Chinese character using a Pinyin IME.

The compositionend event is fired when a text composition system such as an input method editor completes or cancels the current composition session.For example, this event could be fired after a user finishes entering a Chinese character using a Pinyin IME.

也就是说,利用compositionstart和compositionend可以知道中文输入什么时候开始和结束。

代码修改为:

<template>
  <div id="app">
    <input
      type="text"
      :value="filterText"
      @input="onInput"
      @compositionstart="onCompositionStart"
      @compositionend="onCompositionEnd"
    />
    <ul>
      <li v-for="item in filteredList" :key="item">
        {{ item }}
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  name: "app",
  data() {
    return {
      filterText: "",
      list: [
        "The Wealth of Nations",
        "战争与和平",
        "海底两万里",
        "三国演义",
        "嫌疑人X的献身"
      ],
      lock: false
    };
  },
  computed: {
    filteredList() {
      if (!this.filterText) {
        return this.list;
      }
      return this.list.filter(item => item.indexOf(this.filterText) > -1);
    }
  },
  methods: {
    onInput(e) {
      if (!this.lock) {
        this.filterText = e.target.value;
      }
    },
    onCompositionStart() {
      this.lock = true;
    },
    onCompositionEnd(e) {
      this.filterText = e.data;
      this.lock = false;
    }
  }
};
</script>

因为任何输入都会触发input,而输入中文的时候才触发compositionstartcompositionend,所以通过用一个标记lock来避免中文输入过程中触发过滤。

最终效果:

v-model

你可能会发现,使用v-model不会出现文章开头说的中文输入时的过滤问题。查看vue的源码src/platforms/web/runtime/directives/model.js,有这么几行代码:

现在就很清楚了,原来v-model也做了类似的事情。不得不说vue这个库做得真的很细致。