31
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【Next.js+TailwindCSS】TypeScriptを使ってコンポーネントを作ってみよう【fwywd一次採用試験】

Last updated at Posted at 2021-08-11

TypeScriptを試してみたいけど実際どう書くのかイマイチよくわからない人に捧ぐ。
fwywd一次採用試験内容の一部をお借りして、実際に実装してみる。

筆者もTypeScript初心者だから、もっと良い書き方があるかもしれない。
アドバイスがあればぜひよろしくね。

こんな人を対象にしているよ

  • fwywd一次採用試験にTypeScriptを使って挑戦したい
  • ReactやNext.jsでTypeScriptを使用したい
  • Next.js+TypeScriptでコンポーネントを作りたい

けど、書き方がよくわからない人。

想定している前提知識

以下の知識があれば「なんじゃこりゃ」とならず読めるよう想定して執筆している。

  • Reactで親から子へ値を渡す方法(Props)
  • Next.jsの基本知識
  • Tailwind CSSの基本知識

この記事で触れない部分

長くなるのでここでは割愛する。

  • TypeScriptは何か
  • Next.js + TypeScript + Tailwind CSSのプロジェクト作成方法

参考リンク集:
【入門】TypeScriptとは?言語の特徴やJavaScriptとの違いを解説
Next.js (JavaScript/TypeScript) に Tailwind CSS を導入する方法

バージョン情報

next: 11.0.1
typescript:4.3.5
react: 17.0.2
react-dom: 17.0.2

完成物

コンポーネント「ProgressBar.tsx」を作って3つ並べてみる。
完成イメージ.png
拡大Ver
イメージ拡大Ver.png

ファイル構造

使いやすいようにディレクトリ構造をデフォルトから下記へ変更している。
ファイル構造.jpg
ディレクトリ構造の変更方法を知りたかったらこの記事が非常に参考になる。

またディレクトリ構造の変更に伴いbaseUrlとpathsをtsconfig.jsonに登録してある。

tsconfig.json
{
  "compilerOptions": {
    "baseUrl": "src",
    "paths": {
      "@/components/*": ["components/*"]
    }
  }
}

全コード

src/pages/skills.tsx
skills.tsx
import { ProgressBar, Props } from '@/components/Shared/ProgressBar';
const skill1: Props = {
  name: 'Next.js',
  percentWidth: 'w-3/5',
  percent: 60,
};
const skill2: Props = {
  name: 'TypeScript',
  percentWidth: 'w-4/5',
  percent: 80,
};
const skill3: Props = {
  name: 'Tailwind CSS',
  percentWidth: 'w-4/5',
  percent: 80,
};

const skills: Props[] = [skill1, skill2, skill3];
export default function Skills(): JSX.Element {
  return (
    <section
      id='skills'
      className='flex justify-center items-center py-7 w-screen h-screen bg-green-100'
    >
      <div className='p-4 bg-white rounded-md'>
        {skills.map((skill, index) => {
          return (
            <div className='mb-2' key={index}>
              <ProgressBar
                name={skill.name}
                percentWidth={skill.percentWidth}
                percent={skill.percent}
              />
            </div>
          );
        })}
      </div>
    </section>
  );
}

src/components/ProgressBar.tsx
ProgressBar.tsx
export interface Props {
  name: string;
  percentWidth: string;
  percent: number;
}
export const ProgressBar = ({ name, percentWidth, percent }: Props): JSX.Element => {
  return (
    <div className='relative'>
      <div className='flex items-center'>
        <div>
          <span className='inline-block text-sm font-semibold tracking-wider text-text-green uppercase'>
            {name}
          </span>
        </div>
      </div>
      <div className='grid grid-cols-10 text-left'>
        <div className='flex overflow-hidden col-span-8 mt-2 h-3 text-xs bg-bar-lightgreen rounded'>
          <div
            className={`${percentWidth} flex flex-col justify-center text-center text-white whitespace-nowrap bg-bar-green shadow-none`}
          ></div>
        </div>
        <div className='col-span-2 ml-3'>
          <span className='text-sm font-semibold text-text-green lg:text-base'>{percent}%</span>
        </div>
      </div>
    </div>
  );
};

