LoginSignup
33
20

More than 3 years have passed since last update.

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

Last updated at Posted at 2019-04-03

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

前提

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

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

移行手順

拡張子を.riotに変更

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

<yield /><slot /> に変更

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

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

:scope:host に変更

Scoped CSSの書き方が変わりました。こちらも使っていなければ無視で。

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

export default {} を追加する

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

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

ライフサイクルメソッドを書き換える

引数に props, state とありますが、まずは気にせず変換していきます。

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

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

optsprops に変更

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

- <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 を使いましょう。

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

タグプロパティを this.state に入れる

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

- <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 に入れるということですね。

  <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部分からはアクセスしない変数として使っていたので少し残念です)

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

イベントハンドラを export default {} に入れる

  <input type="checkbox" onclick="{ click }" />
  <script>
    export default {
+     click() {
+     // ...
+     }
    }
-   this.click = () => {
-   // ...
-   }
  </script>

refs, tagsthis.$, this.$$ に変更する

refs で取得していた部分を書き換えるのは大変なので、まずは既存の ref属性を使って選択すればいいと思います。
this.$, this.$$ は内部的に querySelectorAll が実行されています。)

  <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です。

- <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 をあげてくれたので、次のバージョンでは解消していると思います。
(2019/04/05追記): 4.0.0 beta-6で解消されました!🎉

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

observableは明示的に import する

v4 から observableriot にバンドルされなくなりました。v3 の時に riot-route が外れたのと似た感じですね。

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

親子間の値の受け渡しは属性を使う

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

  <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 も使えなくなっているので変更します。

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

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

class属性の { enabled: is_enabled } を書き換える

is_enabledtrue の場合だけ class属性に値がセットされるもので、結構便利だったんですけどなくなっちゃいました。
しょうがないので変数に値を直接セットします。

- <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 になってしまうので。

  <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 は削除する

ざっくり言ってしまうと、同一ファイル内であれば親子関係を意識しなくてよくなりました! 🎉
(1ファイル1コンポーネントの場合)

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

  <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 が出来たと思っているので、今回のバージョンアップは正当進化だと捉えています。(移行が苦しいことに変わりはないですが😅)

33
20
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
33
20