React.jsのコードをTypeScript化する
割と簡単にTypeScript化できることが分かりましたので、既存プロジェクトのReact.jsコードに適用してみました。
開発環境
- laravel 5.7
- react 16.2
- typescript 3.3
パッケージのインストール
TypeScript関連のパッケージをインストールします。
$ cd laradock/
$
$ docker-compose up -d nginx mysql
Starting laradock_mysql_1 ... done
Starting laradock_docker-in-docker_1 ... done
Starting laradock_workspace_1 ... done
Starting laradock_php-fpm_1 ... done
Starting laradock_nginx_1 ... done
$
$ docker exec -it laradock_workspace_1 /bin/bash
$
$ npm install --save-dev typescript ts-loader react react-dom @types/react @types/react-dom
$
tsconfig.jsonの生成
プロジェクト配下のnode_modulesにtscコマンドがあれば実行します。直接ファイル作成でも大丈夫です。
$ ./node_modules/.bin/tsc --init
プロジェクト直下に生成されたtsconfig.jsonを以下のように編集します。
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"jsx": "react",
"strict": true,
"experimentalDecorators": true,
"allowJs": true,
},
"exclude": [
"node_modules",
"vendor"
]
}
webpack.mix.jsの修正
既存のlaravelのwebpack.mix.jsファイルを以下のように修正します。
const mix = require('laravel-mix');
mix.react('resources/ts/app.tsx', 'public/js')
.sass('resources/sass/app.scss', 'public/css')
.webpackConfig({
module: {
rules: [
{
test: /\.tsx?$/,
loader: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['*', '.ts', '.tsx'],
},
});
ディレクトリ名、ファイル名の変更
各名称および拡張子をjsからtsに変更します。
変更前
myproject/
└ resources
└ js
├ components
│ └ Article.jsx
├ models
│ └ Todo.js
└ app.js
変更後
myproject/
└ resources
└ ts
├ components
│ └ Article.tsx
├ models
│ └ Todo.ts
└ app.ts
既存のコードに型を追加
TypeScriptの形式に合わせて.ts、.tsxのコードに型を追加していきます。any型はできる限り使わないようにしましたが、使わざるを得ない場面もあると思います。
以下のような感じで型を追加します。
// 修正前:
title = ''
finished = false
constructor(title) {
this.title = title
}
// ↓ ↓ ↓ ↓
// 修正後:
title: string = ''
finished: boolean = false
constructor(title: string) {
this.title = title
}
サンプルコードを置いておきますので参考になれば幸いです。修正前のTodoApp.jsxについては以下で詳しく説明されています。
https://qiita.com/zaburo/items/fbcdd73d8d707357c25f
TodoApp.jsx(修正前)
import React, { Component } from 'react';
import ReactDOM from 'react-dom';
const axios = require('axios');
function RenderRows(props) {
return props.todos.map(todo => {
return (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.title}</td>
<td><button className="btn btn-secondary" onClick={() => props.deleteTask(todo)}>完了</button></td>
</tr>
);
});
}
export default class TodoApp extends Component {
constructor() {
super();
this.state = {
todos: [],
todo: ''
}
this.inputChange = this.inputChange.bind(this);
this.addTodo = this.addTodo.bind(this);
this.deleteTask = this.deleteTask.bind(this);
}
componentDidMount() {
axios
.get('/api/get')
.then((res) => {
this.setState({
todos: res.data
});
})
.catch(error => {
console.log(error)
})
}
inputChange(event){
switch(event.target.name){
case 'todo':
this.setState({
todo: event.target.value
});
break;
default:
break;
}
}
addTodo(){
if(this.state.todo == ''){
return;
}
axios
.post('/api/add', {
title: this.state.todo
})
.then((res) => {
this.setState({
todos: res.data,
todo: ''
});
})
.catch(error => {
console.log(error);
});
}
deleteTask(todo){
axios
.post('/api/del', {
id: todo.id
})
.then((res) => {
this.setState({
todos: res.data
});
})
.catch(error => {
console.log(error);
});
}
render() {
return (
<React.Fragment>
{/* add from */}
<div className="form-group">
<label htmlFor="todo">新規Todo</label>
<input type="text" className="form-control" name="todo" value={this.state.todo} onChange={this.inputChange}/>
</div>
<button className="btn btn-primary" onClick={this.addTodo}>登録</button>
{/* table */}
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>タスク</th>
<th>完了</th>
</tr>
</thead>
<tbody>
{/* 行の描画 */}
<RenderRows
todos={this.state.todos}
deleteTask={this.deleteTask}
/>
</tbody>
</table>
</React.Fragment>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById('todoApp'));
TodoApp.tsx(修正後)
// import React, { Component } from 'react';
// import ReactDOM from 'react-dom';
import * as React from 'react';
import * as ReactDOM from 'react-dom';
const Axios = require('axios');
// import Axios from 'axios';
interface TodoAppProps {
}
interface TodoAppState {
todos: [{id:string, title:string}],
todo: ''
}
function RenderRows(props: any) {
return props.todos.map((todo: {id: string, title: string}) => {
return (
<tr key={todo.id}>
<td>{todo.id}</td>
<td>{todo.title}</td>
<td><button className="btn btn-secondary" onClick={() => props.deleteTask(todo)}>完了</button></td>
</tr>
);
});
}
export default class TodoApp extends React.Component<TodoAppProps, TodoAppState> {
constructor(props: TodoAppProps) {
super(props);
this.state = {
todos: [{id: '', title: ''}],
todo: ''
}
this.inputChange = this.inputChange.bind(this);
this.addTodo = this.addTodo.bind(this);
this.deleteTask = this.deleteTask.bind(this);
}
componentDidMount() {
Axios
.get('/api/get')
.then((res: { data: [{ id: string, title: string }] }) => {
this.setState({
todos: res.data
});
})
.catch((error: string) => {
console.log(error)
})
}
inputChange(event: any){
switch(event.target.name){
case 'todo':
this.setState({
todo: event.target.value
});
break;
default:
break;
}
}
addTodo(){
if(this.state.todo === ''){
return;
}
Axios
.post('/api/add', {
title: this.state.todo
})
.then((res: { data: [{ id: string, title: string }] }) => {
this.setState({
todos: res.data,
todo: ''
});
})
.catch((error: string) => {
console.log(error);
});
}
deleteTask(todo: { id: string, title: string }){
Axios
.post('/api/del', {
id: todo.id
})
.then((res: { data: [{ id: string, title: string }] }) => {
this.setState({
todos: res.data
});
})
.catch((error: string) => {
console.log(error);
});
}
render() {
return (
<React.Fragment>
{/* add from */}
<div className="form-group">
<label htmlFor="todo">新規Todo</label>
<input type="text" className="form-control" name="todo" value={this.state.todo} onChange={this.inputChange}/>
</div>
<button className="btn btn-primary" onClick={this.addTodo}>登録</button>
{/* table */}
<table className="table">
<thead>
<tr>
<th>ID</th>
<th>タスク</th>
<th>完了</th>
</tr>
</thead>
<tbody>
{/* 行の描画 */}
<RenderRows
todos={this.state.todos}
deleteTask={this.deleteTask}
/>
</tbody>
</table>
</React.Fragment>
);
}
}
ReactDOM.render(<TodoApp />, document.getElementById('todoApp'));
まとめ
コーディング時に型エラーをチェックしてくれるTypeScriptは大変便利ですので、できるかぎりJavaScriptから置き換えていきたいところです。
コメントを残す