目次
- React のチュートリアルを Typescript でやってみた【環境構築編】
- React のチュートリアルを Typescript でやってみた【環境構築編2】
- React のチュートリアルを Typescript でやってみた【ソース準備編】
- React のチュートリアルを Typescript でやってみた1
- React のチュートリアルを Typescript でやってみた2 ←ここ
- React のチュートリアルを Typescript でやってみた3
概要
チュートリアル通りにやりますが、端折る箇所もあると思います。
各々保管をお願いいたします。
公式のチュートリアルページ
今回の実施チュートリアル内容
ゲームを完成させる
- Stateのリストアップ
- イミュータビリティは何故重要なのか
- 関数コンポーネント
- 手番の処理
- ゲーム勝者の判定
1.Stateのリストアップ
Squareに値を当てられるようにする
チュートリアルページの様にBoardでSquareの値を管理するように書いてみます。
まずは、square.tsx
の修正
Propsでデータを受け取れるようPropsを定義します。
このとき使用しなくなったStateは削除しておきます。
また、valueの方はString型を定義します。
今回扱う文字がX
とO
のためになりますが、チュートリアルでは他にもnullを扱っています。
Typescriptの型の都合上今回は空文字にします。
しっかりとチュートリアルどおりにやりたい場合は、Stringとnullを同時に扱える定数を提示しそちらを使うようにしましょう。
+ type Props = {
+ value: String
+ }
- type State = {
- value: String
- }
Stringとnullを定義したもの
type SquareVaule = String | null;
type Props = {
value: SquareVaule;
}
次に、board.tsx
の改修を進めていきます。
boardのコンポーネントでsquareのコンポーネントのデータを扱うためStateを定義します。
type State = {
squares: Array<String>;
}
次にコンポーネントの引数の部分について修正
以下のようにしました。
{}
の部分は、Propsに当たります。今回は使用していないため空のものを設定
わかりにくい場合は、Propsの空の定義を作成し入れてあげるといいでしょう。
どちらがいいのかわからないです。その時時によると思います。
class Board extends React.Component<{}, State> {
以下は丁寧にした感じのソースです。
type Props = {}
class Board extends React.Component<Props, State> {
次にコンストラクタを定義します。
以下の用な感じにこちらはほぼチュートリアルのソース通りです。
constructor(props: {}) {
super(props)
this.state = {
squares: Array<String>(9).fill(""),
}
}
あとは、Squareのコンポーネントにstateのデータを指定するだけです。
renderSquare(i: number) {
return <Square value={this.state.squares[i]} />;
}
以上で、Squareに文字列を設定することができるようになります。
マス目がクリックされたときの動作を作成する
マス目がクリックされたときの動作を作成します。
まずは、square.tsx
のPropsを修正します。
boardから渡ってくる動作を受け取れるように以下の様にonClickを設定します。
type Props = {
value: String
+ onClick: () => void;
}
Boardで追加したonClickの部分について作成していきます。
まずは、board.tsx
でSquareにonClickを渡す部分を追加します。
renderSquare(i: number) {
return <Square
value={this.state.squares[i]}
onClick={() => this.handleClick(i)}
/>;
}
次にhandleClickについて作成します。
handleClick(i: number) {
const squares = this.state.squares.slice();
squares[i] = 'X';
this.setState({squares: squares});
}
最後に渡したonClickの動作を動作するようにします。
render() {
return (
<button
className="square"
onClick={() => this.props.onClick()}
>
{this.props.value}
</button>
);
}
2.イミュータビリティは何故重要なのか
特にソースを変更する必要なし。
3.関数コンポーネント
Squareクラスを関数コンポーネントに書き換えます。
引数の部分に型を入れるだけになった。
function Square(props: Props) {
return (
<button className="square" onClick={props.onClick}>
{props.value}
</button>
);
}
4.手番の処理
とりあえず、Stateの中にユーザ判定用のフラグを用意しました。
type State = {
squares: Array<String>;
+ xIsNext: boolean;
}
コンストラクタ内で初期値の設定を追加
constructor(props: {}) {
super(props)
this.state = {
squares: Array<String>(9).fill(""),
+ xIsNext: true,
}
}
マス目がクリックされた際の動作を追加します。
追加した動作
- ○と×の入力切替
- プレイヤーのフラグの変更
handleClick(i: number) {
const squares = this.state.squares.slice();
+ squares[i] = this.state.xIsNext ? 'X' : '○';
this.setState({
squares: squares,
+ xIsNext: !this.state.xIsNext,
});
}
表示プレイヤーの切り替え
const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○');
5.ゲーム勝者の判定
勝敗判定のfunctionを追加します。
引数はStringの配列に設定
calculateWinner(squares: Array<String>) {
const lines = [
[0, 1, 2],
[3, 4, 5],
[6, 7, 8],
[0, 3, 6],
[1, 4, 7],
[2, 5, 8],
[0, 4, 8],
[2, 4, 6],
];
for (let i = 0; i < lines.length; i++) {
const [a, b, c] = lines[i];
if (squares[a] && squares[a] === squares[b] && squares[a] === squares[c]) {
return squares[a];
}
}
return null;
}
勝敗のfunctionを使用しプレイヤーが勝利した場合プレイヤーの表示方法を変更する。
render() {
- const status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○');
+ const winner = this.calculateWinner(this.state.squares);
+ let status;
+ if (winner) {
+ status = 'Winner: ' + winner;
+ } else {
+ status = 'Next player: ' + (this.state.xIsNext ? 'X' : '○');
+ }
勝敗がついた場合やマス目がすべて入力された場合に何も動作しないようにする。
handleClick(i: number) {
const squares = this.state.squares.slice();
+ if (this.calculateWinner(squares) || squares[i]) {
+ return;
+ }
squares[i] = this.state.xIsNext ? 'X' : '○';
this.setState({
squares: squares,
xIsNext: !this.state.xIsNext,
});
}
まとめ
とりあえずゲーム完成までやってみました。
公式通りにやったためそこまで詰まるところはなかった。
型をちゃんと意識すれば良さそうですね。
ここまでのソースは以下になります