Screaming Loud

日々是精進

vue.jsのTypeScriptハマりがち。 ~ eventの型とfocus ~

vue.jsでTypeScriptのeventをHandleしてそれを親コンポーネントに渡したいというときに型を何にすればよいか困ったので、その解決策。 以下2つのhtmlとtypescriptを使うことで解決。

やりたかったこと

  1. input textに入力したら、親コンポーネントに伝搬
  2. マウスオーバーしたら、formのCSSが変わる(例ではクラスを変えてる)

環境

vue: 2.5.11 typescript: 2.9.2

ハマったこと

  1. eventの型がわからなかった。
  2. inputのtextが1文字入れるだけでfocusが外れる。

対応

eventの型

以下の場合、引数の型は正しいのだが、typescriptのcompileが通らない。

 updateInput(event: Event): void {
      const text = event.target.value;
      this.$emit(inputEvent, text);
 },

なぜかというと、Event型はInterfaceなので、 event.targetvalueがないためだ。 ではどうしてやるかというと、 instance of で判定してあげる必要がある。

if (event.target instanceof HTMLInputElement) {
   this.$emit(inputEvent, tevent.target.value);
}

focusの対応

html内のinputイベントは文字を1文字入力するごとに発火する。 mouseoverイベントなどもmouseを乗せるたびに発火する。

inputイベントだけ実装していれば問題ないのだが、mouseイベントも同じinput属性に実装しているかつクラスなどのDOM構成を変えるような実装をしていると、以下の問題が生じる

  1. input textをマウスで選択 (mouseoverイベント発火)
  2. input textに1文字入力する (input, mouseoverイベント発火)
  3. mouseをずらす (mouseoutイベント発火)

このため、なにかinputで文字を入力するたびにmouseoverイベントが発火し、DOMが変更が検知されDOMを作り変えてしまうため、inputからfocusが勝手にはずれてしまう。

対処方法としては、inputで文字を送るのではなく、focusoutとmouseoutで親コンポーネントにemitする。

全体の実装

<template>
    <input class="input-text" type="text" 
    :value="text" :class="{'input-hover': isHover}"
    @mouseover="mouseover" @mouseout="mouseout"
    @focusout="updateInput" />
</template>
<script lang="ts">
import Vue from 'vue';

const inputEvent = 'inputed';
export default Vue.extend({
  props: {
    text: String,
  },
  data() {
    return {
      isHover: false,
    };
  },
  methods: {
    updateInput(event: Event): void {
      event.preventDefault();
      if (event.target instanceof HTMLInputElement) {
        const text = event.target.value;
        this.$emit(inputEvent, text);
      }
    },
    mouseover(event: Event): void {
      event.preventDefault();
      this.isHover = true;
    },
    mouseout(event: Event): void {
      event.preventDefault();
      this.isHover = false;
      if (event.target instanceof HTMLInputElement) {
        const text = event.target.value;
        this.$emit(inputEvent, text);
      }
    },
  },
});
</script>