TypeScriptとEmotion/styled-componentsの型付けパターン徹底解説!React開発の基本
生徒
「Reactでデザインを整えるときにEmotionやstyled-componentsを使っているのですが、TypeScriptでどうやって型を指定すればいいのか分かりません。」
先生
「TypeScriptを使うと、デザインの部品に渡すデータにも型という名前のルールを決めることができます。これによって、間違ったデータを入れて画面が崩れるのを防げるんですよ。」
生徒
「型付けをすることで、安全に見た目を変えられるようになるんですね!具体的な書き方を教えてください!」
先生
「初心者の方でも使いやすいパターンがいくつかあります。まずは基本的な概念から順番に学んでいきましょう!」
1. TypeScriptとCSS-in-JSの基礎知識
現代のフロントエンド開発、特にReactやNext.jsを使った開発では、CSSをJavaScriptの中に直接書くCSS-in-JSという手法がよく使われます。その代表格がEmotionやstyled-componentsです。これらを使うと、コンポーネントという部品単位でスタイルを管理できるため、デザインの修正が非常に楽になります。しかし、初心者の方が最初につまずくのがTypeScriptとの組み合わせです。
TypeScriptは、プログラムの中で扱うデータが数値なのか、文字列なのか、あるいは特定のルールを持ったオブジェクトなのかを厳密にチェックする言語です。このチェック機能をCSSのスタイルにも適用することで、たとえばボタンの色をプログラムの状況に応じて変えるといった処理を、ミスなく安全に実装できるようになります。まずは、この型という概念が、デザインの設計図のような役割を果たすことを理解しておきましょう。
2. プロップスを介したスタイルの動的変更
スタイリングにおいて最も頻繁に使うのが、プロップス(Props)と呼ばれる仕組みです。これは、親となる部品から子となる部品へ渡す引数のようなものです。たとえば、一つのボタンコンポーネントを作っておき、渡すデータによってメインの色にしたり、注意を促す赤色にしたりといった使い分けをします。このとき、どのようなデータを受け取るかをあらかじめ定義しておくのが型付けの役割です。
まずは、一番シンプルなボタンの例を見てみましょう。ここでは、背景色を外部から受け取れるように設定します。プログラミングが初めての方でも、型を定義する部分はデータのルールを決めている場所だと考えれば難しくありません。以下のコードでは、ButtonPropsという名前でルールを作成しています。
import styled from '@emotion/styled';
// デザインのルール(型)を定義します
interface ButtonProps {
primary?: boolean;
backgroundColor: string;
}
// ルールを適用したスタイル付きコンポーネントを作成します
const StyledButton = styled.button<ButtonProps>`
background-color: ${(props) => (props.primary ? 'blue' : props.backgroundColor)};
color: white;
padding: 10px 20px;
border: none;
border-radius: 4px;
`;
上記のコードでは、backgroundColorという名前のデータは必ず文字列(string)でなければならないという約束をしています。もし数値を渡そうとすると、TypeScriptがエラーを出して教えてくれます。これが、安全な開発を支える型付けのメリットです。
3. インターフェースと型の定義方法
TypeScriptでは、データの形を定義するためにinterface(インターフェース)やtype(タイプ)というキーワードを使います。これらは、言わば注文票のようなものです。レストランで注文するときに、どんな料理を出すか決まっているように、プログラムでもどのようなデータが流れてくるかを決めておきます。初心者の方は、まずはinterfaceを使って書く練習をすることをお勧めします。
Emotionやstyled-componentsでよく使われるのは、オプション項目(あってもなくても良い項目)の定義です。変数の名前の後にクエスチョンマークをつけることで、そのデータが空っぽでもエラーにならないように設定できます。これにより、柔軟なデザイン変更が可能になります。次の例では、文字の大きさを変えるパターンを見ていきましょう。
import styled from 'styled-components';
// テキストの見た目を決めるための型定義
type TextProps = {
fontSize: number;
isBold?: boolean;
};
// 型情報をジェネリクス(カッコ記号)で渡します
const CustomText = styled.p<TextProps>`
font-size: ${(props) => props.fontSize}px;
font-weight: ${(props) => (props.isBold ? 'bold' : 'normal')};
color: #333;
`;
このコードでは、fontSizeには数値を、isBoldには真偽値(はい、いいえ)を入れるように設定されています。このように型を明示することで、開発中にどの項目を指定すれば良いかが一目でわかるようになります。
4. テーマ機能との連携と型定義
大規模なアプリを開発するときは、色やフォントのサイズをあらかじめテーマとしてまとめて管理することが一般的です。これにより、サイト全体のカラーを一度に変更できるようになります。このテーマ機能を使うときも、TypeScriptは大活躍します。テーマの中にどのような色が含まれているのかを型として定義しておくことで、打ち間違いによるスタイルの反映ミスをゼロにできるからです。
テーマの型定義は少し高度に見えるかもしれませんが、基本的にはオブジェクトという入れ物の中に何が入っているかを記述するだけです。これを一度設定しておけば、開発効率は劇的に向上します。エディタがテーマの中身を自動で提案してくれるようになるため、色の名前をわざわざ覚える必要がなくなるのです。
import { Theme } from '@emotion/react';
// アプリ全体のテーマの型を定義する例
declare module '@emotion/react' {
export interface Theme {
colors: {
primary: string;
secondary: string;
error: string;
};
spacing: {
small: string;
medium: string;
large: string;
};
}
}
このように定義を行うことで、props.theme.colors.primaryのようにアクセスした際、自動的に補完が効くようになります。パソコンを触り始めたばかりの方でも、スペルミスを防げるこの機能は非常に強力な味方になります。
5. コンポーネントを再利用するためのパターン
プログラミングにおいて大切な考え方の一つに再利用があります。一度作ったボタンやフォームのデザインを、他の場所でも使い回すことです。Emotionやstyled-componentsでは、既存のコンポーネントを拡張して新しいコンポーネントを作ることができます。このとき、元のコンポーネントが持っている型情報を引き継ぐことが重要です。
たとえば、HTML標準のボタンが持っているクリックイベントなどの機能を維持したまま、見た目だけをカスタマイズしたい場合があります。その場合は、Reactが用意している型を継承することで、標準的な機能をすべて使いつつ、独自のスタイルを追加することが可能になります。これにより、プログラミング初心者でも既存の部品を壊すことなく、安全に新しい部品を作り出すことができます。
import React from 'react';
import styled from '@emotion/styled';
// HTML標準のボタンが持つすべての型を受け継ぐ設定
interface IconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
iconName: string;
}
const StyledIconButton = styled.button<IconButtonProps>`
display: flex;
align-items: center;
gap: 8px;
background: transparent;
border: 1px solid #ccc;
cursor: pointer;
`;
// コンポーネント本体の定義
export const IconButton: React.FC<IconButtonProps> = ({ iconName, children, ...props }) => (
<StyledIconButton {...props}>
<i className={`bi bi-${iconName}`} />
{children}
</StyledIconButton>
);
この例では、extendsというキーワードを使って、元のボタンの型を継承しています。これによって、onClickなどの標準的な命令もそのまま使えるようになります。初心者の方は、このように既存の機能を借りてくる方法を覚えると、作れるものの幅が大きく広がります。
6. 条件分岐によるスタイルの切り替え方法
最後に、より実践的なスタイルの切り替え方法について解説します。Webサイトを見ていると、エラーのときに枠線が赤くなったり、成功したときに背景が緑になったりすることがあります。これらはすべてプログラムの中での条件分岐によって制御されています。TypeScriptを使えば、この条件分岐も論理的に組み立てることができます。
文字列の型を特定の単語だけに限定するリテラル型という機能を使うと、たとえば状態をsuccess、error、warningの三種類だけに制限するといったことが可能です。これにより、想定外の状態が入り込む余地をなくし、バグの少ない堅牢なアプリケーションを構築できるようになります。視覚的な変化をロジックで制御する感覚を掴むことが、プログラミング習得の近道です。
import styled from '@emotion/styled';
// 特定の文字列しか受け付けないように制限する型
type Status = 'success' | 'error' | 'warning';
interface StatusBoxProps {
status: Status;
}
const StatusBox = styled.div<StatusBoxProps>`
padding: 15px;
border-radius: 8px;
border: 2px solid;
// 状態に応じて色を決定する処理
${({ status }) => {
switch (status) {
case 'success':
return 'color: green; border-color: green; background-color: #e6ffed;';
case 'error':
return 'color: red; border-color: red; background-color: #fff1f0;';
case 'warning':
return 'color: orange; border-color: orange; background-color: #fffbe6;';
}
}}
`;
このコードでは、statusという項目にsuccess以外のデタラメな文字を入れようとすると、パソコンが即座に間違いを指摘してくれます。プログラムが正しく動くことを保証しながら、美しいデザインを作り上げていく過程は、パズルを解くような楽しさがあります。基本を大切に、少しずつ型付けのパターンを増やしていきましょう。