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

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

https://fwywd.com/news/recruitment-1st

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

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

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

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

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

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

# この記事で触れない部分
長くなるのでここでは割愛する。

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

参考リンク集:
[【入門】TypeScriptとは？言語の特徴やJavaScriptとの違いを解説](https://www.pasonatech.co.jp/workstyle/column/detail.html?p=2761)
[Next.js (JavaScript/TypeScript) に Tailwind CSS を導入する方法](https://fwywd.com/tech/next-tailwind)

# バージョン情報
>next: 11.0.1
typescript：4.3.5
react: 17.0.2
react-dom: 17.0.2

# 完成物
コンポーネント「ProgressBar.tsx」を作って３つ並べてみる。
![完成イメージ.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/ce12e35c-a7e5-4cdb-8bc2-65cbd892f34f.png)
拡大Ver
![イメージ拡大Ver.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/5e869e73-f6c3-91bd-f723-a3fe59bea57b.png)

# ファイル構造
使いやすいようにディレクトリ構造をデフォルトから下記へ変更している。
![ファイル構造.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/0fd641ea-401e-3f10-d1f3-c3cea30cb890.jpeg)
ディレクトリ構造の変更方法を知りたかったらこの記事が非常に参考になる。

https://fwywd.com/tech/next-base-url

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

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

# 全コード

<details><summary>src/pages/skills.tsx</summary><div>

```ruby: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>
  );
}

```

</div></details>

<details><summary>src/components/ProgressBar.tsx</summary><div>

```ruby: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>
  );
};
```

</div></details>

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


```ruby: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の作成

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

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

```ruby:ProgressBar.tsx
export const ProgressBar = ({ name, percentWidth, percent }: Props): JSX.Element => {
  return (
...
```
「name, percentWidth, percent」をPropsとして受け取る。型はinterface Propsを参照しているため、違うプロパティ名を書くと怒られる。
![error1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/6993d15f-05c5-2a38-0754-d9974170320d.png)
上の図では「namaeeeなんてプロパティはinterface Propsの中にないよ！」って怒られている。

## 表示部分の実装
受け取ったPropsはそれぞれ以下のように反映させている。

```ruby: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に反映させる時の書き方が少々特殊なので注意。間違えると反映されない。

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

# 手順２． skills.tsxの作成
test/src/pagesに新しいファイル「skills.tsx」を作成する。

```ruby: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のインポート
まず手順１で作ったプログレスバーを使えるようインポートする。

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

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

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

```ruby: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％)にしてくれるスグレモノ。

### エラー例その１
ProgressBar.tsxからインポートしてきたinterface Propsを型定義として使っているから、
違う型で書くと怒られる。
![error2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/304e6048-d184-c3f2-0297-6a71a7cc3642.png)
「percentはnumberって定義したじゃん！stringで書かないで！」と怒られている図。
const skill2のpercentは数字で書いてるから怒られていない。

### エラー例その２
interface Propsの中に存在しないプロパティを書いても怒られる。
![error3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1845828/e19c3bf4-47f9-f4f4-6714-b7e9daeab7f6.png)
「interface Propsの中にそんなプロパティは存在しないでしょ！」と怒られている。

## インターフェースProps配列の作成
プログレスバー３つをmap関数でかっこよく表示するため配列を作る。

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

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

## 表示部分の実装
ProgressbarにPropsを渡して表示。

```ruby: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。

```ruby: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とブランチ管理編【採用試験対策】](https://qiita.com/emjo1804/items/9daea4cd42980f179032)
[ 【Next.js＋TypeScript】Tailwind CSSの環境構築Q&A](https://qiita.com/emjo1804/items/503fd4d029168631a6b5)
[【Next.js＋TypeScript】fwywdの一次採用試験攻略ガイド～試験の流れ編【採用試験対策】](https://qiita.com/emjo1804/items/62dabbc059a94778fd25)