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
opts
を props
に変更
子コンポーネントに値を渡すために、呼び出し側で属性として設定するものですね。
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
, tags
を this.$
, 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 から observable
は riot
にバンドルされなくなりました。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_enabled
が true
の場合だけ 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 が出来たと思っているので、今回のバージョンアップは正当進化だと捉えています。(移行が苦しいことに変わりはないですが😅)