【Riot.js】<slot />と<yield />の差異を埋めるcode snippet

たふみ
DE LIKER
Published in
5 min readDec 21, 2019

Riot.js Advent Cal 2019 の21日目の記事です。

TL;DR

export default {
onBeforeMount() {
let fr = document.createDocumentFragment()
Array.from(this.root.children).map(v => fr.appendChild(v))
this.state.fr = fr
}
}

Background

Riot.jsを数年前から巨大monolithic PHPとHTML密結合serviceの部分的な置き換え & 新機能投入に使用しています。Backendからjson | GraphQLでdataを取ってきてfrontでどうにかするという概念がほぼ存在していません。

仕様書などが存在せず前任者がすでにいないために、backendを書き直したりするのも大変で、良い落とし所となっています。もちろん体力のある会社や今からserviceを提供するところはこんなことしないほうがいいです。でも長く運用だけを続けているものは、ある程度こういう状況は仕方ないと私は思うのです。そこで少しでも負担を減らしたり、一部から全体に良い影響を与えるためにこのようなことをしています。この一部だけを、というのにRiot.jsは最適でした。
おかげさまで少しずつ環境は改善されています。

Since Riot.js v4 announcement

Riot.jsは個人的にv3のころがとても好みなので、正直v4が出たときはあまり好きになれませんでした。そんな中でも上記の置き換えでいい役目を果たしてくれたのが <yield /> でした。

v3のころのyieldは、こちらの例を見ていただければわかりやすいかと思いますが、

index.html にtag my-tagをmountしていると考えましょう。my-tag はこんな感じです。

<my-tag>
<div>
<yield />
<h3>from tag</h3>
</div>
<style>
div {
background: red;
}
</style>
</my-tag>

rootから1つdivを挟んだところにyieldがあります。これをv3で実行すると、index.htmlから

<my-tag>
<h2>from index.html (This should be inside red)</h2>
</my-tag>

としてmountしてやると、きちんとh2がdiv内に入り、背景が赤くなります。

これを同じくv4で <slot /> を使うと、

このように、div内に入りません。

これはRiot.js v4から

  • <slot /> はRiot tagからの受け渡しのみ(Riotのcomponentが入れ子になっているときに使うものになった)になったこと
  • もともとmountする前に存在していたchildrenのあとに、componentの中身がappendされるだけになったこと(もとからあったものは消えない)

という変更が入ったためです。なおこれは作者さんによるとパフォーマンスによるものだそうです。

しかしこれでは困ってしまいます。私の要件では、frontendをfrontendとして扱わず、単なる部品でしかないので、ある意味json色つけ隊ならぬhtml色つけ隊みたいなことをしたいわけです。そうして初めて、このreplacementで使用する旨味が効いてくるのです。

しかし、この手法がbad practiceであろうこと、やはりdataはdataとして扱えるべきであること、などを考えた結果、そこで、index.htmlからmountされるときに、

  • 子要素をdata化する
  • 子要素を削除する

ことをすれば、このcomponentが将来的にjsonからのdataであろうとうまいこと処理できるようになるし、一石二鳥だなと考えました。

Solution

DOM treeから削除したいがデータとしては残したい、performanceに顕著な影響は出したくはない… そうして思いついたのが、fragmentを使用することです。子要素をfragmentに入れてしまえば、elementはDOM treeから隔絶されて、なおかつそのあと煮るなり焼くなりが楽しく行えます。

そうして冒頭のcodeにいたりました。

export default {
onBeforeMount() {
let fr = document.createDocumentFragment()
Array.from(this.root.children).map(v => fr.appendChild(v))
this.state.fr = fr
}
}

それぞれのcomponentにおいて、mountされる前の子要素がstate.frに押し込まれます。あとは悪手ではありますが、querySelectorなりflat化してjsonに押し込むなり好きにして再利用できます。もちろん、ただ消したいだけでもこのコードは有効です。ただdiscardしているわけではないので、それにしか用がないときは、もう少し良い方法があるかもしれません。

おわり

--

--