前提
まずスモールスタートで理解していこう。
簡単そうなのはBadge
かな。
`Badge` is a component that emphasizes the status of an item to make it immediately recognizable.
このBadge
コンポーネントをこんな感じで使う場合を考える。
このとき、Badge
コンポーネントに渡されるpropsは以下の様になっている。
useComponentStyle
(+ setStyles
)
まず、この行。
useComponentStyle
というフックに"Tag"
というコンポーネント名とpropsを渡して、その結果をタプルとして受け取っている。
ここでまず疑問に思うべきことは2つ。
useComponentStyle
とは何をしているフックなのか。
useComponentStyle
フックの結果として得られるstyles
とmergedProps
とは何を表現しているのか。
useComponentStyle
フックの実装を見てみる。
これを見ると、useComponentStyle
フックの実体はほぼほぼsetStyles
という関数であることがわかる。
じゃあ次にsetStyles
関数の実装を見てみる。
この処理の全体をざっくり眺めると…
- 現在のテーマやカラーモードをする(フックはif文のコードブロック内で実行できないため)
- (
isProcessSkip
がtrue
であれば)テーマ、カラーモード、propsを考慮したスタイルの計算が実行される
- 計算結果を返す
という流れのようだ。
1. 現在のテーマやカラーモードをする(フックはif文のコードブロック内で実行できないため)
まず処理1について。これら2行で現在のテーマやカラーモードを取得している事がわかる。
2. (isProcessSkip
がtrue
であれば)テーマ、カラーモード、propsを考慮したスタイルの計算が実行される
setStyles
の大部分を占めているのがこの処理2。
ここでコンポーネントに設定すべきスタイルの計算を実行していることが想像できる。
どこで使っているのかはわからないけど、isProcessSkip
フラグを利用するとこの処理をスキップすることができるらしい。
そこまで分かったら、if(!isProcessSkip)
の中身だけ読めば良い。
まずはこの処理。先述のuseTheme
フックの実行によって取得したThemeオブジェクトの中から、今回対象となるコンポーネントのスタイルを取得している。
例えば、Yamada UIのStorybookで使用されているThemeに相当するThemeオブジェクトは以下のような構造になっている。
これを見ると、Badge
コンポーネントに関するテーマはcomponents.Badge
というプロパティパスで取得できることが分かる。
今回、useComponentStyle
フックを以下のようにして実行していることを思い出す。
だからこの処理は、
実際はこの様に実行されていることが分かる。
というようになっている。
こうして得られたコンポーネントスタイルとpropsをマージして、仮引数props
を更新している。
(props
そのものを更新しているのはあまりお利口ではないと思う…)
例えば今回のケースでは、componentStyle?.defaultProps ?? {}
の評価結果は以下のようになっており、
一方でfilterUndefined(props)
の評価結果は以下のようになっている。
その結果、props
は以下の値で更新される。
componentStyle?.defaultProps ?? {}
側のcolorScheme
とvariant
が、props
側のcolorScheme
とvariant
でそれぞれ上書きされている。
そして次の処理に進む。今、componentStyle
はTruthyであることが自明なので、以下の以下のif文の中の処理は実行される。
まず、以下の処理によってprops
からchildren
プロパティが削除され、args
という変数が作成される。
その結果、変数args
の中身はこうなっている。
次にこの部分の処理を見ていく。
getStyles
関数についてはこの後ちゃんとメモる。
結論だけ書くと、変数styles
にはThemeオブジェクトのcomponents.Badge.baseStyle
プロパティパスに格納されている以下のオブジェクトが格納される。
次にこの部分を見ていく。
1つ前の処理から類推すれば、「変数variantStyles
にはThemeオブジェクトのcomponents.Badge.variants
プロパティパスに格納されているオブジェクトが格納されるのでは?」と推測できる。
今、props.variant
は"outline"
という文字列が格納されていることは自明。
一方で、componentStyle.variants
にはどんな値が格納されているだろう?
今までは省略していたので、ちゃんと確認しておく。
この結果から分かるように、componentStyle.variants
下の各プロパティの値にはtheme
、colorMode
、themeScheme
(をまとめたオブジェクト)を引数として受けとり、それを下に計算されたスタイル(をまとめたオブジェクト)を返す関数が定義されていることが分かる。
ということは、この処理でやっていることはprops.variant
に対応する関数を実行して、その結果として得られるスタイルをvariantStyles
に格納していることである事が分かる。
その際、パラメータとしてtheme
、colorMode
、themeScheme
、args
(をまとめたオブジェクト)を渡している。
だいたい想像がついたところでgetModifierStyles
関数の実装を確認する。
今、getModifierStyles
関数の第一引数value
の実引数はprops.variant
、つまり"outline"
という文字列が格納されている。
なので、ここで実際に実行されるのはこの分岐である。
ここで、getStyles
関数の実装も確認する。
これまでに登場している関数の呼び出し関係をきっちり整理していくと、引数stylesOrFunc
には以下の関数が、
引数props
にはtheme
、colorMode
、themeScheme
、args
(をまとめたオブジェクト)が格納されている。
isMulti
がfalse
であることは自明なので、getStyles
の処理は「stylesOrFunc
に渡された関数を、props
を引数として渡して実行した結果をstyles
に格納する」という処理に相当する。
引数stylesOrFunc
に渡されている関数の中身の詳細は一旦忘れることにして、この関数の実行結果は以下のオブジェクトになる。
このオブジェクトが変数modifierStyles
に格納される。
次にここの処理だが、ここは以下の理由からundefined
という空オブジェクトが格納される(詳しい考察は省略)。
- 今
Badge
コンポーネントにsize
はpropsとして設定してない
componentStyle.sizes
には対応するテーマが定義されていない
そして、このようにして計算したsizeStyles
とvariantStyles
をstyles
にマージしていく。
その結果得られるstyles
がstylesRef.current
と一致していなければstylesRef.current
をstyles
の値で上書きする。
今stylesRef.current
は空オブジェクト({}
)のはずなので、この上書きは当然実行される。
ここでやっとif(!isProcessSkip)
内の処理は完了する。
setStyles
関数の最後の処理として、この部分を実行する。
今、propsRef.current
は空オブジェクト{}
でありprops
は以下のオブジェクトが格納されている。
そのため、propsRef.current
はprops
の値で当然上書きされる。
3. 計算結果を返す
そして、setStyles
はstylesRef.current
とpropsRef.current
をまとめたタプルを返す。
実際の値は以下のようになっているはず。
omitThemeProps
useComponentStyle
フックの処理を一通り確認できたところで、次にomitThemeProps
関数を確認する。
omitThemeProps
関数は以下のような実装になっており、デフォルトではsize
、variant
、colorScheme
プロパティに対応する値を除外したオブジェクトを返す。
mergedProps
は今このようなオブジェクトが格納されている。
なので、omitThemeProps
関数を実行した結果、変数className
にはundefined
が、変数rest
には
というオブジェクトが格納される。
CSSUIObject
次にこの部分について見ていく。
ここでは、Badge
コンポーネントに当て込みたいスタイルを一つのオブジェクトとしてまとめている。
変数styles
には以下のオブジェクトが格納されているので、
変数css
は以下のオブジェクトが格納される。
JSXを返す
最後に、今までの結果を素に、ネイティブの<span>
タグにスタイルを当てたJSXを返す。
これがBadge
コンポーネントになる。
ここの処理はYamada UIにおけるfactory
の概念を理解する必要がある。
これは別のページにメモる。