React + TypeScriptでLPを作ってみた。

# はじめに

おはようございます。こんにちは。こんばんは。
Watataku です。

今回の記事は React を使って簡単に LP を制作してみたいと思います。
自分的に React を学びたての方で、アウトプットに「何を作ろうか」と困っている方におすすめだと思っています。
ちなみに今回は TypeScript で書いていきます。

- 制作物 →[https://react-ts-lp-five.vercel.app](https://react-ts-lp-five.vercel.app)
- リポジトリ →[https://github.com/watataku8911/react-ts-lp](https://github.com/watataku8911/react-ts-lp)

# React で LP を作る

```html:LP.html
<div class="card">
  <div class="inner">
    <img src="./images/image01.jpeg" class="img" />
    <div class="description">
      <h1>コンテンツ１</h1>
      <p>
        テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト
      </p>
    </div>
  </div>
</div>
<div class="card">
  <div class="inner">
    <img src="./images/image02.jpeg" class="img" />
    <div class="description">
      <h1>コンテンツ２</h1>
      <p>
        テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト
      </p>
    </div>
  </div>
</div>
<div class="card">
  <div class="inner">
    <img src="./images/image03.jpeg" class="img" />
    <div class="description">
      <h1>コンテンツ３</h1>
      <p>
        テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト
      </p>
    </div>
  </div>
</div>
```

コンテンツが増えていくたびに上記のような`<div>`地獄になりコードが読みにくくなります。
なのでそれを React で解決させます。

## 解決法

**データ(data..json)とビュー(Card.tsx)を分け、ループさせる。**

```json:data.json
[
  {
    "title": "コンテンツ１",
    "description": "テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト",
    "imagePath": "./images/image01.jpeg"
  },
  {
    "title": "コンテンツ２",
    "description": "テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト",
    "imagePath": "./images/image02.jpeg"
  },
  {
    "title": "コンテンツ３",
    "description": "テキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキストテキスト ",
    "imagePath": "./images/image03.jpeg"
  }
]
```

```tsx:Card.tsx
import React from "react"
import styles from "./Card.module.css"


type Props = {
    title: string
    description: string
    imagePath: string
}
const Card = (props: Props) => {
    return(
            <section className={styles.contents}>
                <div className={styles.inner}>
                    <img src={props.imagePath} alt={props.title} className={styles.img} />
                    <div className={styles.description}>
                        <h1 className={styles.title}>{props.title}</h1>
                        <p className={styles.text}>{props.description}</p>
                    </div>
                </div>
            </section>
    )
}
export default Card;
```

```tsx:App.tsx
import React, {ComponentProps} from 'react';
import data from "./data.json"

const App = () => {
    return(
        <div className="card">
        {data.map((item: ComponentProps<typeof Card>, index: number) => {
          return(
            <Card title={item.title} description={item.description} imagePath={item.imagePath} key={index} />
          )
        })}
      </div>
    )
}
```

このように`data.json(データ)、Card.tsx(ビュー)`を分けることにより、
効率が良くなりまたコードの可読性も良くなります。

# スクロールアニメーションの実装

LP でよく見かけると思いますが、スクロールしていくと要素が出現していくあれです。
これを React で実装するためにライブラリを入れていきます。
[animate.css](https://animate.style/)と[react-inview-monitor](https://github.com/snipsco/react-inview-monitor)です。

```console
$ npm install animate.css react-inview-monitor
```

```tsx:Card.tsx
import React from "react"
import styles from "./Card.module.css"
import InViewMonitor from "react-inview-monitor"


type Props = {
    title: string
    description: string
    imagePath: string
}
const Card = (props: Props) => {
    return(
            <section className={styles.contents}>
                <div className={styles.inner}>
                    <InViewMonitor classNameNotInView='hidden' classNameInView='animate__animated animate__fadeInLeft slower'>
                        <img src={props.imagePath} alt={props.title} className={styles.img} />
                    </InViewMonitor>
                    <div className={styles.description}>
                        <InViewMonitor classNameNotInView='hidden' classNameInView='animate__animated animate__fadeInRight slower'>
                            <h1 className={styles.title}>{props.title}</h1>
                        </InViewMonitor>
                        <InViewMonitor classNameNotInView='hidden' classNameInView='animate__animated animate__fadeInUp slower'>
                            <p className={styles.text}>{props.description}</p>
                        </InViewMonitor>
                    </div>
                </div>
            </section>
    )
}
export default Card;
```

このように**react-inview-monitor モジュール**を読み込み、スクロールアニメーションさせたい要素に**InViewMonitor コンポーネント**で囲んでやれば簡単にスクロールアニメーションが実装できます。
ただし、今回の場合**react-inview-monitor モジュール**を読み込んだ時点でエラーが出ます。なぜでしょう？

それは今回 Typescript で書いているため、「型がない」とエラーが出ます。

## 外部ライブラリを使う時の型定義がない時の解決法

２種類解決策があります。
まずひちつめに**tsconfig.json**で**"noImplicitAny": false**としてあげ暗黙的 any を許してあげる。
でもこれをしちゃうと Typescript で書いている意味がなくなっちゃいますよね(笑)
なので、新たに**型定義ファイル(.d.ts)**を作ってやる。

```ts:reacy-inview-monitor.d.ts

declare module 'react-inview-monitor' {
  export default function InViewMonitor(
    children?: any,
    classNameNotInView?: string,
    classNameInView?: string,
    classNameAboveView?: string,
    intoViewMargin?: string,
    onInView?: Function,
    onNotInView?: Function
  ): JSX.Element
}
```

この作成したファイルを*src/@types フォルダ*を作成し、その中で保存すると**react-inview-monitor モジュール**が無事動くようになり、スクロールアニメーションが実装できます。

# フォームの実装

お問い合わせなどフォームの実装方法です。[実際のコード](https://github.com/watataku8911/react-ts-lp/blob/master/src/components/Form/Form.tsx)

```tsx
const Form = () => {
  const [firstName, setFirstName] = useState("");

  const inputFirstName = useCallback(
    (event) => {
      setFirstName(event.target.value);
    },
    [setFirstName]
  );

  return (
    <input
      type="text"
      name="firstName"
      placeholder="firstNameを入力してください。"
      value={firstName}
      onChange={inputFirstName}
    />
  );
};
```

テキストボックスに入力があるたびに**onChange(inputFirstName)**が実行されて、**setFirstName**で**state(firstName)**を更新してます。
つまり入力した内容が**firstName**になっています。

React ではテキストボックスを置いておくだけでは入力ができず、上記のように実装してあげないといけないのです。