React + TypeScript に入門したので基本をまとめてみた

最近になって React + TypeScript に入門したので、自分へのメモがてら、基本的なことをまとめてみました。

## 前提

TypeScript 初心者です。間違いや違和感等々ありましたらコメントで教えていただけると助かります。
JavaScript・React はある程度理解していることが前提です。
TypeScript・React の環境構築や`tsconfig.json`の設定には触れません。

## TypeScript の基礎

### 「明示的な型定義」と「型推論」

TypeScript の型定義には**明示的な型定義**と**型推論**があります。
明示的な型定義は人間が型を指定するのに対し、型推論は TypeScript がいい感じに型を推論してくれます。
実際のコーディングでは型推論を使いつつ、必要な時だけ明示的な型定義をすればいいと思いますが、この記事では型の理解を手助けするために明示的な型定義を使用していきます。

### 基本的な型定義

明示的な型定義の方法です。

```ts
let str: string = "hoge"; // string型
```

上記の形をアノテーション（型注釈）といいます。他にも明示的な型定義にはアサーションという方法がありますが、アサーションについては後ほど記載します。

#### 代表的なプリミティブ 型

一般的に使用頻度の高い代表的なプリミティブ型です。

```ts
let str: string = "hoge"; // string型
let num: number = 1; // number型
let bool: boolean = true; // boolean型
```

#### null / undefined 型

null と undifined にはそれぞれ固有の型が用意されています。下記のように宣言することはできますが、単体ではあまり使いどころがないかもしれません。

```ts
let n: null = null; // null 型
let u: undefined = undefined; // undefined 型
```

#### リテラル 型

プリミティブ型のうちの特定の値だけを許容する型になます。

```ts
const str: "hoge" = "hoge"; // "hoge"
const one: 1 = 1; // 1
const trueFlg: true = true; // true

// let でも指定した値以外は再代入不可
let two: 2 = 2;
two = 3; // エラー
```

Union Type（Union Type については後ほど説明）と併用することで、より便利な型定義を実現できます。

```ts
let color: "red" | "green" | "blue" = "red";
color = "green";
color = "yellow"; // エラー
```

#### Any 型

型が不明な時に型チェックを無効にする型になります。TypeScript の恩恵が受けられないため。可能な限り使わないようにするのが吉です。

```ts
let value: any = 0;
value = "hoge"; //これでもコンパイルが通る
```

#### Void 型

値を返さない関数の戻り値で使います。

```ts
const func = (): void => {
  console.log("hoge");
};
```

#### Array 型

配列の型です。

```ts
const strArr: string[] = ["red", "green"]; // 配列の中には string 型
const arrNum: number[] = [1, 2]; // 配列の中には number 型

strArr[0] = "blue";
strArr[0] = false; // エラー
```

#### Tuple 型

要素の数がわかっている配列を表現できる型です。

```ts
const tupleArr: [string, number] = ["Hanako", 26];
```

#### Object 型

オブジェクト型は非プリミティブな型を表します。

```ts
const obj: {
  name: string;
  age: number;
} = {
  name: "Taro",
  age: 20,
};
obj.name = "Sato";
obj.birthday = "1990/1/1"; // エラー
```

#### Interface

`Interface` を使うとオブジェクト型に名前をつけることができます。

```ts
interface Obj {
  name: string;
  age: number;
}
const myObj: Obj = {
  name: "Hanako",
  age: 18,
};
```

#### Type Alias

型に名前をつける方法として `Type Alias` もあります。
`Interface` はオブジェクト専用ですが `Type Alias` はオブジェクト以外にも使用可能です。

```ts
type Location = string;
let location: Location = "Saitama";

type Obj = {
  name: string;
  age: number;
};
const myObj: Obj = {
  name: "Takashi";
  age: 28;
};
```

