styled-components

目次
  • 解説
  • 1. CSS-in-JS
  • 2. スタイルに変数を使用する
問題に挑戦!
進捗を変更する




解説

1. CSS-in-JS


「カラークイズ」アプリの仕組みは実装することができました。最後にデザインを整えましょう。現在、出題BOXにのみインラインスタイルでスタイリングをしていますが、ReactではCSS-in-JSと呼ばれるJavaScript内でCSSを記述するスタイリング方法が使用できます。CSS-in-JSを利用することで、各コンポーネントごとにスタイルの管理ができるほか、スタイルの動的な変更も簡単に行えます。

このコースではstyled-componentsという人気のCSS-in-JSライブラリを使用してスタイリングを行います。

まずは、簡単なスタイルを指定して要素を中央に表示しながら、styled-componentsの基本的な使用方法を学習しましょう。

手順1

(1)ColorQuiz.tsx」ファイル内最下部に、以下のコードを追記してください。
const StyledApp = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
`;
(2)以下の画像を参考に、「styled-components」ライブラリから「styled」をインポートしてください。

※Quick Fixが表示されない場合は以下のコードを参考にuseEffectをimportしてください。

import styled from 'styled-components';
(3)return」内のフラグメント(<></>)を以下のタグに変更してください。
<StyledApp>
  .
  .
  .
</StyledApp>

スタイルを適用して、アプリ内の要素を中央に寄せることができました。

想定結果
解説

styled-componentsでは、「styled.elementName」という構文を使用して、スタイルを適用したい要素(divやbutton等)を指定します。そして、その後に続くテンプレートリテラル(``)内にCSSを記述することでスタイルを定義することができます。

const StyledButton = styled.button`
  // ここにスタイルを記述していく
`;

ここで定義したコンポーネントを、通常のコンポーネントと同様に使用することができます。

<StyledButton>送信</StyledButton> {/* スタイルの適用されたbutton要素 */}

また、ネストしたスタイルを定義することで子要素にもスタイルを反映することができます。

return (
  <StyledDiv>
    <p>注意事項</p>
  </StyledDiv>
);

const StyledDiv = styled.div`
  margin: 0 auto;

  p {
    color: red;

    &:hover {
      opacity: 0.5;
    }
  }
`;

2. スタイルに変数を使用する


出題BOXに指定しているインラインスタイルを、作成したスタイルコンポーネントに移動しましょう。

手順2

(1)以下のコードを参考に、インラインスタイルを削除してdivタグにクラスを追加してください。
※JSXではclass属性を指定する際には「className」を使用します。
<StyledApp>
  <h1>カラークイズ</h1>
  <div className="correctBox"></div> {/* インラインスタイルを削除 */}
  <div>
(2)以下のコードを参考に、インラインスタイルで定義していたスタイルを、スタイルコンポーネントに追記してください。
const StyledApp = styled.div`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .correctBox {
    width: 150px;
    height: 150px;
    background-color: correctColor;
  }
`;

このままでは、出題BOXの背景色は表示されません。background-colorに指定している「correctColor」は「ColorQuiz」コンポーネント内で定義しているため、スタイルコンポーネント内からは参照できないためです。

スタイルコンポーネント内で変数を使用するために、プロパティを通して変数の受け渡しをしましょう。

(3)以下のコードを参考に、スタイルコンポーネントに変数を受け渡しできるようにコードを修正してください。
<StyledApp color={correctColor}>
const StyledApp: any = styled.div<{ color: string }>`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .correctBox {
    width: 150px;
    height: 150px;
    background-color: ${props => props.color};
  }
