最近angularのためにtsの勉強をしていたので、いきなりangularをやるより慣れ親しんだreactでかるくかいてみようということで。
そしたら、僕の知っているreactではないreactもいたので困ったところをまとめていきたいと思います。
React.Componentを継承するにはジェネリクスを使う必要がある
ほうほう、ジェネリクスは勉強したぞってことでここはそんなに困らなかった、、と思いきや、propsがないときはどうしたらいいんだろうとなりました。
class Todo extends React.Component<{}, TodoState> {
}
{}
を渡してあげればいいだけでした。簡単。TodoState
でinterfaceを渡してやるとあとはいい感じにしくれます。
constructorには引数が必須
constructor(props: any) {
super(props);
...
}
普通にreactを書くときはsuperに引数はあってもなくも問題ありませんでしたが、tsでは必須のようです。
今回はpropsがないのでany
にしてありますが、本来はTodoProps
に当たるinterfaceで型を指定するのがいいでしょう。
inputの値が取得できない
reactならおきまりのe.target.value
ですが、これを書いてみたところうまく取得できませんでした。
というかそもそも、eの型ってなんやねんってなり調べてみると、、
React.FormEvent<HTMLInputElement>
型みたいです。なので、
private handleChnage(e: React.FormEvent<HTMLInputElement>) {
this.setState({ value: e.currentTarget.value });
}
ってしてやると、e.target.value
と同じ挙動になりました。ジェネリクスでHTMLInputElement
を指定してやらないとこれまたコンパイルエラーを吐かれます。
動的なObjectのkeyを設定できない(やり方がややこしいっぽい)
private handleChnage(e: React.FormEvent<HTMLInputElement>) {
this.setState({ [e.currentTarget.value]: e.currentTarget.value });
}
としたいところですが、これだとエラーを吐かれます。うまいことやればいけるようですが、それならこのままでいいかなってことで今回は変更してません。
jsxの型は何?
reactといえばjsxで書くのが一般的ですよね。これを変数に、、と思ったところで、はて、これの型は一体?となりました。
調べてみると、JSX.Element
だそうです。思ったより普通w
例えば、jsxの配列を返す関数は以下のように宣言できます。
private todoList(): JSX.Element[] {
return this.state.todos.map(t => <li key={t.id}>{t.content}</li>);
}
シンプルです。笑
分割代入における型宣言
reactではthis.state...
とかthis.props...
とか長ったるいのが多いので、分割代入で、
const { todos, value } = this.state;
とすることが多いです。では、このときどうやって型宣言をするんでしょう。
調べてみると、
const { todos, value }: TodoState = this.state; // TodoStateはinterface
としたらいいみたいです。
interfaceを使わずにもかけるんですが、
const { todos, value }: { todos: ITodo[]; value: string } = this.state;
と長くなってしまうのでよくありません。(そもそもこのコードでもinterfaceが使われている。)
なのでinterfaceを使ってスマートに書くのがいいでしょう。
そもそも、型推論に任せてしまってもいい気はしますが、、笑
classとinterfaceで名前が衝突しないように注意!
interface TodoState {
todos: Todo[];
value: string;
}
interface Todo {
id: number;
content: string;
}
class Todo extends React.Component<{}, TodoState> {
...
}
最初このように書いていたのですが、これだとTodoが競合してどこかでおかしくなります。
僕の場合は、
const { todos, value }: TodoState = this.state;
const todo: Todo = {
id: todos.length,
content: value
};
としたところ、Todoクラスに必要なメソッドがないよと言われて???となりました。コンパイラも競合してることを教えてくれたらいいのに、、
interface TodoState {
todos: ITodo[];
value: string;
}
interface ITodo {
id: number;
content: string;
}
のように、名前を変えることで解決しました。
通りで他のプロジェクトとかをみていると接頭辞のIが付いていたのか、、w
Iをつけるかnamespaceを分けるといいのかも。
Todoアプリのサンプル
最後に僕が作ったTodoアプリのサンプルを掲載しておきます。
import * as React from "react";
interface TodoState {
todos: ITodo[];
value: string;
}
interface ITodo {
// ここをTodoにするとコンンポーネントと名前が衝突する。
id: number;
content: string;
}
class Todo extends React.Component<{}, TodoState> {
constructor(props: any) {
super(props);
this.state = { todos: [], value: "" };
}
private handleChnage(e: React.FormEvent<HTMLInputElement>) {
this.setState({ value: e.currentTarget.value });
}
private addTodo() {
const { todos, value }: TodoState = this.state;
const todo: ITodo = {
id: todos.length,
content: value
};
this.setState({ todos: todos.concat(todo), value: "" });
}
private todoList(): JSX.Element[] {
return this.state.todos.map(t => <li key={t.id}>{t.content}</li>);
}
render() {
return (
<div>
<input
type="text"
name="value"
value={this.state.value}
onChange={e => this.handleChnage(e)}
/>
<button onClick={() => this.addTodo()}>add</button>
<ul>{this.todoList()}</ul>
</div>
);
}
}
export default Todo;