そろそろ Riot v4 への移行をしようじゃないか

Riot.jsの`v3`から`v4`へは破壊的な変更が含まれています。
試しに自作のライブラリの一部を `v4` へ移行してみたのですが、コードとドキュメントを読みながら試行錯誤の連続だったので、移行手順としてまとめてみました。

# 前提
執筆時点の最新版 `4.0.0-rc.1` を対象にしています。

**参考にした情報**
https://github.com/riot

# 移行手順

## 拡張子を.riotに変更
まずはじめにファイルの拡張子を変更します。
実は変更しなくても動くんですが、「v4に移行するぞー！」という意思表明で。

## `<yield />` を `<slot />` に変更
ライブラリを作る時にはよく使いますが、そうでなければ見たことがない人も多いかもです。使ってなければ無視しちゃってください。

```diff
- <label><yield /></label>
+ <label><slot /></label>
```

## `:scope` を `:host` に変更
Scoped CSSの書き方が変わりました。こちらも使っていなければ無視で。

```diff
  <style>
-   :scope {
+   :host {
      display: block;
    }
  </style>
```

## `export default {}` を追加する
テンプレートのHTML部分からアクセスするものはすべて `export default {}` の中に入れるようになりました。

```diff
  <script>
+   export default {
+   }
  </script>
```

## ライフサイクルメソッドを書き換える
引数に `props`, `state` とありますが、まずは気にせず変換していきます。

```diff
  <script>
    export default {
+     onMounted(props, state) {
+     // ...
+     }
    } 
-   this.on('mount', () => {
-   // ...
-   })  
  </script>
```

ライフサイクルメソッドは全部で６つあるので、他に使っているものがあれば同様に書き換えてください。
`onBeforeMount`, `onMounted`, `onBeforeUpdate`, `onUpdated`, `onBeforeUnmount`, `onUnmounted`

## `opts` を `props` に変更
子コンポーネントに値を渡すために、呼び出し側で属性として設定するものですね。
v3 のscript部分では `this.opts.xxx` とも書けたので、それもあれば変更をお忘れなく。

```diff
- <label>{ opts.label }</label>
+ <label>{ props.label }</label>

  <script>
    export default {
      onMounted(props, state) {
-       sampleFunction(opts.checked)
+       sampleFunction(props.checked)
      }
    }
  </script>
```
`opts` では値の上書きや追加ができましたが、 `props` ではどちらもできなくなっています。
状態を保持するには `this.state` を使いましょう。

```diff
- opts.checkd = false
+ this.state.checked = false
```

## タグプロパティを `this.state` に入れる
タグプロパティは公式の呼び名じゃなかったかも。コードを見て察していただければ💦

```diff
- <input type="checkbox" checked="{ checked }" />
+ <input type="checkbox" checked="{ state.checked }" />
  <script>
    export default {
+     state {
+       checked: false
+     },
      onMounted(props, state) {
-       this.checked = opts.checked
+       this.state.checked = props.checked
      }
    }
-   this.checked = false
  </script>
```

ちなみに、テンプレート変数を小さく保つための変数は `state` ではなく `export default {}` 配下に移動します。状態を保持するものだけ `state` に入れるということですね。

```html
  <my-component>
    <!-- state.val ではない -->
    <p>{ val }</p>

    <script>
      export default {
        onBeforeUpdate() {
          // this.state.val ではない
          this.val = some / complex * expression ^ here
        }
      }
    </script>
  </my-component>
```

## プロパティを `export default {}` に入れる
v4 ではstatic変数になり、インスタンス間で共有されるようになっていました。そのまま v3 の感覚で使っていると事故るので移動させます。
（テンプレートのHTML部分からはアクセスしない変数として使っていたので少し残念です）

```diff
  <script>
    export default {
+     defaultChecked: false
    }
-   let defaultChecked = false
  </script>
```

## イベントハンドラを `export default {}` に入れる
```diff
  <input type="checkbox" onclick="{ click }" />
  <script>
    export default {
+     click() {
+     // ...
+     }
    }
-   this.click = () => {
-   // ...
-   }
  </script>
```

