🦧

Reactで最初に作るTypographyのComponentの作り方をちょっと丁寧に説明してみる。

2021/08/11に公開
5

はじめに

日々答えがないものをGitHubをあさりながらやっている系のエンジニアです。好きなものはひぐらしとTypeScriptです。
アカウントは、 https://zenn.dev/ojin です。
何かおかしい所などありましたら遠慮なくコメントしていただければ幸いです。

環境

  • react, react-dom v17.0.2
  • next v11.0.1
  • typescript v4.3.5
  • sass v1.35.2

この記事では、Componentを作る時に自分が意識していることを書いておこうと思います。

この記事では、Componentを自分が作る時に気をつけていることやその手順を書きます。参考になればと思います。今回は、外部のライブラリを使わず環境に書いたものだけでComponentを作成します。

例えば、TypographyというComponentを作ってみます

  • 基本的にはテキストを表示したい時に使うComponentであり、特別なStateなどを持たない、いわゆるPresentational Componentと分類することになります。

まずは、最初にDirectoryをどう設計するか考えます

  • 今回は、これ以上分割しないComponentということでatomsに突っ込みます。
    • /src/components/lib/atoms/Typography/

次にscssを定義します

  • 基本的に、ここではComponent自体のスタイルとPropsとして受け取った値によるComponentの見た目のスタイルを定義します。
    • このタイミングでどういうパターンの見た目が存在するかは設計しておく必要があります。
    • Typographyだと以下を考えています。他にもありますが、まずは簡単に3つほど。
      • 文字のサイズ
      • 太字
      • 文字の色
  • 重要なのはよく言われることですがComponentにmarginを持たせないことです。
  • 使いやすさを重視し、見た目を切り替えたときに考えうるようなスタイルを定義しておきます。
// variables.scss

// color
$default-color: #2b2b2b;
$primary-color: #000082;
$secondary-color: #d04221;

// font
$default-font-size: 16px;
$h3-font-size: 24px;
$h2-font-size: 28px;
$h1-font-size: 32px;
// style.module.scss
@import '@/styles/variables';

.Typography {
  color: $default-color;
  font-size: $default-font-size;
  white-space: pre-line;
}

.h1 {
  font-size: $h1-font-size;
}

.h2 {
  font-size: $h2-font-size;
}

.h3 {
  font-size: $h3-font-size;
}

.bold {
  font-weight: bold;
}

.primary {
  color: $primary-color;
}

.secondary {
  color: $secondary-color;
}

次に実際のComponentをTSXにより作成します

  • 先程scssで定義した見た目をPropsに定義してあげます。
    • bold: true | false
    • size: 'h1' | 'h2' | 'h3'
    • color: 'primary' | 'secondary'
  • 次にComponentにProps値に応じたスタイルの付与を行います。
  • 最後にclassNameもComponentに与えられるようにします。
    • Componentの配置やmarginなどを親から指定できるようにします。
      • chakra-uiなどのようにmtやmb, mr, mlのようなmarginを直接指定するようなInterfaceもありではあります。
// index.tsx

export type TypographyProps = {
  text: string;
  bold?: boolean;
  size?: SizePattern;
  color?: ColorPattern;
  className?: string;
}

const Typography = ({ text, bold, size, color, className }: TypographyProps): ReactElement => {
  return (
    <div
      className={`${styles.Typography} ${
        size === 'h1' ? styles.h1 : size === 'h2' ? styles.h2 : size === 'h3' ? styles.h3 : ''
      } ${bold ? styles.bold : ''} ${
        color === 'primary' ? styles.primary : color === 'secondary' ? styles.secondary : ''
      } ${className ? className : ''}`}
    >
      {text}
    </div>
  );
};

export default Typography;

利用するとき

  • 必須のtext以外にサイズやカラー、太字などを操作することができます。
  • おそらく使っていて他にこのPropsを指定したい、などの要望が出てくると思います。
    • この出てきた時にまた考えたいのがPropsに追加するのか、別のComponentとするのか、です。→これに関してはまた別の記事で書きたいと思います。

普通に使う

<Typography text="ojin" size="h3" color="primary" bold />

marginなどを入れたいとき

.header {
  margin: 15px;
}
<Typography text="ojin" size="h1" bold className={styles.header} />

以上です。拙い記事ですが、よろしくおねがいします。

Discussion

プログラミングをするパンダプログラミングをするパンダ

私はTailwind CSSを使って同じことをしてるのですが、CSS Modulesでもやはりクラス名は似た感じになるのだなと参考になりました👍

ofa-chanofa-chan

なるほど、そうなんですね!Tailwind CSSは現場ではあまり使っておらず、似た感じになるのは知らなかったです。

nap5nap5

ワークアウトアラウンドの一つとしてこのようにしてもいいかもとは思いました。

定義側

import { FC } from "react";

import clsx from "clsx";
import { twMerge } from "tailwind-merge";
import { ClassNameValue } from "tailwind-merge/dist/lib/tw-join";
import { match } from "ts-pattern";

import type { Simplify } from "type-fest";

type Props = Simplify<{
  component: React.ElementType;
  variant: "h1" | "h2" | "h3" | "h4" | "p" | "legend" | "caption";
  children: React.ReactNode;
  className: ClassNameValue;
}>;

const Typography: FC<Props> = ({
  component,
  variant,
  className,
  children,
  ...props
}) => {
  const Component = component;

  // https://tailwindcss.com/docs/font-size
  const fontStyle = match(variant)
    .with("h1", () => clsx(`text-3xl`))
    .with("h2", () => clsx(`text-2xl`))
    .with("h3", () => clsx(`text-xl`))
    .with("h4", () => clsx(`text-lg`))
    .with("p", () => clsx(`text-base`))
    .with("legend", () => clsx(`text-sm`))
    .with("caption", () => clsx(`text-xs`))
    .exhaustive();

  return (
    <Component className={twMerge(fontStyle, className)} {...props}>
      {children}
    </Component>
  );
};

export default Typography;

使用側

<>
  <Typography component={"h1"} variant="h1" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"h2"} variant="h2" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"h3"} variant="h3" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"h4"} variant="h4" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"p"} variant="p" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"p"} variant="legend" className="font-bold">
    Demo Form
  </Typography>
  <Typography component={"p"} variant="caption" className="font-bold">
    Demo Form
  </Typography>
  <Typography
    component={"p"}
    variant="caption"
    className="font-bold text-red-500"
  >
    Demo Form
  </Typography>
</>

デモコードです。

https://codesandbox.io/p/sandbox/delicate-https-tyiii5?file=%2Fsrc%2Ffeatures%2Finput%2Fcomponents%2FTypography%2FTypography.tsx

簡単ですが、以上です。

ofa-chanofa-chan

Tailwind CSSのサンプルありがとうございます!大変参考になりました!