LoginSignup
36
30

More than 5 years have passed since last update.

TypeScriptでReactを書いてみて困ったところ

Posted at

最近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;
36
30
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
36
30