手順1. ProgressBar.tsxの作成

まずコンポーネントを作る。
test/src/componentsに新しいファイル「ProgressBar.tsx」を作成する。

ProgressBar.tsx
export interface Props {
  name: string;
  percentWidth: string;
  percent: number;
}
export const ProgressBar = ({ name, percentWidth, percent }: Props): JSX.Element => {
  return (
    <div className='relative'>
      <div className='flex items-center'>
        <div>
          <span className='inline-block text-sm font-semibold tracking-wider text-text-green uppercase'>
            {name}
          </span>
        </div>
      </div>
      <div className='grid grid-cols-10 text-left'>
        <div className='flex overflow-hidden col-span-8 mt-2 h-3 text-xs bg-bar-lightgreen rounded'>
          <div
            className={`${percentWidth} flex flex-col justify-center text-center text-white whitespace-nowrap bg-bar-green shadow-none`}
          ></div>
        </div>
        <div className='col-span-2 ml-3'>
          <span className='text-sm font-semibold text-text-green lg:text-base'>{percent}%</span>
        </div>
      </div>
    </div>
  );
};

interfaceの作成

ProgressBar.tsx
export interface Props {
  name: string;
  percentWidth: string;
  percent: number;
}

ページコンポーネントのskills.tsxでも使うので「export」を忘れずに。