オブジェクトの型指定における `Interface` と `Type Alias` の違いについてはこの記事では記載しませんが、公式ドキュメントに記載がありましたので、ご参考までに。
[https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces](https://www.typescriptlang.org/docs/handbook/2/everyday-types.html#differences-between-type-aliases-and-interfaces)

#### 関数の型

引数と戻り値に型定義をします。

```ts
const sampleFunc = (arg: string): string => {
  return arg;
};
console.log(sampleFunc("fuga")); // "fuga"

// 戻り値がないときは void 型を指定
const sampleEchoFunc = (arg: string): void => {
  console.log(arg);
};
sampleEchoFunc("hoge"); // "hoge"
```

`?`を使うこと引数がオプショナルな型定義を実現できます。
オプショナルな型定義では値が `undefiend` になる可能性があるので、そのことを考慮して処理を書く必要があります。

```ts
interface posOptions {
  xPos?: number; // xPos : number | undefined
  yPos?: number; // xPos : number | undefined
}

const getPosition = (opts: posOptions) => {
  // xPost と yPos が undefined になる可能性も考えた処理を書く必要がある
  let xPos = opts.xPos === undefined ? 0 : opts.xPos;
  let yPos = opts.yPos === undefined ? 0 : opts.yPos;
  // ...
};

getPosition(); // 引数がなくても関数を呼び出せる
getPosition({ xPos: 1 });
getPosition({ xPos: 1, yPos: 1 });
```

#### Promise 型

`Promise<>` の形で戻り値を表します。

```ts
const PromiseFunc = (arg: string): Promise<void> => {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve(arg);
    }, 5000);
  }).then(result => {
    console.log(result);
  });
};

PromiseFunc("Hello World"); // 5秒後に "Hello World"
```

### Union Type

複数の型のうち、どれか一つの型にに当てはまる場合に使います。いわゆる `OR` 的な使い方です。

```ts
const printId = (id: number | string) => {
  console.log("Your ID is: " + id);
};
// OK
printId(101);
// OK
printId("202");
```

### 型の結合

`&`を使うことによって型を結合することができます。

```ts
type Sample1 = {
  hoge: string;
};

type Sample2 = {
  fuga: string;
};

type CombineSample = Sample1 & Sample2;

// type CombineSample = {
//   hoge: string;
//   fuga: string;
// };
// 上記と同等になる。
```

### typeof と keyof

`typeof` を使用すると既存の変数から型の定義を取得できます。
`keyof` を使用するとオブジェクトの key をリテラルユニオン（リテラル型と Union Type の併用）として取得できます。

```tsx
const obj = {
  fistName: "Taro",
  lastName: "Yamada",
};
const myobj: typeof obj = {
  fistName: "Hanako",
  lastName: "Satou",
  age: 20, // age は obj に存在しないのでエラーとなる
};

type objType = { fistName: string; lastName: string };
// objType のプロパティを String Union Type として継承
const key: keyof objType = "fistName"; // key : "fistName" | "lastName"
```

`typeof` を型ガードとして使うこともできます。

```tsx
function printId(id: number | string) {
  if (typeof id === "string") {
    // string 型の時の処理
    console.log(id.toUpperCase());
  } else {
    // number 型の時の処理
    console.log(id);
  }
}
```

### ジェネリクス

ジェネリクスを使用すると型の確定を遅延することができます。

```ts
// valueの型がまだ決まっていない
interface GSample<T> {
  value: T;
}

// valueの値は string 型になる
const sampleString: GSample<string> = {
  value: "hoge",
};

// valueの値は number 型になる
const sampleString: GSample<number> = {
  value: 9,
};
```

関数定義でジェネリクスを使うと、関数宣言時に引数の型を未確定にできます。

```ts
// 引数の型は決まってない
const GenericsFunc = <T>(arg: T): T => {
  return arg;
};

// 引数の型は string
const result1 = GenericsFunc<string>("hoge");

// 引数の型は number
const result2 = GenericsFunc<number>(10);
```

アロー関数でジェネリクスを書く、かつ tsx ファイルの場合は extends をつける必要があります。でないと JSX の文法でエラーが出ます。

```tsx
// tsx　ファイルの時は extends をつける
const GenericsFunc = <T extends {}>(arg: T): T => {
  return arg;
};
```

### アサーション

TypeScirpt で推論された型を任意の型に上書きしたい時に使います。アサーションには `as` と `<>` を使う 2 種類の方法があります。
`as` と `<>` は基本的に同等ですが、`<>` は JSX の構文と衝突する可能性があるため `as` が推奨されます。
実際の値と関係なく型定義ができてしまうため、値の型とアサーションの型が一致するとき以外は使用してはいけません。

```ts
const canvas1 = document.getElementById("canvas"); // canvas1 : HTMLElement | null
const canvas2 = document.getElementById("canvas") as HTMLCanvasElement; // canvas2 : HTMLCanvasElement
const canvas3 = <HTMLCanvasElement>document.getElementById("canvas"); // canvas3 : HTMLCanvasElement

// NG
// このような指定もできるため、値とアサーションの型が一致するとき以外は使用してははいけない。
const possible = true as false;
```

### 型推論について

明示的な型定義をしなくても、TypeScript はいい感じに型を推論してくれます。
基本的に型推論が期待する結果と違う時のみ、明示的に型定義をすればいいと思います。
また、`const` と`let` で型推論の結果が異なる場合があるので、気をつける必要があります（基本的には `cosnt` と `let` の挙動の違いが反映されるイメージです）。

```ts
// 型推論 const と let の違い
let color = "red"; // color は string 型
const color = "red"; // color は "red" のみ

// 明示的な型定義を使用したほうがいい場合の例
let color: "red" | "green" | "blue" = "red"; // color は "red", "green", "blue" のどれか
const canvas = document.getElementById("canvas") as HTMLCanvasElement; // canvas : HTMLCanvasElement
```

TypeScript がどの型を推論しているかというのは、各エディタのヒントなどで確認できます。
（以下は Visual Studio Code で確認している様子)
![cap.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1027861/b6b22db9-98d1-1761-66a9-47f0b79e65a0.png)

