TypeScriptでReact 3

https://qiita.com/y_ohr/items/7c6e9264a8b1f16651d2 の続き。

TypeScriptでReactアプリを作成します。

# 環境
```shell
$ yarn --version
1.12.3
$ create-react-app --version
2.1.8
```

# 更新可能なinput作成

前回の最後で作成したinputを、更新可能にします。

```tsx:src/index.tsx
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';

ReactDOM.render(<App bar="Hello!" />, document.getElementById('root'));
```

`App.tsx`は`diff`で。

```diff:src/App.tsx
diff --git a/src/App.tsx b/src/App.tsx
index 14af72f..833f91e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -22,14 +22,31 @@ class App extends React.Component<Foo, {}> {
         this.state = {
             list: this.expressions
         };
+
+        this.handleXChange = this.handleXChange.bind(this);
+        this.handleYChange = this.handleYChange.bind(this);
+    }
+
+    private handleXChange(i: number, e: any) {
+        this.expressions[i].x = parseInt(e.target.value);
+        this.setState({
+            list: this.expressions
+        });
+    }
+
+    private handleYChange(i: number, e: any) {
+        this.expressions[i].y = parseInt(e.target.value);
+        this.setState({
+            list: this.expressions
+        });
     }

     render() {
         return this.expressions.map((d, i) =>
             <div key={d.n}>
-                <input type="number" value={d.x} />
+                <input type="number" value={d.x} onChange={(e) => this.handleXChange(i, e)} />
                 <span>+</span>
-                <input type="number" value={d.y} />
+                <input type="number" value={d.y} onChange={(e) => this.handleYChange(i, e)} />
                 <span>=</span>
                 <input type="number" value={d.x + d.y} />
             </div>
```

# VSCodeでデバッグ

少し脱線して、デバッグ設定。
`.vscode/launch.json`を追加。

```json:/.vscode/launch.json
{
    // IntelliSense を使用して利用可能な属性を学べます。
    // 既存の属性の説明をホバーして表示します。
    // 詳細情報は次を確認してください: https://go.microsoft.com/fwlink/?linkid=830387
    "version": "1.2.0",
    "configurations": [
        {
            "type": "chrome",
            "request": "launch",
            "name": "Launch Chrome against localhost",
            "url": "http://localhost:3000",
            "webRoot": "${workspaceFolder}"
        }
    ]
}
```

`tsconfig.json`にて`"sourceMap": true`を追加。

```diff:tsconfig.json
diff --git a/tsconfig.json b/tsconfig.json
index 377f514..0219ac3 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -17,9 +17,10 @@
     "resolveJsonModule": true,
     "noEmit": true,
     "jsx": "preserve",
-    "isolatedModules": true
+    "isolatedModules": true,
+    "sourceMap": true
   },
   "include": [
     "src"
   ]
-}
+}
```

