はじめに
長いタイトルですが今回は短編です。
飽くまでこんな方法がそういえばあったな、くらいでご覧ください。
最近名前聞かないけれど
aphrodite
https://github.com/Khan/aphrodite
私もNextを使い始めてからは存在をすっかり忘れていましたが、最近は専らaphroditeを使用しています。
- スタイリングをするだけのコンポーネントとロジックを持つコンポーネントを完全に分割できる
- ベースのスタイルを持ったコンポーネントを定義して、上書きする形で自由なスタイルを当てられる
- サーバーサイドでhead内にスタイルを先に読み込むことができるので、スタイルの読み込み遅延が発生しづらい。
この辺のパフォーマンスとか管理面のメリットについては'19年のairbnbのReact confでのプレゼンが参考になります。
https://www.youtube.com/watch?v=fHQ1WSx41CA
以下、使用例(Next.js)
import Document, {
Html,
Head,
Main,
NextScript,
DocumentContext
} from 'next/document';
import { StyleSheetServer } from 'aphrodite';
type WithStyleProps = {
ids: string[];
css: {
content: string;
renderedClassNames: string[];
};
};
export default class MyDocument extends Document<WithStyleProps> {
static async getInitialProps({ renderPage }: DocumentContext) {
const { html, css } = StyleSheetServer.renderStatic(() => renderPage() as any) as {
html: any;
css: {
content: string;
renderedClassNames: string[];
};
};
const ids = css.renderedClassNames;
return { ...html, css, ids };
}
render(): JSX.Element {
const { css, ids } = this.props;
return (
<Html>
<Head>
<style data-aphrodite dangerouslySetInnerHTML={{ __html: css.content }} />
</Head>
<body>
<Main />
<NextScript />
{ids && (
<script dangerouslySetInnerHTML={{
__html: `window.__REHYDRATE_IDS = ${JSON.stringify(ids)}`,
}}
/>
)}
</body>
</Html>
);
}
}
import { AppProps } from 'next/app';
import { StyleSheet } from 'aphrodite';
if (typeof window !== 'undefined') {
StyleSheet.rehydrate(window.__REHYDRATE_IDS);
}
const App = ({ Component, pageProps }: AppProps): JSX.Element => {
return <Component {...pageProps} />;
}
import { StyleDeclaration, css, StyleSheet } from 'aphrodite';
import { HTMLAttributes } from 'react';
export type ContainerStyles = StyleDeclaration<{
container: unknown;
}>;
type Props = HTMLAttributes<HTMLDivElement> & {
styles?: ContainerStyles;
};
const containerBaseStyles = StyleSheet.create({
container: {
background: 'transparent';
position: 'relative';
}
});
const Container: forwardRef<HTMLDivElement, Props>(({ styles, className, children, ...props }, ref) => (
<div ref={ref} {...props} className={`
${css(containerBaseStyles.container, styles && StyleSheet.create(styles).container)} ${className}
`}
>
{children}
</div>
)
);
export default Container;
このContainer
コンポーネントはただのdivを出力するコンポーネントですが、
独自のクラス名を渡すこともできるし、aphroditeのお作法に乗っ取りstyles
のpropsにオブジェクト形式でスタイルを渡すこともできます。
コレを使うことで、例えばカラーパレットやpaddingなどの値をcss変数ではなくtypescriptの変数で持たせておくこともできます。
css変数が動かないブラウザ(IEなど)での利用が想定される場合など、おすすめです。
import { FC } from 'react';
import Container from './Container';
const SomeComponent: FC = () => (
<Container styles={{
container: {
background: 'red',
':before': {
content: '""',
position: 'absolute'
}
}
}}>
<p>Some child elements.</p>
</Container>
);
追記
https://techlife.cookpad.com/entry/2021/03/15/090000
CSS in JSについてはcookpad様も採用されたらしい。
aphrodite自体は決して新しくないけど、CSS in JS自体は支持されてますね。
今の人気はemotionとかstyled-componentなのかな。
以上でした。