Vue.jsのイベントハンドラを勉強する

# イベントハンドラ

## イベントの購読（サブスクライブ）

**v-on**ディレクティブを使うことで、DOMイベントの購読、イベント発火時のjavascriptの実行

```html
<div id="example">
  <p>ボタンを押すとカウントが増えます</p>
  <p>カウント： {{ counter }}</p>
  <button @click="counter += 1">カウントアップ！</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  data: {
    counter: 0
  }
})
```

ブラウザ上ではボタンを押すたびにカウントが増えていきます。

## メソッドイベントハンドラ

イベントハンドラのロジックは複雑になっていくので、**v-on**属性の値に式を記述し続けるのはアンチパターンです。
そのため**v-on**は呼び出したいメソッド名を記述する事ができます。

```html
<div id="example">
  <p>ボタンを押すとカウントが増えます</p>
  <p>カウント： {{ counter }}</p>
  <button @click="countUp">カウントアップ！</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  data: {
    counter: 0
  },
  methods: {
    countUp:function(){
      this.counter += 1
    }
  }
})
```

## インラインメソッドハンドラ
メソッド名を直接指定する代わりに、インラインメソッドを指定することもできます。

```html
<div id="example">
  <p>{{ message }}</p>
  <button @click="say('こんちわ')">こんにちは</button>
  <button @click="say('こんばんわ')">こんばんわ</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  data: {
    message: '挨拶'
  },
  methods: {
    say:function(message){
      this.message = message
    }
  }
})
```

