TypeScript × React-Tutorial: Tic-Tac-Toe / 三目並べ

この記事では，以下のサイトを解説をします．

https://react.dev/learn/tutorial-tic-tac-toe

https://ja.react.dev/learn/tutorial-tic-tac-toe

自分が自習時に作成したプログラムとサイトは，以下になります．

https://github.com/tomtkg/React-Tutorial

https://tomtkg.github.io/React-Tutorial/

「React-Tutorial: Tic-Tac-Toe / 三目並べ」にTypeScriptで取り組みました．
その時に自分が分からなかった箇所，詰まった箇所を中心に解説をします．

# はじめに
「React-Tutorial: Tic-Tac-Toe / 三目並べ」は，ゲームを作りながら学べるReact初心者向けサイトです．
サイトで記述されている言語は，JavaScriptです．
サイトの言語がJavaScriptなのにTypeScriptで挑んだため，色々な警告やエラーに直面しました．

React，TypeScript未経験の自分がいきなりチュートリアルに挑んだ時，欲しかった解説を残しておきます．


# ローカルでTypeScriptプロジェクトを起動
チュートリアルに入る前に，ローカルでTypeScriptプロジェクトを起動します．
まず，以下からNode.jsをダウンロードします．

https://nodejs.org/en

そして，以下のコマンドを実行します．
```Bash
npm install -g npx
npx create-react-app react-tutorial --template typescript
cd react-tutorial
npm start
```
以下の画面が出てきたらOKです．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/6a5ad2fa-9080-ea24-300e-060a6d03099e.png)

# エラーを確認
チュートリアルで利用・編集するのは，`react-tutorial/src/App.tsx`です．
例として，プログラムの全文（`import React ... default App;`）を以下に書き換えます．
```tsx: App.tsx
export default function App() {
  return <p> Hello world! </p>
}
```
画面が以下のように変化します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/d62db205-6f80-d845-c433-c369e87f1149.png)
次に，同じく`App.tsx`の全文をチュートリアルの最初に出てくる`App.js`（116行）に書き換えます．
画面が以下のように変化します．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/96e503c0-905b-8b92-5d80-fed37c92ce56.png)
「完成したときにどのような見た目になるか、以下で確認できます。」というプログラムを利用したのに9件のエラーが出てきます．
JavaScriptでは許容されるがTypeScriptでは許容されない記述がエラーとして出ています．
完成したときの見た目はチュートリアルのサイトで確認することにして，先に進みましょう．

# チュートリアルに挑戦
チュートリアルを読み進め，途中に出てくるプログラムで`App.tsx`を書き換えます．
例えば，以下のプログラムで以下の画面が出てきます．
```tsx: App.tsx
export default function Square() {
  return (
    <>
      <button className="square">X</button>
      <button className="square">X</button>
    </>
  );
}
```
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/0a41e1a9-336a-5880-2952-84222643108f.png)
`react-tutorial/src/index.css`を編集し，`margin: 0;`の記述を削除，[`styles.css`](https://github.com/tomtkg/React-Tutorial/blob/main/src/styles.css)の内容を追記すると，画面表示がチュートリアルのサイトと同じになります．
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/6fa4694b-f82f-f869-6f0c-f54e935c4c6a.png)

# 引数の型に関するエラーを修正1（[05.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/05.tsx)）
チュートリアルを進めていくと，以下のエラーが出ます．
`Binding element 'value' implicitly has an 'any' type.`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/6b3d6284-6768-89a6-d6a2-8d6a1936c3d4.png)
これは，関数`Square`の引数`value`の型が指定されていないために起きたエラーです．
1行目を以下のように書き換えて，型情報を追加するとエラーが解消されます．
```diff_tsx: App.tsx
- function Square({ value }) {
+ function Square({ value }: { value?: string }) {
```
ここで選択候補となる`value`の型は，4種類あります．
VSCode上で`value`にカーソルを合わせた時に表示される情報は，以下になります．
* `{ value }: { value: any }` ➡ `(parameter) value: any`
* `{ value }: { value?: any }` ➡ `(parameter) value: any`
* `{ value }: { value: string }` ➡ `(parameter) value: string`
* `{ value }: { value?: string }` ➡ `(parameter) value: string | undefined`

1, 3番目の型は，プログラムの9行目以降でエラーが出ます．
`Property 'value' is missing in type '{}' but required in type '{ value: any; }'.`
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/5bcefef3-2c7b-6c1b-85e7-215cc66b6029.png)
関数`Square`の引数`value`が必須にも関わらず，その値が無いためにエラーが発生しています．
`<Square value="1" />`のように書き換えていくとエラーが解消されます．

2, 4番目の型は`undefined`を許容しており，関数`Square`の引数`value`が無くても問題ないため，エラーが出ません．
JavaScriptやTypeScriptにおいて，`value`の後の`?`（クエスチョンマーク，疑問符）はオプションパラメータと呼び，重要な意味があります．
関数の引数やインターフェースのプロパティを省略可能であることを意味します．

