JavaScriptをTypeScriptに置き換えてみた！

# はじめに
こんにちは！WEBエンジニア転職を目指しているK.Yです！
以下のJavaScriptコードをTypeScriptに置き換えてみました！

React/JavaScriptで実装した記事もあります。

https://qiita.com/ken887265/items/a6dfc1f62ee185b986b2

https://qiita.com/ken887265/items/8d6255a5aba95897ca67

https://qiita.com/ken887265/items/f8850ec0fd5757c2bd39

https://qiita.com/ken887265/items/6104486dd665a462ea1c

:::note warn
重複している部分は省略しています！
:::
## コード
```typescript
index.tsx

import React from 'react';
import { createRoot } from 'react-dom/client';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';

const container = document.getElementById('root');
const root = createRoot(container!); // TypeScriptではnullチェックが必要です

root.render(
  <React.StrictMode>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </React.StrictMode>
);
```

```typescript
App.tsx

import './App.css';
import { Routes, Route } from 'react-router-dom';
import PostsList from './PostsList';
import DetailsPage from './DetailsPage';
import InquiryPage from './InquiryPage';

const App: React.FC = () => {
  return (
    <Routes>
      <Route path="/" element={<PostsList />} />
      <Route path="/post/:id" element={<DetailsPage  />} />
      <Route path="/inquiry" element={<InquiryPage />} />
    </Routes>
  );
};

export default App;
```

```typescript
PostsList.tsx

import React from 'react';
import { useEffect, useState } from 'react'
import './App.css';
import { Link } from 'react-router-dom';

type ArticleType = {
id: number;
createdAt: string;
categories: string[];
title: string;
content: string;
}

type PostsType = {
  posts: ArticleType[];
}

const PostsList: React.FC = () => {

  const formatDate = (dateString: string) => {
    const date = new Date(dateString);
    const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'numeric', day: 'numeric' };

    return date.toLocaleDateString('ja-JP', options);
  };

  const [posts, setPosts] = useState<PostsType>({ posts: [] })
  const [loading, setLoading] = useState<boolean>(true); // ローディング状態を追加

  useEffect(() => {
    const fetchData = async () => {
      try {

        const response = await fetch("https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/posts");
        const data = await response.json() as PostsType

        setPosts(data);

      }  finally {
        setLoading(false); // データ取得が完了したらローディングを終了
      }
    };

    fetchData();
  }, []);

  if (loading) {
    return <div>Loading...</div>; // ローディング中の表示
  }

      
  return (

    <div className="App">
      <header className="header-App">
        <Link className="link" to="/">Blog</Link>
        <Link className="link" to="/inquiry">お問い合わせ</Link>
      </header>

      {
        Array.isArray(posts.posts) && posts.posts.map(article => (
          <div key={article.id} className="posts-info">
            <ul className="post-list">
              <li className="post-item">
                <Link to={`/post/${article.id}`}>
                  <div className="date">{formatDate(article.createdAt)}</div>
                  <div className="programming-language">{article.categories.map((category, idx) => (
                    <span key={idx} className="category-box">{category}</span>
                  ))}</div>
                  <div className="title">{article.title}</div>
                  <div className="content" dangerouslySetInnerHTML={{ __html: article.content }}>
                  </div>
                </Link>
              </li>
            </ul>
          </div>
        ))}

    </div>
  );
    }

export default PostsList;
```

