Open11

solid js の Solid Tagged Template Literals のメモ

nikogolinikogoli

signal 呼び出しの際の記法

document が↓と説明するように、${...} に入れるものには注意が必要。

Reactive expression must be manually wrapped in functions to be reactive.

const [count, setCount] = createSignal(0)
return html`
  <div>${count}</div>          <!-- 🆗: 変更は追跡される -->
  <div>${count()}</div>        <!-- ❌: 変更は追跡されない -->
  <div>${() => count}</div>    <!-- 🆗: 変更は追跡される -->
  <div>${() => count()}</div>  <!-- 🆗: 変更は追跡される -->
`
nikogolinikogoli

制御フローコンポーネントに signal を渡すとき

const [bol, setBool] = createSignal(false)
return html`
  <${Show} when=${bol}>...</Show>          <!-- 🆗: 変更は追跡される -->
  <${Show} when=${bol()}>...</Show>        <!-- ❌: 変更は追跡されない -->
  <${Show} when=${() => bol}>...</Show>    <!-- ❌: 変更は追跡されない -->
  <${Show} when=${() => bol()}>...</Show>  <!-- 🆗: 変更は追跡される -->
`

ただし、「signal を使った条件式を用いる」が「派生signal は定義したくない」場合は、when = ${() => bol() + piyo}の記法を使うしかない。

nikogolinikogoli

カスタムのコンポーネントに signal を渡すとき

  • コンポーネント内部の呼び出し記法が <p>${props.count}</p>のとき
    return html`
      <${Counter} count=${count}>          <!-- ❌: 変更は追跡されない -->
      <${Counter} count=${count()}>        <!-- ❌: 変更は追跡されない -->
      <${Counter} count=${() => count}>    <!-- 🆗: 変更は追跡される-->
      <${Counter} count=${() => count()}>  <!-- ❌: 変更は追跡されない -->
    `
    
  • コンポーネント内部の呼び出し記法が <p>${props.count()}</p>のとき
    return html`
      <${Counter} count=${count}>          <!-- 🔥: エラーになる -->
      <${Counter} count=${count()}>        <!-- 🔥: エラーになる -->
      <${Counter} count=${() => count}>    <!-- ❌: 変更は追跡されない-->
      <${Counter} count=${() => count()}>  <!-- 🔥: エラーになる -->
    `
    
  • コンポーネント内部の呼び出し記法が <p>${() => props.count}</p>のとき
    return html`
      <${Counter} count=${count}>          <!-- 🆗: 変更は追跡される -->
      <${Counter} count=${count()}>        <!-- ❌: 変更は追跡されない -->
      <${Counter} count=${() => count}>    <!-- 🆗: 変更は追跡される-->
      <${Counter} count=${() => count()}>  <!-- 🆗: 変更は追跡される -->
    `
    
  • コンポーネント内部の呼び出し記法が <p>${() => props.count()}</p>のとき
    return html`
      <${Counter} count=${count}>          <!-- 🔥: エラーになる -->
      <${Counter} count=${count()}>        <!-- 🔥: エラーになる -->
      <${Counter} count=${() => count}>    <!-- 🆗: 変更は追跡される -->
      <${Counter} count=${() => count()}>  <!-- 🔥: エラーになる -->
    `
    
nikogolinikogoli

まとめ:template literal で signal を用いるための記法

成功することがない『カスタムの Comp. に渡して${props.count()}で使う』場合を省くと、以下のようになる。

${count} ${count()} ${() => count} ${() => count()}
<p>{...}</p>などの通常時 🆗 🆗 🆗
制御フロー Comp. に渡す 🆗 🆗
カスタムの Comp. に渡して${props.count}で使う 🆗
カスタムの Comp. に渡して${() => props.count}で使う 🆗 🆗 🆗
カスタムの Comp. に渡して${() => props.count()}で使う 🔥 🔥 🆗 🔥
nikogolinikogoli

カスタムのコンポーネントの中で signal を使う

上述のように、カスタムのコンポーネントに signal を渡すときは ${() => count} が最も安全。

この記法で signal を渡した状態で、以下の2つを比較した。

  1. props.countとして直接htmlで使う記法 (上の実験とほぼ同じ内容)
  2. props.count を利用した新しい変数を作ってそれをhtmlで使う記法
それぞれの記法の結果

新しく作成した変数を使う場合、それがprops.countを利用した無名関数、つまり派生 signal でなければうまく機能しない。

  • ❌:const is_even = (props.count() %2 == 0) ? true : false
  • 🆗:const is_even = () => (props.count() %2 == 0) ? true : false
