LoginSignup
80
30

More than 1 year has passed since last update.

SolidJS「分割代入しないでクレメンス」ワイ「Why」

Last updated at Posted at 2022-06-04

SolidJS君さぁ...

Reactとは違い、statepropsを分割代入すると、reactivityが失われます。どういうことかと言うと、

const Component = (props) => {
  const { isLoading } = props
  return (
    <Show when={!isLoading} fallback={<div>Loading...</div>}>
      <div>Some Content</div>
    </Show>
  )
}

こうやってisLoadingを分割代入すると、値が変わっても表示が切り替わらなくなります。
createSignalcreateStoreによって作成された状態に関しても同じです。

なぜなのか

公式ドキュメントのこのページの2番目のポイントに詳しく書いてありますね。
個人的な解釈をざっくりと書きます。

まず分割代入とは

const { isLoading } = propsというのは言い換えると、isLoadingという名前の変数にprops.isLoadingの値をアサインしているということです。
const isLoading = props.isLoadingと書くとわかりやすいですね。
自分は、「isLoadingという変数は、props.isLoadingの一時的なエイリアスでしかない」と解釈しています。

更新されるのはpropsの値のみ

新しい値が渡された時、props.isLoadingの値が更新されるのであって、isLoadingというただのエイリアスが直接更新されるわけではありません。

関数が一度しか実行されないのが理由

//この関数は一度しか実行されないから、
const Component = (props) => {
  const { isLoading } = props //isLoadingという変数に値がアサインされるのは最初にここでだけ。
  return (
    //isLoadingというエイリアスの値はずっと変わらないからビューも変化しない。
    <Show when={!isLoading} fallback={<div>Loading...</div>}>
      <div>Some Content</div>
    </Show>
  )
}

Reactなら再レンダリングの度に関数が上から下まで実行されるので、分割代入もその度に行われますが、SolidJSはそもそも再レンダリングが起きません。
props.isLoadingの値が変わってもisLoadingという「一時的なエイリアス」にアサインし直すことはありません。

Reactivityを保持するためには

さっきのページによると

  • jsxの中
  • リアクティブな関数(createEffectなど)の中

から直接propsstateにアクセスすれば、値は再評価されるとあります。

const Component = (props) => {
  return (
    // jsxの中で直接props.isLoadingにアクセスしているからちゃんと更新される。
    <Show when={!props.isLoading} fallback={<div>Loading...</div>}>
      <div>Some Content</div>
    </Show>
  )
}

いちいちprops.なんて書きたくないよ!

たしかに。
幸い解決法はあります。

関数で包んであげる

大事なのは、jsx(もしくはリアクティブな関数)の中で値にアクセスすることなので、関数で包んで、その関数をjsx内で呼び出せば解決します。

まあこれは分割代入とは違いますが、一つの値をpropsから取り出したい時に使えます。

const Component = (props) => {
  const isLoading = () => props.isLoading
  return (
    <Show when={!isLoading()} fallback={<div>Loading...</div>}>
      <div>Some Content</div>
    </Show>
  )
}

実際にprops.isLoadingにアクセスしているのは、jsx内でisLoadingという「関数」を呼んでいる時なので値はちゃんと更新されます。

少しまとめます。

//jsx内でprops.someValueにアクセスしているので⭕
return (
  <div>{props.someValue}</div>
)
//jsx外でprops.someValueにアクセスしているので❌
const { someValue } = props
return (
  <div>{someValue}</div>
)
//関数を通して、jsx内でprops.someValueにアクセスしているので⭕
const someValue = () => props.someValue
return (
  <div>{someValue()}</div>
)

ライブラリを使う

最初から言えよって感じですが。
destructureという、solidjs-communiyの方々が開発したライブラリです。(2022/6/10時点ではexperimental)
使い方はとってもシンプル。

const Component = (props) => {
  const { isLoading } = destructure(props)
  return (
    <Show when={!isLoading()} fallback={<div>Loading...</div>}>
      <div>Some Content</div>
    </Show>
  )
}

注意すべき点は、さきほどの例と同じくisLoadingは関数であるということです。

このライブラリに関しては別記事で詳しく解説するつもりですが、待てない方は公式ドキュメントを読んでください。

propsを2つに分けたい

propsの一部だけを子コンポーネントに渡したい」という場合、分割代入を使いがちですよね。

const Component = (props) => {
  const { title, ...childProps } = props
  return (
    <>
      <h1>{title}</h1>
      <Child {...childProps} />
    </>  )
}

こういった場合に便利なのがsplitPropsです。

const Component = (props) => {
  const [local, childProps] = splitProps(props, ['title'])
  return (
    <>
      <h1>{local.title}</h1>
      <Child {...childProps} />
    </>
  )
}

デフォルトの値を設定したい

これも普段は分割代入を使いますよね。

const Component = ({ title = 'default title', ...props }) => {
  return (
    <h1>{title}</h1>
  )
}

こういった場合にはmergePropsを使います。

const Component = (props) => {
  props = mergeProps({title: 'default title'}, props)
  return (
    <h1>{props.title}</h1>
  )
}

結論

分割代入ができないという制約を面倒くさいと思うかは人次第ですが、個人的にはそこまで大きな問題だとは捉えていません。
実を言うとprops.と書くのは、値がpropsからの物だとひと目で分かるの好きですし。
クソデカオブジェクトをまるっと分割代入して、変数名が縦長に並ぶよりprops.と書くほうが好みです。
最悪destructureを使えばいいですしね。ちなみにdestructureがbuilt-inではない理由としては、「みんなそんなに分割代入しないでしょ」とのことです。ほんまか?

なにはともあれ、SolidJSでは分割代入しないでね!というお話でした。

80
30
2

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
80
30