React + Typescriptを初学者向けに書いてみました。
始める前に
・基本的なTypescriptの知識
・React Hooks
が分かっていることが望ましいです。
React + Typescriptの環境構築
以前に npm install -g create-react-app を使ってグローバルに create-react-app をインストールしたことがある場合、npxが常に最新バージョンを使用するようにするために、 npm uninstall -g create-react-app を使ってパッケージをアンインストールすることをお勧めします。
create-react-app のグローバルインストールはサポートされなくなりました。
npx create-react-app my-app --template typescript
// my-appの部分は任意の名前でOK
yarn start
で、ローカル環境で動くか確認。
公式が提供している為、ダウンロード後すぐに動かせられるのが嬉しい。
動き始めたら、
App.css
App.test.tsx
logo.svg
serviceWorkers.tsx
を削除
index.tsx
App.tsx
を以下のように変更。
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
ReactDOM.render(
<React.StrictMode>
<App />
</React.StrictMode>,
document.getElementById('root')
);
import React from 'react';
function App() {
return (
<div className="App">
</div>
);
}
export default App;
これで真っ白な画面になったかと思いますので、ここから実際に実装してみたいと思います。
Todo.tsxの導入
まずは、src配下に/components/Todoディレクトリを作成。
import React from 'react';
const Todo = () => {
return <ul>これはTodo.tsxから来ています。</ul>
}
export default Todo;
これでTodoコンポーネントをエクスポートできるので、App.tsxに取り込みます。
import React from 'react';
import Todo from './components/Todo';
//React.FC = React.FunctionalComponentの略。
const App: React.FC = () => {
return (
<div className="App">
<Todo />
</div>
);
}
export default App;
画面はこんな感じ

Todo.tsxで型を定義する
Todo.tsxに型を実装していきます。
string値を持つidとstring値を持つtextを、TodoPropsの中に定義します。
import React from 'react';
// interfaceは、新しい型を定義する為に使います。
interface TodoProps {
items: {id: string, text: string}[]
};
const Todo: React.FC<TodoProps> = props => {
return (
<ul>これはTodo.tsxから来ています。</ul>
)
}
export default Todo;
App.tsxはまだいじっていないので、コンソール上だとこんなエラー。
//App.tsx
Property 'items' is missing in type '{}' but required in type 'TodoProps'.
TodoPropsでitemsという名前のプロパティを使うなら、App.tsx内で定義して欲しい!と言っています。
なので、App.tsxを次のように変更。
import React from 'react';
import Todo from './components/Todo';
const App: React.FC = () => {
const todo = [{id: 'タスク1', text: 'ご飯を作る'}]
return (
<div className="App">
<Todo items={todo} />
//itemsは、{todo}
</div>
);
}
export default App;
これで、エラーが消えましたが、描画されるのは
<ul>これはTodo.tsxから来ています。</ul>
部分なので、こちらをmap関数を使って、変更します。
import React from 'react';
interface TodoProps {
items: {id: string, text: string}[]
};
const Todo: React.FC<TodoProps> = props => {
return (
<ul>
{props.items.map(list => (
<li key={list.id}>{list.text}</li>
))}
</ul>
)
}
export default Todo;
とすれば、textで書いた
ご飯を作る
という文字列が描画されるかと思います。また、
items: {id: string, text: string}
とitemsにはnumberではなくstring値を指定しているので、
const todo = [{id: 11, text: 'ご飯を作る'}]
と、idに数字を渡すと、以下の画像のように