nikogolinikogoli

まとめ:signal は基本的に「関数の中」で呼び出す

  1. signal を普通に使うとき
html`<span> ${ () => `count: ${count()}` } </span>` // 無名関数でラップその1
html`<span> count: ${ () => count() } </span>`   // 無名関数でラップその2

const text = () => `count: ${ count() }`    // 派生 signal としてラップ
html`<span>${ () => text() }</span>`
  • その他 🆗:<span>${count}</span><span>${text}</span> [1]
  • その他 ❌:<span>${count()}</span><span>${text()}</span>
     
  1. signal を制御フローの判定で使うとき
<${Show} when=${() => count() %2 == 0}> ... </Show>
  • 派生 signal を使う場合 🆗:<${Show} when=${is_even}> ... </Show>
  • 派生 signal を使う場合 ❌:<${Show} when=${is_even()}> ... </Show>
     
  1. signal をカスタムコンポーネントに props として渡すとき
// 渡すとき
html`<${MyComp} count=${() => count}><//>` 

// コンポーネントの中で使うとき (signal を普通に使うときと同じ)
html`<span> ${ () => `count: ${props.count()}` } </span>` // 無名関数でラップその1
html`<span> count: ${ () => props.count() } </span>`   // 無名関数でラップその2

const text = () => `count: ${ props.count() }`    // 派生 signal としてラップ
html`<span>${ () => text() }</span>`

その他の性質も「1. signal を普通に使うとき」と同様

脚注
  1. が、上の実験でわかるように、この記法でうまく動くかどうかは props の渡し方や新しい変数の定義方法に依存する ↩︎

nikogolinikogoli

Tabler Icons を使う

https://zenn.dev/hashrock/scraps/7732a65939361e

hashrock さんの方法は solidjs ではうまくいかないので、solidjs 用に調整された?ものを使う
https://github.com/x64Bits/solid-icons

使い方の説明が雑すぎてライブラリとアクセスパスの対応が分かりづらいが、ここから推測できる。
というわけで Tabler Incons は /tbであり、以下のようにして利用できる。stroke width の設定は solidJS の style 設定機能を使う[1][2]

import { render } from "https://cdn.skypack.dev/solid-js/web"
import html from "https://cdn.skypack.dev/solid-js/html"
import { TbPlaylist } from "https://esm.sh/solid-icons@1.0.1/tb"

const App = () => {
  return html`
    <div>
      <${TbPlaylist} size='36'  style=${ {"stroke-width": 1} }><//>
    </div>
`}
render(App, document.body)

脚注
  1. props の指定をstroke-widthのようにハイフンにすると solidjs がエラーを出す。tailwind では class='[stroke-width:1]'が機能するらしいが、twind ではうまくいかない ↩︎

  2. style 設定機能は、コールバック形式にしなくても template literal 内で機能する ↩︎

nikogolinikogoli

コンポーネントからの ref の転送

https://www.solidjs.com/tutorial/bindings_forward_refs
template literal の callback を要求する性質によって動作がわかりやすい[1]

親.ts
const App = () => {
  let some_elem
  return html`
    <div>
      <${MyComp} ref=${(el) => some_elem= el}><//>
    </div>
`}
render(App, document.body)
子.ts
export function MyComp( props ){
  return html`
    <div>なにか1</div>
    <div>なにか2</div>
    <div ref=${(el) => props.ref(el)} >ref に格納したい element </div>
  `
}
脚注
  1. と思ったがそうでもない? some_elem = el(el) => some_elem=elは等しくはないよな... まあ自身を取得する処理関連で ref は when とか同様の特殊処理になっている気はする。 ↩︎

nikogolinikogoli

use どうやって使うか問題

公式のシンタックスシュガー云々を基準にすると、(el) => ref = el ではなく (el) => func(el)しているのでは?

nikogolinikogoli

タグ付き template literal 内部での${}の挙動とその利用

基本的に「関数を変数名で指定して渡す」 or 「完全に固定されたデータを渡す」の2択

  • ${function}なら関数が渡されてその関数が html に結び付けられる
  • ${function()}なら関数の template 評価時の返り値が渡されてその値が html に結び付けられる
  • なので、 signal getter を${function()}で記述してしまうと signal の変更が追跡されない
     

この性質の結果、template literal 内部の signal 関連の記述は、全て派生 signal を要求する。
したがって、${() => function()+1}のような『一時的な無名の派生 signal を作成して渡す』記述になる。

  • が、ref や when や onClick のような named な部分は記法は同じだが処理が違うっぽい?