React with TypeScript さらっと一通り知ってみる
プロジェクトの作成
create-react-app
公式サイトにあるとおりに以下のコマンドで雛形を作ります。
$ npx create-react-app my-app --template typescript $ cd my-app
eslint の導入
下記の記事の通り eslint と Prettier も入れておきます。この記事では詳細は省いて実施した内容だけ書きます。
$ npx eslint --init
npx eslint --init
では、「To check syntax, find problems, and enforce code style」を選びました。私はチェック方法に Airbnb を選びましたが、任意のものを選べばOKです。 TypeScript の利用は y (=yes) としています。
Prettier の導入
Prettier をインストールします。それと、テスティングフレームワークの Jest で警告がでないようにモジュール追加します。
$ npm install ---save-dev prettier eslint-config-prettier eslint-plugin-prettier $ npm install --save-dev eslint-plugin-jest
$ npm run xxx
のようにして使えるコマンドに $ npm run lint
を追加します。この後、ファイル保存時に自動で lint が掛かる設定を入れるので、これは無くても問題はないです。
// package.json { // 中略 "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", "eject": "react-scripts eject", "lint": "eslint src --ext .ts,.tsx" // <=ADD(eslint の実行) }, // 中略 }
eslint の設定
eslint の設定ファイルを変更します。モジュールを動かすための追加行は // <=ADD
と記載してます。警告を変更したいための追加行は // <=ADD(不要な警告抑止)
と記載してます。
// .eslintrc.js module.exports = { env: { browser: true, es6: true, 'jest/globals': true, // <=ADD }, extends: [ 'plugin:react/recommended', 'airbnb', 'plugin:prettier/recommended', // <=ADD 'prettier/@typescript-eslint', // <=ADD ], // 中略 plugins: ['react', '@typescript-eslint', 'prettier', 'jest'],// <=ADD ※「, 'prettier', 'jest'」の部分 rules: { 'react/jsx-filename-extension': ['error', { extensions: ['.jsx', '.tsx'] }], // <=ADD(不要な警告抑止) 'import/extensions': [ 'error', { extensions: ['.js', '.jsx', '.json', '.ts', '.tsx'] }, ], // <=ADD(不要な警告抑止) 'spaced-comment': ['error', 'always', { markers: ['/ <reference'] }], // <=ADD(不要な警告抑止) 'prettier/prettier': 'error', // <=ADD }, settings: { 'import/resolver': { node: { extensions: ['.js', '.jsx', '.ts', '.tsx'], }, }, }, // <=ADD(不要な警告抑止) };
eslint から Prettier を呼び出せるように設定されました。 Prettier の設定は上記ファイルにも書けますが、何が Prettier の設定かわかりにくくなりますので .prettierrc
に書くことにしました。 Prettier のデフォルトは文字列リテラルがダブルクォートになります。シングルクォートにしたいのでその設定を入れておきます。
// .prettierrc { "singleQuote": true, }
VSCode の設定
VSCode の設定です。セーブ時に eslint のフォーマッターを掛けます。eslint は Prettier に関連づいているので、 Prettier の設定も反映されます。
// .vscode/setting.json { "editor.codeActionsOnSave": { "source.fixAll.eslint": true }, "eslint.format.enable": true }
自動生成される src\serviceWorker.ts
は警告が出てしまいます。そこで、一行目に警告は出さなくて良いよと追記します。自動生成されるソースですので、今回は極力変更せずに警告抑制だけの対応としました。
// src\serviceWorker.ts // 1行目に以下を追加 /* eslint-disable no-console, no-use-before-define, no-param-reassign */
これで npm run lint
を実行します。改行コードやフォーマットで警告が出るので、これらは対処します。
参考
今回の設定で特に参考になったサイトは以下でした。
以下は eslint の公式サイト。コメントで警告を抑制する件は「Disabling Rules with Inline Comments」の項にあります。
基本の書き方
TSX を記述する
JavaScript に HTML タグ的なものを埋め込めるのが JSX です。TypeScript の場合は TSX と呼ばれ、拡張子も .tsx
になります。
ここでは、App.tsx を編集して動きを確認してみることにします。
以下のように書き換えて、 $ npm start
で開発サーバを立ち上げ、ブラウザで表示を確認してみます。
// src/App.tsx import React from 'react'; import './App.css'; type Item = { id: number; title: string; }; function App() { const items: Item[] = [ { id: 1, title: 'item1' }, { id: 2, title: 'item2' }, { id: 3, title: 'item3' }, ]; return ( <div className="App"> <ul> {items.map((item) => ( <li key={item.id}>{item.title}</li> ))} </ul> </div> ); } export default App;
上記のソースはオブジェクトの配列をリスト表示する例です。
items
というオブジェクトの配列を定数として用意し、 <li>
の一覧を出力します。
オブジェクトには type
で型を用意します。 type
は型や型の組み合わせに別名をつける TypeScript の構文です。今回は id
, title
という要素を持つ Item
という型を作っています。
const items: Item[] = ...
は、 Item
の配列(= Item[]
)を型とする items
という定数を宣言しています。
関数の return
に、出力するHTMLを記述できます。この中で処理(式、変数、など)を使う場合は {}
の中に記述します。 <ul>
の中で items
を繰り返し <li>
に変換して出力するプログラムになっています。
配列.map((要素) => 処理)
で 配列
を一つひとつ処理するプログラムが書けます。 要素
に対する処理を 処理
に記述できます。 items
を取り出した結果が item
で、それから <li key={item.id}>{item.title}</li>
というHTMLタグを作り出しています。
ちょっと特殊な話になりますが、 <li>
や <tr>
のような繰り返し出力するタグの場合、Reactから key
という属性を記述することを求められます。これは React の仕様で、 key
は必ず一意になる値である必要があります。以下の例では、オブジェクトに id
を用意し、これを設定しています。
関数コンポーネント
新しいタグを作り、更に別ファイルにしてみます。
タグは関数かクラスで作ることができます。ここでは関数で作る関数コンポーネントの話をします。最初の例にあった App
も関数コンポーネントでしたが、次の例は、関数コンポーネントを新規に作り、そしてファイルを分割する方法になります。
// src/components/Child.tsx import React from 'react'; type Props = { title: string; // children 以外はHTMLの属性のように利用できる children: React.ReactNode; // children はHTMLを受け取る場合の特殊な要素 }; const Child: React.FC<Props> = (props) => { return ( <div> <h1>{props.title}</h1> {props.children} </div> ); }; export default Child;
App
とは違い、関数をアロー関数の形式にしました。 function Child () { ... }
ではなく const Child = () => { ... }
にしています。
TypeScript なので、関数の引数と返却値には型が必要です。関数の返却値には React.FC
という型を利用しています。関数コンポーネント(FunctionComponent)のことで、React が TypeScript 用に用意してくれています。 React.FC
はジェネリクス( <>
の中)で引数の型を指定できる仕様になっています。
引数の型に使うため Props
という名前(名称は任意でOK)の type
を作成しています。この Props
でHTMLの属性のようなモノを作ります。 title
はHTMLの属性(のようなモノ)として使えます。 title
以外にも任意で増やすことができます。
children
は特殊な値でありキーワードです。この名称は、HTML要素を受け取るルールになります。 <Child title="1">2</Child>
と書いたときに、 1
が title
に、 2
が children
にあたります。 children
以外のスペルは使えない特殊なものだと考えてください。
最初なので Child
の引数は props
とあえて書きました。ですが eslint 的には非推奨のため警告がでます。以下のように訂正しておくと良いです。
const Child: React.FC<Props> = (title, children) => { // ... 中略 ... };
次に作成した Child
を利用する App.tsx
です。
// src/App.tsx import React from 'react'; import './App.css'; import Child from './components/Child'; // 別ファイルのコンポーネントを取り込む。拡張子は不要。 function App() { return ( <div className="App"> <Child title="from App."> <a href="https://www.google.com/">link</a> </Child> </div> ); } export default App;
title
は <Child>
タグの属性として使えるようになります。
children
は <Child>
と </Child>
に囲まれた部分を指します。以下の例では <a href="https://www.google.com/">link</a>
にあたります。
関数コンポーネントで状態を扱う(Hooks で State を使う)
React Hook の登場により、関数でも状態が持つことができるようになりました。
useState
は2つの値を返却する
React Hook で登場した useState
という機能を追加します。 import
文に React の useState
を利用する(= import React, { useState } from 'react';
)と追記します。
useState
は【状態】をひとつ作る機能です。作る【状態】は1つなのですが、2つの値を返却します。 useState
関数は、【状態】の初期値を引数にとり、「【状態】」と「その【状態】を変更する関数」を配列で返却してくれます。2つ返却されることを最初に頭に入れておくと理解がしやすくなります。
Hooks の実例
ソースでの記述内容を例に説明します。
import React, { useState } from 'react'; // , { useState } を追加しました import './App.css'; function App() { const [count, setCount] = useState(0); // Hook で Stateを作成する。 const handleIncrement = () => { setCount(count + 1); // useState で作った setCount を呼びだす。 count の値を変更できる。 }; return ( <div className="App"> <p>{count}</p> <button type="button" onClick={handleIncrement}> increment </button> </div> ); } export default App;
useState(0)
:[count, setCount]
を返却してくれる。0
は 状態( =count
)の初期値になる。[count, setCount]
count
: 状態。関数内では定数として利用できる。setCount
: 状態( =count
)を変更する関数。関数内で呼び出すことができる。
setCount(count + 1);
:count
をcount + 1
に変更する。
初期値が 0
のカウンタを作っています。
作られた【状態】であるカウンタは、 <p>{count}</p>
で表示されます。
ボタン <button type="button" onClick={handleIncrement}>increment</button>
をクリックするとカウントアップする仕組みになっています。 handleIncrement
という関数を定数で作っています。 const handleIncrement = () => { ... }
の部分です。この中で setCount(count + 1)
をして、カウンタの値をインクリメントしています。
なぜ Hooks が必要であるか
さて、なぜこんなまどろっこしいことをしなくてはならないのか。普通に関数内に const count = 0;
を宣言して count = count + 1;
でカウントアップできないのか。
React は画面描画するタイミングで関数が再実行されます。もし const count = 0;
として宣言をしていたら、ボタンを押して再描画するタイミングで再度 const count = 0;
が実行されます。つまり永遠に 0
になるわけです。
この問題を解決するには、関数とは別のところで【状態】を持つ必要があります。
方法は色々ありますが、 React Hooks はその解決方法のひとつになります。
Redux で状態を扱う
Hooks の限界
関数コンポーネントで状態を持つことができたのが Hooks でした。
例えば、Hooksで作った setCount
のような状態を変更する機能をいろんなコンポーネントで使いたくなったとします。その場合は、 setCount
を Props
の要素として渡していくことになります。
BaseComponent // count を表示する。 setCount を持っている。 ├ Child1Component // count を上げたい └ Child2Component └ GrandChildComponent // count を下げたい
極端ですが、上記の例の場合は setCount
を BaseComponent -> Child1Component
と BaseComponent -> Child1Component -> GrandChildComponent
の2つのルートで渡す必要があります。
BaseComponent
は useState
で [count, setCount]
を作ります。他のコンポーネントは Props
で setCount
を受け取ります。 Child2Component
なんかは count
も setCount
も関係ないのに、孫( GrandChildComponent
)に setCount
を渡すために Props
で setCount
を受け取る必要があるわけです。
これがReact界隈でよく言われるところの「バケツリレー」です。
アプリ全体で State 管理する Redux の登場
このバケツリレーが辛い場合、アプリ全体で State 管理してくれる機能が欲しくなります。 そのひとつが Redux です。
Redux を簡単に使える Redux-Toolkit の登場
この Redux が意外と厄介でわかりにくいものになっています。 概念を理解するのが大変で、それを実現するコードも複雑に感じられるものになっています。
で、概念だけは知っておく必要がありますが、コードは簡単に書きたい。そこで作られたのが Redux-Toolkit です。
以降、コードを書くと長い解説になってしまうのでここでは Redux-Toolkit を使うことを前提とした Redux の基礎知識と要点だけ書きます。
State を扱う
やりたいのは状態を保持し、必要に応じて状態を変更することです。以降、Redux が管理する状態を State と記載します。
5つの登場人物
Redux の話の中でよく出てくる登場人物です。
- State : 状態。今回管理したいものです。
- Action : State の変更指示内容です。
- Store : State, ActionCreator, Reducer を保持します。まずはこれを作ります。
- ActionCreator : Action を作る関数です。
- Reducer : Action を受け取り State を変更します。
Store の中に他の4つすべて入っているイメージでOKです。 Reducer, Action, ActionCreator は初出の概念で分かりにくいので次で解説します。
処理の流れ
ボタンを押したら状態(State)が変わる、みたいな処理を作りたいと思います。 そのボタンがあるのは React で作っているコンポーネント(= Component)です。 以下は、その Component からスタートしてどのように処理が流れるかの図です。
Component ↓ 呼び出す ActionCreator(Action を作る関数です) ↓ Actionを渡す Reducer(State を変更します) ↓ 変更する State ↓ 参照される Component
何を作るか?
Redux を使って作り込むのは Store です。前述しましたが、 Store に State , ActionCreator , Reducer が含まれています。
これらを作る関数が Toolkit から提供されています。それが createSlice
です。
コンポーネントは何をするか?
React で作るコンポーネントからは以下の2つを行います。
- 状態を見る → State の参照
- 状態を変更する → ActionCreator を呼び出す
この2つを行うための関数は React-Redux から提供されています。
useSelector
: State の参照useDispatch
: ActionCreator を呼び出す
これらを踏まえて...
これらの基礎概念だけ知っていれば、後は他のサイトのサンプルソースを読んで作り込みができるのではないかと思います。
まずは基本の公式サイト。
この記事は分かりやすかったですね。特に createSlice
の図がありがたい。これだけでも価値ありです。
クラスコンポーネント
関数コンポーネントが充実しているんでそれでいいんじゃないかと思ったりしますが、クラスでもコンポーネントが作れるので簡単に紹介。(歴史的にはクラスでしかコンポーネントが作れなかったのが、関数コンポーネントが発展してきたという経緯があります。状態はクラスでしか作れなかったのですが、 Hooks のおかげでクラスである必要がなくなりました。)
props
HTMLタグにある、 id
, class
, src
, href
のような属性を独自に作るものです。これらの属性と同様、利用箇所(呼び出し元)から固定の値が渡されます。
class Hello extends React.Component { msg = "initial message." // msg属性に指定がなかった場合の初期値 constructor(props) { super(props); this.msg = props.msg; } render () { return ( <p>{this.msg}</p> ); } } function App() { return ( <Hello msg="この値が「this.msg」の部分に渡される" /> ); }
state
props
とは違い、利用箇所(呼び出し元)から変更可能な値です。クラスに state
として保持します。変更時は setState()
で新しい state
を丸ごと設定し直すところが特徴的です。
class Hello extends React.Component { constructor(props) { super(props); // state は通常オブジェクトにします。 this.state = { counter: 0 }; this.handleClick = this.handleClick.bind(this); } handleClick(e) { // setState で state を書き換えます。変更後のオブジェクトを設定します。 this.setState((state)=>({ counter: state.counter + 1 })); } // onClick で handleClick メソッドを呼び出します render () { return ( <div> <p>{this.state.counter}</p> <button onClick={this.handleClick}>plus</button> </div> ); } } function App() { return ( <Hello /> ); }
context
決まった値を用意し、様々なコンポーネントで取り出すものです。
props
と違うのは様々なコンポーネントで利用可能な値であること。グローバルな定数のイメージに近いです。
値を変更する場合は Provider
を使います。
Themeなどに利用することを想定されているようです。
// context を用意します。 const data = 'initial message'; // 初期値 const ThemeContext = React.createContext(data); // <Hello /> の中で context を使います。 class Hello extends React.Component { static contextType = ThemeContext; render () { return ( <p>{this.context}.</p> ); } } function App() { const newData = 'new message'; // Hello を呼び出すと初期値が使われます。 // Provider を通すと Hello 内の context を変更できます。 return ( <div className="App"> <Hello /> <ThemeContext.Provider value={newData}> <Hello /> </ThemeContext.Provider> </div> ); }