Riot v4 で子コンポーネントのメソッドを実行する

この記事は **[そろそろ Riot v4 への移行をしようじゃないか](https://qiita.com/black-trooper/items/4294899fb4a1ff563460)** の続きです。
v3 から v4 への移行を考えている方は先に読んでもらえればと。

----
Riot v4 では子コンポーネントの関数が実行できなくなっています。
コンポーネント間で**依存関係を持たないように**という思想だと理解してはいますが、どうしても必要なケースがあったので実行できる方法を考えてみました。

# Riot v3 ではどうやっていた？

`refs` でコンポーネントを取得すれば、タグメソッド（this.xxx）が簡単に実行できました。

```html
<app>
  <child ref="child"></child>
  <button type="button" onclick="{ showChild }">Show child</button>
  
  <script>
    this.showChild = () => {
      this.refs.child.show()
    }
  </script>
</app>

<child>
  <script>
    this.show = () => {
      // ...
    }
  </script>
</child>
```

# Riot v4 ではどうするの？

`riot-observable` を使ってイベントを登録しておき、親から `trigger` で発動すればうまくいきました。
順を追って説明します。

## 1. 親コンポーネントで `riot-observable` を初期化して、子コンポーネントに渡す

```diff:app.riot
  <app>
-   <child ref="child"></child>
+   <child observable="{ observable }"></child>
    <button type="button" onclick="{ showChild }">Show child</button>
    <script>
-     this.showChild = () => {
-       this.refs.child.show()
-     }
+     import observable from 'riot-observable'
+     export default {
+       onMounted(props, state) {
+         this.observable = observable(this)
+       },
+       showChild() {
+         this.refs.child.show() // とりあえず v3 記法のまま
+       }
+     }
    </script>
  </app>
```

## 2. 子コンポーネントで、`uid` を生成する
プロパティに `index` を追加します。
プロパティはv4からstatic変数になった特性を活かして、 `onMount` でインクリメントして `uid` を生成しています。

```diff:child.riot
  <child>
    <script>
+     let index = 0
+     export default {
+       onMounted(props, state) {
+         this.uid = `child-${index++}`
+       },
+     }
      this.show = () => {
        // ...
      }
    </script>
  </child>
```

親コンポーネントからアクセスできるように `id` 属性に `uid` をセットします。
（`id`を使いたくない場合は、 `data-xxx` とかなんでもいいです）

```diff:child.riot
- <child>
+ <child id="{ uid }">
```

## 3. 親コンポーネントから受け取った `observable` にイベントを登録する
`id` + イベント名（この例ではshow）で登録します。

```diff:child.riot
  <child id="{ uid }">
    <script>
      let index = 0
      export default {
        onMounted(props, state) {
          this.uid = `child-${index++}`
+         props.observable.on(`${this.uid}-show`, () => {
+           show()
+         })
        },
      }
-     this.show = () => {
+     function show() {
        // ...
      }
    </script>
  </child>
```

## 4. 親コンポーネントからイベントを実行する
2.で設定した`id`属性を使って`uid`を取り出してイベントを実行すると、目的のメソッドが実行されます。👏

```diff:app.riot
  <app>
    <child observable="{ observable }"></child>
    <button type="button" onclick="{ showChild }">Show child</button>
    <script>
      import observable from 'riot-observable'
      export default {
        onMounted(props, state) {
          this.observable = observable(this)
        },
        showChild() {
-         this.refs.child.show()
+         this.observable.trigger(`${this.$('child').id)}-show`
        }
      }
    </script>
  </app>
```

# 補足：`riot.install`で`observable`を渡しちゃってもいい

例では `observable` を親コンポーネントで生成して**属性**で渡していましたが、
`riot.install` を使ってすべてのコンポーネントに`observable`を渡してしまってもいいと思います。

```javascript:index.js
import observable from 'riot-observable'
const obs = observable()
riot.install(function (component) {
  component.obs = obs
})
```