はじめに
tic-tac-toeのチュートリアルをTypescriptで、というのは結構たくさんあって参考にしながら、最終までなんとかやってみました。
きっかけは、@m0aさんのTypeScriptを使ってreactのチュートリアルを進めると捗るかなと思った(実際捗る)で、わからないなりに最後までやってみて、なんとか動くところまでいけました。
Typescriptへの書き換えも楽しくなってきたので、次はもう少し理解を深めようかということでHello!Worldからやってみるかと思います。
新しい環境を作る
npx create-react-app [folder] --typescript
して、フォルダを準備します。このとき、informationSiteとかそんな感じのフォルダ名をつけようとするとエラーになるので、information_siteといった具合に修正することになりました。
既存ファイルを修正する
今回はApp.tsxを修正していくことにしました。
デフォルトでできるファイルにもApp.tsxを修正したらリロードしてね的なことが書いてあるので素直に従います。
JSXを埋め込む作業で、実際はfunctionを作ってそこで要素のレンダリングをして描画という流れになっていますが、こちらについては読んで納得したので、飛ばします。
- 親要素から子要素を呼び出す
- 子要素は受け取ったパラメータに沿って内容をレンダリングして、その結果を返す
- 親要素は受け取ったレンダリング内容をまとめてReactDOMに返す
- ReactDOMは、戻ってきた内容に従って、更新の有無を判断して描画する
という流れだということはなんとなくわかってきました。
関数呼び出し
次は関数の呼出の形に修正します。
Typescriptでは、interfaceを定義することでその要素にどのような内容が含まれるかを明示できます。
C言語の構造体のようなものという理解なので、とりあえず、今回は名前要素の書換をするということで、Welcomeで使うWelcomeProps(Welcome Property)を定義しました。
interface WelcomeProps {
name: string;
}
function Welcome(props: WelcomeProps) {
return <h1>Hello, {props.name}</h1>;
}
const App: React.FC = () => {
return (
<div className="App">
<Welcome name="Sara" />
<Welcome name="Cahal" />
<Welcome name="Edite" />
<Welcome name="Everyone" />
</div>
);
}
export default App;
親要素からは、タグについているプロパティがPropsとして渡ってくるので、その内容が合ってないとエラーになります。
例えば、Welcomeのnameプロパティをcheckプロパティに書き換えてみます。
<Welcome check="Sara" />
当然のことながら(?)コンパイル時点で次のようなエラーメッセージが出て終わります。
TypeScript error in src/App.tsx(21,8):
Type '{ check: string; }' is not assignable to type 'IntrinsicAttributes & WelcomeProps'.
Property 'check' does not exist on type 'IntrinsicAttributes & WelcomeProps'. TS2322
プロパティ 'check' は、WelcomePropsに含まれていないので、事前チェックに引っかかります。
型チェックがあることでここで止まってくれますが、型チェックされる機会がないと「なぜか動かない」がまれによくあり、見つけたときの「なんでこんなところ間違ってんだ…」に悩まされずに済むのではないかと思います。
コピペするとよくあるアレです。
もう少しだけ進んだ要素
次に、stateとライフサイクル に進みます。
チュートリアルでは時計を作り続けていますが、使っているファイルはそのままHello, Worldです。
特に意味はありません。
propsを設定するのはわかったので、次はstateを使ってみようと軽い気持ちで修正をいれました。
動作イメージとしては
- Welcomeのnameプロパティに何も設定しなかったときにeveryOneといれて、適当にお茶を濁す
- そうでなければ、もらったプロパティをそのまま名前として設定する
です。そのような動作を付け加えました。
import React from 'react';
// import logo from './logo.svg';
import './App.css';
interface WelcomeProps {
name: string;
}
interface WelcomeState {
name: string;
}
class Welcome extends React.Component<WelcomeProps> {
constructor(props: WelcomeProps) {
super(props);
this.state = {name: props.name ? props.name : 'everyOne', };
}
render() {
return <h1>Hello, {this.state.name}</h1>;
}
}
const App: React.FC = () => {
return (
<div className="App">
<Welcome name=""/>
<Welcome name="Cahal" />
<Welcome name="Edite" />
<Welcome name="Everyone" />
</div>
);
}
export default App;
TypeScript error in src/App.tsx(27,35):
Property 'name' does not exist on type 'Readonly<{}>'. TS2339
動きません。
WelcomeStateなんていう、stateに使いそうな定義まで書いてるのに!
とはいえ、「書けばいいんじゃないかな」くらいの話で雰囲気で書いてます。
そして、理解しないまま書いている以上、このエラーメッセージの意味がわかっていないので、リファレンス調べました。
クラス化するときに渡せるパラメータは、2つあって、それはどうやらプロパティ(props)とステート(state)ということらしいです。
class Welcome extends React.Component<WelcomeProps> {
このときの引数は、本来は
class Welcome extends React.Component<props,state> {
となるべきでした。
しかし、このとき書いた内容は、stateが省略されていたので、代わりに{}(空オブジェクト)を指定してくれています。
エラーメッセージが「Readonly{}にはnameなんてプロパティないよ」ということで、そりゃ確かに空オブジェクトにnameなんていうプロパティはあるわけないなあとエラーの意味が理解できました。
次のように書き換えます。
class Welcome extends React.Component<WelcomeProps, WelcomeState> {
今度は問題なくコンパイルされて、画面も表示されています。
せっかく登録していたinterface WelcomeStateにも使い道がでてきてよかった。
残り部分
残りの部分は、現状それほど使う場所でもないので、斜め読みしながらまとめました。
そのうち必要になるとは思いますが、ここまでの内容を何度も見直しながらまとめたので、これ以上詰め込まなくてもいいやというのが本音。
ライフサイクルメソッド
今回は必須メソッドであるconstructorとrenderしか扱いませんでしたが、コンポーネントには他にも開始終了のタイミングで呼び出されるメソッドやいくつかのメソッドが存在します。
- componentDidMountはその要素がDOMツリーに追加されて描画が始まるときに呼ばれる
- componentWillUnmountはその要素がDOMツリーから削除されるときに呼ばれる
DOMツリーという言葉があるかわかりませんが、各クラスなどはすべてReactが作った土台というか幹から枝葉のようにぶらさがるイメージなので、このように表現しました。
それぞれ、初期化と後処理に使えるメソッドだけど、今のところは存在だけ覚えておきます。
AndroidのonCreateとonDestroyみたいなものだと考えて、onResumeやonPauseないのかはあとで調べます。
非同期、直接変更不可、マージ
stateに対する注意点は、とにかく変更するならばsetStateするしないとダメなようです。
特にブラウザ上で見るモノは描画以外に、データ通信など非同期で行われるイベントがたくさんあるので、同期を取っているとレスポンスが悪くなります。
その辺りのこともあって、非同期処理が基本でさらには現在処理している部分以外でもstateにデータをくっつける可能性がある
まとめ
- Typescriptは、interfaceで型定義できるので、そちらをちゃんと記述する
- エラーが出てもメッセージをよく読む
- どんな引数を取るのか知らべるならば、リファレンスを調べてみる
- ドキュメントのAPI REFERENCE
- state使って処理するならば、なにがあってsetStateを使う
次は、イベント処理からですが、続くかどうかはわかりません。