JavaScriptではなくTypeScriptを利用しているので，その作法に則り，なるべく厳密に型を指定します．
`{ value }: { value?: any }`は，なるべく使わないようにします．
初めは`{ value }: { value?: string }`と記述し，後ほど`{ value }: { value: string }`にリファクタリングするのがお勧めです．

# useStateの型に関するエラーを修正（[08.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/08.tsx)）
チュートリアルを進めていくと，以下のエラーが出ます．
`Argument of type '"X"' is not assignable to parameter of type 'SetStateAction<null>'.`
これは，関数`Square`の引数`value`の型が指定されていないために起きたエラーです．
1行目を以下のように書き換えて，型情報を追加するとエラーが解消されます．


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/308259/6b3d6284-6768-89a6-d6a2-8d6a1936c3d4.png)
これは，`useState`で利用する`value`の型が指定されていないために起きたエラーです．
`useState`に関する行を以下のように書き換えて，型情報を追加するとエラーが解消されます．
```diff_tsx: App.tsx
- const [value, setValue] = useState(null);
+ const [value, setValue] = useState<string | null>(null);
```
VSCode上で`value`にカーソルを合わせた時に表示される情報は，`const value: string | null`に変化します．
JavaScriptとTypeScriptでは，`undefined`と`null`は異なる型です．
`null`型を`undefined`型で代用できる場合は，`const [value, setValue] = useState<string>();`に書き換えても問題ありません．

# 引数の型に関するエラーを修正2（[10.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/10.tsx)）
チュートリアルを進めていくと，以下のエラーが出ます．
`Binding element 'value' implicitly has an 'any' type.`
`Binding element 'onSquareClick' implicitly has an 'any' type.`
これは，関数`Square`の引数`value`と`onSquareClick`の型が指定されていないというエラーです．
引数が2つ以上ある場合，以下のように型エイリアス（Type Alias）を使うことが一般的です．
```diff_tsx: App.tsx
+ type Props = {
+   value: string,
+   onSquareClick?: React.MouseEventHandler<HTMLButtonElement>,
+ }

- function Square({ value, onSquareClick }) {
+ function Square({ value, onSquareClick }: Props) {
  return (
    <button className="square" onClick={onSquareClick}>
      {value}
    </button>
  );
}
```
`type Props = {...}`が型エイリアスです．
型エイリアスを使わずに`Square()`の括弧内に型を書いてもエラーが消えますが，可読性や変数の再利用を考慮すると非推奨です．

# 引数の型に関するエラーを修正3（[12.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/12.tsx), [14.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/14.tsx), [16.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/16.tsx)）
チュートリアルを進めていくと，以下のエラーが出ます．
`Parameter 'i' implicitly has an 'any' type.`
`Parameter 'squares' implicitly has an 'any' type.`
`Parameter 'nextSquares' implicitly has an 'any' type.`
これらの修正には，型エイリアスを使わなくても良いと思います．
以下のように変更します．
```diff_tsx: App.tsx
- function handleClick(i) {
+ function handleClick(i: number) {
```
```diff_tsx: App.tsx
- function calculateWinner(squares) {
+ function calculateWinner(squares: string[]) {
```
```diff_tsx: App.tsx
- function handlePlay(nextSquares) {
+ function handlePlay(nextSquares: string[]) {
```

# 引数の型に関するエラーを修正4（[17.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/17.tsx), [19.tsx](https://github.com/tomtkg/React-Tutorial/blob/main/src/tutorial/19.tsx)）
チュートリアルを進めていくと，再び引数の型に関するエラーが出てきます．
新しく`BoardProps`を作成して，エラーを修正します．
```diff_tsx: App.tsx
+ type BoardProps = {
+   xIsNext: boolean,
+   squares: string[],
+   onPlay: (nextSquares: string[]) => void,
+ }

- function Board({ xIsNext, squares, onPlay }) {
+ function Board({ xIsNext, squares, onPlay }: BoardProps) {
```
関数`jumpTo`を以下のように修正します．
```diff_tsx: App.tsx
- function jumpTo(nextMove) {
+ function jumpTo(nextMove: number) {
```

以上の情報があれば，TypeScriptで「React-Tutorial: Tic-Tac-Toe / 三目並べ」が完了できます．

# 終わりに
この記事で紹介したチュートリアルは，Reactを実践しながら学ぶことができ，実際にweb画面で動くものが作れるお勧めの教材です．
JavaScriptを利用するのであれば，この記事に記載したエラーに直面することなく，チュートリアルのwebサイトだけでスムーズに学習できると思います．

開発環境がTypeScript × Reactの場合，この記事を参考にしながらチュートリアルに取り組んでみてはいかがでしょうか．
JavaScriptとTypeScriptの違いも勉強できて，一石二鳥です．

関連記事:

https://qiita.com/tomtkg/items/380fc28d21e7b126f0b3

https://qiita.com/tomtkg/items/3e6173a231fdbe463302