```typescript
DetailsPage.tsx

import React from 'react';
import { useEffect, useState } from "react";
import { Link, useParams } from "react-router-dom";
import "./App.css";

type detailsType = {
  id: number;
  createdAt: string;
    thumbnailUrl: any;
  categories: string[];
  title: string;
  content: string;
};

type ApiResponse = {
  post: detailsType;
}

const DetailsPage: React.FC = () => {
  const { id } = useParams();

  const formatDate = (dateString: string): string => {
    const date = new Date(dateString);
    const options: Intl.DateTimeFormatOptions = {
      year: "numeric",
      month: "numeric",
      day: "numeric",
    };
    return date.toLocaleDateString("ja-JP", options);
  };

  const [detailsData, setDetailsData] = useState<detailsType | null>(null);
  const [loading, setLoading] = useState<boolean>(true); // ローディング状態を追加

  useEffect(() => {
    const fetchData = async () => {
      try {

        const response = await fetch(
          `https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/posts/${id}`
        );
        const result = await response.json() as ApiResponse;

        setDetailsData(result.post);
      } finally {
        setLoading(false); // データ取得が完了したらローディングを終了
      }
    };

    fetchData();
  }, [id]);

  if (loading) {
    return <div>Loading...</div>; // ローディング中の表示
  }

  if (!detailsData) return <div>投稿が見つかりません</div>;

  return (
    <div className="App">
      <header className="header-App">
        <Link to="/" className="link">
          Blog
        </Link>
        <Link to="/inquiry" className="link">
          お問い合わせ
        </Link>
      </header>

      <div style={{ border: "none" }} className="posts-info">
        <ul className="post-list">
          <li key={detailsData.id} className="post-item">
            <div className="img">
              <img src={detailsData.thumbnailUrl} alt="img" />
            </div>
            <div className="date">{formatDate(detailsData.createdAt)}</div>
            <div className="programming-language">
              {detailsData.categories.map((category, idx) => (
                <span key={idx} className="category-box">
                  {category}
                </span>
              ))}
            </div>
            <div className="title">{detailsData.title}</div>
            <div
              style={{ display: "block" }}
              className="content"
              dangerouslySetInnerHTML={{ __html: detailsData.content }}
            ></div>
          </li>
        </ul>
      </div>
    </div>
  );
};

export default DetailsPage;
```

```typescript
InquiryPage.tsx

import React from 'react';
import { Link } from "react-router-dom";
import { FormEvent, useState } from "react";
import "./App.css";

type InquiryType = {
  name: string ;
  email: string;
  message: string;
};

type ErrorsType = {
  name?: string;
  email?: string;
  message?: string;
};

const InquiryPage: React.FC = () => {
  const [inquiryData, setInquiryData] = useState<InquiryType>({
    name: "",
    email: "",
    message: "",
  });
  const [errors, setErrors] = useState<ErrorsType>({});
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);

  const handleChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => {
    const { id, value } = e.target;
    setInquiryData((prevData) => ({ ...prevData, [id]: value }));
  };

  const validate = () => {
    const tempErrors: ErrorsType = {};
    if (!inquiryData.name) tempErrors.name = "お名前は必須です。";
    if (!inquiryData.email) tempErrors.email = "メールアドレスは必須です。";
    if (!inquiryData.message) tempErrors.message = "本文は必須です。";
    setErrors(tempErrors);
    return Object.keys(tempErrors).length === 0;
  };

  const handleSubmit = async (e: FormEvent): Promise<void> => {
    e.preventDefault();
    if (!validate()) return;

    setIsSubmitting(true);

    try {
      const response = await fetch(
        "https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/contacts",
        {
          method: "POST",
          headers: { "Content-Type": "application/json" },
          body: JSON.stringify(inquiryData),
        }
      );

      if (!response.ok) throw new Error("Network response was not ok");

      alert("送信しました");
      setInquiryData({ name: "", email: "", message: ""});
      setErrors({});
    } catch (error) {
      console.error("Error submitting form:", error);
    } finally {
      setIsSubmitting(false);
    }
  };

  const handleClear = () => {
    setInquiryData({ name: "", email: "", message: "" });
  };
  return (
    <div className="App">
      <header className="header-App">
        <Link to="/" className="link">
          Blog
        </Link>
        <Link to="/inquiry" className="link">
          お問い合わせ
        </Link>
      </header>

      <div className="inquiry">
        <h1>問合わせフォーム</h1>
        <form id="myForm" onSubmit={handleSubmit}>
          <div className="formItem">
            <label>
              <dl>
                <dt>お名前</dt>
                <div className="text">
                  <dd>
                    <input
                      type="text"
                      id="name"
                      maxLength={30 as number}
                      value={inquiryData.name}
                      onChange={handleChange}
                      disabled={isSubmitting}
                    />
                  </dd>
                  {errors.name && <span>{errors.name}</span>}
                </div>
              </dl>
            </label>

            <div className="label">
              <label>
                <dl>
                  <dt>メールアドレス</dt>
                  <div className="text">
                    <dd>
                      <input
                        type="text"
                        id="email"
                        value={inquiryData.email}
                        onChange={handleChange}
                        disabled={isSubmitting}
                      />
                    </dd>
                    {errors.email && <span>{errors.email}</span>}
                  </div>
                </dl>
              </label>
            </div>
            <div className="label">
              <label>
                <dl>
                  <dt>本文</dt>
                  <div className="text">
                    <dd>
                      <textarea
                        id="message"
                        maxLength={500 as number}
                        value={inquiryData.message}
                        onChange={handleChange}
                        disabled={isSubmitting}
                        rows={10 as number}
                      />
                    </dd>
                    {errors.message && <span>{errors.message}</span>}
                  </div>
                </dl>
              </label>
            </div>
          </div>
          <div className="btn">
            <input type="submit" value="送信" disabled={isSubmitting} />
            <input
              type="reset"
              value="クリア"
              onClick={handleClear}
              disabled={isSubmitting}
            />
          </div>
        </form>
      </div>
    </div>
  );
};

export default InquiryPage;

```
## TypeScript
TypeScriptとは、JavaScirptの代替言語(altJS)の一種。
JavaScirptは性質上、型が厳密に意識したコーディングができません。
ある程度規模の大きなアプリでは、型の曖昧さは、潜在的なバグを生む原因になります。

