前回の続きです。 いよいよ三目並べづくりに入ります。
インタラクティブなコードをつくる
盤面のマスをクリックするとX
が現れるようにします。
はじめにSquare
コンポーネントにstate
を持たせます。
公式チュートリアルではこのように実装されています。
class Square extends React.Component { constructor(props) { super(props); this.state = { value: null, }; } render() { return ( <button className="square" onClick={() => alert('click')}> {this.props.value} </button> ); } }
useStateを使って関数コンポーネントで実装
関数コンポーネントではHooksの関数であるuseState
を用いて実装できます。
以下が実装したコードです。
const Square: VFC<SquareProps> = () => { const [value, setValue] = useState<string | null>(null); // useState(' ')でもOKだが、チュートリアルに合わせてnullが代入できるように型を設定する return ( <button type="button" className="square" onClick={() => { setValue('X'); }} > {value} </button> ); };
const [value, setValue] = useState<string | null>(null);
ですが、useState
はこの配列の第一引数にstate、第2引数にstateのセッターとなる関数が代入されます。ここではvalue
、setValue
としています。
useState
はジェネリクスによりstateの型を設定できます。ここではstring
またはnull
型としています。
また、useStateの引数にはstateの初期値を代入でき、ここではチュートリアルに従いnull
に設定しています。
表示部分の修正
return内のonClick
を少し変更します。
onClick={() => this.setState({value: 'X'})
をonClick={() => {setValue('X');}}
に変更value
の値を表示する部分で、{this.state.value}
を{value}
に変更
これによりJS特有のthisの挙動に悩む必要がなくなるため、大きなメリットになっています。
割愛しますが、JSのthisの挙動は4種類あり、混乱を招く要因となっているようです(りあクト!より)。
ここまでのコードはこちら
ゲームを完成させる
ようやく三目並べの完成パートです。
Stateのリフトアップ
前のステップで、stateを各Squareコンポーネントに持たせていましたが、Boardコンポーネントにstateを持たせて、Squareコンポーネントからアクセス、変更するようにします。
完成したコードがこちら↓です。
import { VFC, useState, ReactElement } from 'react'; import ReactDOM from 'react-dom'; import './index.css'; // Squareの中身の型、XかOか空(null)の3通り type FillSquare = 'X' | 'O' | null; type SquareProps = { value: FillSquare; onClick: () => void; }; const Square: VFC<SquareProps> = (props) => { const { value, onClick } = props; return ( <button type="button" className="square" onClick={onClick}> {value} </button> ); }; const Board: VFC = () => { const [squares, setSquares] = useState<FillSquare[]>(Array(9).fill(null)); const handleClick = (i: number): void => { const squaresSlice = squares.slice(); squaresSlice[i] = 'X'; setSquares(squaresSlice); }; const renderSquare = (i: number): ReactElement => ( <Square value={squares[i]} onClick={() => handleClick(i)} /> ); const status = 'Next player: X'; return ( <div> <div className="status">{status}</div> <div className="board-row"> {renderSquare(0)} {renderSquare(1)} {renderSquare(2)} </div> <div className="board-row"> {renderSquare(3)} {renderSquare(4)} {renderSquare(5)} </div> <div className="board-row"> {renderSquare(6)} {renderSquare(7)} {renderSquare(8)} </div> </div> ); }; const Game: VFC = () => ( <div className="game"> <div className="game-board"> <Board /> </div> <div className="game-info"> <div>{/* status */}</div> <ol>{/* TODO */}</ol> </div> </div> ); // ======================================== ReactDOM.render(<Game />, document.getElementById('root'));
Boardコンポーネント
const [squares, setSquares] = useState<FillSquare[]>(Array(9).fill(null));
ここは前のステップと同じで、squaresというstateを設定しています。
このstateの型はFillSquare
の配列になっていて、初期値にはnull
で満たされた要素数9の配列が渡されます。
FillSquare
という型エイリアスはプログラムの先頭付近で設定されていて、type FillSquare = 'X' | 'O' | null;
となっています。これは、各SquareコンポーネントにはX
かO
かnull
しか代入されないからです。
renderSquare
次に、renderSquare関数ですが、TypeScriptでは次のように書けます。
const renderSquare = (i: number): ReactElement => (
<Square value={squares[i]} onClick={() => handleClick(i)} />
);
引数と戻り値の型が設定され、number型のi
を引数にとり、ReactElementを返す関数であることがひと目で分かるようになりました。
C++を最もよく使ってきた私としては、型が明示されるとすごくスッキリします。
なお、クラスコンポーネントで書かれていたthis
も消えています。
handleClick
公式チュートリアルと順番が前後しますが、ここでhandleClick関数についても書いておきます。
クリックされたマスをXにする関数です。stateのsquares
をslice()
でコピーし、i番目のマスをXにしたあと、新しい配列をstateにセットし直しています。
stateを直接書き換えないのは、非破壊的であることが望ましいとされる関数型プログラミングの思想によるものです(まだ関数型プログラミングを完全に理解しきれていませんが…)。 公式チュートリアルでは、「イミュータビリティはなぜ重要なのか」のパートで解説されています。
Squareコンポーネント
最後にSquareコンポーネントです。
type SquareProps = { value: FillSquare; onClick: () => void; };
︙
const Square: VFC<SquareProps> = (props) => { const { value, onClick } = props; return ( <button type="button" className="square" onClick={onClick}> {value} </button> ); };
はじめに、propsの型を定義しています。FillSquare型のvalue
と、戻り値を持たない関数onClick
を持ちます。
これをコンポーネント定義内でconst { value, onClick } = props;
のように受け取って使っているシンプルなコードです。
これでチュートリアルもようやく半分くらいでしょうか。
残りも頑張って書いていきます!