CSS Hooks and the state of CSS-in-JS
記事は上記記事を意訳したものです。
※当ブログでの翻訳記事は元サイト様に許可を得て掲載しています。
フロントエンドアプリケーションとコンポーネントベースのアーキテクチャが範囲と複雑さを増すにつれ、モジュラーで保守しやすいスタイルシートを書くことが大きな課題となってきています。従来のCSSのカスケード特性のため、セレクター名の衝突、不要なコードの蓄積、スタイルのカプセル化の困難さが生じやすく、これにより多くの開発者がインラインスタイルに頼るようになってきています。
インラインスタイルは、コンポーネントのマークアップ、CSS、機能を1か所にまとめることができ、HTMLとは別のCSSファイルを行き来する手間を避け、セレクターロジックを介した間接的なスタイル参照の誤りを削減することができます。
しかし、インラインスタイルには分離の問題、再利用性の低さ、特異性の問題など、一般的に好まれていない特性があります。メディアクエリや:hover
、:active
などの擬似クラスをインラインCSSで使えないことも、開発者がインラインスタイルに抱く懸念を正当化しています。
これがCSS in JSの発明と普及につながりました。CSS in JSは、リアルタイムにDOM にスタイルシートを生成・挿入します。CSS in JSはインラインスタイルのカプセル化の利点を保ちながら、ダイナミズム、メンテナンス性、再利用性といった従来のCSSの柔軟性を取り戻しています。しかし、すべてにはデメリットがあり、これがCSS Hooksという独特のソリューションにつながりました。
本記事では、CSS Hooksの仕組みと、コンポーネントのスタイリングにもたらす利点について詳しく見ていきます。CSS HooksがCSS in JSエコシステムの中でどのように位置づけられるかも探ります。CSS in JSにはトレードオフを伴う多様な手法がありますからね。また、最近の変化や、JavaScript フレームワークにおける今後の方向性についても分析していきます。
CSS Hooksとは
CSS Hooksは、フックを使ってスタイルのルールを宣言するReactベースのソリューションです。CSS HooksはCSS in JSのカプセル化とダイナミズムをReactアプリに持ち込みつつ、ReactデベロッパーにとってなじみのあるAPIを使用するフックを使用しています。スコープ付きのスタイル、メディアクエリなどの機能をインラインスタイルで利用できるようになります。これにより、JavaScript内でのCSS記述の従来の痛みを改善しています。
このアプローチにより、コンポーネントに紐付けられたスタイルを保ちつつ、コードの可読性とメンテナンス性が向上します。内部的にはCSS Hooksが動的にスタイルシートを挿入し、コンポーネントに結び付けられたクラス名を生成することで、名前の衝突なくスコープ付けを実現しています。
CSS Hooksの仕組み
CSS Hooksは新しいものを作り出すのではなく、インラインスタイルの可能性を広げることに焦点を当てています。つまり、インラインスタイルを完全に置き換えるのではなく、その上に機能を追加しているのです。
例えば、CSS Hooksにより:hover
や:active
などの擬擬似クラスを、構文を変更せずに使えるようになりました。これは完全にインラインスタイルでできることとは思えませんが、CSS Hooksがそれを現実のものにしています - ランタイムの挿入や ビルドステップは不要です。
CSS HooksはReact、Prereact、Solid.js、Qwikで動作し、ここではViteにReact設定を使って説明します。まず、css-hooks
というプロジェクトを作成し、Viteをインストールしましょう:
$ npm create vite@latest css-hooks -- --template vanilla-ts
次に、作成したフォルダに移動し、CSS Hooksライブラリをインストールします:
cd css-hooks-playground npm install && npm install @css-hooks/react
これで、基本的なViteのテンプレートページができあがりました。
次に、CSS Hooksのシステムを作成します。src
フォルダにcss.ts
ファイルを作成し、以下のコードを入力しましょう:
import { createHooks } from "@css-hooks/react"; export const { styleSheet, css } = createHooks({ hooks: { "&:active": "&:active", "&:hover": "&:hover", }, debug: import.meta.env.DEV, });
ここでは、@css-hooks/core
ライブラリからcreateHooks
関数をインポートし、それを呼び出してフックシステムを作成しています。得られたcreateHooks
関数を使って、フックを定義し、スタイルを管理します。
上記以外のフレームワークでCSS Hooksを使う場合は、CSSスタイルを表す JavaScript オブジェクトを受け取り、無効なエントリをフィルタリングし、プロパティ名をCSSの規則に従って書式設定し、それらを1つの文字列に結合するstyleObjectToString
関数を追加する必要があります:
export function styleObjectToString(obj: Record<string, unknown>) { return Object.entries(obj) .filter( ([, value]) => typeof value === "string" || typeof value === "number", ) .map( ([property, value]) => `${/^--/.test(property) ? property : property.replace(/[A-Z]/g, x => `-${x.toLowerCase()}`)}: ${value}`, ) .join("; "); }
この機能はアプリフレームワークに組み込まれていることが一般的なので、React、Prereact、Solidjs、Qwikを使っている場合は、この関数をcss.ts
ファイルに追加する必要はありません。
次に、main.ts
ファイルにスタイルシートをインポートして、アプリに組み込みます。src/main.tsx
ファイルに移動し、スタイルシートをインポートし、styleSheet
関数を使って動的に生成されたCSSスタイルを挿入します:
import React from 'react' import ReactDOM from 'react-dom/client' import App from './App.tsx' import './index.css' import { styleSheet } from './css.ts' ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <style dangerouslySetInnerHTML={{ __html: styleSheet() }} /> <App /> </React.StrictMode>, )
これで、コード内でインラインスタイルとして:hover
と:active
を使えるようになりました。これを確認するため、カウンターボタンをターゲットにし、クリック時に拡大し(active
時)、ホバー時に色が変わるように更新します。これらの変更をすべてインラインスタイルで実装します。app.tsx
ファイルに次のコードを追加してください:
<button onClick={() => setCount((count) => count + 1)} style={css({ transition: "transform 75ms", on: $ => [ $("&:active", { transform: "scale(5)" }), $("&:hover", { background: "#1b659c", }), $("&:active", { background: "#9f3131", }) ] })} > count is {count} </button>
結果は以下のようになります:
CSS Hooksを正しくインストールし、擬似クラスをインラインスタイルで使うエレガントなソリューションを活用できました。
CSS in JSの現状
近年、コンポーネントベースのアーキテクチャがより一般的になってきたことで、CSS in JSはウェブアプリケーションのスタイリングのメインストリームアプローチとなっています。コンポーネントのカプセル化、ダイナミックなスタイリング、デベロッパーエクスペリエンスの向上など、多くの利点があります。しかし、最近の進歩により、CSS in JSライブラリにとって重大な問題が生じています。
React 18やNext.jsでのサーバーコンポーネントの導入は、ブラウザに送信されるJavaScriptの量を最適化する方向への移行を示しています。特定のコンポーネントをサーバー上でレンダリングすることで、クライアント側での hydration に必要なJSの量を減らすことができます。
しかし、これはクライアントサイドのJavaScriptと hydration に大きく依存しているCSS in JSライブラリにとって課題となります。CSS in JSによって動的に生成されたスタイルは、サーバーレンダリングの出力と水和された出力の間で、ひどいスタイルのフラッシュを避けるために、ページ読み込み時に存在している必要があります。Styled Componentのようなライブラリは、hydration プロセス中にコレクトされたスタイルを注入することで、この問題に対処しています。
しかし、サーバーコンポーネントの登場により、静的なサーバーレンダリングされたマークアップと、ページを水和するために送信される残りのJSの間に分断が生じます。この断片化により、どのスタイルがhydration 中に注入されるべきかを推測するのが大半の現在のCSS in JSソリューションにとって困難になります。なぜなら、スタイリングのコードとコンポーネント自体の両方がサーバーとクライアントにまたがる可能性があるためです。
CSS in JSライブラリは、サーバーレンダリングされたコンポーネントと、水和を処理するクライアントコンポーネントを明確に分離するようにリファクタリングする必要があるでしょう。動的な値のような機能もサーバーレンダリングと水和の一貫性に課題をもたらすため、サーバーコンポーネントのためのスタイル注入について新しい慣例が必要かもしれません。
要約すると、React やNext.jsが推奨するサーバーサイドレンダリングへの移行は、CSS in JSの水和アプローチを洗練させる緊急性を生み出しています。静的なUIと水和されたUIが共存する分断された環境に対処する必要があるため、これらの統合プロセスを滑らかにすることが重要です。
一部のライブラリはすでにこれに適応しており、CSS Hooksはサーバーサイドレンダリングとコンポーネントが引き起こす課題に取り組むために最適な位置にあるライブラリの1つです。CSS Hooksのアーキテクチャは、コンポーネントのスコーピング、静的な抽出、遅延水和モード、ユニバーサルレンダリングの認識を中心に設計されており、最新のサーバーサイドレンダリングのニーズに合わせて滑らかに適応できるようになっています。CSS in JSの利点を失うことなく、サーバーレンダリング最適化アプリを構築できます。
結論
CSS in JSは、急速なイノベーションと多様なアプローチがある一方で、安定性と標準化を模索する、興味深い進化段階にあります。新しいライブラリが次々と登場し、既存のものもバージョンやシンタックスの変更を経続けています。
多くの面でCSS in JSは、コンポーネントスタイリングの最先端を代表しています。しかし、まだ多くの点が動き続けているため、より明確な勝者がフロントエンドエコシステム全体に出現するまでには、さらに数年かかるかもしれません。
現時点では、CSS in JSは統一されたアプローチというよりも、むしろ幅広い手法のカテゴリーとして機能しています。そして、CSSの欠点やフロントエンドコードの複雑性のスケーリングに苦しむ開発者にとって、CSS Hooksはコンポーネントのスタイリングをレベルアップするための強力なソリューションを提供しています。
ユーザーのCPUを食い尽くすフロントエンドはありますか?
ウェブフロントエンドが複雑化するにつれ、リソース集約的な機能がブラウザにますます多くを要求するようになっています。本番環境でのすべてのユーザーの側面CPUの使用状況、メモリ使用状況などを監視・追跡したい場合は、LogRocketのようなツールを試してみるといいでしょう。
LogRocketはウェブアプリ、モバイルアプリ、ウェブサイトで起こることをすべて記録する、いわばDVRのようなツールです。問題の原因を推測するのではなく、キーとなるフロントエンドのパフォーマンス指標を集計・レポートし、ユーザーセッションとアプリケーションの状態を再生したり、ネットワークリクエストをログ記録したり、エラーを自動的に表示したりできるということです。
ウェブアプリとモバイルアプリのデバッグを近代化する - 無料で始められます。とてもいいツールです。フロントエンドのパフォーマンスを改善する上で非常に有用です。