## React と TypeScript

### 関数コンポーネント

TypeScript における関数コンポーネントの基本的な書き方を以下に示します。

#### 基本形

`React.FC`という React 独自の型を使用します。`React.FC`はジェネリクス型なので Props を`<>`で型定義できます。

```tsx
import React from "react";

type Props = {
  name: string;
};
const Sample: React.FC<Props> = ({ name }) => {
  return <div>Hello {name}!</div>;
};

export default Sample;
```

`React.FC` ではなく `React.VFC`を使ったほうがいいという説もあります。

`React.FC` と `React.VFC` の違いについてはこの記事では記載しませんが、詳しく知りたい方は以下の記事に載っていましたので、ご参考までに。
[Function Components | React TypeScript Cheatsheets](https://react-typescript-cheatsheet.netlify.app/docs/basic/getting-started/function_components)

#### children を受け取る

`children` を受け取る時は `React.ReactNode` を使います。

```tsx
import React from "react";

type Props = {
  children: React.ReactNode;
};
const Sample: React.FC<Props> = ({ children }) => {
  return <div>{children}}</div>;
};

export default Sample;
```

#### オプショナルな Props を渡す

`?`を使うことでオプショナルな型定義ができます。オプショナルな値では `undefined` の可能性が生じることを考慮する必要があります。

```tsx
// x も y オプショナル
type Props = {
  x?: number; // x : number | undefined
  y?: number; // y : number | undefined
};

// オプショナルな値は undefined の可能性があるので初期値を設定している
const CalcSum: React.FC<Props> = ({ x = 0, y = 0 }) => {
  const sum = x + y;
  return <div>合計: {sum}</div>;
};
const App: React.FC = () => {
  return <CalcSum x={2} />;
};
```

#### EventCallback と型定義

props で受け取る EventCallback の定義が必要です。以下に `onChnage` の時の例を示します。

```tsx
type InputProps = {
  value: string;
  onChange: (e: React.ChangeEvent<HTMLInputElement>) => void;
};
const Input: React.FC<InputProps> = ({ onChange, value }) => {
  return (
    <div>
      <input type="text" onChange={onChange} value={value} />
    </div>
  );
};

const App: React.FC = () => {
  const [inputValue, setInputValue] = useState("");
  const onChange = (e: React.ChangeEvent<HTMLInputElement>) => {
    setInputValue(e.target.value);
  };
  return (
    <div>
      <div>{inputValue}</div>
      <Input value={inputValue} onChange={onChange} />
    </div>
  );
};
```

その他の EventCallback については数が多くなるのでこの記事では記載しませんが、以下の Qiita 記事が非常にまとまっていたので、詳しく知りたい方はそちらをご参照ください。投稿者の方、ありがとうございます。
[any 型で諦めない React.EventCallback](https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24)
下記は同記事からの一部引用になります。

> ```ts
> type Props = {
>   onClick: (event: React.MouseEvent<HTMLInputElement>) => void;
>   onChange: (event: React.ChangeEvent<HTMLInputElement>) => void;
>   onkeypress: (event: React.KeyboardEvent<HTMLInputElement>) => void;
>   onBlur: (event: React.FocusEvent<HTMLInputElement>) => void;
>   onFocus: (event: React.FocusEvent<HTMLInputElement>) => void;
>   onSubmit: (event: React.FormEvent<HTMLFormElement>) => void;
>   onClickDiv: (event: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
> };
> ```

### Hooks と型

Hooks も型推論が効くので、必要な時だけ明示的な型定義をします。

#### useState

明示的な型定義をしたいときはアサーションを使います。

```tsx
const [val, setVal] = useState(false); // これで問題ない
const [val, setVal] = useState<boolean | null>(null); // Nullable にしたい場合はアサーションを使う
```

#### useEffect

`useEffect` を使うときには戻り値に気をつけます。
`関数` or `undefined` 以外は返さないようにします（ただこれは TypeScript を使ってなくても同様です）。

```ts
// NG
useEffect(
  () =>
    setTimeout(() => {
      /* ... */
    }, 1000),
  []
);

// OK
useEffect(() => {
  setTimeout(() => {
     /* ... */
  }, 1000);
}, []);
```

#### useRef

`useRef` の場合は初期値に null が入るケースが多いのでアサーションで指定しています。

```ts
const App: React.FC = () => {
  // 明示的な型定義が必要
  const ElementRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    // 型ガードが必要
    if (!ElementRef.current) throw Error("ElementRef is not assigned");
    console.log(ElementRef.current.getBoundingClientRect());
  }, []);

  return <div ref={ElementRef}>app</div>;
};
```

#### useReducer

`reducer` における `state` と `action` に対して明示的な型定義をしています。
action を`ACTIONTYPE` で定義。
state の型は `typeof` を使うことで `initialState` の型を流用できます。

```tsx
const initialState = { count: 0 };

type ACTIONTYPE =
  | { type: "increment"; payload: number }
  | { type: "decrement"; payload: number };

const reducer = (state: typeof initialState, action: ACTIONTYPE) => {
  switch (action.type) {
    case "increment":
      return { count: state.count + action.payload };
    case "decrement":
      return { count: state.count - action.payload };
    default:
      throw new Error();
  }
};

const App: React.FC = () => {
  const [state, dispatch] = useReducer(reducer, initialState);
  return (
    <div>
      Count: {state.count}
      <button onClick={() => dispatch({ type: "decrement", payload: 1 })}>
        Count Down
      </button>
      <button onClick={() => dispatch({ type: "increment", payload: 1 })}>
        Count Up
      </button>
    </div>
  );
};
```

#### useContext

`Provider` の value にセットする値の型定義以外は特に目新しいものはありません。
`createContext` で初期値を `null` にする場合はアサーションが必要です。

```tsx
interface AppContextInterface {
  name: string;
  age: number;
}
const sampleAppContext: AppContextInterface = {
  name: "Yamada Taro",
  age: 20,
};

const AppContext = createContext<AppContextInterface | null>(null);

const ChildComponent: React.FC = () => {
  // useContext(AppCtx)! の ! は not-null のアサーション
  const appContext = useContext(AppContext)!;

  return (
    <p>
      {appContext.name}は{appContext.age}歳です。
    </p>
  );
};

const App: React.FC = () => {
  return (
    <AppContext.Provider value={sampleAppContext}>
      <ChildComponent />
    </AppContext.Provider>
  );
};
```

## まとめ

今回は基本的なことについてまとめてみましたが、当然ながらカバーしきれていない範囲も多くあります。
ただ、この記事にまとめたことは React + TypeScript において使用頻度の高いものだと思うので、ゼロから学ぶ際やチートシート的な使い方として参考にしていただけると幸いです。
また TypeScript は公式ドキュメントが充実しており初心者にもわかりやすく書かれていると感じたので、読んだことのない方は是非一度読んでみることをお勧めします。

## 参考 URL

[TypeScript: TypeScript 学習の第一歩](https://www.typescriptlang.org/ja/docs/)
[React TypeScript Cheatsheets | React TypeScript Cheatsheets](https://react-typescript-cheatsheet.netlify.app/)
[Introduction - TypeScript Deep Dive](https://basarat.gitbook.io/typescript/)
[any 型で諦めない React.EventCallback](https://qiita.com/Takepepe/items/f1ba99a7ca7e66290f24)