`;

コンポーネントにプロパティとして変数を渡すと、スタイルコンポーネント内部で「${props => props.プロパティ名}」のように関数を使用して、変数を参照することができます。TypeScriptを使用している場合は、プロパティの型({ color: string })を指定する必要があるので注意してください。

お疲れ様でした。下記のソース全文を参考にスタイルを調整すれば見本と同様のアプリが完成します。正解した回数をスコアとして表示したり、回答の選択肢の数をプルダウンで増減できるようにするなど、新しい機能の追加もぜひ試してみてください。

問題

実践問題


(1)変数「result」の値が「正解」の場合は、結果表示のテキストが赤になるようにスタイルを指定してください。

解答


(問1)解答を表示
<StyledApp color={correctColor} result={result}>
const StyledApp = styled.div<{ color: string; result: string }>`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  .correctBox {
    width: 150px;
    height: 150px;
    background-color: ${(props) => props.color};
  }

  h4 {
    color: ${(props) => (props.result === '正解' ? 'red' : 'black')};
  }
`;

ソース全文を表示
import { useEffect, useState } from 'react';
import styled from 'styled-components';

export const ColorQuiz = (): JSX.Element => {
  const [result, setResult] = useState<string>('');
  const [questionColors, setQuestionColors] = useState<string[]>([]);
  const [correctColor, setCorrectColor] = useState<string>('');

  useEffect(() => {
    newGame();
  }, []);

  useEffect(() => {
    if (result === '正解') {
      newGame();
    }
  }, [result]);

  const newGame = () => {
    const newQuestionColors: string[] = generateRandomColor();
    setQuestionColors(newQuestionColors);
    setCorrectColor(newQuestionColors[Math.floor(Math.random() * 3)]);
  };

  const answer = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
    const selectedColor: string = e.currentTarget.value;
    if (selectedColor === correctColor) {
      setResult('正解');
    } else {
      setResult('不正解');
    }
    setTimeout(() => {
      setResult('');
    }, 2000);
  };

  const generateRandomColor = () => {
    const colorArray: string[] = [];

    for (let i = 0; i < 3; i++) {
      const color: string = '#' + Math.floor(Math.random() * 16777215)
          .toString(16)
          .padStart(6, '0');
      colorArray.push(color);
    }
    return colorArray;
  };

  return (
    <StyledApp color={correctColor} result={result}>
      <h1>
        <span>カ</span>
        <span>ラ</span>
        <span>ー</span>
        <span>ク</span>
        <span>イ</span>
        <span>ズ</span>
      </h1>
      <div className="correctBox"></div>
      <div className="buttonGroup">
        {questionColors.map((color) => (
          <button value={color} key={color} type="button" onClick={answer}>
            {color}
          </button>
        ))}
      </div>
      <h4>{result}</h4>
    </StyledApp>
  );
};

const StyledApp = styled.div<{ color: string; result: string }>`
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;

  h1 {
    span {
      display: inline-block;
      width: 50px;
      height: 50px;
      text-align: center;
      line-height: 50px;
      margin: 0 4px;
      border-radius: 4px;
    }
    span:nth-child(1) {
      background-color: #dcedff;
    }
    span:nth-child(2) {
      background-color: #b6c649;
    }
    span:nth-child(3) {
      color: white;
      background-color: #161229;
    }
    span:nth-child(4) {
      background-color: #ffe1a0;
    }
    span:nth-child(5) {
      background-color: #e58c8a;
    }
    span:nth-child(6) {
      color: white;
      background-color: #880d1e;
    }
  }

  .correctBox {
    width: 150px;
    height: 150px;
    border-radius: 100px;
    background-color: ${(props) => props.color};
  }

  h4 {
    color: ${(props) => (props.result === '正解' ? 'red' : 'black')};
  }

  .buttonGroup {
    margin: 40px 0;

    button {
      margin: 0 20px;
      color: white;
      background-color: #999999;
      border: none;
      border-radius: 10px;
      width: 100px;
      height: 40px;
      font-weight: bold;
      cursor: pointer;

      &:hover {
        opacity: 0.5;
      }

      &:disabled {
        cursor: default;
        opacity: 0.5;
      }
    }
  }
`;