ブレークすればデバッグ成功。
![image.png](https://qiita-image-store.s3.amazonaws.com/0/215001/e11e8715-e6ec-34b6-c200-f2cd54c193a5.png)

参考：https://code.visualstudio.com/docs/typescript/typescript-debugging#_clientside-debugging

# enum,interface,abstract class

せっかくなので、なるべく型を使うようにして実装します。
式の演算子を`enum`化。

```tsx:src/App.tsx
enum Operator {
    plus = "+",
    minus = "-",
    times = "×",
    divide = "÷",
}
```

`interface`に上記`enum`追加。

```tsx:src/App.tsx
interface IExpression {
    n: number;
    x: number;
    o: Operator;
    y: number;
    Caluculate(): number;
}
```

`abstract class`で上記`interface`を実装。`setter`,`getter`でもいけた。

```tsx:src/App.tsx
abstract class ExpressionBase implements IExpression {
    private _n: number = 0;
    get n(): number { return this._n; }
    set n(value: number) { this._n = value; }

    private _x: number = 0;
    get x(): number { return this._x; }
    set x(value: number) { this._x = value; }

    private _o: Operator = Operator.plus;
    get o(): Operator { return this._o; }
    set o(value: Operator) { this._o = value; }

    private _y: number = 0;
    get y(): number { return this._y; }
    set y(value: number) { this._y = value; }

    public constructor(n: number, x: number, o: Operator, y: number) {
        this._n = n;
        this._x = x;
        this._o = o;
        this._y = y;
    }

    public Caluculate(): number {
        switch (this.o) {
            case Operator.plus: return this.x + this.y; break;
            case Operator.minus: return this.x - this.y; break;
            case Operator.times: return this.x * this.y; break;
            case Operator.divide: return this.x / this.y; break;
            default: return 0;
        }
    }
}
```

実体classは今のところ空。

```tsx:src/App.tsx
class Expression extends ExpressionBase { }
```

# 全文

```tsx:src/App.tsx
import React from "react";

type Foo = {
    bar: string;
}

enum Operator {
    plus = "+",
    minus = "-",
    times = "×",
    divide = "÷",
}

interface IExpression {
    n: number;
    x: number;
    o: Operator;
    y: number;
    Caluculate(): number;
}

abstract class ExpressionBase implements IExpression {
    private _n: number = 0;
    get n(): number { return this._n; }
    set n(value: number) { this._n = value; }

    private _x: number = 0;
    get x(): number { return this._x; }
    set x(value: number) { this._x = value; }

    private _o: Operator = Operator.plus;
    get o(): Operator { return this._o; }
    set o(value: Operator) { this._o = value; }

    private _y: number = 0;
    get y(): number { return this._y; }
    set y(value: number) { this._y = value; }

    public constructor(n: number, x: number, o: Operator, y: number) {
        this._n = n;
        this._x = x;
        this._o = o;
        this._y = y;
    }

    public Caluculate(): number {
        switch (this.o) {
            case Operator.plus: return this.x + this.y; break;
            case Operator.minus: return this.x - this.y; break;
            case Operator.times: return this.x * this.y; break;
            case Operator.divide: return this.x / this.y; break;
            default: return 0;
        }
    }
}

class Expression extends ExpressionBase { }

class App extends React.Component<Foo, {}> {
    private expressions: Expression[] = [
        new Expression(0, 0, Operator.plus, 1),
        new Expression(1, 2, Operator.minus, 3),
        new Expression(2, 4, Operator.times, 5),
        new Expression(3, 6, Operator.divide, 7),
    ];

    public constructor(props: any) {
        super(props);
        this.state = {
            list: this.expressions
        };

        this.handleXChange = this.handleXChange.bind(this);
        this.handleOChange = this.handleOChange.bind(this);
        this.handleYChange = this.handleYChange.bind(this);
    }

    private handleXChange(i: number, e: any): void {
        this.expressions[i].x = e.target.value as number;
        this.setState({
            list: this.expressions
        });
    }

    private handleOChange(i: number, e: any): void {
        this.expressions[i].o = e.target.value as Operator;
        this.setState({
            list: this.expressions
        });
    }

    private handleYChange(i: number, e: any): void {
        this.expressions[i].y = e.target.value as number;
        this.setState({
            list: this.expressions
        });
    }

    public render(): JSX.Element[] {
        return this.expressions.map((d, i) =>
            <div key={d.n}>
                <input type="number" value={d.x} onChange={(e) => this.handleXChange(i, e)} />
                <select value={d.o} onChange={(e) => this.handleOChange(i, e)}>
                    <option value={Operator.plus}>{Operator.plus}</option>
                    <option value={Operator.minus}>{Operator.minus}</option>
                    <option value={Operator.times}>{Operator.times}</option>
                    <option value={Operator.divide}>{Operator.divide}</option>
                </select>
                <input type="number" value={d.y} onChange={(e) => this.handleYChange(i, e)} />
                <span>=</span>
                <input type="number" value={d.Caluculate()} />
            </div>
        )
    }
}

export default App;
```

動作確認。

![image.png](https://qiita-image-store.s3.amazonaws.com/0/215001/72d70be0-fcea-aefb-7572-5e956d6674cf.png)


# 感想

- インターフェースは`public`のみ
- `getter`は`get name()`、`setter`は`set name()`
- `constructor()`は戻り値型指定不可。`void`も。
- リテラルでオブジェクトを作ると、メソッドも値も求められた
- 配列の要素の値を更新して`setState()`すると、全てレンダリングされている模様。更新された要素のみレンダリングできないか？
- データを外部から渡したい

以上