Riot.js v4 子コンポーネントを操作する

[Riot.js Advent Calendar 2019](https://qiita.com/advent-calendar/2019/riotjs) の15日目が空いていたので埋めます。(ちょっと過ぎちゃったケド)

## はじめに

@black-trooper さんの[Riot v4 で子コンポーネントのメソッドを実行する](https://qiita.com/black-trooper/items/69ea1182520237b6aba2)の通り、v3の時は子コンポーネントのメソッドを実行したりということはよくやっていたんですが、v4から簡単には出来なくなっています。

[riot-observable](https://github.com/riot/observable)でも良いのですが、もうちょっと気軽に呼びたかったので少し調べてみました。
observable自体は便利なので、興味がある方は@black-trooper さん記事を参考に！

## コンポーネントを使う

`riot.component`の戻り値はコンポーネントに関する情報を持っているのでここから操作できます。

```html:child.riot
<my-child>
  <p>{ state.message } World!!</p>

  <script>
    export default {
      state: {
        message: ""
      },

      onMounted(props, state) {
        this.greeting(props.message);
      },

      greeting(msg) {
        this.state.message = msg;
        this.update();
      }
    }
  </script>
</my-child>
```
```html:app.riot
<app>
  <div id="child"></div>
  <input type="button" value="Goodbye" onclick="{ call_greeting }">
  <input type="button" value="Hola" onclick="{ call_greeting }">
  <input type="button" value="Adios" onclick="{ direct_greeting }">

  <script>
    import { component } from 'riot'
    import Child from './child.riot'

    let child;
    export default {
      onMounted(props, state) {
        // 子コンポーネントをマウント
        child = component(Child)(document.getElementById('child'), {
          message: 'Hello'
        });

        // 子コンポーネントのstateを取得
        console.log(child.state.message); // Hello
      },

      // 子コンポーネントのメソッドを呼び出し
      call_greeting(e) {
        child.greeting(e.target.value);
      },

      // 子コンポーネントのstateを直接変更
      direct_greeting(e) {
        child.state.message = e.target.value;
        child.update();
      }
    }
  </script>
</app>
```
```javascript:index.js
import { component } from 'riot'
import App from './app.riot'

component(App)(document.getElementById('root'));
```

## riotファイルの中でコンポーネント使いたくないんだけど？

ですよね。
それにはv4でなぜ取得できないのか調べる必要があります。

mountの[ソース](https://raw.githubusercontent.com/riot/riot/master/riot%2Bcompiler.js)を確認。

```javascript
      mount(element, state, parentScope) {
                     :
        element[DOM_COMPONENT_INSTANCE_PROPERTY] = this; // add eventually the 'is' attribute
```

エレメントのプロパティには入れていますが、キーがSymbolとなっています。

`DOM_COMPONENT_INSTANCE_PROPERTY = Symbol('riot-component')`

## 邪道版 子コンポーネントを操作

`Symbol`で持っているということさえわかれば十分。

```html:app.riot
<app>
  <my-child message="Hello"></my-child>
  <input type="button" value="Goodbye" onclick="{ call_greeting }">
  <input type="button" value="Hola" onclick="{ call_greeting }">
  <input type="button" value="Adios" onclick="{ direct_greeting }">

  <script>
    let child;

    export default {
      onMounted(props, state) {
        const elm = this.$("my-child");
        // 子コンポーネントの情報を取得
        child = elm[Object.getOwnPropertySymbols(elm).find(symbol => symbol.toString() === "Symbol(riot-component)")];
      },

      // 子コンポーネントのメソッドを呼び出し
      call_greeting(e) {
        child.greeting(e.target.value);
      },

      // 子コンポーネントのstateを直接変更
      direct_greeting(e) {
        child.state.message = e.target.value;
        child.update();
      }
    }
  </script>
</app>
```
```javascript:index.js
import { component, register } from 'riot'
import App from './app.riot'
import Child from './child.riot'

register('my-child', Child);
component(App)(document.getElementById('root'));
```

無事呼べました！