## `refs`, `tags` を `this.$`, `this.$$` に変更する
`refs` で取得していた部分を書き換えるのは大変なので、まずは既存の `ref`属性を使って選択すればいいと思います。
（`this.$`, `this.$$` は内部的に [querySelectorAll](https://developer.mozilla.org/ja/docs/Web/API/Document/querySelectorAll) が実行されています。）

```diff
  <h1 ref="header">My todo list</h1>
  <ul>
    <custom-li>Learn Riot.js</custom-li>
    <custom-li>Build something cool</custom-li>
  </ul>

  <script>
    export default {
      onMounted() {
-       const title = this.refs.header
-       const items = this.tags['custom-li']
+       const title = this.$("[ref='header']") // single element
+       const items = this.$$('custom-li') // multiple elements
      }
    }
  </script>
```
もちろん `this.$('h1')` でアクセスするように修正して、 `ref`属性を削除してもOKです。

```diff
- <h1 ref="header">My todo list</h1>
+ <h1>My todo list</h1>
  <ul>
    <custom-li>Learn Riot.js</custom-li>
    <custom-li>Build something cool</custom-li>
  </ul>

  <script>
    export default {
      onMounted() {
-       const title = this.$("[ref='header']") // single element
+       const title = this.$("h1") // single element
        const items = this.$$('custom-li') // multiple elements
      }
    }
  </script>
```


## 属性にExpressionと文字列を設定している場合は変更する
**こちらは4.0.0beta-5までのバグです。**
@nibushibu さんが [issue](https://github.com/riot/riot/issues/2669) をあげてくれたので、次のバージョンでは解消していると思います。
(2019/04/05追記)： **4.0.0 beta-6で解消されました！🎉**

```diff
- <div id="{ state.name }-{ state.surname }">
+ <div id="{ state.name + '-' + state.surname }">
    { state.name } - { state.surname }
  </div>
```

## observableは明示的に import する
v4 から `observable` は `riot` にバンドルされなくなりました。v3 の時に `riot-route` が外れたのと似た感じですね。

```diff
  <script>
+   import observable from 'riot-observable'
    export default {
      onMounted(props, state) {
+       this.observable = observable(this)
      },
      onClick() {
-       this.trigger('click')
+       this.observable.trigger('click')
      }
    }
  </script>
```

## 親子間の値の受け渡しは属性を使う
子コンポーネントのタグプロパティにアクセスできなくなりました。
アクセスできなくなったということは、今までやっていたのが悪手だったということですね。😓

```diff
  <app>
    <su-checkbox ref="checkbox1">
      Make my profile visible
    </su-checkbox>

-   <p>{ refs.checkbox1.checked ? 'on' : 'off' }</p>
+   <p>{ $("[ref='checkbox1']").getAttribute('checked') ? 'on' : 'off' }</p>
  </app>

- <su-checkbox>
+ <su-checkbox checked="{ state.checked }">
    <!-- ... -->
    <script>
-     this.checked = false
+     export default {
+       state: {
+         checked: false
+       }
+     }
    </script>
  </su-checkbox>
```

同様に親コンポーネントにアクセスする `this.parent` も使えなくなっているので変更します。

```diff
  <app>
-   <child />
+   <child title="{ title }" />
    <script>
      export default {
        title: 'Sample title'
      }
    </script>
  </app>

  <child>
-   { parent.title }
+   { props.title }
  </child>
```


## class属性の `{ enabled: is_enabled }` を書き換える
`is_enabled` が `true` の場合だけ class属性に値がセットされるもので、結構便利だったんですけどなくなっちゃいました。
しょうがないので変数に値を直接セットします。

```diff
- <div class="{ enabled: is_enabled }">
+ <div class="{ enabled }">
  </div>
  <script>
    export default {
+     onBeforeUpdate() {
+       this.enabled = this.is_enabled ? 'enabled' : ''
+     }
    }
  </script>
```

## ローカル関数で this を使う場合は 引数で渡す
`this` でアクセスできるのはライフサイクルメソッドとイベントハンドラだけなので、これらからローカル関数を呼び出している場合は引数で渡す必要があります。
v3 では `const tag = this` とか出来たんですが、上述の通り static になってしまうので。

```diff
  <script>
    export default {
      state: {
        title: 'Sample title'
      }
      onMounted(props, state) {
-       method1()
+       method1(this)
      }
    }
-   function method1() {
-     console.log(this.state)
+   function method1(tag) {
+     console.log(tag.state)
    }
  </script>
```


## ネストしたコンポーネントで親コンポーネントにアクセスするための `parent` は削除する
ざっくり言ってしまうと、同一ファイル内であれば親子関係を意識しなくてよくなりました！ 🎉
（１ファイル１コンポーネントの場合）

ライブラリを作成している身としては一番うれしかったところです。

```diff
  <greeting>
    <p>Hello <slot/></p>
  </greeting>

  <user>
    <greeting>
-     <b>{ parent.text }</b>
+     <b>{ text }</b>
    </greeting>

    <script>
      export default {
        text: 'world'
      }
    </script>
  </user>
```

# おわりに
全体的に色んな書き方があった `v3` に対して、規約を明確にしてきた `v4` という印象を受けました。
規約が緩かったから [Riot.js Style Guide](https://qiita.com/risacan/items/d782fa24ad4d4f8b73f8) が出来たと思っているので、今回のバージョンアップは正当進化だと捉えています。（移行が苦しいことに変わりはないですが😅）