インラインステートメントハンドラでオリジナルのDOMイベントを参照する方法があります。  
特別な **$event**変数を使うことでメソッドに[DOMイベント](https://developer.mozilla.org/ja/docs/Web/API/Document_Object_Model/Events)を渡すことができます。

```html
<div id="example">
  <p>{{ message }}</p>
  <button @click="say('こんちわ', $event)">こんにちは</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  data: {
    message: '挨拶'
  },
  methods: {
    say:function(message, event){
      console.log(event)
      this.message = message
    }
  }
})
```

console.log()でボタンが押された時にDOMイベントを出力しています。  
気になる人は実装してみてください。

## イベント修飾子
イベントハンドラ内での[event.preventDefault( )](https://qiita.com/tochiji/items/4e9e64cabc0a1cd7a1ae)、または[event.stopPropagation( )](https://iwb.jp/return-false-preventdefault-stoppropagation/)の呼び出しは、様々な場面で必要になります。
DMOの込み入った処理をおこなうよりも、単純なデータロジックだけになっていた方が扱いすいです。
Vue.jsはこの問題を解決するために**v-on**のためにイベント修飾子(event modifiers)がサポートされています。

### .stop
通常、同じイベントをハンドルしたDOMがネストされている場合、親要素に向かってイベントが連鎖する。
下記の例ではdiv(親要素)の中にbutton(子要素)があり、両方とも同じイベントをハンドルしています。
**.stop**を使用することでhandler(子要素)は実行されるが、handler(親要素)は実行されない。

```html
<div id="example">
  <p>.stopなし</p>
  <div @click="handler('親要素')">
    <button @click="handler('子要素')">
      イベント実行ボタン
    </button>
  </div>

  <p>.stopあり</p>
  <div @click="handler('親要素')">
    <button @click.stop="handler('子要素')">
      イベント実行ボタン
    </button>
  </div>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'のイベント実行')
    }
  }
})
```

### .prevent
Event.preventDefault()を呼ぶ。v
下記の例ではイベント(handler())は実行されるが画面遷移は行われない

```html
<div id="example">
  <p>.preventなし</p>
  <a href="https://qiita.com/Sthudent_Camilo/items/58b8c21af0db5bea2340" @click="handler('Vue.js を勉強する Session1に遷移する')">
    Vue.js を勉強する Session1に遷移
  </a>

  <p>.preventあり</p>
  <a href="https://qiita.com/Sthudent_Camilo/items/58b8c21af0db5bea2340" @click.prevent="handler('Vue.js を勉強する Session1に遷移しない')">
    Vue.js を勉強する Session1に遷移しない
  </a>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element)
    }
  }
})
```
### .capture
キャプチャーモードでDOMイベントをハンドルする。
ルート要素から順番にイベントが実行される。

```html
<div id="example">
  <div @click.capture="handler('親要素')">
    <button @click="handler('子要素')">イベントを実行</button>
  </div>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

handler('親要素') -> handler('子要素')の順番で実行されます。

通常、イベントが発生すると、キャプチャーフェーズでルート要素から要素を探し、ターゲットフェーズで発生要素を検出、パブリングフェーズでルートまで要素をさかのぼりますが、キャプチャーモードにするとキャプチャーフェーズでイベントが発生します。

### .self
実行した要素のみが処理を行われ、他の要素のイベントは実行されない。

```html
<div id="example">
  <div @click.self="handler('親要素')">
    <button @click="handler('子要素1')">イベントを実行</button>
    <button @click="handler('子要素2')">イベントを実行</button>
  </div>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

### .native
コンポーネントのルート要素上のネイティブイベントをハンドルする
※コンポーネントについて勉強不足なため今回はこんな感じだよ程度に記載

```html
<!-- コンポーネントをクリックするとhandlerが呼び出される -->
<my-component @click.native="handler"></my-component>

<!-- コンポーネントをクリックしてもhandlerが呼び出されない -->
<my-component @click="handler"></my-component>
```

### .once
1回だけイベントが実行される。

```html
<div id="example">
  <button @click.once="handler('初回イベント実行')">イベントを実行</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

### .passive
event.preventDefault()を呼び出さない事を明示する。
（.preventとの併用不可）


### 修飾子は繋げることができる

```html
<div id="example">
  <div @click="handler('親要素')">
    <button @click.stop.once="handler('子要素')">
      イベント実行ボタン
    </button>
  </div>  
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

イベント修飾子は繋げて書くことができ、上記の例では子要素だけ実行されるイベントが１回だけ行われ、そのあとは親要素のイベントだけ呼ばれるようになります。

## キー修飾子
キーボードイベントを検出するにあたって、特定のキーチェックが必要になった時にVue.jsでは**v-on**に対してキー修飾子を追加することができます

+ .entser
+ .tab
+ .delete (windowsだとbackspace)
+ .esc
+ .space
+ .up
+ .down
+ .left
+ .right

上記の**キー修飾子**から **.enter** を利用して例を作ります。
下記の例ではbuttonは左クリックでは実行されず、Enterキーで実行されます。

```html
<div id="example">
  <button @keyup.enter="handler('イベント実行')">
    Enterで実行ボタン
  </button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

[*KeyboardEvent.key*](https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent/key/Key_Values)で公開されているキャメルケースのキー名は、ケバブケースに変換することで修飾子として直接使用できます。

## キーコード
**<font color="red">【注意】</font>**
[keyCode](http://faq.creasus.net/04/0131/CharCode.html)イベントの使用は非推奨で、新しいブラウザではサポートされない可能性があります。
Vue.jsで使用することはできますが、今回は説明しません。

## システム修飾子

下記の修飾子を使用すると、対応するキーが押された時にのみキーボードのイベントリスナを実行できます。

+ .ctrl
+ .alt
+ .shift
+ .meta

**<font color="red">【注意】</font>**
Macintosh キーボードの場合、meta はコマンドキー（⌘）です
Windows のキーボードでは、meta はウィンドウキー（⊞）です
Sun Microsystems のキーボードでは、メタは実線のダイヤモンド（◆）とマークされています。
特定のキーボードでは、特に MIT や Lisp マシンのキーボードと Knight キーボード、space-cadet キーボード、メタのような後継機には “META” と表示されます。 Symbolics のキーボードでは、 “META” または “Meta” というラベルが付いています。

```html
<div id="example">
  <button @keyup.enter.ctrl="handler('イベント実行')">
    Enterで実行ボタン
  </button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

**<font color="red">【注意】</font>**
修飾子キーは通常のキーとは異なり、**keyup** イベントで使用する場合、イベント発生時に押されていなければならない。
言い換えると、keyup.ctrl は ctrl を押しながら何かのキーを離したときにのみ、実行されます。
ctrl キーだけを離しても、トリガされません。
トリガされることを望むなら、ctrl ではなく keyup.17 のように **keyCode** を使用します。

### .exact 修飾子
イベントを実行するために必要なシステム修飾子の正確な組み合わせを制御する。

```html
<div id="example">
  <!-- これは Ctrl に加えて Alt や Shift キーが押されていても発行されます -->
  <button @click.ctrl="handler('イベント実行')">@click.ctrl</button>

  <!-- これは Ctrl キーが押され、他のキーが押されてないときだけ発行されます -->
  <button @click.ctrl.exact="handler('イベント実行')">@click.ctrl.exact</button>

  <!-- これは システム修飾子が押されてないときだけ発行されます -->
  <button @click.exact="handler('イベント実行')">@click.exact</button>
</div>
```

```javascript
let app = new Vue({
  el: '#example',
  methods: {
    handler:function(element){
      alert(element + 'が実行されました')
    }
  }
})
```

## マウスボタンの修飾子

イベント実行のハンドリングを特定のマウスのボタンのみに制限します。
使用方法は上記の修飾子と同様です。

## なぜHTMLにイベントリスナを記述するのか
**v-on**を利用することでいくつかの利点があるからです。

+ HTML テンプレートを眺めることで、JS コード内のハンドラ関数を探すことを容易にします

+ JS内のイベントリスナーを手作業で加える必要がないので、ViewModelをDOM依存のない純粋な処理にできます。これはテスタビリティも向上します。

+ ViewModel が消去されるときに、すべてのイベントリスナーは自動で削除されます。手動でそれらの消去をおこなうことを気にする必要がない。

## あとがき
今回の記事ではhtmlとjsのコードにタグを付けずに掲載することにしました。
Qiita独特の機能らしくmdをエディタでプレビューするときに上手く反映されず、変更を加えるのが手間だったためです。

## 参考資料
[[メモ]Vue.jsイベント修飾子一覧](https://qiita.com/Yorinton/items/f7eb54f05609750da7f5)

[Vue.js公式リファレンス](https://jp.vuejs.org/v2/guide/events.html)