Skip to content

はじめに

長いタイトルですが今回は短編です。
飽くまでこんな方法がそういえばあったな、くらいでご覧ください。

最近名前聞かないけれど

aphrodite

https://github.com/Khan/aphrodite

私もNextを使い始めてからは存在をすっかり忘れていましたが、最近は専らaphroditeを使用しています。

  1. スタイリングをするだけのコンポーネントとロジックを持つコンポーネントを完全に分割できる
  2. ベースのスタイルを持ったコンポーネントを定義して、上書きする形で自由なスタイルを当てられる
  3. サーバーサイドで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なのかな。

以上でした。