🎫

ReactでメディアのUIを作ってみる

2024/08/14に公開

メディアとは?

左側に画像、右側にテキストを配置するモジュールを一般的に「メディア」と呼びます。
テキストはタイトルと説明文で構成され、メディアクエリ適用時は、画像とテキストを縦並びにします。

趣味で作ったCSSの本の内容を少し参考して作りました。

Reactで画像を使うときは、publicというディレクトリに配置してください。

これが作ったサンプル

import React from 'react';
import styled from 'styled-components';

const MediaWrapper = styled.div`
  display: flex;
  align-items: center;

  @media (max-width: 768px) {
    display: block;
  }
`;

const ImageWrapper = styled.figure`
  flex: 0 1 27.58333%;
  margin-right: 3.33333%;

  @media (max-width: 768px) {
    margin-right: 0;
    margin-bottom: 20px;
  }
`;

const Image = styled.img`
  width: 100%;
`;

const Body = styled.div`
  flex: 1;

  & > *:last-child {
    margin-bottom: 0;
  }
`;

const Title = styled.h3`
  margin-bottom: 10px;
  font-size: 1.125rem;
  font-weight: bold;
`;

interface MediaProps {
  imageSrc: string;
  imageAlt: string;
  title: string;
  content: string;
}

const MediaComponent: React.FC<MediaProps> = ({ imageSrc, imageAlt, title, content }) => {
  return (
    <MediaWrapper>
      <ImageWrapper>
        <Image src={imageSrc} alt={imageAlt} />
      </ImageWrapper>
      <Body>
        <Title>{title}</Title>
        <p>{content}</p>
      </Body>
    </MediaWrapper>
  );
};

export default MediaComponent;

App.tsxで呼び出して使ってみる。

import MediaComponent from "./ui-component/MediaComponent";

function App() {
  return (
    <div className="App">
      <MediaComponent imageSrc={"public/assets/android.jpg"} imageAlt={"Androidユーザーが増えた背景"} title={"android"} content={`
        Androidユーザーが増えた背景には、価格の安さと性能の向上があります。Androidは、
        スマートフォンのOSの中でも比較的安価で購入できるため、多くの人が利用しています。
        また、AndroidはGoogleが提供しているため、Googleのサービスとの親和性が高いことも理由の一つです。
        日本国内でもiPhoneのシェアが高いですが、半分の価格で買えて、性能も十分なAndroidスマートフォンが
        近年多く売れています。Pixelシリーズなど、Googleが提供しているスマートフォンが人気です。
        `} />
    </div>
  );
}

export default App;

もし右・左の向きを作りたいときは、どうするのか?

styled-componentsに、Propsを使って、右か左かを判定するロジックを追加すると意図した通りのことができました。普通のHTML, CSSでやっても難しいのだろうか...

|で区切って独自のデータ型を定義することができるリテラル型とユニオン型の組み合わせが役に立ちました。

sample
/**
 * ユニオン型を使って複数の型を組み合わせる方法を学びましたが、もし同じユニオン型をプログラムの複数の場所で使い
 * 回したい場合、毎回すべての型を注釈として書き出すのは非効率です。TypeScript では、特定の型に名前を割り当て
 * て再利用する機能を提供しており、これを型エイリアスと呼びます。型エイリアスを定義することで、既存の型に簡潔な
 * 名前を与えて、変数のように何度でも参照することが可能になります。これはコードの可読性を高めるだけでなく、複雑
 * な型の管理を容易にします。
 */
type Role = number | string; // number 型と string 型のユニオン型にRoleという名前をつける
type EventType = "click" | "hover" | "keydown"; // リテラル型のユニオン型にEventTypeという名前をつける

/**
 * 型エイリアスの宣言は変数宣言に似ています。type キーワードの後にエイリアス名を記述し、等号(=)の右側に任意の
 * 型を記述します。ただし、型エイリアスの名前には制約があり、TypeScript や JavaScript の予約語は使用できません※3
。
 * これは通常の変数宣言と同様です。また、型エイリアスの名前は通常、パスカルケース(各単語の先頭文字が大文字の
 * キャメルケース)で記述されるのが慣例です。任意の型に、型エイリアスを使用して名前を付けることができます。
 * 型エイリアスは let や const で宣言された変数と同様にブロックスコープ※4

   の特性を持ち、宣言されたブロック内でのみ

 * 有効です。
 * 型エイリアスを使用して変数の型を指定する方法は、これまで見てきた型注釈を使った方法と同じです。
 */


// Role型だとnumber型とstring型のどちらかを受け付ける
function getRole(role: Role) {
  console.log(role);
}

getRole(1); // 1
getRole("admin"); // admin

// function type EventType
function clickHandler(event: EventType) {
  console.log(event);
}

clickHandler("click"); // click

function hoverHandler(event: EventType) {
  console.log(event);
}

hoverHandler("hover"); // hover

function keydownHandler(event: EventType) {
  console.log(event);
}

keydownHandler("keydown"); // keydown

union & literalでこんなことできるか....

import React from 'react';
import styled from 'styled-components';

interface WrapperProps {
  imagePosition: 'left' | 'right';
}

const MediaWrapper = styled.div<WrapperProps>`
  display: flex;
  align-items: center;
  flex-direction: ${props => props.imagePosition === 'left' ? 'row' : 'row-reverse'};

  @media (max-width: 768px) {
    display: block;
  }
`;

const ImageWrapper = styled.figure<WrapperProps>`
  flex: 0 1 27.58333%;
  margin-right: ${props => props.imagePosition === 'left' ? '3.33333%' : '0'};
  margin-left: ${props => props.imagePosition === 'right' ? '3.33333%' : '0'};

  @media (max-width: 768px) {
    margin-right: 0;
    margin-left: 0;
    margin-bottom: 20px;
  }
`;

const Image = styled.img`
  width: 100%;
`;

const Body = styled.div`
  flex: 1;

  & > *:last-child {
    margin-bottom: 0;
  }
`;

const Title = styled.h3`
  margin-bottom: 10px;
  font-size: 1.125rem;
  font-weight: bold;
`;

interface MediaProps {
  imageSrc: string;
  imageAlt: string;
  title: string;
  content: string;
  imagePosition: 'left' | 'right';
}

const MediaComponent: React.FC<MediaProps> = ({ imageSrc, imageAlt, title, content, imagePosition }) => {
  return (
    <MediaWrapper imagePosition={imagePosition}>
      <ImageWrapper imagePosition={imagePosition}>
        <Image src={imageSrc} alt={imageAlt} />
      </ImageWrapper>
      <Body>
        <Title>{title}</Title>
        <p>{content}</p>
      </Body>
    </MediaWrapper>
  );
};

export default MediaComponent;

使ってみよう。Propsを使って、プロパティに画像のパスなどの値を渡す。

import React from 'react';
import MediaComponent from './ui-component/MediaComponent';

const App: React.FC = () => {
  return (
    <div>
      <MediaComponent
        imageSrc="public/assets/android.jpg"
        imageAlt="Left aligned image"
        title="左寄せ画像のタイトル"
        content="これは左寄せ画像のコンテンツです。"
        imagePosition="left"
      />
      <MediaComponent
        imageSrc="public/assets/android.jpg"
        imageAlt="Right aligned image"
        title="右寄せ画像のタイトル"
        content="これは右寄せ画像のコンテンツです。"
        imagePosition="right"
      />
    </div>
  );
};

export default App;

おおいい感じにできた!

まとめ

まさか、styled-componentsを使うときに、union型が使えるとは知りませんでした。文法の知識は必要ですが、何か作らないと、どんなものに使えるのかわからないものですね。

Discussion