Vue.jsで日本語入力中にdataを書き換えるカスタムディレクティブ作った

# 経緯

Vue.jsの「v-model」では日本語入力中（未確定状態）ではdataを書き換えてくれない。
日本語入力途中でのサジェスト表示実装などで必要となりましたので、カスタムディレクティブ化しました。

# v-modelだとどうなる？

```html

<div id="app1">
  <h2>v-model使用</h2>
  <p>{{ text }}</p>
  <input type="text" v-model="text">
  <p>日本語入力が確定するまでdataに反映されない。</p>
</div>
```

```javascript

// v-modelで実装
const app1 = new Vue({
  el: '#app1',
  data: {
    text: ''
  }
});
```

<p data-height="265" data-theme-id="0" data-slug-hash="odpYWK" data-default-tab="html,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="Vue.js 日本語入力 v-model" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/odpYWK/">Vue.js 日本語入力 v-model</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

# eventListenerで実装

`keyup`と`compositionend`で更新することで、候補選択時などもdataを書き換えることができます。

```html
<div id="app2">
  <h2>eventListenerで実装</h2>
  <p>{{ text }}</p>
  <input type="text" ref="input" :value="text">
  <p>日本語入力中でもdataへ反映されるがVueへmethodsなど結構記述増える</p>
</div>
```

```javascript
// eventListenerで実装
const app2 = new Vue({
  el: '#app2',
  data: {
    text: ''
  },
  methods: {
    update (e) {
      this.text = e.target.value;
    }
  },
  mounted () {
    this.$nextTick(() => {
      const $input = this.$refs.input;
      $input.addEventListener('keyup', this.update);
      $input.addEventListener('compositionend', this.update);
    });
  }
});
```

<p data-height="265" data-theme-id="0" data-slug-hash="jxYrXg" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="Vue.js 日本語入力 eventListener" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/jxYrXg/">Vue.js 日本語入力 eventListener</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>

これをカスタムディレクティブにすると。

# カスタムディレクティブで実装

```html
<div id="app3">
  <h2>カスタムディレクティブで実装</h2>
  <p>{{ text }}</p>
  <input type="text" v-composition-model="text">
</div>
```

```javascript

function vCompositionModelUpdate (el, { value, expression }, vnode) {
  // data書き換え
  vnode.context[expression] = el.value
}

Vue.directive('composition-model', {
  bind: function (el, binding, vnode) {
    el.value = binding.value
    el.addEventListener('keyup', () => vCompositionModelUpdate(el, binding, vnode))
    el.addEventListener('compositionend', () => vCompositionModelUpdate(el, binding, vnode))
  },
  // dataが直接書き換わったときの対応
  update: function (el, { value }) {
    el.value = value
  }
})

const app3 = new Vue({
  el: '#app3',
  data: {
    text: ''
  }
});

```

<p data-height="265" data-theme-id="0" data-slug-hash="aGEBqx" data-default-tab="js,result" data-user="RikutoYamaguchi" data-embed-version="2" data-pen-title="Vue.js 日本語入力 カスタムディレクティブ" class="codepen">See the Pen <a href="https://codepen.io/RikutoYamaguchi/pen/aGEBqx/">Vue.js 日本語入力 カスタムディレクティブ</a> by Rikuto Yamaguchi (<a href="https://codepen.io/RikutoYamaguchi">@RikutoYamaguchi</a>) on <a href="https://codepen.io">CodePen</a>.</p>
<script async src="https://static.codepen.io/assets/embed/ei.js"></script>