エラーを吐いてくれるので、安全なReactでの実装が出来ます。
ここからは、Hooksを組み合わせたコードを書いてみます。
useRefコンポーネントを使ってコンソール上にテキストを出力させる
React HooksのuseRefを使って、Todoリストを作ってみます。
まずは、以下のように新しいファイルを定義。
import React from 'react';
import Todo from './components/Todo';
import AddTodo from './components/AddTodo';
const App: React.FC = () => {
const todo = [{id: 'タスク1', text: 'ご飯を作る'}]
return (
<div className="App">
<AddTodo />
<Todo items={todo} />
</div>
);
}
export default App;
components配下にはAddTodo.tsxを作ります。中身はこんな感じにします。
import React, { useRef } from 'react';
const AddTodo: React.FC = () => {
// ()でundefined指定することはuseRefは出来ないので、初期値はnull。
const textInputRef = useRef<HTMLInputElement>(null);
// フォーム内で何かがsubmitされたら、useRefが発火する
const todoSubmitHandler = (event: React.FormEvent) => {
// preventDefaultでDOMをレンダーする事を防ぐ。
// これが無いと、inputを押した瞬間に再レンダーされ、入力した内容が消えてしまいます。
event.preventDefault();
// 後述
const enteredText = textInputRef.current!.value;
console.log(enteredText);
};
return (
<form onSubmit={todoSubmitHandler}>
<div>
<label htmlFor="todo-text">Todo Text</label>
{/* ref {textInputRef}を追加して、レンダーされる場所を明記 */}
<input type="text" id="todo-text" ref={textInputRef} />
</div>
<button type="submit">ADD todo</button>
</form>
);
};
export default AddTodo;
AddTodo.tsxの解説
const enteredText = textInputRef.current!.value;
console.log(enteredText);
の部分ですが、textInputRef の部分にカーソルを当てると
const textInputRef: React.RefObject<HTMLInputElement>
と、VSCode上で表示してくれます。これは、return内のinput要素(HTMLInputElement)と接続していることが分かります。しかし、textInputRefを定義した際に、
const textInputRef = useRef<HTMLInputElement>(null);
と、初期値をnullの状態にしたので、例えば
const enteredText = textInputRef.current.value;
// error オブジェクトは 'null' である可能性があります。
currentの!マークを外すとTypeScript側から怒られてしまいます。
そのため、nullでも大丈夫である事をTypeScriptに伝えるために、!マークをつける必要があります。
実行するとこんな感じになります。
inputElement内に"宿題をする"入力
↓
ADD todoボタン押下
↓
consoleに表示
useStateを使って、コンソールから画面に反映させる
コメントアウトした部分に解説を書きました。
import React, { useState } from 'react';
import Todo from './components/Todo';
import AddTodo from './components/AddTodo';
// modelの導入は、可読性を上げるため(後述)
import { ToDo } from './todo.model';
const App: React.FC = () => {
// ToDoの中身がidとtextなので、ここでは[]を代入。
const [todos, setTodos] = useState<ToDo[]>([]);
const todoAdd = (text: string) => {
setTodos(prevTodos => [
...prevTodos,
{ id: Math.random().toString(), text: text }
// これにより、{id: "0.8759291112629384", text: "宿題をする"}のようなデータをもつテキストが生成される。
]);
};
return (
<div className="App">
// AddTodo.tsxから渡されたテキストデータがここに入る
<AddTodo todoAdded={ todoAdd } />
<Todo items={todos} />
</div>
);
}
export default App;
次にsrc配下にtodo.model.tsファイルを作成する。
(作らなくても良いのですが、可読性を上げるために、モデルファイルを作ります。)
export interface ToDo {
id: string;
text: string;
}
todo.model.tsを作らない場合、
const [todos, setTodos] = useState<{id: string; text: string}[]>([]);
と、useStateの後ろの部分が冗長化してしまいます。モデル化をしておく事で、読みやすくなるので覚えておくと良いかと思います!ちなみに読み込み速度は、このような影響範囲になるそうです。
TypeScriptのモデル生成速度比較
次にAddTodo.tsxを編集します。
import React, { useRef } from 'react';
// typeで型付けと継承をしています。
type AddTodoProps = {
todoAdded: (todoText: string) => void;
}
const AddTodo: React.FC<AddTodoProps> = props => {
const textInputRef = useRef<HTMLInputElement>(null);
const todoSubmitHandler = (event: React.FormEvent) => {
event.preventDefault();
const enteredText = textInputRef.current!.value;
// AddTodoProps関数のtodoAddedに入力されたテキストを渡している。
props.todoAdded(enteredText);
};
return (
<form onSubmit={todoSubmitHandler}>
<div>
<label htmlFor="todo-text">Todo Text</label>
<input type="text" id="todo-text" ref={textInputRef} />
</div>
<button type="submit">ADD todo</button>
</form>
);
};
export default AddTodo;
完成
上記のような実装にすると、以下の画像のように出力されます。
(ChromeのReactDevloperTools拡張を使用。)
削除機能は、filterを使って組み込めるので、よければ挑戦してみてください。
まとめ
HooksとTSを使って、Todoアプリを作ってみました。
小規模なアプリケーションであれば、TSが無くとも開発速度に影響は出にくいと思いますが、これが大規模開発になればなるほど、重要性が高まるんだなと。
んー、TS偉大。
参考
英語ですが。
Understanding TypeScript - 2020 Edition
https://www.udemy.com/course/understanding-typescript/