そうしたJSの弱点を補うのがaltJSの役割。
altJSは、トランスパイラーによって、JSに変更してから実行されます。

## index.tsx (Reactアプリケーションのエントリーポイント)
```typescript
import { createRoot } from 'react-dom/client';

const container = document.getElementById('root');
const root = createRoot(container!); // TypeScriptではnullチェックが必要
```
・`container!:` TypeScriptの非nullアサーション演算子で、`container`がnullではないことを保証します。

## App.tsx (アプリケーションのルートコンポーネント)
```typescript
const App: React.FC = () => {
  return (
    <Routes>
      <Route path="/" element={<PostsList />} />
      <Route path="/post/:id" element={<DetailsPage />} />
      <Route path="/inquiry" element={<InquiryPage />} />
    </Routes>
  );
};
```
・`React.FC:` TypeScriptの型エイリアスで、React Functional Componentを示します。
  constによるコンポーネントを定義できる型です。
  (各ファイルにも同様に、関数コンポーネントに型付けする)

## posts.ts (記事のデータ)
```typescript
type Posts = {
  id: number;
  title: string;
  thumbnailUrl: string;
  createdAt: string;
  categories: string[];
  content: string;
}

export const posts: Posts[] = [....]
```
・`TypeScriptの型エイリアス`で、複合的な型を纏めたもの。
  型ごとに毎度表すのは大変なので、型エイリアスを活用すると便利です！
・このファイルのみ、`.ts`拡張子になっていますが、`JSX`を含まないファイルなので`.ts`にしています。
　 `JSX`を含む場合は、`.tsx`拡張子に指定すべきです。

## PostsList.tsx (記事一覧ページ)
```typescript
type ArticleType = {
  id: number;
  createdAt: string;
  categories: string[];
  title: string;
  content: string;
}

type PostsType = {
  posts: ArticleType[];
}
```
・`ArticleType`という名前の型を定義し、
 その中に各プロパティ(posts.tsの記事データ)の型を指定しています。

・`posts: ArticleType[]:`
postsは`ArticleType`の配列であることを示しています。つまり、この型は複数の記事を含むオブジェクトを表します。

```typescript
const formatDate = (dateString: string) => {
    const date = new Date(dateString);
    const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'numeric', day: 'numeric' };

    return date.toLocaleDateString('ja-JP', options);
  };
```
・`const formatDate = (dateString: string)`
引数`dateString`に`string`型を付与。
TypeScriptでは、「変数: 型」の形式を`型アノテーション`を付与するのが基本ですが、
全ての変数に型を付与しなければいけないわけではありません。

例)`let age = 30;`
変数ageでは、ageは数値であることは明らかなので、「age: number」としたのと等価と
見なされます。

```typescript
const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'numeric', day: 'numeric' };
```
・`Intl.DateTimeFormatOptions:` 
  これは`Intl.DateTimeFormat`オブジェクトのオプションの型

```typescript
const [posts, setPosts] = useState<PostsType>({ posts: [] })
const [loading, setLoading] = useState<boolean>(true);
```
・`<PostsType>:` postsの型を`PostsType`と指定しています。これにより、`posts`が`PostsType`型のデータを持つことをTypeScriptに示します。

・`<boolean>:` loadingの型を`boolean`と指定しています。
 これにより、`loading`が真偽値を持つ意味になります。

```typescript
useEffect(() => {
  const fetchData = async () => {
    try {
      const response = await fetch("https://1hmfpsvto6.execute-api.ap-northeast-1.amazonaws.com/dev/posts");
      const data = await response.json() as PostsType;
      setPosts(data);
    } finally {
      setLoading(false); // データ取得が完了したらローディングを終了
    }
  };

  fetchData();
}, []);

```
・`as PostsType`は、変換したデータが`PostsType`型であることをTypeScriptに示します。