ProgressBar.tsx
export const ProgressBar = ({ name, percentWidth, percent }: Props): JSX.Element => {
  return (
...

「name, percentWidth, percent」をPropsとして受け取る。型はinterface Propsを参照しているため、違うプロパティ名を書くと怒られる。
error1.png
上の図では「namaeeeなんてプロパティはinterface Propsの中にないよ!」って怒られている。

表示部分の実装

受け取ったPropsはそれぞれ以下のように反映させている。

ProgressBar.tsx
export const ProgressBar = ({ name, percentWidth, percent }: Props): JSX.Element => {
  return (
    <div className='relative'>
      <div className='flex items-center'>
        <div>
          <span className='inline-block text-sm font-semibold tracking-wider text-text-green uppercase'>
            {name} 
          </span>
        </div>
      </div>
      <div className='grid grid-cols-10 text-left'>
        <div className='flex overflow-hidden col-span-8 mt-2 h-3 text-xs bg-bar-lightgreen rounded'>
          <div
            className={`${percentWidth} flex flex-col justify-center text-center text-white whitespace-nowrap bg-bar-green shadow-none`} 
          ></div>
        </div>
        <div className='col-span-2 ml-3'>
          <span className='text-sm font-semibold text-text-green lg:text-base'>{percent}%</span>
        </div>
      </div>
    </div>
  );
};

受け取ったPropsをclassNameに反映させる時の書き方が少々特殊なので注意。間違えると反映されない。

ProgressBar.tsx
 /*わかりやすいようにCSS記述を一部省略*/
<div className={`${percentWidth} flex flex-col`}></div>

手順2. skills.tsxの作成

test/src/pagesに新しいファイル「skills.tsx」を作成する。

skills.tsx
import { ProgressBar, Props } from '@/components/Shared/ProgressBar';
const skill1: Props = {
  name: 'Next.js',
  percentWidth: 'w-3/5',
  percent: 60,
};
const skill2: Props = {
  name: 'TypeScript',
  percentWidth: 'w-4/5',
  percent: 80,
};
const skill3: Props = {
  name: 'Tailwind CSS',
  percentWidth: 'w-4/5',
  percent: 80,
};

const skills: Props[] = [skill1, skill2, skill3];
export default function Skills(): JSX.Element {
  return (
    <section
      id='skills'
      className='flex justify-center items-center py-7 w-screen h-screen bg-green-100'
    >
      <div className='p-4 bg-white rounded-md'>
        {skills.map((skill, index) => {
          return (
            <div className='mb-2' key={index}>
              <ProgressBar
                name={skill.name}
                percentWidth={skill.percentWidth}
                percent={skill.percent}
              />
            </div>
          );
        })}
      </div>
    </section>
  );
}

上から順に解説していく。

ProgressBar.tsxのインポート

まず手順1で作ったプログレスバーを使えるようインポートする。

skills.tsx
import { ProgressBar, Props } from '@/components/Shared/ProgressBar';

interface Propsもインポートしているところがポイント。再利用することでコードの記述量を減らせる。

定数の作成

プログレスバーの中身を書いていく。3つバーを作って並べる予定だから3つ作る。

skills.tsx
import { ProgressBar, Props } from '@/components/Shared/ProgressBar';
const skill1: Props = {
  name: 'Next.js',
  percentWidth: 'w-3/5',
  percent: 60,
};
const skill2: Props = {
  name: 'TypeScript',
  percentWidth: 'w-4/5',
  percent: 80,
};
const skill3: Props = {
  name: 'Tailwind CSS',
  percentWidth: 'w-4/5',
  percent: 80,
};

percentWidthの「w-3/5 (w-4/5)」はTailwind CSS由来のクラス名。こうやって書くだけでwidthを60%(と80%)にしてくれるスグレモノ。

エラー例その1

ProgressBar.tsxからインポートしてきたinterface Propsを型定義として使っているから、
違う型で書くと怒られる。
error2.png
「percentはnumberって定義したじゃん!stringで書かないで!」と怒られている図。
const skill2のpercentは数字で書いてるから怒られていない。

エラー例その2

interface Propsの中に存在しないプロパティを書いても怒られる。
error3.png
「interface Propsの中にそんなプロパティは存在しないでしょ!」と怒られている。

インターフェースProps配列の作成

プログレスバー3つをmap関数でかっこよく表示するため配列を作る。

skills.tsx
const skills: Props[] = [skill1, skill2, skill3];

string[]やnumber[]みたいにinterface配列Props[]も作れちゃうのが嬉しいところ。

表示部分の実装

ProgressbarにPropsを渡して表示。

skills.tsx
export default function Skills(): JSX.Element {
  return (
    <section
      id='skills'
      className='flex justify-center items-center py-7 w-screen h-screen bg-green-100'
    >
      <div className='p-4 bg-white rounded-md'>
        {skills.map((skill, index) => {
          return (
            <div className='mb-2' key={index}>
              <ProgressBar
                name={skill.name}
                percentWidth={skill.percentWidth}
                percent={skill.percent}
              />
            </div>
          );
        })}
      </div>
    </section>
  );
}

コードの記述量が減ってカッコよくなるのでmap関数を使用している。

map関数を使うときはkeyを記述しないと動かないので注意。
ここでは配列のインデックスをkeyとして使用している。
ユニークな(他と被らない)値であればなんでもいいのでkey={skill.name}とかにしてもOK。

skills.tsx
        {skills.map((skill, index) => {
          return (
            <div className='mb-2' key={index}>
              <ProgressBar
                name={skill.name}
                percentWidth={skill.percentWidth}
                percent={skill.percent}
              />
            </div>
          );
        })}

これでおしまい。

おわりに

ここまで読んでくれてありがとう。
ちょっとでも参考になったら「いいね!」を押してオラにモチベーションを与えてくれると嬉しいな。
他の記事も良かったら読んでみてね。

【Git Flow】チーム開発のお作法~GitHubとブランチ管理編【採用試験対策】
【Next.js+TypeScript】Tailwind CSSの環境構築Q&A
【Next.js+TypeScript】fwywdの一次採用試験攻略ガイド~試験の流れ編【採用試験対策】

31
20
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
31
20

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?