## DetailsPage.tsx (記事詳細ページ)
```typescript
type detailsType = {
  id: number;
  createdAt: string;
    thumbnailUrl: any;
  categories: string[];
  title: string;
  content: string;
};

type ApiResponse = {
  post: detailsType;
}
```
・`type detailsType`
  detailsTypeという名前で型を定義。
  その中に各プロパティの型をそれぞれ指定。

```typescript
type detailsType = {
  id: number;
  createdAt: string;
    thumbnailUrl: any;
  categories: string[];
  title: string;
  content: string;
};

type ApiResponse = {
  post: detailsType;
}
```
・`const DetailsPage: React.FC`
 　関数コンポーネントに、`React.FC`型を付与。

・`post: detailsType`
 　このオブジェクトは`detailsType`型のデータを持つことを意味します。
## InquiryPage (お問い合わせフォーム)
```typescript
type InquiryType = {
  name: string ;
  email: string;
  message: string;
};

type ErrorsType = {
  name?: string;
  email?: string;
  message?: string;
};
```
・`type ErrorsType`
 `type ErrorsType`という名前の型を定義。

・`name?: string`, `email?: string`, `message?: string;`
　 この型定義は、フォームのエラーメッセージを管理するためのオブジェクトの構造を定義。
  ?マークはオプショナルプロパティを示すTypeScriptのシンタックスです。
  ユーザーが名前, email, messageを入力しなかった場合に表示するエラーメッセージを格納します。

```typescript
const InquiryPage: React.FC = () => {
  const [inquiryData, setInquiryData] = useState<InquiryType>({
    name: "",
    email: "",
    message: "",
  });
  const [errors, setErrors] = useState<ErrorsType>({});
  const [isSubmitting, setIsSubmitting] = useState<boolean>(false);
```
・`InquiryType: inquiryData`
  InquiryType: inquiryDataの型を`InquiryType`と指定しています。これにより、`inquiryData`が`InquiryType`型のデータを持つことをTypeScriptに示します。

・ErrorsType: errorsの型を`ErrorsType`と指定しています。これにより、`errors`が`ErrorsType`型のデータを持つことをTypeScriptに示します。

```typescript
const handleChange = (
    e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>
  ) => {
    const { id, value } = e.target;
    setInquiryData((prevData) => ({ ...prevData, [id]: value }));
  };
```
・`e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>`
　イベントハンドラーの部分なので、`e`の型は`React.ChangeEvent`で定義。
 　`HTMLInputElement`または`HTMLTextAreaElement`から発生するイベントである
  ことをTSに示しています。

```typescript
const validate = () => {
    const tempErrors: ErrorsType = {};
    if (!inquiryData.name) tempErrors.name = "お名前は必須です。";
    if (!inquiryData.email) tempErrors.email = "メールアドレスは必須です。";
    if (!inquiryData.message) tempErrors.message = "本文は必須です。";
    setErrors(tempErrors);
    return Object.keys(tempErrors).length === 0;
  };
```
・`ErrorsType: tempErrors`
  ErrorsType: tempErrorsの型を`ErrorsType`と指定しています。これにより、`tempErrors`が`ErrorsType`型のデータを持つことをTSに示します。

```typescript
const handleSubmit = async (e: FormEvent): Promise<void> => {
    e.preventDefault();
    if (!validate()) return;

    setIsSubmitting(true);
```
・`(e: FormEvent)`
 `e`の型をFormEventで指定。`FormEvent`はフォームの送信イベントを表します。

・`: Promise<void>`
 戻り値の型。
 つまり、この関数は`Promise`を返し、その`Promise`は何も値を返さないことを示しています。

 ## ポイント
・静的型付け
 　変数や関数の引数、戻り値に対して型を明示することで、コードの品質と保守性が向上します。型情報を利用することで、コンパイル時に多くのエラーを検出でき、予期しないバグを防ぐことができます。

・型注釈
  　変数や関数のパラメータに対して、型を明示的に指定することを意味します。
   例) 
```typescript
let integer: number = 10;
```

・型推論
　明示的な型注釈を省略できるため、コードをより簡潔で読みやすくすることができます。
  特に、文脈から変数の型が明らかな場合に有効になります。
   例) 
```typescript
let integer2 = 10;
```
## おわり
JavaScriptをTypeScriptに置き換えて実装してみました！
TSは需要ある言語ですし、大規模な開発で使われるので取得して損はないですね！