You are on page 1of 193

りあクト!

TypeScript で始めるつらくない React 開発


第2版

大岡由佳 著

くるみ割り書房
第2版まえがき

第 2 版まえがき

『りあクト! TypeScript で始めるつらくない React 開発』の初版は、2018 年 10 月 8 日に池袋サンシ

ャインシティで開催された、国内最大の技術同人誌即売イベント「技術書典5」にて、初めて世に出

ました。React という国内では「難解で取っつきづらい」という評価がついて回っていた Web フロン

トエンドの一技術をテーマにした本で、内心「大量に売れ残ったらどうしよう」とびくびくしながら

の参加だったのですが、蓋を開けてみれば大盛況。当日のうちに上・下巻合わせて約 600 部を頒布。

その後、pixiv が運営するネットショップサービス BOOTH でも電子版のダウンロード販売を開始 し、


*1

初公開から五ヶ月目にして通算1千部を達成する運びとなりました。今の時代、技術書は商業誌でも

初版は1千部∼3千部という状況の中、これは同人誌の世界ではかなり希な部類に入る成功例だそう

です。

世の中に React を題材とした技術書はたくさんありましたが、React 周辺技術の進化のスピードが

著しく、数ヶ月前の情報がすぐに陳腐化してしまうという状況と、Web フロントエンドの開発者へ

の TypeScript の急速な普及というトレンドの中、マーケットにおける空白をタイミングよく突くこと

ができたのも勝因のひとつだったでしょう。また技術書にはめずらしい、登場人物による会話での説

明が読者に好意的に受け入れられたのも幸運でした。なお読者の方々からいただいた反響・感想のツ

イートは Togetter にまとめてある ので、気になった方はご参照ください。


*2

初版では著者の執筆における計画性の欠如により、当初予定していた非同期 API 通信を内容に盛り

込めなかったのですが、読者からいただいた声の中には「ぜひ改訂版か続編で書いてほしい」といっ

たものがありました。それが今回、第2版を出そうと考えた当初の動機でした。しかしそれに加えて、

初版で説明した技術に関してだけでも、その頒布から半年もたたない間にこれだけの事件が起きてし

まいました。

*1 https://oukayuka.booth.pm/

*2 React の入門書『りあクト! TypeScript で始めるつらくない React 開発』みんなの反応


https://togetter.com/li/1279508

2
・ React Conf 2018 基調講演での Hooks の発表とα版リリース。2019 年 3 月にリリースされた

React 16.8 にて正式に盛り込まれる。

・ Hooks の発表を受けて Recompose の開発中止がアナウンスされる。

・ Create React App 本家による TypeScript の正式サポートが開始される。それにより、

create-react-app-typescript は非推奨に。

・ TSLint チームが ESLint に合流、近く将来的に TSLint が非推奨になるとのアナウンスが出され

る。

・ TypeScript FSA の更新が本格的に滞り、現状の 3.0.0 β 2 を最後に今後のリリースがほぼ絶望

的となる。

・ TypeScript が 3.1 から 3.3 にメジャーアップデート。おそらくこの本が出るころには 3.4 が正

式にリリースされている見通し。

・ React 公式のドキュメントおよびチュートリアルの大部分が日本語化される。

あらためて、Web フロンドエンド業界における技術の進化のスピードにおののいています。特に

初版でまるまる一章をさいて説明した Recompose が、頒布から 1 ヶ月もしない間に開発中止がアナ

ウンスされたことはショックでした。そういった事情のため、今回の改版に当たって内容をかなり大

幅に書き直しています。くわしくは「初版からの差分について」の章をご覧ください。それにしても

React 周辺の技術革新のスピード感、執筆スパンが一年単位と言われる商業誌ではフォローするのが

ほぼ不可能なのではないかと思うほどです。

改版作業は当初見積もっていた分量を大幅に上回り、かなり難航しました。しかしそのおかげで、

この第2版は 2019 年の現時点において React の最新技術とそれを理解するために必要な要素が網羅

された、類を見ない書になったのではないかと思います。ぜひ、国内の React による SPA 開発の現

場に携わっている方を始め、これから業務で React を使う予定のある方、また個人的な興味から React

を学びたいと考えている方など、多くの方々に読んでいただきたいと考えています。

本書を通じてできるだけ多くの方が React についての深い理解を得られ、つらさではなく楽しさを

感じながら React 開発ができるようになりますように。

3
初版まえがき

初版まえがき

『りあクト! TypeScript で始めるつらくない React 開発』をお買い上げいただき、まことにありが

とうございます。一見してふざけたタイトルだと思われたかもしれませんが、内容は React を初めて

学ぶ、学び始めたけれどなかなか理解が進まなかった、また途中で挫折してしまった、そんな人たち

に向けたまじめな入門書です。

React は Facebook で開発され自社サービスに段階的に導入されていたものが、2015 年にオープン

ソースソフトとして公開された、Web アプリケーションフロントエンドの UI を構築するための

JavaScript ライブラリです。その登場はたちまちフロントエンド開発者たちの注目を集め、あっとい

う間にその分野でのデファクトスタンダードともいえる地位に上り詰めました。

npm trends による各パッケージダウンロード数の比較

4
npm のダウンロード数の統計では、翌年の 2016 年には早々に jQuery を抜き、2018 年 10 月の現在

にいたるまでライバルたちを寄せ付けずダントツの1位となっています。そして Facebook や Instagram

にとどまらず Twitter、Airbnb、Netflix、Uber と軒並み有名どころのネットサービスに採用され 、変


*1

わりどころでは Nintendo Switch の eShop インターフェースにも React が使われるようになっていま

す。また React の登場で他の多くのフロントエンドフレームワークが影響を受けて変遷し、そのため

中には根本から作り直してほぼ別物になってしまったプロダクトもあるほどです。

その React 登場当時の衝撃と興奮を、残念ながら著者はリアルタイムでは体験していません。その

ときはフロントエンドにあまり興味がなかったせいもありますが、まだドキュメントが少なく周辺情

報も不足しており、かみくだいて解説してくれる人もあまりいない状況でしたので、著者の頭では「な

んだか難しそう。JavaScript でそんなに複雑なことする必要あるの?」と思うだけでした。そのすご

さが理解できなかったのです。

しかし、そうこうするうちに SPA(Single Page Application)の重要性の高まりとともに React が採

用された話をよく聞くようになり、さらには iOS や Android のネイティブアプリが開発できる React

Native が登場して盛り上がりを見せ、そのシーンが無視できなくなってきたため、私も重い腰を上げ

て学習を始めました。ところが React は最初の印象通りに著者にとってはかなり難易度の高い技術で、

また「なぜこれをするのにこんなに回りくどいことを書く必要があるのだろう?」と疑問に思う点も

いくつかあり、なかなか先に進めないでいました。

一方で Qiita にあげられる記事などでは、ガチ勢と目される人たちが「React は簡単!」と書いて

いるのをよく見かけたのですが、そんなときは文系出身エンジニアの著者とは頭の出来が違うのだろ

うかと卑屈な考えが頭をもたげたりもしました。それでもなんとか React を使えるようになったのは、

以前に Scala を学んでいて関数型プログラミングの考え方になじんでいたおかげが大きいと思います。

その後、フロントエンドエンジニアとして3つの現場を経験してチームを統括したり、経験の浅い他

のメンバーに React を教える立場になったりもしましたが、今ならなぜ当時の著者が「React は難し

い」と感じたか、そしてガチ勢の人たちが「React は簡単」と自慢(?)のように言っていたのかが

よくわかります。

React はごくシンプルな思想のもと、恐ろしく複雑なシステムでも大きな破綻がなく信頼性の高い

*1 Sites Using React https://github.com/facebook/react/wiki/Sites-Using-React

5
初版まえがき

アプリケーションを作ることを可能にする技術です。そのシンプルな思想を貫徹しようとして、とき

には回りくどい書き方を開発者に強要してきますが、それはとっつきやすさを犠牲にしてでもコード

の秩序を維持しテスタビリティを確保するために必要なことと React の開発者たちが考えているため

です。私は当初、その思想を理解しないまま徒手空拳で React の世界に飛び込んだために、やみくも

な迷走をしたのでした。

「simple」と「easy」
、日本語ではよく混同して使われることもありますが、似て異なる概念です。現

実の場においては両者が並び立つのが難しい場面が往々にしてあります。React とよく比較される Vue

は「easy」なフレームワークと言えるでしょう。入門者のことを考慮して敷居が下げられていて、段

階的に複雑なやり方にステップアップできるような作りになっています。しかしそれは同じことを実

現するのに唯一の正解というものがなく様々なやり方が許容されており、原理の一貫性に欠けるとい

う側面を併せ持ちます。

いっぽうの React は「simple」なライブラリです。コンポーネント指向、宣言的な View 構築、単方

向データフローといった原理が一貫しており、それを遵守するために一見回りくどくコードの記述量

が増えるやり方を要求されることも多々ありますが、React で正しく開発したアプリケーションは非

常に複雑なシステムでも見通しがよくて読みやすく、テストやデバッグがしやすく、メンテナンス性

や拡張性が高いものになります。

ただその原理こそシンプルですが、そこに高い理解度と応用力が求められるため、React は学習コ

ストが低いとは決して言えません。またそれらの原理だけを抜き出して説明されても、初学者にはま

ったくピンとこないでしょう。でもそれを使う必要にせまられたとき、その背景にある思想やメリッ

トを噛み砕いて説明してくれて、初学者が抱きがちな疑問を解消してくれる、都合のいい話ですがそ

んな人が隣にいれば React は難しくないはずなのです。確かに現実にはなかなかそんな人に恵まれる

機会はありません。だから著者はこの本をふたりの、経験者のシニアエンジニアと初学者の駆け出し

エンジニアによる対話形式で書こうと思いたちました。

あの、自分の理解が煮詰まっていたときにこの説明を誰かがしてくれていたら、すんなり理解でき

ていたはずなのに……。著者自身のそんなありえない後出しの願望を架空のキャラによって満たすこ

とができれば、これから React を学びたいと思っている人たちに喜ばれる本が書けるのではないかと

考えました。
「React は難しくて勉強するのがつらい」
「Redux は回りくどくてその必要性がわからな

い」
「JSX がキモいから React に抵抗がある」等々、そんな愚痴や不満を口にしている方々にぜひ読ん

6
でいただきたい。そして React への(著者から見れば)不当な評価を覆してぜひ React を愛するよう

になってほしい、そんな目論見を抱きつつ本書を執筆しました。

対話ベースのペアプログラミングによる生産性の向上は数々の論文で報告されていますし、Ruby

の作者まつもとゆきひろ氏も、テディベアプログラミングのようにあえて誰かに説明する体裁をとる

ことで開発者はより生産的になれると述べています。軽い気持ちから始めた対話体による執筆でした

が、こうして自分で読み直してみても、その試みは成功しているように思えます。
(※自画自賛は著者

の性格なので、ご容赦ください)

なお本書は入門書の体をとっていますが、基本的に Web アプリケーション開発経験のあるエンジ

ニアながらも React は未経験といった方が、可及的速やかにチームの開発に参加できるようにするこ

とを目的として構成されており、内容をそのために必要な情報量に絞り込んでいます。ゼロから趣味

のアプリを作り上げられるようになりたい、といった方には向いていないかもしれません。また文中

に示される、特定技術や思想への見解・感想はあくまで著者個人の考えであることも併せてご理解願

います。

サンプルコードが置いてある場所は https://github.com/oukayuka/ReactBeginnersBook-2.0
*2

です。本文中で随時、個別のファイルを引用していますが、ページの都合上ところどころ、説明の本

筋とは関係ない部分を断りなく省略してあるなど完全に同じではありませんので、ご承知おきくださ

い。

*2 初版のサンプルコードは https://github.com/oukayuka/ReactBeginnersBook

7
本書について

本書について

登場人物

柴崎雪菜(しばさき・ゆきな)

とある都内のインターネットサービスを運営する会社のフロントエンドエンジニア。React 歴は 2 年

半ほど。本格的なフロントエンド開発チームを作るための中核的人材として、今の会社に転職してき

た。チームメンバーを集めるため採用にも関わり自ら面接も行っていたが、彼女の要求基準の高さも

あってなかなか採用に至らない状態が続く。そこで「自分が React を教えるから他チームのエンジニ

アを回してほしい」と上層部に要望を出し、社内公募が実行された。

秋谷香苗(あきや・かなえ)

柴咲と同じ会社のエンジニアで新卒二年目。入社以来もっぱらサーバーサイド開発に携わっていたが、

柴崎のメンバー募集に志願してフロントエンド開発チームに参加した。そこで柴崎から「一週間で戦

力になって」と言われ、マンツーマンで教えを請うことになるが……。

8
初版からの差分について

初版からの差分について

初版から本書第2版への変更部分については以下の通りです。

・ Create React App 本家が TypeScript を直接サポートし、create-react-app-typescript はパッケ

ー ジ そ の も の が 非 推 奨 に なっ た た め 、 create-react-app-typescript を 使 用 して い た 部 分 を

create-react-app に入れ替えた。

・ 「2-5. 便利な配列やオブジェクのトリテラル」に、分割代入についての説明を追加。

・ 「3-5. クロージャ」の章を追加。

・ 「3-6. ジェネレータ」の章を追加。

・ 「4-2. 型のバリエーション」に、never 型についての説明を追加。

・ 「4-3. 配列とオブジェクト」の交差型と共用型についての説明の誤りを訂正。および TypeScript

3.4 から導入された Readonly 型についての説明を追加。

・ 「4-5. コンパイル設定」に、絶対パスインポートについての説明を追加。

・ TypeScript の構文チェックツール TSLint については ESLint への統合プランが発表され*1、近

い将来に非推奨になるとアナウンスがあったため、
「6-1. TSLint」の内容を「6-2. ESLint」に変

更。加えて全てのサンプルコードの TSLint の環境を ESLint に移行。

・ 「7-5. 関数コンポーネント」で Stateless Functional Component(SFC)の呼称を Function

Component(FC)に変更。

・ Hooks が導入され Recompose の開発中止が宣告されたため、第 8 章の内容を「合成するぞ

Recompose」から「Hooks で関数コンポーネントを強化する」に置き換える形で刷新。

・ 「10-3. Redux の使い方」で、最新の Redux および React Redux に対応した型解決の記述法

に切り替えた。

・ TypeScript FSA の更新が滞っているため、


「10-4. Flux Standard Action」の内容を TypeScript

FSA を使わない手法を使ったものに書き換えた。

・ 「10-5. Redux DevTools」の内容を『りあクト! TypeScript で極める現場の React 開発』に

*1 TSLint in 2019 https://medium.com/palantir/tslint-in-2019-1a144c2317a9

9
本書について

委譲。

・ 「第 11 章 Redux で非同期処理を扱う」の内容を追加。

・ その他使用している主なソフトウェアを、2019 年 3 月時点の最新バージョンにアップデート。

10
本文中で使用している主なソフトウェアのバージョン

本文中で使用している主なソフトウェアのバージョン

・ React 16.8.6

・ Create React App 2.1.8

・ TypeScript 3.4.3

・ React Router 5.0.0

・ react-router-dom 5.0.0

・ Redux 4.0.1

・ React Redux 7.0.2

・ Redux-Saga 1.0.2

11
目次

目次

第 2 版まえがき ……………………………………………………………………2

初版まえがき ………………………………………………………………………4

本書について ………………………………………………………………………8
登場人物 …………………………………………………………………………………………………………8

初版からの差分について ………………………………………………………………………………………9

本文中で使用している主なソフトウェアのバージョン ……………………………………………………11

目次 …………………………………………………………………………………12

プロローグ …………………………………………………………………………15

第 1 章 こんにちは React …………………………………………………………17


1-1. 環境構築 …………………………………………………………………………………………………17

柴崎さんオススメの VSCode 拡張リスト ……………………………………………………………19

1-2. Hello, World! ……………………………………………………………………………………………19

1-3. Yarn コマンド ……………………………………………………………………………………………23

第 2 章 ナウでモダンな JavaScript ………………………………………………26


2-1. ECMAScript ………………………………………………………………………………………………26

2-2. 変数の宣言 ………………………………………………………………………………………………27

2-3. アロー関数 ………………………………………………………………………………………………28

2-4. クラス構文 ………………………………………………………………………………………………30

2-5. 便利な配列やオブジェクトのリテラル ………………………………………………………………31

2-6. 非同期処理を扱う ………………………………………………………………………………………33

第 3 章 関数型プログラミングでいこう ………………………………………37
3-1. 関数型プログラミングは何がうれしい? ……………………………………………………………37

12
3-2. コレクションの反復処理 ………………………………………………………………………………38

3-3. 関数型プログラミングの概要 …………………………………………………………………………39

3-4. 高階関数 …………………………………………………………………………………………………41

3-5. クロージャ ………………………………………………………………………………………………42

3-6. ジェネレータ ……………………………………………………………………………………………44

3-7. カリー化と関数の部分適用 ……………………………………………………………………………45

第 4 章 型のある TypeScript は強い味方 ………………………………………48


4-1. TypeScript は今やメジャー言語 ………………………………………………………………………48

4-2. 型のバリエーション ……………………………………………………………………………………52

4-3. 配列とオブジェクト ……………………………………………………………………………………56

4-4. 関数の型定義 ……………………………………………………………………………………………60

4-5. コンパイル設定 …………………………………………………………………………………………61

4-6. モジュールの型定義 ……………………………………………………………………………………63

第 5 章 拡張記法 JSX ………………………………………………………………65


5-1. JSX とは何であるか、何ではないのか ………………………………………………………………65

5-2. JSX の文法 ………………………………………………………………………………………………67

第 6 章 Lint と Prettier でコードをクリーンに …………………………………71


6-1. ESLint ……………………………………………………………………………………………………71

6-2. Prettier ……………………………………………………………………………………………………74

6-3. 組み合わせとカスタマイズ ……………………………………………………………………………75

第 7 章 何はなくともコンポーネント …………………………………………77
7-1. React の基本思想 …………………………………………………………………………………………77

7-2. Props をコンポーネントに受け渡す ……………………………………………………………………79

7-3. コンポーネント内部の状態を規定する Local State …………………………………………………86

7-4. コンポーネントのライフサイクル ………………………………………………………………………92

1. Mounting フェーズ ……………………………………………………………………………………93

2. Updating フェーズ ……………………………………………………………………………………93

3. Unmounting フェーズ …………………………………………………………………………………93

13
目次

4. Error Handling フェーズ………………………………………………………………………………93

7-5. 関数コンポーネント ……………………………………………………………………………………98

7-6. Presentational Component と Container Component …………………………………………………101

第 8 章 Hooks で関数コンポーネントを強化する ……………………………104


8-1. Hooks 登場以前の話 ……………………………………………………………………………………104

8-2. Hooks とは何か …………………………………………………………………………………………107

8-3. State Hook で Local State の管理 ………………………………………………………………………109

8-4. Effect Hook でライフサイクルを扱う …………………………………………………………………112

8-5. Custom Hook で独自の Hook を作る …………………………………………………………………116

8-6. その他の Hooks …………………………………………………………………………………………119

第 9 章 ルーティングで URL を制御する ………………………………………123


9-1. SPA のルーティング ……………………………………………………………………………………123

9-2. React Router にまつわるあれこれ ……………………………………………………………………124

9-3. React Router の使い方 …………………………………………………………………………………127

第 10 章 Redux でアプリの状態を管理する …………………………………137


10-1. Flux アーキテクチャ …………………………………………………………………………………137

10-2. Redux の登場 …………………………………………………………………………………………139

10-3. Redux の使い方 ………………………………………………………………………………………143

10-4. Flux Standard Action …………………………………………………………………………………154

第 11 章 Redux で非同期処理を扱う …………………………………………161


11-1. React で非同期通信を扱ういくつかの方法 …………………………………………………………161

11-2. Redux Thunk vs. Redux-Saga …………………………………………………………………………163

11-3. Redux-Saga を使いこなす ……………………………………………………………………………170

エピローグ ………………………………………………………………………185

あとがき …………………………………………………………………………187

著者紹介 …………………………………………………………………………189

14
プロローグ

「おはようございます、芝崎さん。本日からお世話になります、秋谷香苗です!」

「はい、秋谷さんね。こちらこそよろしくお願いします。私はこのフロントエンドチームの、って言

っても今のところは私と秋谷さんとのふたりだけなんだけど、リーダーの柴崎雪菜です」

「柴咲さんのこと、私知ってましたよ。芝崎さん、転職してきて間もないけどできる女性エンジニア

って社内で噂になってましたし。今回、もちろん前から React に興味があったのもあるんですけど、

柴咲さんに教わっていっしょに働けるチャンスだっていうので、社内公募に応募したんです」

「……そ、そう。ありがとう。やる気は十分ってことですね。じゃあ今から、私があなたに何を期待

しているか、何をやってもらうかを説明していこうと思うけど、いいですか?」

「はい、よろしくお願いします!」

「その前に、秋谷さんはいま入社何年目? あと持ってるスキルについても教えてくれるかな」

「新卒で入社して二年目です。入社してから一年ちょっと Rails での Web アプリ開発に携わってまし

た。使える言語は Ruby と、それにちょっとだけ JavaScript を。触ってたのは jQuery を使ってモニョ

モニョするくらいですが。あと、業務では使ったことがありませんけど、入社前に Java を学んでまし

た」

「うん、わかりました。React については?」

「興味があったので自分で勉強しようとしたんですけど、
途中で難しくて挫折しちゃいました……。Vue

も触ってみたんですけど、こっちのほうが Rails と考え方が近くてわかりやすいな、と思っちゃいま

した。すみません……」

「いや、謝ることはないけども。そうだね、MVC の考え方になじんでる人は Vue のほうがとっつき

やすいと思う。Vue の設計パターンは MVC に近い MVVM だし」

「MVVM ?」

「Model、View、ViewModel からなる構成、といっても説明が長くなるので今は省くけど、HTML

のテンプレートに処理結果を埋め込んでいくやり方が Rails や他のサーバーサイド Web フレームワー

クと共通してるよね」

「はい、私もそう思いました」

15
プロローグ

「React の設計パターンはそもそもパラダイムが違うから、その思想を理解しないまま飛び込んでも

なかなか身につかないんだよね。でもそれらは随時、説明していくので心配しないで」

「はい! ありがとうございます!」

「あはは、いい返事だね。で、これから何をやってもらうかだけど。今から私がマンツーマンでつい

て、あなたに React 開発を叩き込みます。そうね、一週間で私とペアプログラミングで開発に参加し

てもらえるレベルになってもらいたいかな」

「えっ、たったの一週間でですか?! ムリムリ、実質五日間しかないじゃないですか?!」

「いや、それだけあれば十分でしょう。私、教えるのうまいので」

「……ええええ? 不安だなぁ」

「ふふふ、だーいじょうぶ。Rails は使いこなしてたんでしょう? なら原理さえ理解できれば React

は難しくないから」

「……わかりました! 柴咲さんがそう言われるなら、覚悟を決めてがんばります!!」

16
1-1. 環境構築

第 1 章 こんにちは React

1-1. 環境構築

「ではまず、環境の構築からやってもらおうかな。まずは Node のインストール。やりかたは色々あ

って、Mac なら Homebrew で入れるのが簡単で手っ取り早いんだけど、私たちはプロだから、プロジ

ェクトごとに違ったバージョンを共存させることが必要になることがあります」

「はい」

「なのでバージョンマネージャを使ってインストールしておくの。メジャーなのは nvm と ndenv。ウ

チでは ndenv を使ってるので、秋谷さんにもそれを入れてください」

「Ruby でも rvm と rbenv がありますね。前のチームでは rvm を使ってましたけど」

「私が ndenv を使うのは、ディレクトリごとに実行バージョンを使い分けられること、anyenv 経由で

インストールすることで rbenv(Ruby)とか pyenv(Python)とかと設定をあるていど共通化できるこ

ととかがその理由かな」

「なるほど。じゃ、私もこれを機会に Ruby も anyenv から rbenv に移行しちゃいますね」


『anyenv ndenv install』とかで検索すれば、インストール方法は見つかるから」

「だいじょうぶです! すぐやります!」

―― 5分後 ――

「柴崎さん、できました!」

「おっ、早いね。じゃ、node -v と npm -v を実行してみて」

「……実行、と。それぞれ『v11.10.0』
『6.8.0』が表示されました」

「うん、いいね。次は npm install -g yarn ね」

「あの、あまりわかってないんですけど、Yarn って何ですか?」

「まず npm は Ruby で言うところの RubyGems と Rails の Bundler を引っくるめたものだね。Node

モジュールパッケージの追加・削除に加えて、各パッケージ間のバージョン整合とかも自動的にやっ

17
第1章 こんにちはReact

てくれる。Yarn は Facebook 製の npm 改良版みたいなもの。高速だったりコマンドのタイピング数が

少なかったりいろいろ使い勝手がいいので、React 界隈では npm より Yarn を使う人のほうが多いよ

うで、ウチでも使ってるの。さっき打ってもらったのは、Yarn をグローバル環境にインストールする

コマンドだね」

「へー、ほんとだ。どこからでも yarn コマンドが使えるようになりました」

「次はエディタね。秋谷さんは今、何のエディタ使ってる?」

「えーと、前のチームでは皆さん Vim の人が多かったので、なんとなく私も Vim を使ってました」

「あー、Rubyist は Vim 大好きだからねえ。でもウチでは Visual Studio Code、長いので略して『VSCode』

って呼ぶけど、それを使ってもらいます」

「えっ、チームでエディタを指定されるんですか?」

「本当は各自好きなものを使っていいよと言いたいんだけど、これから入ってくるであろうメンバー

も含めて Developer Experience を共有する意味でも全員 VSCode を使ってもらおうかなって思ってま

す。VSCode を採用する理由のひとつは TypeScript と相性がよくて型の整合性チェックや Null 安全性

のチェックとかを自動的にやってくれること。あとプラグインが豊富で、コードの自動整形とかも簡

単にできる。

理由のもうひとつは、実際の開発は常にペアプログラミングでやってもらうんだけど、ひとつの画面

をいっしょにふたりで見ながら開発する従来のやり方じゃなく、Visual Studio Live Share を使ってそ

れぞれ自分のマシンで同時コーディングをしていく予定だから」

「同時コーディング?」

「Google Docs で複数人のドキュメント同時編集はやったことあるよね? Live Share を使うとあれ

と同じようなことがコーディングでできるようになるの」

「へー、今はそんなことができるようになってるんですね。近未来だ……。わかりました、がんばっ

て私も VSCode 使いになります!」

「Vim の操作に慣れてるなら、VSCodeVim っていう拡張を入れると、Vim と同じキーバインディン

グでコードが書けるようになるよ」

「あ、それ嬉しいです」

「ちなみにプロジェクトのルートに .vscode/ というディレクトリを作って、そこに settings.json を

置くと VSCode の設定を、extensions.json を置くと推奨の拡張をチームで共有できるようになるよ。

これ以降のサンプルコードにも置いておくので、一度は気にして見ておいて」

18
1-2. Hello, World!

「了解です!!」

柴崎さんオススメの VSCode 拡張リスト

・ Prettier ※ …… コード自動整形ツール Prettier を VSCode に統合する。

・ ESLint ※ …… JavaScript の静的コード解析ツール ESLint を VSCode に統合する。

・ stylelint ※ …… CSS 用のリンター stylelint を VSCode に統合する。

・ VSCodeVim …… VSCode 上で走る Vim エミュレータ。キーバインディングを Vim 形式に変

更するだけでなく、Undo-Redo 履歴や単語検索などの管理空間を独立させたり VSCode 本体と

マージしたりもできる。

・ Bracket Pair Colorizer …… マッチする括弧を色分けして教えてくれる。

・ indent-rainbow …… インデントの階層を色分けして見やすくしてくれる。

・ vscode-icons …… 左ペインの Explorer のファイルアイコンをバリエーション豊かにしてくれ

る。

・ VS Live Share …… 複数人によるリアルタイムのコーディングコラボを実現する。

1-2. Hello, World!

「はい、じゃあ次はお約束の『Hello, World!』をやってもらいましょうか」

「どんな技術もまずはそこからですね。でも React で新規のプロジェクトを作るときはどうするんで

すか? Rails なら rails new だったり、Vue なら vue create ってコマンドが用意されてますけど」

「残念ながら、React にはそんな便利なコマンドはありません。そもそも公式が『React はフレームワ

ークではなく、単なる UI ライブラリだ』って言ってるくらいだし。一昔前なら、適当なボイラープ

19
第1章 こんにちはReact

レートを落としてきてカスタマイズするやり方が多かったんだけど、今は Facebook が Create React

App っていう、そのまんまな名前のコマンドモジュールを出してくれているので、それを使わせても

らいます」

「よかった。ちゃんとあるんですね」

「適当なディレクトリで npx create-react-app hello-world --typescript を実行してみて。あ、npx と

いうのは npm のパッケージをいちいちインストールしなくてもそのバイナリが実行できるコマンド

ね」

「なるほど。……はい、実行完了しました」

「じゃ次は、作成されたディレクトリの中に移動してから yarn start を実行」

「おおっ、Chrome が開いてでっかい React のシンボルマークがぐるぐる回ってます」

「デフォルトだとそのアプリ専用の Node サーバが 3000 番ポートで立ち上がって、普段使ってるブラ

ウザが開いてその画面を表示するようになってるの」

「なんか文章も書いてありますね。
『Edit src/App.tsx and save to reload.』だそうです。このファイ

ルを開きますか?」

「うん、でもちょっと待って。まずは public/index.html を見てみよう」

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<meta name="theme-color" content="#000000">
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
<title>React App</title>
</head>
<body>
<noscript>
You need to enable JavaScript to run this app.
</noscript>
<div id="root"></div>
</body>
</html>

「…… HTML のガワだけですね。body タグの中身が <div id="root"></div> になってますけど、実

20
1-2. Hello, World!

体はどこにあるんでしょうか?」

「うん、次は src/index.tsx を見てみようか」

import React from 'react';


import ReactDOM from 'react-dom';
import './index.css';
import App from './App';

ReactDOM.render(<App />, document.getElementById('root'));

「さっきの div タグの ID が『root』でしたけど、document.getElementById('root') があやしそうで

すね」

「カンがいいね。では最後に、さっき画面で編集しろと言われていた src/App.tsx を開いてみて」

import React, { Component } from 'react';


import logo from './logo.svg';
import './App.css';

class App extends Component {


render() {
return (
<div className="App">
<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
Edit <code>src/App.tsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank" rel="noopener noreferrer"
>
Learn React
</a>
</header>
</div>
);
}
}

export default App;

21
第1章 こんにちはReact

「????」

「まあ、いきなりじゃわかんないだろうね。順番に説明していくからしっかり聞いてて。

まず React で作られるアプリケーションは、すべてコンポーネントの組み合わせで構成されるの。

コンポーネントというのは、今の段階では『任意の HTML タグでくくられる単位の内容物』と考え

ていればいいよ。そして React ではコンポーネントは JavaScript で記述される。でも HTML タグの出

力をいちいち JavaScript の関数コールで記述するのは読みづらいから、JSX という JavaScript の中に

HTML タグがそのまま書ける拡張記法を使うのよ。

src/App.tsx は JSX で <App> タグの中身を定義したファイルになるの。正確には TypeScript に拡張

された JSX、つまり TSX だけど」

「えーっと、App.tsx で <App> タグの中身を定義してエクスポート、それを index.tsx でインポートし

てるわけですね」

「そう、import App from './App'; というのがそこだね。インポート時の拡張子は省略できるから。

Create React App でプロジェクトを生成したときには react-scripts っていうコマンドモジュールがイン

ストールされてるんだけど、これがエントリーファイルとして src/index.tsx を public/index.html に

結びつけてくれてる。まあこのあたりは私もふだん忘れてるところなので、そういうもんなんだって

くらいの理解でいいよ。そんなにいじらないとこだし」

「わかりました」

「じゃ、src/App.tsx をこんな感じに変更してから保存してみてくれる?」

<header className="App-header">
<img src={logo} className="App-logo" alt="logo" />
<p>
- Edit <code>src/App.tsx</code> and save to reload.
+ Hello, World!
</p>

「あっ、勝手にブラウザがリロードされて、ページの内容が『Hello, World!』に書き換わりました」

22
1-3. Yarnコマンド

「Create React App で生成されたプロジェクトは、ホットリロードが有効になってるからね。まあ、

まだいろいろ納得はいってないだろうけど、これでいちおう『Hello, World!』はこれで完成」

「そうですね。あまり自信はないですけど、とりあえず手順はおぼえましたし、雰囲気はわかったの

でよしとします」

「うん。細かいところについては、おいおい理解していけばいいよ」

1-3. Yarn コマンド

「さっき生成したコードをリポジトリに上げたものがあるので*1、ちょっと別のディレクトリに落とし

てきてくれる?」

「……はい、ローカルに展開しました」

「じゃ、このアプリを起動してみて」

「えっと yarn start でしたよね。実行……と。あれ? エラーになりましたよ」

*1 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/01-hello/03-hello

23
第1章 こんにちはReact

「うん、ディレクトリを見てみて。node_modules/ ディレクトリがないでしょ。パッケージモジュール

のインストールが必要なの。yarn install ってコマンドを実行しましょう」

「おおっ、なんかいっぱいダウンロードしてますね。yarn start を実行したら、今度は localhost:3000

にアプリが立ち上がりました」

「ちなみに install は省略できるので、ただ yarn と打っただけでも結果は同じね。Yarn は多機能な

コマンドだけど、メインの役割はパッケージモジュールの管理なので、そこから説明していこう。と

りあえずよく使うのはこれくらいかな」

・ yarn add <PACKAGE_NAME> …… 任意のパッケージをインストールする

・ yarn remove <PACKAGE_NAME> …… 任意のパッケージをアンインストールする

・ yarn upgrade <PACKAGE_NAME> …… 任意のパッケージを最新バージョンに更新する

・ yarn info <PACKAGE_NAME> …… 任意のパッケージについての情報を表示する

「yarn add でインストールされたパッケージの情報は、ルートディレクトリの package.json に記述さ

れる。ちょっとファイルを開いてみて」

{
"name": "hello-world",
"version": "0.1.0",
"private": true,
"dependencies": {
"@types/jest": "24.0.6",
"@types/node": "11.9.4",
"@types/react": "16.8.4",
"@types/react-dom": "16.8.2",
"react": "^16.8.3",
"react-dom": "^16.8.3",
"react-scripts": "2.1.5",
"typescript": "3.3.3333"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},

24
1-3. Yarnコマンド

「dependencies の中にパッケージらしき名前とバージョン番号がありますね。これのことですか?」

「その通り。コマンドを使わず、この JSON ファイルの dependencies 情報を編集してから yarn を実

行しても add や delete 相当のことができるよ。ついでだからその下の scripts のところも説明してお

こうか」

「はい、お願いします!」

「yarn help で Yarn コマンドの一覧が見られるけど、その中には start なんてコマンドはない。なの

に yarn start が実行できるのはなぜか。これは npm-scripts と呼ばれるもので、package.json の中に

"scripts" としてエントリを記述しておくと、あたかも npm コマンドや Yarn コマンドのように実行

できるの。だから yarn start を実行すると./node_modules/.bin/react-scripts start が走るわけ。パ

スの解決を自動で行ってくれるのでコマンドは直に書けるけどね」

「えー、そうだったんですか。じゃ、ここに自分でエントリを追加すれば、yarn コマンドで実行でき

るんですか?」

「そうだね。後々、構文チェックのためのコマンドのエントリとかを追加していく予定。その他、ド

キュメントやスタイルガイドを生成させたりとか、そうやって作った npm-scripts を git の任意コマン

ドの実行に割り込んで起動させたりもできるよ」

「へえ――、便利ですね」

「それから、アプリを起動するときは専用のターミナルアプリじゃなく VSCode にターミナルの機能

があるので、そっちを使ったほうがいいよ。エラーがあったとき、エラー表示を command +左クリ

ックで該当ファイルが開くからね」

「なるほど、ありがとうございます!」

25
第2章 ナウでモダンなJavaScript

第 2 章 ナウでモダンな JavaScript

2-1. ECMAScript

「とりあえず『Hello, World!』はできたけど、React についてくわしく見ていく前に、まず言語につ

いて学んでおいてもらう必要がありそう。秋谷さんは、JavaScrpt はさわったことはあるんだよね?」

「あっ、はい。Rails アプリの View 部分で、ちょっと凝った UI を実現するために jQuery を使ったり

することがありましたので」

「そっか。じゃあ ES5 以前の JavaScript しか知らないんだ」

「いーえすご?」

「正式には『ECMAScript 5』
。ECMAScript(エクマスクリプト)は JavaScript の標準仕様で、近年

はほぼ毎年のように更新されてるの。2019 年初旬現在での最新版は ES2018、つまり『ECMAScript

2018』で、今年のどこかの段階で ES2019 がリリースされることになってる」

「ES5 は通し番号ぽいのに、ES2018 は年号なんですね。Windows や Office みたい」

「2015 年に公開された ES6 がイコール ES2015 で、以降はずっと年号でバージョンが表現されてる

ね。ECMAScript には大きなパラダイム転換の時期があって、ES6 でモダンな仕様が一気に盛り込ま

れたの。だから ES5 までの JavaScript しか知らない人には、今の JavaScript は大げさに言って別の言

語と思ってもらってもいいくらい」

「……うっ、そうなんですね」

「実際に使うのは TypeScript だけど、TypeScript は基本的に JavaScript に型リテラルを拡張しただけ

の言語であとの仕様は共通なので、まずは最新仕様の JavaScript を学んでもらいます。私が担当して

いるプロジェクトでは ES2018 をすでに使っているので、ES2018 ベースで話を進めていきます」

「はい、お願いします!」

26
2-2. 変数の宣言

2-2. 変数の宣言

「まずは変数の宣言から。これまで変数の宣言には var を使っていたと思うけど、これはもう金輪際、

使ってはいけません」

「え、絶対にダメなんですか?」

「絶対にダメです。私が許しません。まず再代入の必要がない場合は const を、どうしても再代入の

必要がある場合だけ let を使いましょう」

「わかりました。でもそこまで var を使っちゃダメと言われる理由を教えてください」

「もちろん。じゃ、とりあえずこのコードを見てみて。結果はどうなると思う?」

var n = 0;

if (true) {
var n = 50;
var m = 100;
console.log(n);
}

console.log(n);
console.log(m);

「ふたつめの console.log(n) は 0 ですよね? それに最後の行は m を参照できなくてエラーになる

んじゃないでしょうか。だから 50, 0 ときて最後に Reference Error になると思います」

「ふふふ、じゃ node コマンドで実行してみようか」

「えっ、50, 50, 100 が表示された……?」

「var で定義された変数のスコープってね、関数単位なんだよ。だから制御構文のブロックをすり抜

けてしまう。Ruby を始めとする大多数の言語の変数スコープはブロック単位だよね。その考え方に

慣れたプログラマが var でコーディングしてると、うっかりバグを生んでしまいやすいでしょう。だ

から ES6 で一般的なブロックスコープの const と let が導入されたの」

「はー、なるほど。了解です。もう var は使いません!」

「素直でいいね。じゃ、次行ってみようか」

27
第2章 ナウでモダンなJavaScript

2-3. アロー関数

「秋谷さんは CoffeeScript を使ったことはある?」

「はい、ちょっと前まで Rails のプロジェクトでは生の JavaScript じゃなく、標準添付の CoffeeScript

を使うのが普通でしたから」

「みたいだねー。知ってるなら話が早い。ES6 で導入されたアロー関数リテラルというのがあるんだ

けど、これは実際に見てもらったほうが早いね」

// 従 来 の 関 数 宣 言
function plusOne(n) {
return n + 1;
}

// ア ロ ー 関 数 の 宣 言 そ の 1
const plusOne = (n) => { return n + 1; };

// ア ロ ー 関 数 の 宣 言 そ の 2
const plusOne = n => n + 1;

「これ、3つとも同じことなんですよね? JavaScript のアロー関数って、本当に CoffeeScript の関数

に似てますね」

「アロー関数式の『その1』と『その2』も比べてみて。引数がひとつだけの場合はカッコが省略で

きるのと、本文が return 文だけのときは、return が省略できる」

「へー、こっちのほうがスッキリ見やすくていいですね」

「だから省略できるときは省略しましょう。ただし、function での関数宣言とアロー関数式での宣言

は this の挙動が変わるので気をつけてね。その違いを理解するために、次のコードを実行してみて」

const obj1 = {
num: 444,

28
2-3. アロー関数

fn: function() {
console.log(this.num);
}
};

const obj2 = {
num: 888,
fn: () => {
console.log(this.num);
}
};

obj1.fn(); // 444
obj2.fn(); // undefined

「……えっ? これってどういうことですか?」

「funciton による関数宣言では、その文脈における直上のオブジェクトが this になる。直上に定義

したオブジェクトがない場合はグローバルオブジェクトね。いっぽう、アロー関数式では this はその

関数それ自体になるの」

「うーん、わかったようなわからないような……」

「この例だけではピンと来ないかもね。関数に引数として関数を受け渡したりすると、この違いを意

識してないと思った挙動と異なることが出てきたりするんだけど、今の段階では知識として知ってお

いてくれればいいから」

「はい。じゃ、とりあえずおぼえておきます」

「それから function での定義とアロー関数での定義、どちらの表現を使うべきかだけど。これは好

みかなとも思うんだけど、this の挙動を統一する必要もあるし、どうしても仕方がないとき以外、極

力アロー関数を使うようにしましょう。見た目もいかにも関数型っぽくてイケてるし」

「カッコよく書けたほうがモチベも上がりますしね」

「お、秋谷さんもそう思う? ちなみにあとでコーディングルールをユーザーに強制するツールを紹

介するんだけど、私のカスタマイズ設定ではアロー関数で書かないと怒られるように専用のプラグイ

ン を組み込んでルールを設定してます」
*1

「あはは、徹底してますね……」

*1 eslint-plugin-prefer-arrow https://github.com/TristonJ/eslint-plugin-prefer-arrow

29
第2章 ナウでモダンなJavaScript

「あと、これはアロー関数に限ったことじゃないけど、ES2015 から関数のデフォルト引数が使える

ようになったので、補足しておくね」

const plusOne = (n = 0) => n + 1;

console.log(plusOne(5)); // 6
console.log(plusOne()); // 1

「引数のカッコの中で = で値を代入するのは、Ruby と全く同じ書き方ですね。これ 2015 年以前の

JavaScript では使えなかったんですか。知らなかった……」

「まあ今やたいていの言語にはそなわってる機能だからね。使えるときは積極的に使っていこう」

2-4. クラス構文

「次はクラス構文。これは Ruby や Java をやってきた秋谷さんには簡単だよね。ES6 から JavaScript

にもクラスが使えるようになりました」

「へー、そうなんですね。JavaScript は普通にクラス使えなくて、プロトタイプ?とかいう仕組みを使

って擬似的にやる必要があるって聞いてました」

「ES5 まではそうだったね。とりあえずサンプルコードを見てもらおうかな」

class Bird {
constructor(name) {
this.name = name;
}

chirp() {
console.log(`${this.name} が 鳴 き ま し た `);
}

static explain(name) {

30
2-5. 便利な配列やオブジェクトのリテラル

console.log(`${name} は 翼 が あ っ て 卵 を 生 み ま す `);
}
}

class FlyableBird extends Bird {


constructor(name) {
super(name);
}

fly() {
console.log(`${this.name} が 飛 び ま し た `);
}
}

const bd1 = new Bird(' ペ ン ギ ン ');


bd1.chirp(); // ペ ン ギ ン が 鳴 き ま し た
Bird.explain(' カ ラ ス '); // カ ラ ス は 翼 が あ っ て 卵 を 生 み ま す

const bd2 = new FlyableBird(' ス ズ メ ');


bd2.fly(); // ス ズ メ が 飛 び ま し た

「素直な構文ですね。これといって不明なところはなさそうです!」

「ふふ、さすがだねー。注意点としては、コンストラクタは継承されないので constructor() と明示

的に書く必要があることくらいかな。サンプルコードにはついでにテンプレートリテラルをさらっと

差し込んでおいたんだけど、わかった?」

「文字列を通常のシングルやダブルのクォートじゃなくバッククォートで囲んだ上で、変数名を ${}

で囲うと値が展開されるやつですよね。Ruby でも #{} という書き方があったんでだいじょうぶです」

2-5. 便利な配列やオブジェクトのリテラル

「はい。それじゃ配列やオブジェクトの便利な書き方を見ていこうか。まずは分割代入からね」

const [n, m] = [1, 4];


console.log(n, m); // 1 4

31
第2章 ナウでモダンなJavaScript

const obj = { name: 'Kanae', age: 24 };


const { name, age } = obj;
console.log(name, age); // Kanae 24

「Ruby にも同じように多重代入、n, m = 1, 4 って書き方がありますね。よく使われるんですか?」

「React の開発では両方ともよく使われてるよ。特にオブジェクトの分割代入のほうは、React 向けに

これを使って書くことを強制する構文チェックのルール も提供されてるよ。上の例を const name =


*2

obj.name; みたいに書くと怒られるの」

「へー、そんなになんですか」

「うん。だから積極的に使っていきましょう。

それから次はコレクションの展開構文。これも口で説明するより、コードで見てもらったほうが早

そう」

const arr1 = ['A', 'B', 'C'];


const arr2 = [...arr1, 'D', 'E'];
console.log(arr2); // [ 'A', 'B', 'C', 'D', 'E', 'F' ]

const obj1 = { a: 1, b: 2, c: 3 };
const obj2 = { ...obj1, d: 4, e: 5 };
console.log(obj2); // { a: 1, b: 2, c: 3, d: 4, e: 5 }

「なるほどー、配列やオブジェクトの名前の前に『...』をつけることで中身が展開されるんですね。

これは便利そう」

「この書き方をスプレッド構文と言って、
『...』をスプレッド演算子と呼ぶの。ちなみに配列のスプ

レッド構文は ES6 から、オブジェクトのスプレッド構文は ES2018 から使えるようになった。ウチで

ES2018 を使ってるのは、この便利な構文が使えるのが大きいね。React でも多用される構文だからお

ぼえといて。

それじゃ、次はこんな構文ね」

*2 Prefer destructuring from arrays and objects (prefer-destructuring)


https://eslint.org/docs/rules/prefer-destructuring

32
2-6. 非同期処理を扱う

const foo = 65536;


const obj = { foo, bar: 4096 };
console.log(obj); // { foo: 65536, bar: 4096 }

「obj の最初の要素、キーがないですね。これは変数 foo がオブジェクトの中でその変数名がオブジ

ェクトキーに、変数値がその値になってるわけですか」

「その通り。これはプロパティ名のショートハンドとかって呼ばれる書き方。ES6 から導入された構

文で、こちらも React のプロダクトではよく使われてるのを見かけるね」

「へぇ――。こういった書き方、知らずに見てたら混乱したかもしれないですけど、見た目もスッキ

リするしいいですね。私も使いこなしていきたいです!」

2-6. 非同期処理を扱う

「JavaScript では時間のかかる処理は、ほぼ非同期なのが前提です。Rails だと API リクエストを行っ

てそこから得た値を加工して表示、みたいな処理は何の気なしに続けて順番に書いてただろうけど、

JavaScript で考えなく同じことをやろうとすると、リクエストの結果が返ってくる前に加工処理に進ん

で表示されたりしてしまいます」

「えっ? それ困ります」

「だよねー。ちょっと次のコードを実行してみて」

const wakeUp = ms => {


setTimeout(() => { console.log(' 起 き た '); }, ms);
};

const greet = () => {


console.log(' お や す み ');
wakeUp(2000);

33
第2章 ナウでモダンなJavaScript

console.log(' お は よ う ! ');
}

greet();


『おやすみ』と『おはよう!』が即座に表示されて、しばらくしてから『起きた』が表示されまし

た」

「setTimeout() は指定した時間だけ遅延して任意の関数を実行するものね。ここではわかりやすくこ

れを使ったけど、JavaScript では通信とかローカルファイルの読み込みとかの外部アクセス処理は、ほ

ぼ断りなく非同期が前提になってるからね」

「え――っ、そうなんですか?」

「React コンポーネントのレンダリングだってそうだよ。上の階層からとか関係なく、前処理が終わ

ったものから他を待たずに表示されていくの」

「まず Controller でページ全体の処理を行って、そこからまとめて View に渡してレンダリングされ

る Rails とは全然ちがいますね」

「いいところに気がついたねー。その話題は後にとっておくとして、非同期処理のプロセスを待って

もらうやり方を見ていこう。まずは ES6 から導入された Promise 構文ね」

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const greet = () => {


console.log(' お や す み ');

sleep(2000)
.then(() => {
console.log(' 起 き た ');
console.log(' お は よ う ! ');
})
.catch(err => {
console.error(' 睡 眠 例 外 で す : ', err);
})
}

greet();

「今度は『おやすみ』が表示された後、しばらくして『起きた』
『おはよう!』と表示されました」

34
2-6. 非同期処理を扱う

「sleep() 関数が Promise クラスオブジェクトを返してるよね。これを then() のメソッドチェーンで

つなぐことで、非同期処理をひとつひとつ処理が終わるのを待って順番に実行していくことができる

ようになるの。ここでは then() はひとつだけだけど、複数つないでいくことができる」

「へえ―――。最後の catch は例外の捕捉ですよね?」

「そう。ES2018 からは、例外のあるなしにかかわらず最後にかならず実行される finally も使える

ようになったよ」

「うーん、でもあまり読みやすい構文じゃないですよね」

「JavaScript 独特の書き方だからね。Ruby に慣れた秋谷さんには、というより他の言語を学んできた

たいがいのプログラマには直感的じゃないかも。だから ES2017 から async/await という構文が導入


*3

されたわけなんだけど、こっちのコードも見てくれる?」

const sleep = ms => new Promise(resolve => setTimeout(resolve, ms));

const greet = async () => {


console.log(' お や す み ');

try {
await sleep(2000);
console.log(' 起 き た ');
console.log(' お は よ う ! ');
} catch (err) {
console.error(' 睡 眠 例 外 で す : ', err);
}
}

greet();

「実行結果は同じですけど、なんかすっきりと見やすくなりましたね」

「async で定義された Async 関数は、本文中に await を前置きすることで、他の Async 関数の実行結

果を待ってくれるようになるの」

「あれ? でもここの sleep() は Async 関数じゃないのでは?」

*3「エイシンク・アウェイト」と読む。
「asynchronous(非同期な)
」と「await(∼を待ち構えてい
る)
」から。「アシンク」ではないので注意。

35
第2章 ナウでモダンなJavaScript

「さすが目のつけどころがいいねー。察しがよくて助かるよ。そう、async/await は実のところ Promise

構文のシンタックスシュガー*4 なの。Async 関数は隠されてるだけで暗黙のうちに Promise オブジェク

トを返してるの」

「はー、なるほど」

「一般的なプログラマにはこっちの書き方のほうが直感的だし、コールバック関数が好きな人ってあ

まりいないんじゃないかな。余計な階層が増えるし。だから非同期処理はどうしても仕方がない場合

を除いて async/await を使って書きましょう」

「わかりました! こっちのほうが直感的というのは、私も同感です」

*4 「糖衣構文」とも。プログラミング言語において、読み書きのしやすさのために導入される書き方で、複雑でわ
かりにくい書き方と全く同じ処理になる文を、よりシンプルでわかりやすい記述法で書くことができるもののこと。

36
3-1. 関数型プログラミングは何がうれしい?

第 3 章 関数型プログラミングでいこう

3-1. 関数型プログラミングは何がうれしい?

「はーい、次はお待ちかねの関数型プログラミング講座です」

「ううっ、敷居高そう……」

「まあまあ、そう難しく考えないで。React による開発ではオブジェクト指向の出番はあまりなくて、

関数型プログラミングの考え方を求められます。これはもともと、HTML を構成する DOM のイン

タラクティブな操作という入り組んだ超複雑な難問に立ち向かうため、関数型のアプローチが最適だ

と Facebook の開発者たちが考えたからだね」

「どうして関数型のアプローチだと有効なんですか?」

「クラスから生成されたオブジェクトは内部に状態を抱えていて、それによって振る舞いが変わって

しまうでしょう。具体的にはメンバー関数の実行結果がメンバー変数に依存する。つまり副作用が大

きくて予測がつきづらい」

「うーん、私はそういうもんだと思ってきたので、それが問題だと言われてもピンと来ません……」

「Rails では処理はあくまで同期的だし、そこまで複雑なことはしないからね。でも非同期処理が関

わってくると、その不確実性は人間の頭で予測するのはほぼ不可能なレベルになる。別のプロセスか

らの処理がそのオブジェクトの状態を変えてしまっていたら、想定したのと違う振る舞いが起きかね

ない」

「……それはそうですね」

「前にも言ったように JavaScript は通信するのもローカルファイルを読むのも、基本全て非同期だか

らね」

「そうでした」

「だからこそ状態を内包してしまうクラスを用いた書き方は、どうしても使う必要があるところだけ

に限定しておいたほうがいいの。その必然性は、React の学習が進むにつれて実感してくるでしょう。

ところでここまで『関数型』って何気なく言ってきたけど、秋谷さんは『関数型プログラミング』

ってどういうことか理解してる?」

37
第3章 関数型プログラミングでいこう

「うーん、map() とか reduce() とかをよく使うイメージですかね?」

「ほ――、なんとなくわかってるじゃない。
『関数』っていうのは、数学の関数と同じものなんだよ。y =

f(x) ってアレね。数学の関数は、与えた引数が同じなら返り値は常に一定でしょう? そういう同じ

入力に対して同じ作用と同じ出力が保証されていることを参照透過性っていうんだけど、そんなふう

に内部に状態を抱えず、副作用を極力排したプログラミングを行うことで、非同期な DOM の書き換

えという難問に対処してるのが React なの」

「なるほどー。でもどうしても状態を抱えることが必要な場面ってありますよね? たとえばユーザ

ーのログイン状態とか。そういうのって関数型で処理できるんですか?」

「おっ、鋭いところをついてくるねー。それに関してはもっと後で説明することになるんだけど、ち

ょっとだけ触れておくと、React でよく用いられるのは状態を引数にとって新しい状態を返すやり方

ね。そして別の引数に対して、返される状態の差分が常に一定であることが保証されればいいわけ。

そうやって状態も外部データとして扱い、振る舞いを分離させて副作用を抑えるのが関数型のやり方

と言えるかな」

「ん―――?」

「まあ具体的なコードがないとピンとこないだろうから、先に進みましょう。さっきあなたが挙げて

くれた、map() や reduce() からね」

3-2. コレクションの反復処理

「コレクションの反復処理構文にいってみようか。これもまずサンプルコードを見てもらったほうが

いいね」

const arr = [1, 2, 3, 4, 5, 6, 7, 8];

console.log(arr.map(n => n * 2)); // [ 2, 4, 6, 8, 10, 12, 14, 16 ]


console.log(arr.filter(n => n % 3 === 0)); // [ 3, 6 ]
console.log(arr.find(n => n > 4)); // 5

38
3-3. 関数型プログラミングの概要

console.log(arr.every(n => n !== 0)); // true


console.log(arr.some(n => n > 8)); // false
console.log(arr.includes(5)); // true
console.log(arr.reduce((n, m) => n + m)); // 36
console.log(arr.sort((n, m) => n < m)); // [ 8, 7, 6, 5, 4, 3, 2, 1 ]

・ map() は対象の配列の要素一つ一つを加工した新しい配列を作る

・ filter() は条件に適合する要素だけを抽出して新しい配列を作る

・ find() は条件に適合した要素をひとつだけ取り出す。見つからない場合は undefind を返す

・ every() はすべての要素が条件を満たすかを真偽値で返す

・ some() は条件を満たす要素がひとつでもあるかを真偽値で返す

・ includes() は指定した要素が含まれるかを真偽値で返す

・ reduce() は配列の要素を、与えた式で畳み込んだ値を返す

・ sort() は各要素を、与えた条件によって並び替えた新しい配列を返す

「一気に説明するとこんな感じかな。だいじょうぶそう?」

「ところどころ Ruby と同じなので、だいたいわかります」

「よし。ちなみにこれらのうち includes() だけが ES2016 で、あとは ES5 から存在してるものね。

これらが関数型プログラミングで多用されるのは、非破壊的な処理を行ってくれるから。map() や

filter() は元の配列の値を一切いじらずに、新しい配列を生成して返すよね。関数型プログラミング

は副作用を嫌うと説明したと思うけど、だから相性がいいの」

3-3. 関数型プログラミングの概要

「駆け足で強引に納得させた気もするけど、arr.map(n => n * 2) ってちゃんと理解できてる?」

「えっ?」

「だって、これを書き換えるとこうなるんだよ」

39
第3章 関数型プログラミングでいこう

const double = n => n * 2;


arr.map(double);

「map() 関数の引数として double() という関数を渡してる。関数の引数に関数を渡すなんて、秋谷さ

んにはなじみのない手法だと思うけど」

「……言われてみればそうですね。なんとなくわかってた気になってただけかも」

「さらに突っ込むと、1行目の const double = n => n * 2; だって違和感あるはずだよ。だって変数に

関数を代入してるんだから。そんなことしたことないでしょ?」

「ううっ」

「じゃ、そこも含めてあらためて確認していこうか。一般的に関数型プログラミングでは、だいたい

以下のようなことが普通に行われます」

1. 名前のない使い捨ての関数(無名関数)が使える

2. 変数に関数を代入できる(=変数に無名関数を代入することで名前をつけられる)

3. 関数の引数に関数を渡したり、戻り値として関数を返すことができる(高階関数)

4. 関数に特定の引数を固定した新しい関数を作ることができる(部分適用)

5. 複数の高階関数を合成して 1 つの関数にできる

「さっきの n => n * 2 が1でいう無名関数だね。これを const double = n => n * 2; のように変数に代

入することができるというのが2で言ってること」

「ああ、だんだんわかってきました」

「よしよし。3と4はあらためて次で説明してあげるよ。5についてはもっと後じゃないと実例が示

せないから、とりあえず棚上げにしておく」

40
3-4. 高階関数

3-4. 高階関数

「高階関数とは、引数に関数をとったり、戻り値として関数を返したりする関数のこと。英語では

Higher Order Function といいます」

「関数を引数にとる関数…………」

「もうやったじゃない。map() や filter() がそうだよね。arr.map(n => n * 2) は、n => n * 2 という

無名関数を引数として渡されてる」

「あっ、そうか」

「戻り値として関数を返す関数、というのはもうちょっと複雑になる。たとえばこのコード」

const hof = (ex, fn) => {


return n => fn(n + ex);
};

const plusOneDouble = hof(1, n => n * 2);


console.log(plusOneDouble(4)); // 10

「関数 hof は、渡された第二引数の関数に、その引数として第一引数を加えて実行する関数を返す関

数。ちょっとややこしいけど。だから plusOneDouble は、その引数に 1 を加えて2倍する関数になる。

実行してみて」

「10 が表示されました。(4 + 1) × 2 = 10 だからか。うーん」

「ふふ、わかるけどなんでこんなことするんだろうって、モヤモヤした顔してるね。必要な場面に迫

られて実践を積んでいかないと、この便利さはわからないだろうから、とりあえずやり方だけおぼえ

てくれてればいいよ。

あと return は省けるから、関数 hof は (ex, fn) => n => fn(n + ex) とも書ける。というか省略でき

るものは省略したいので、むしろこっちのほうで書いてほしいかな」

「了解です」

「後のほうで、高階関数の応用としてコンポーネントを引数にとってコンポーネントを戻り値として

返す Higher Order Component(HOC)というものが出てきます。これは React でコンポーネントに

機能を付与するためによく使われるテクニックなんだけど、原理はこの高階関数と同じだから、これ

41
第3章 関数型プログラミングでいこう

がわかってると HOC の理解も早くなるよ」

3-5. クロージャ

「次はクロージャについて」

「クロージャって LISP の一種とかじゃなかったでしたっけ」

「そっちは Clojure ね。Java で実装された LISP の一方言なプログラミング言語。これから説明するの

は Closure。日本語では『関数閉包』と言われる書き方。説明のために、まずはやりたいことをクラ

スを使って書いてみるね」

class Counter {
constructor(initialCount) {
this.c = initialCount;
}

increment() {
return this.c++;
}
}

const counter = new Counter(1);


console.log(counter.increment(), counter.increment(), counter.increment()); // 1 2 3

「実行ごとに値が加算されていくカウンターですね。クラスなら簡単に実現できますけど、これを関

数だけでやれと言われると難しい気がしますね。外にグローバル変数を用意して、それを参照するよ

うにすれば書けなくはないですけど……」

「うん。わかっていると思うけど、グローバル変数に依存した処理を書き散らすのはコードの可読性

を落とし、各モジュールの独立性を損なって予期せぬ挙動を引き起こすので、基本的には禁物だよ。

42
3-5. クロージャ

で、こういう場合にクロージャを使うとうまく書けるの。上の例で実現している機能をクロージャを

使って書くとこうなる」

const counterMaker = (initialCount) => {


let c = initialCount;
const increment = () => c++;

return increment;
};

const count = counterMaker(1);


console.log(count(), count(), count()); // 1 2 3

「あっ、変数 c の値が実行ごとにクリアされることなく加算されていってる! なんでこういう挙動

になるんでしょうか?」

「これはね、まず関数 counterMaker が最後に関数 increment を返してるから、その戻り値を代入した

count の中身は内部の関数 increment なのよ。だからそれを実行すると counterMaker 内部の increment

が実行される。increment が単体で呼ばれるわけでなく、あくまで counterMaker の環境の中で実行さ

れているから変数 c の値が毎回リセットされることなく蓄積されていくわけ」

「んん―――、わかったような気もするけど、なんか強引に納得させられたような……」

「それを言うなら、クラスを使った書き方だって同じだと思うよ。要は慣れの問題。自分でいくつか

クロージャを使ったコードを書いてみれば考え方になじめるんじゃないかな。

ちなみにこのサンプルは戻り値として関数を返している高階関数との合わせ技だけど、クロージャ

は必ずしも関数を返す必要はないから、そこは勘違いしないでおいて。あくまでクロージャっていう

のは、単に親関数スコープの変数を参照する関数のことを指すので」

「わかりました。ところで、クロージャは React 開発の中でよく使われるんですか?」

「関数でコンポーネントを実装するときに、その中で外のスコープの変数を参照する関数を定義する

ことはよくあるから、それは結果としてクロージャになるかな。その場合は、まああまりクロージャ

と意識することはないかもしれないけど」

43
第3章 関数型プログラミングでいこう

3-6. ジェネレータ

「値を保持しつつ繰り返し処理や逐次処理を行うための手段に JavaScript が提供しているものとして、

他にジェネレータ関数というものがあるの。ジェネレータの記述方法はちょっと独特で、function*

という宣言文と yield という返却構文を用いられるんだけど、これについては口で説明するよりまず

サンプルコードを実行してみたほうが早いね」

function* rangeGenerator(end, start = 0) {


let n = 0;

for (let i = start; i < end; i++) {


n += 1;
yield i;
}
}

const gen = rangeGenerator(3);


console.log(gen.next()); // { value: 0, done: false }
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: undefined, done: true }

「return 文がないのに、戻り値は関数を備えたオブジェクトなんですね」

「そこも特殊だよね。戻り値のオブジェクトから next()という関数が実行できるんだけど、それによ

って yield で返された値がその戻り値オブジェクトの value プロパティに格納される。そしてそれ以

上ジェネレータの中で yield 文が実行されることがなくなると、value プロパティは undefined になり、

done プロパティが true になる」

「……見た目もそうですけど、使い方もかなりクセのある機能ですよね。芝崎さんはこれをどんな場

面で使ってるんですか?」

「自分で一から書くときでジェネレータがどうしても必要になった場面は正直なところ、今までない

44
3-7. カリー化と関数の部分適用

ね。ただ、これは Redux-Saga*1 というウチのチームでも採用してるライブラリを使った処理を書くと

きに頻出する記法なので、今のこの段階で教えておきたかったの」

「なるほど。そのときになってあわてないよう、しっかりおぼえておきますね」

3-7. カリー化と関数の部分適用

「ところで秋谷さんはカリー化って言葉、聞いたことある?」

「なにそれおいしそう。コロッケにカレー粉を入れてカレーコロッケにするみたいなことですか?」

「うんうん、模範的な回答をありがとう。確かにスペルは同じ『curry』だけどね。でもこれは人名か

ら由来してるので、カレーとは関係ない。Haskell Curry っていう 20 世紀アメリカの偉い数学者」

「Haskell って、超難しいことで有名な純粋関数型言語ですよね。その Haskell さんですか?」

「うん、プログラミング言語は Pascal や Ada みたいに昔の高名な学者さんにちなんだ名前をつける

ことがよくあるけど、Haskell もその系統だね。中でも Haskell Curry さんは名が関数型言語、姓が関

数型プログラミングの一概念にちなまれてる稀有な例なの」

「すごい。リアルで『俺が……、俺こそが関数型プログラミングだ!』みたいな発言が許される人で

すね」

「もう故人だけどね。それでカリー化(currying)とは何かという話に戻るけど、カリー化とは端的

に言うと『複数の引数をとる関数を、ひとつだけ引数をとる関数に分割してネストさせること』なの」

「????」

「コードを参照したほうが早いね」

const multi = (n, m) => n * m;


console.log(multi(2, 4)); // 8

*1 https://redux-saga.js.org/

45
第3章 関数型プログラミングでいこう

const curriedMulti = n => {


return m => n * m;
}
console.log(curriedMulti(2)(4)); // 8

const simpleCurriedMulti = n => m => n * m;


console.log(simpleCurriedMulti(2)(4)); // 8

「n と m の引数をとってその積を返すだけの関数ですね。ふたつめは、n を引数にとり『m を引数にと

って n と m の積を返す関数』を返す関数……ってことでしょうか?」

「そう、これはさっきやった高階関数だね。こうやって、ひとつずつの値を返す関数がネストした高

階関数にすることが『カリー化』

「なるほど、わかってきました。実行時にカッコがふたつ並んでるのは……」

「curriedMulti(2) が『m を引数にとって m * 2 を返す関数』を返すから、その関数を実行するために

はもうひとつカッコが必要なの」

「そうか、関数がネストしてるんですもんね。それでえーっと、みっつめは単に return 文を省略した

だけですね」

「そうだね。高階関数の説明のときにも、こっちの書き方をすすめたけど、できるだけシンプルなほ

うで書きましょう。じゃ、次はカリー化された関数の『部分適用』について説明しよう」

const multi = n => m => n * m;


console.log(multi(3)(5)); // 15

const triple = multi(3);


console.log(triple(5)); // 15

「さっきのカリー化された高階関数 multi の外側の関数に 3 を渡したものに triple という名前をつ

けてますね」

「うん、だから triple は multi のひとつめの引数に3を渡した関数ということ」

「なるほど、こうするとどんな数を渡しても、常に3倍される関数が作れるんですね」

「そう、こんなふうにカリー化された関数の一部の引数を固定して新しい関数を作ることを『関数の

部分適用』っていうの」

46
3-7. カリー化と関数の部分適用

「なるほどー。柴崎さんの説明を聞くまでは、
『関数型プログラミング』って言葉を見ただけで難し

そうって尻込みしてましたけど、こうやって段階を踏んで教えてもらえたおかげで、かなり理解でき

ました。ありがとうございます!」

「うん、でもこれが関数型プログラミングの全てってわけじゃないけどね……。React を使って開発

する上で必要なものに絞って教えたつもり」

「React で開発するときって、高階関数とかよく出てくるんですか?」

「うーん、たとえば npm の便利なモジュールを使おうとしたら、インターフェースが高階関数だった

りすることはままあるね。設定のための値はほぼ不変なのでそこだけ部分適用した関数を取り回した

り、みたいに使ったり。サンプルをコピペすれば使えなくもないけど、理解して使うのとそうじゃな

いのとでは全然ちがうと思う。

さらに、後のほうでこれもあらためて説明するけど、React を使う上でのテクニックで『コンポー

ネントを引数にとってコンポーネントを返す関数』というものを使う場面が、ちょっと高度なことを

しようとすると出てくるの。それを理解する上で高階関数の概念は知っておいたほうが絶対いいと思

う」

「……そうなんですね。以前の私は、関数型プログラミングへの理解がほとんどないまま突撃したせ

いで玉砕しちゃったのかもしれません。でもこうやって少しずつ理解を積み上げていくと、見える世

界が広がってきて、前はわからなかったことも理解できるような気がします」

「よし。その気持ちを忘れずに、どんどん先に行ってみよう」

47
第4章 型のあるTypeScriptは強い味方

第 4 章 型のある TypeScript は強い味方

4-1. TypeScript は今やメジャー言語

「はい、じゃ次は TypeScript についての説明にいこうか」

「このチームのメイン開発言語って聞いてます。でも疑問なんですけど、JavaScript を生で使ってる人

のほうが多いはずなのに、どうして TypeScript を使うんですか? ES2018 ならモダンな仕様が盛り

込まれていて、わざわざ AltJS を使う必要性はなさそうですけど」


*1

「それについての理由はいくつかあるね。でもそれを説明する前に、TypeScript がマイナーというイ

メージを修正してもらおうかな」

「……すいません、マイナーだと思ってましたが違うんですか?」

「GitHub のリポジトリでの統計による調査*2 では、2018 年の月間アクティブユーザーランキングで

TypeScript は 10 位に入ってる。ちなみに Ruby は 11 位ね」

「えええ、Ruby より上なんですか?!」

「それだけじゃない。成長率ランキングでは、Go に次いで僅差の 2 位」

「私の知らないところでそんなことに……」

「Rubyst の人たちは CoffeeScript に振り回された過去があるせいで、AltJS にあまりいいイメージを

持ってないのかもしれないね。ちなみにこの調査によると、TypeScript のシェアが CoffeeScript を上

回ったのは 2015 年の年末あたりで、そう昔のことじゃない」

「AltJS って昔は色々あったけど最近は聞かないので、すぐ廃れてしまうイメージでした」

「TypeScript だけが例外だったと言えるかもね。2018 年 10 月に npm の運営会社が発表した npm ユ

*1 Alternative JavaScript の略で、JavaScript の代替言語を指す。 AltJS で書かれたプログラムは、コンパイラによっ


て最終的に JavaScript コードに変換される。

*2 Ranking Programming Languages by GitHub Users


https://www.benfrederickson.com/ranking-programming-languages-by-github-users/

48
4-1. TypeScriptは今やメジャー言語

ーザー 16,000 人を対象にした調査*3 では、なんと半数近くの 46%が TypeScript を使っていると答えて

いるの」

「ええー!」

「この結果には私もびっくりしたけど」

「これは世界のトレンドだけど、日本もその例外じゃない。この写真を見て。これは 2018 年 11 月

に開催された国内最大の JavaScript カンファレンス『東京 Node 学園祭 2018』のエントランスで参加

者が自分の使っている言語にシールを貼っていく投票ボードの途中結果を私が撮影したものなんだけ

ど」

「えっ、TypeScript が圧倒的じゃないですか!」

「カンファレンスに参加するような意識高めの人たちの間での自主投票だから実際の現場ではここま

*3 npm and the future of JavaScript https://slides.com/seldo/npm-future-of-javascript#/

49
第4章 型のあるTypeScriptは強い味方

での差はつかないんだろうけど、今や現場では ES5 を直接書いてる派はもちろん、最新 ECMAScript

を Babel をかませて使っている派よりも、TypeScript 派の勢力が上回りつつある状況だね。

さらに今では Create React App や React Native も本家が公式に TypeScript をサポートし始めたし、

React コミュニティだけに限っても本家が推していた Flow*4 は今や見る影もなく、TypeScript 勢力は

日々拡大してる」

「……なるほど、私の認識がまちがってたことはわかりました。でもどうしてですか? TypeScript

が今こんなに使われるようになってるのって」

「これは私の考えだけど、静的型付け、型推論、Null 安全性という最近のプログラミング言語のトレ

ンドを押さえつつ、それ以外の部分はまんま JavaScript と文法が同じ、というのが大きかったと思う」

「うーん、型ってそんなに必要ですか? 今ひとつピンとこないんですけど……」

「Vim で Ruby を書いてた秋谷さんの感覚ではそうかもね。でも考えてみて、最近の人気言語、Go

とか Rust とか Kotlin とか Swift とかって、みんな静的型付け、型推論、Null 安全性を備えてる」

「言われてみればそうなんでしょうけど……」

「ひと昔前は Python とか Ruby といった動的型付け言語が人気だったよね。いちいち型を書かなくて

いい手軽さがウケたんだと思う。Java なんか文字列だけで3つくらい型があって、取り扱いの煩雑さ

といったらなかった。

でも Ruby も普及拡大につれて、それなりの規模でのチーム開発が普通になっていった。そんな中

でソフトウェアテストの重要性が叫ばれるようになり、TDD が Ruby コミュニティから広まっていっ

たのは、静的な型がないために引数や返り値の型検証をテストを書くことで保証する必要性があった

というのも理由のひとつだったんじゃないかな。

いっぽう、新しく生まれた静的型付け言語たちは型推論を備えるようになった。型推論とはいちい

ち型を書かなくても、処理系が文脈から型を予測して型付けしてくれる機能のこと。もちろんすべて

のケースで省略できるわけじゃないけど、形を書く煩雑さからプログラマをある程度開放してくれた。

そして静的型付け言語は、次に Null 安全性を手に入れた。プログラムで一番多いバグは Null アク

セスの例外。Java なら NullPointerException で、Ruby なら Nil への NoMethodError だね。Null 安全

な言語は、コンパイルの段階で Null になる可能性のあるコードをチェックしてはじいてくれる。

*4 Facebook 製の JavaScript 静的型チェッカー。Flowtype と呼ぶことも。OCaml で実装されている。


https://flow.org/

50
4-1. TypeScriptは今やメジャー言語

TypeScript も 2016 年リリースのバージョン 2.0 で正式にこれに対応したの」

「んん――」

「まあ口だけの説明じゃ、わかりづらいよね。ちょっと私の PC の画面を見てくれる?」

―――― カタカタカタカタ…… ――――

「あっ。型の不整合や Null になる可能性のあるところを、リアルタイムで VSCode が指摘してくれて

る!」

「そう静的型付け言語は IDE と相性がいいの。いちいちコンパイルしなくても、コーディング中に


*5

リアルタイムに問題箇所を指摘してくれる。これは Vim とか Emacs ではなかなか難しいよね。型推論、

Null 安全性、そしてモダンな IDE。静的型付け言語がこれらを手に入れたことで、動的型付け言語

の型を書かなくていいというアドバンテージは薄まり、いっぽうで堅牢なコードが効率よく書ける静

的型付け言語のメリットが強調されるようになった。これが近年のプログラミング言語のトレンドだ

ね」

「そうだったんですね。知りませんでした……」

「TypeScript はそれに加えて、最新の ECMAScript に盛り込まれている非同期処理や関数型プログラ

ミングへの対応といった他のトレンドも備えてる。また開発元はあの天下の Microsoft で、継続的に

盤石のサポートを続けていて、さらには Google が標準言語として採用しているという後ろ盾もある。

まあ Google の採用は、Angular の開発言語だからというのもあるけど」

「はあああ、目から鱗が落ちた思いです。なんか俄然、TypeScript やる気になってきました!」

「いいねー。じゃ、次からは実際にコードを実行しながら TypeScript を学んでいこう」

*5統合開発環境。コンパイラ、テキストエディタ、デバッガなどをひとつの UI から利用できるように
したもの。VSCode のような高機能エディタもそれに含まれる。

51
第4章 型のあるTypeScriptは強い味方

4-2. 型のバリエーション

「まず TypeScript の実行環境をグローバルインストールしようか。npm i -g typescript ts-node を実

行して。インストールしたらパスを通すために、シェルを再実行してね」

「……っと。はい、終わりました」

「まず TypeScript のプリミティブ型についてざっと説明するね。これは JavaScript と全く共通で以下

の 6 種類になります」

・ number (ex. 3)

・ string (ex. 'foo')

・ boolean (ex. true, false)

・ symbol

・ null

・ undefined

「symbol はオブジェクトのキーにできたりするんだけど、JSON へのパースができないのであまり使

う機会がないね。undefined はその変数が定義されていないときの値で、null とは明確に区別されま

す」

「なるほど。それぞれの型の値の真偽を教えてもらえますか?」

「いい質問だね。それぞれ、0, ''(=空文字), null, undefined が false で、あとは全部 true になるよ。

ちょっと ts-node でいろいろ試してみようか。


ts-node は TypeScript の REPL*6 コマンドで、
TypeScript

の挙動を確認したいときに私はよく使ってる。npm install -g ts-node typescript でグローバル環境

にインストールしとくといいよ。

あとは TypeScript Playground(http://www.typescriptlang.org/play/)みたいなサイトもブラウザ

ですぐ確認できて便利だね」

*6Read-Eval-Print Loop の略。主にインタプリタ言語において、対話的にコードが実行できる環境のこ


とをいう。

52
4-2. 型のバリエーション

> let n: number = 3;


> n = 'foo';
[eval].ts(2,1): error TS2322: Type '"foo"' is not assignable to type 'number'.

> if (n) { console.log('`n` is true') }


`n` is true

> n = null;
null

「あれ? null は代入できちゃうんですか?」

「strictNullChecks オプションが有効になってないからね。ちょっと第1章で生成したプロジェクト

のルートディレクトリから、tsconfig.json というファイルを今いるカレントディレクトリにコピって

こよう。その上で、この tsconfig.json を置いたディレクトリで ts-node を再実行してみるね」

> let s = 'foo';


'foo'
> typeof s
'string'

> s = null;
[eval].ts(2,1): error TS2322: Type 'null' is not assignable to type 'string'.

「今度は Null 代入で怒られたよね。TypeScript には strictNullChecks オプションというものがあっ

て、これが設定ファイルやコンパイルオプションで有効になってないと、厳密な Null 安全性を保証し

てくれないの。前に戻って tsconfig.json のファイルの中を見てみて。strict オプションが true にな

ってるでしょう? これは strictNullChecks を始めとする strict 系のオプションをまとめて設定する

もので、今の Create React App ではデフォルトで true になってるの。だから Null 代入で怒られる」

「なるほどー」

「あとついでに型推論についても、さらっとデモしてるので見ておいて。typeof 演算子を使うと、文

字列でその変数の型を教えてくれるんだけど、定義で let s: string = 'foo'; のように指定しなくて

も string 型になってるのがわかるでしょ?」

「あ、ほんとだ」

53
第4章 型のあるTypeScriptは強い味方

「こんな感じで、わざわざ型を指定しなくてもその型が明らかな場合は、処理系が推測して適切な型

をつけてくれる。これを『型推論』と呼ぶの。VSCode なら、任意の変数の上にマウスカーソルをホ

バーさせると、ポップアップでその型を教えてくれるよ」

「へー、便利ですね」

「Null 安全性の話に戻るけど、逆に strictNullChecks が有効になってる場合でも、あえてその変数の

値に Null を許容したい場合はどうするか。この後で改めて説明する共用体型というものを使います」

> let i: number | null = 1;


1
> i = null;
null

「ふむふむ。これは number か null のどちらかが入る型ってことですよね。これって null 以外でも

いけるんですか?」

「できるよ、ほら」

> let some: number | string | undefined = 'bar';


> some = 50;
50
> some = undefined;
undefined

「さらには、こんなこともできる」

> let pet: 'cat' | 'dog' | 'rabbit' = 'dog';


> pet
'dog'
> pet = 'cat';
'cat'
> pet = 'hamster';
[eval].ts(4,1): error TS2322: Type '"hamster"' is not assignable to type '"cat" | "dog" | "rabbit"'

54
4-2. 型のバリエーション

「これは単純な string 型ではなく 特定の文字列のみを許可するストリングリテラル型、または文字

列リテラル型と呼ばれるものなの。単独ではあまり使い途がないけど、
『|』 で列挙することで enum

のように定義することができる。TypeScript にも enum 型はあって文字列 enum でも同じようなこと

はできるんだけど、こっちのほうが React 開発ではよく使われてるね。

最後にちょっと特殊な型である any と never について説明しておこうか。まず、any で定義した変数

は、その名の通りどんな型の値でも受けつけるようになります」

> let val: any = 100;


100
> val = 'buz';
'buz'
> val = null;
null

「え? これ TypeScript の意味ないんじゃ……」

「そう、any で定義するとそこだけ JavaScript に退化するようなものと言えるね。これを使うのは一

種の負けかもしれない。でもたとえば JSON ファイルをパースしてそのままオブジェクトとして使う

場合とか、事前に一律の型を当てはめるのが難しかったりする。そういうときはやむをえず any を指

定するよね」

「ふーん、なるほど」

「次に never。これは何ものも代入できない型、ということなんだけど」

「え? 何も代入できないなら使いようがなくないですか?」

「ところがそうでもないんだな、これが。ちょっとこのサンプルコード*7 を VSCode で開いてみて」

const greet = (friend: 'serval' | 'caracal' | 'cheetah') => {


switch (friend) {
case 'serval':
return 'Hello, Serval!';
case 'caracal':
return 'Hello, Caracal!';

*7 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/04-typescript/04-never.ts

55
第4章 型のあるTypeScriptは強い味方

case 'cheetah':
return 'Hello, Cheetah!';
default:
const check: never = friend;
}
};

console.log(greet('serval')); // Hello, Serval!

「default 節のとこ、これ何やってるんですか?」

「うん、じゃあちょっと case 'cheetah': から始まる2行を削除してみてくれる?」

「あっ、VSCode が変数 check のところで『Type '"cheetah"' is not assignable to type 'never'.』と

エラーを出しました」

「そう、このままだと check に 'cheetah' が入ってしまう可能性があるから、never 型には代入でき

ない。だからコンパイルできないって怒られてるの。こうやって使うことで、case 文の漏れを未然に

チェックすることができるわけ。裏技ぽいけど、実際の現場では重宝するよ」

「なるほどー。うーん TypeScript、奥が深いですね……」

4-3. 配列とオブジェクト

「それじゃ次は配列についてね」

> const arr1: number[] = [1, 2, 3];


[ 1, 2, 3 ]
> const arr2: Array<number> = [1, 2, 3];
[ 1, 2, 3 ]

「上と下、どっちでも同じことなんだけど、一般的なコーディングルールでは後者のジェネリクスを使

ったものではなく、前者のようなスタイルを要求されることが多いので、前者の形で書きましょう」

56
4-3. 配列とオブジェクト

「はい」

「続いては、オブジェクトの型について」

const john: { name: string, age: number } = { name: 'John', age: 25 };

interface User {
name: string;
age?: number;
}
const jane: User = { name: 'Jane', age: 27 };
const Jack: User = { name: 'Jack' };

type Person = User;


const rick: Person = { name: 'Rick', age: 31 };

「ざっと書いてみたけど、わかるかな?」

「まず、オブジェクトの型は最初の例のように、変数宣言時に直に書くことができるけど、interface

文で定義することもできると?」

「そう。それから?」

「インターフェースの定義では、プロパティ名の後ろに『?』がつくと省略可能になる?」

「そうだね」

「type はインターフェース型を代入するためのものでしょうか」

「うん、それも正解。これは Type Alias といって、インターフェース型に別の名前をつけられるもの

なんだけど、単独で使うより型同士の合成時に使われることが多いね。こんな感じに」

interface Foo { hoge?: number, fuga: string };


interface Bar { hoge: number };
interface Buz { piyo: string };

type FooBar1 = Foo & Bar; // { hoge: number, fuga: string }


type FooBar2 = Foo | Bar; // { hoge?: number, fuga: string } or { hoge: number }
type FooBuz1 = Foo & Buz; // { hoge?: number, fuga: string, piyo: string }
type FooBuz2 = Foo | Buz; // { hoge?: number, fuga: string } or { piyo: string }
type BarFooBuz = Bar & (Foo | Buz);
// { hoge: number, fuga: string } or { hoge: number, piyo: string }

57
第4章 型のあるTypeScriptは強い味方

「んん、ちょっと入り組んでますね。えっと……」

「型の合成の説明については、以下にまとめてみたので、今のコードと照らし合わせながら読んでみ

て」

・ 交差型(Intersection Type) …… 複数の型をひとつにまとめたもの。


『&』を使う。合成し

た型のすべてのプロパティを備えるが、同じ名前のプロパティが省略可能と必須だと、必須

が優先される。

・ 共用体型(Union Type) …… 渡された複数の型のいずれかが適応される型。


『|』を使う

「React ではコンポーネントの引数の型合成を行うことが多いんだけど、そちらでは『&』を用いる交

差型がよく使われるね。ところで型の拡張については、 Interface 文に変な挙動があってね、同じ名前

で複数回定義すると上書きじゃなく継承されていくの。こんな感じ」

interface User { name: string }


interface User { age: number }
interface User { gender: 'm' | 'f' }
const user: User = { name: 'jane', age: 27, gender: 'f' };

「……これは気持ち悪いですね」

「だから既存のオブジェクト型を拡張したいときは、型合成を行って Type Alias で名前をつけるやり

方を採りましょう。順番としては、まっさらの状態では interface でオブジェクト型を定義、既存の

オブジェクト型の拡張や合成が必要なときは type を使う感じかな。

じゃ最後に、const で配列やオブジェクトの変数を宣言したときの注意してほしい点について説明

しておこうか」

> const arr = [1, 2, 3];


> arr[0] = 7;
7
> arr
[ 7, 2, 3 ]

58
4-3. 配列とオブジェクト

> const obj = { a: 1, b: 2 };


> obj.b = 5;
5
> obj
{ a: 1, b: 5 }

「えっ、const で宣言した変数って不変なんじゃなかったでしたっけ」

「うん、だから変数自体の再代入とかはできないよ。でも各要素の上書きや追加はできちゃうんだな、

これが」

「なんか中途半端ですね」

「だから安全性のためにどうしても配列やオブジェクトを中身までイミュータブルにしたい場合、こ

れまでは Immutable.js や Immer といったライブラリを導入する人たちもちらほらいた。でもけっこ


*8 *9

うコードが冗長になってめんどくさいので、あまりメジャーなやり方にはならなかったみたい。で、

それから最新の TypeScript 3.4 でついに Readonly な型が正式に提供されてね。こんなふうに書くこ

とができるようになったの」

> const arr1: ReadonlyArray<string> = ['foo', 'bar'];


> const arr2: readonly string[] = ['foo', 'bar']; // 上 の 書 き 方 と 結 果 は 同 じ

> arr1[0] = 'buz'; // error TS2542


> arr2[2] = 'buz'; // error TS2542

> const obj1: Readonly<{ foo: number }> = { foo: 2 };

> obj1.foo = 8; // error TS2540

「へえ、よさそうじゃないですか」

「うん、私もそのうち導入したいと思ってる。でもリリースされたばかりで使用実績があまりなくて、

React や Redux で使う際のベストプラクティスがまだ見えてない状態なので、それまでは様子見かな。

*8 https://immutable-js.github.io/immutable-js/

*9 https://github.com/mweststrate/immer

59
第4章 型のあるTypeScriptは強い味方

だから今のところは書く側が気をつけて、インデックスを指定して値を上書きするようなコードを

書かないようにしましょう。たとえばオブジェクトの任意の部分を変更したオブジェクトが必要な場

合、そのプロパティを直に書き換えるんじゃなくて、こんなふうに書くといいよ」

> const obj = { red: 'ff0000', green: '00ff00' };


> const newObj = { ...obj, green: '00ee00', blue: '0000ff' };
> newObj
{ red: 'ff0000', green: '00ee00', blue: '0000ff' }

「なるほど、スプレッド演算子を使って任意の要素だけを置き換えた新しいオブジェクトを返すんで

すね。このやり方なら副作用を生まずに処理できますね」

4-4. 関数の型定義

「次は関数の型定義ね。TypeScript では関数を宣言するとき、戻り値は型推論で省略できるけど、引

数は必ず指定する必要があるので気をつけて。下のサンプルでは、あえて戻り値の型は省略せずに書

いてます」

> const add = (n: number, m: number): number => n + m;


> add(1, 3);
4

> function subtr(n: number, m: number): number { return n - m; }


> subtr(5, 4);
1

> const hello = (): void => { console.log('Hello!') };


> hello();
Hello!

60
4-5. コンパイル設定

「アロー関数では戻り値の型宣言って、引数カッコの後に書くんですね」

「最初の変数名の横に書きそうになるよね。でも、それだと無名関数を定義したときにその中で戻り

値型を定義できない。こういうものだと思って受け入れましょう。

それでさっき関数の戻り値の型は明示しなくても型推論が効くと言ったけど、その推論した戻り値

の型を抽出する方法があるの。ReturnType というもの」

> const aloha = () => 'Aloha!';


> type Greeting = ReturnType<typeof aloha>;
> const chao = (): Greeting => 'Chao!';

「へー。でもこれ、どういうときに役立つんですか?」

「複数の関数の戻り値型をまとめて共用体型を作りたいときとか。今はイメージしにくいかもしれな

いけど、React で Global State をハンドリングするためのメジャーなライブラリに Redux というもの

があって、先のほうで学ぶ予定なんだけど、それを TypeScript で使うときにこの ReturnType が出て

くるよ」

「なるほど、先回りして教えてくれてるわけですか。ありがとうございます!」

4-5. コンパイル設定

「さっき strictNullChecks オプションの話をしたときにも言ったけど、TypeScript のコンパイル設定

は tsconfig.json というファイルで行われます。以下は、Create React App がデフォルトで生成する

tsconfig.json の中身ね」

{
"compilerOptions": {

61
第4章 型のあるTypeScriptは強い味方

"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "preserve"
},
"include": [
"src"
]
}

「TypeScript のコンパイラは明示的に指定されなかった場合、実行されたカレントディレクトリから、

親ディレクトリへとさかのぼって tsconfig.json ファイルを探し出して読み込むの。ここでは strict

が true になっているので、さっきこのファイルがあるディレクトリで ts-node を実行したとき、Null

許容ではない変数に Null を代入したら怒られたよね」

「はい」

「それぞれのオプションが何を意味するかは、随時公式サイト で随時参照してね。ひと昔前は、開
*1

発しやすくするためにいくつか手を入れる必要があったんだけど、今の Create React App が生成する

tsconfig はほぼ完璧なので、何も考えずそのまま使えます。いい時代になったよね……(遠い目)

「ふーん、そうなんですね……」

「あと現状の注意点として、Create React App の最新バージョン 2.1.8 現在、TypeScript 環境で絶対

パスインポートがサポートされていないの」

「絶対パスインポート?」

*1 TypeScript Complier Options https://www.typescriptlang.org/docs/handbook/compiler-options.html

62
4-6. モジュールの型定義

「これを見てみて。上の行が相対パスインポート、下の行が絶対パスインポート」

import { MyAwesomeComponent } from './components/MyAwesomeComponent ';


import { MyAwesomeComponent } from 'components/MyAwesomeComponent ';

「なるほど。これだけではアレですけど、たとえばディレクトリをまたがったときに相対パスインポ

ートだと ../../foo/bar/Buz のように親ディレクトリをさかのぼってパスを記述しなきゃいけないと

ころが src/ ディレクトリを起点に頭から書き下せるわけですね」

「そう。わかりやすいしファイルを移動させたときにいちいちパスを書き直さなくてもいいから、ぜ

ひ使いたい機能なんだけど、未サポートなんだよね。次の 2.2.0 でサポートされる予定*11 なので、動

向をチェックしながらリリースされ次第、既存のコードも絶対パスに書き換えていく予定なのでおぼ

えておいて」

「わかりました」

4-6. モジュールの型定義

「npm には、Rubygems とか他のどの言語よりも多い無数のモジュールライブラリが存在してるんだ

けど、ほとんどが JavaScript で書かれていて、TypeScript 製のものはまだ少ないの。でも TypeScript

でも JavaScript 製の膨大な npm 資産を活用したいよね」

「それはそうですね」

「だから TypeScript にはそのための仕組みが用意されてる。JavaScript で書かれたライブラリでも、

拡張子 .d.ts の型定義ファイルさえ用意すれば TypeScript にインポートして使える機能があるよ」

「おおー」

*11 2.2.0 Milestorne https://github.com/facebook/create-react-app/milestone/60

63
第4章 型のあるTypeScriptは強い味方

「最近では、ライブラリの作者自身が TypeScript ユーザーのために最初から型定義ファイルを用意し

てくれていることもめずらしくないね。何せ今や npm ユーザーの約半数が TypeScript を使っている

わけだから。でもまだまだ、そういうモジュールは多数派とは言える状況じゃない。そこで

DefinitlyTyped*12 の出番になる」

「なんかかっこいい名前が出てきましたね」

「DefinitlyTyped は、有志のユーザーたちが自分の使いたいモジュールの型定義ファイルを作り、集め

て提供しているプロジェクト。普通に GitHub の一リポジトリなんだけど、数が膨大すぎるて GitHub

の Web インターフェースのページでは表示しきれないくらい。

お目当てのパッケージの型ファイルを探す方法は、一番簡単なのは npm のモジュール名の頭に

@types/ をつけること。たとえば React 用ルーティングライブラリの React Router は自前で型定義ファ

イルが用意されていないので、npm のサイトで @types/react-router で探してみる。または yarn info

@types/react-router を実行してみる」

「あ、yarn info で見つかりましたよ!」

「自前で用意されてるのか DefinitlyTyped にあるのかわからないときは、yarn info @types/... で探

すといいよ。自前で用意されてる場合は『非推奨:このパッケージはフェイクで、オリジナルのライ

ブラリが型定義ファイルを提供しているので、これをインストールする必要はありません』と表示さ

れるから」

「へー、親切ですね」

「ただ DefinitlyTyped の型定義ファイルは、あくまで有志の第三者による非公式のものなので、動作

が完全には保証されてないことをおぼえておいて。またオリジナルのバージョン更新についていけて

なくて、最新版に対応していないこともある。私も実際に入れてみて、部分的に動かなかったことが

あるし」

「ありゃー」

「だからオリジナルのライブラリが最初から TypeScript で書かれていたときなんか、小さくガッツポ

ーズとか出ちゃうよね」

「あはは、柴咲さんかわいいー」

*12 http://definitelytyped.org/

64
5-1. JSXとは何であるか、何ではないのか

第 5 章 拡張記法 JSX

5-1. JSX とは何であるか、何ではないのか

「はい、今日はまず JSX について学んでいきましょう。


『Hello, World』を作ったときにもうさわった

わけだけど、秋谷さんは JSX とは何なのかわかる?」

「最初、Rails でいう ERB や Haml のようなテンプレート言語かと思ってたんですよね。でもこれま

で見た限りだとちがくて、なんかロジック部分とビュー部分が混在しているようでとっつきづらく感

じました」

「大事なことを言ったね。そう、JSX はテンプレート言語じゃない。JSX とは『JavaScript eXtension』

の略で、JavaScript に HTML ライクな構文を拡張したもの。あくまで JavaScript がベースであって、

HTML の DOM 構文にロジックを埋め込むテンプレート言語とはアプローチが逆になってるの」

「どうして Rails とかと反対のアプローチなんですか? HTML をベースに、必要なところだけロジ

ックを埋め込んだほうがわかりやすいと思うんですが」

「従来のサーバーサイド型 Web アプリケーションは、ひとつの URL リクエストに対して最終的にひ

とつの静的な HTML ページを吐くだけでいいようなものが多かったのでそれでもよかったけどね。

でも今は、Web でもネイティブアプリ並みに高度な UI が求められるようになってきた。随時起きる

複数のサーバとの非同期な通信にそのデータのキャッシュ、ローカルに永続化されたデータとされて

ないデータ。それと同時に、選択中のタブやページネーションといった UI の状態も管理する必要が

あったりと、要求される水準は高まる一方。たとえば Facebook の Web アプリは、同じページの中で

刻一刻とタイムラインが書き換わり、リアルタイムに返事やいいね!の数が更新されたり、それらの

通知アラートが来たり、同じ画面の中で複数ユーザーとの同時チャットもできたりするよね」

「たしかに。あれを素の Rails で作れと言われたら頭を抱えちゃいますね……」

「複雑な問題を解決するために人間が採る普遍的なアプローチは、大きく複雑な対象を小さく単純な

要素に分解することで、それぞれを理解しやすくする方法。MVC のアーキテクチャではひとつの URL

リクエストに対して、Controller が起点となって Model を使ってそのページに必要なデータの取得・

加工を行い、最後に View に描画をさせる方法で複雑な問題に対応してきた。でもこのやり方だと3

65
第5章 拡張記法JSX

分割より細かく分割するのは難しいよね」

「そうですね」

「React の開発者は DOM のインタラクティブな書き換えという複雑な問題に対応するため、アプリ

を独立性の高いコンポーネントという単位に分割し、そこにロジックとデザインを完結させて閉じ込

める方法を採った。ひとつのページを役割別に MVC の 3 つに分けて処理するよりも、見た目と機能

が完結した無数のコンポーネントを組み合わせていくほうが、より複雑なアプリケーションを破綻な

く構築できると思わない? 前にもちょっと説明したけど、React では各コンポーネントはロジック


さ み だ れ
レベルでもデザインレベルでもそれぞれ相互に独立していて、処理が終わったものから五月雨的にレ

ンダリングされていくの」

「ページ全体の Controller の処理が終わらないと View の描画が始まらない Rails とは、考え方が全然

ちがいますね」

「そう。そして細かく分割したコンポーネントは、それ自身が受け取ったデータによって大きく振る

舞いを変えることが多いの。インタラクティブ性が低い View であれば、テンプレート型式のほうが

スッキリ書けるかもしれない。でも React の開発者は、ロジックを書きやすくすることを優先して

JavaScript でコンポーネントを記述することを選んだ。これはまあ好みかもしれないけど、ロジック

優先なのは同じプログラマとして私は共感できる選択だね。従来の MVC アプリケーションでは、View

のロジック部分を記述する制御構文そのテンプレート言語の独自リテラルで、表現力が限られていて

凝ったことをするのが難しかったし」

「うーん、なんとなくわかります」

「さらに言うと今の React は、スマホアプリが開発できる React Native、デスクトップアプリが開発

できる React Desktop、VR アプリが開発できる React 360 と、そのフィールドが Web フロントエンド

にとどまらず多岐に渡ってる。これは React 開発の基本概念がプラットフォームを超えたアプリケー

ションの抽象化に適しているということの証左とも言えるよね。これらはそれぞれ、用意された XML

の拡張記法を使って書くわけだけど、それを考えると React にとっての HTML リテラルは、Web フ

ロントエンドを対象のプラットフォームにした際のコンポーネントを記述するためのひとつの方便で

しかないわけ。一枚岩の静的な HTML を返すために開発されたテンプレート言語とは、根本の次元

で思想がちがうんだよ」

「なるほど。それにしても柴咲さん、アツく語りますね……」

「さんざん『JSX なんてキモい記法を採用してる React が何年後も残るわけない』なんて言われてきた

66
5-2. JSXの文法

からね。この話はまあ、このへんにしておくけど。

じゃ、あらためて JSX はあくまで JavaScript がベース、というのを理解してもらうためにこのコー

ドを見てもらおうかな」

<h1>Hello, world!</h1>
React.createElement('h1', 'Hello, world');

「JSX ではこのふたつは全く同じ意味になるの」

「なるほど。つまり前者は一見、普通の HTML に見えるけど、実は React.createElement() 構文の

シンタックスシュガーってことですね」

「その通り。後者だけでコンポーネントを最後まで書ききることはできるけど、実際のプログラミン

グでは複数の階層が入りくんでくるわけで、そうなるとまあ読みづらいよね。JSX をキモいと批判す

る人たちの中には、後者のほうがわかりやすいと言い張る人もいるけど、そのやり方を貫徹している

コードを GitHub とかでも見かけたことがないし」

「それはそうでしょうね……」

「ちなみに、これまで JSX、JSX って言ってきたけど、話をややこしくしないためにそう言ってただ

けで、実際に私たちが使うのは TypeScript 拡張の TSX だからね。


『TypeScript eXtension』

「最初の
『Hello, Wold!』
で生成されたファイルも拡張子は .tsx でしたね。
JavaScript ベースと TypeScript

ベースということ以外に、ふたつつのあいだに他に違いはあるんですか?」

「おっ、いい質問だ。そうだね、JSX は拡張子が .jsx じゃなくて .js でも怒られないというユルさ

があるけど、TSX は拡張子が .tsx じゃないとダメってことくらいかな」

5-2. JSX の文法

「じゃあ、JSX の文法をざっと説明していこうか」

67
第5章 拡張記法JSX

「単に JavaScript の中に HTML のタグが書けるだけじゃないんですか?」

「そう単純でもなくてね。たとえば次のタグ属性はこう置き換えられてる」

・ class → className

・ for → htmlFor

「あ、JavaScript の予約語とかぶってるんですね?」

「察しがいいね。あとたとえば、普通の HTML では onChange は小文字だけで onchange って書いて

も動くけど、JSX では正確に onChange ってキャメルケースで書かないと認識されない」

「変数名とかと同じ扱いなわけですね」

「そうだね。あとはテンプレート言語でいう制御構文のような書き方をどんなふうにやるか、最初に

やった『Hello, World』の src/App.tsx を書き換えたサンプルコードと作ってみたので、ちょっと見

てみよう」

import React, { Component } from 'react';


import logo from './logo.svg';
import './App.css';

class App extends Component {


render() {
const logoOptions = {
alt: "logo",
className: "App-logo",
src: logo
};
const title = " こ ん に ち は React";
const targets = ["World", "Kanae", "Yukina"];

return (
<div className="App">
<header className="App-header">
{
// コ メ ン ト は こ う 書 く
}
<img {...logoOptions} />
{title && <p>{title}</p>}
{targets.map(target => (
<p>Hello, {target}!</p>
))}

68
5-2. JSXの文法

</header>
</div>
);
}
}

export default App;

「まず変数の埋め込みから」

「あ、これはわかります。{title} や {target} みたいに書かれてる部分ですよね。{} で変数名を囲

むと値が展開されるわけですか」

「うんうん、理解が早いね。じゃ if 文に相当する書き方を見ようか。これは {title && <...>} の部

分だね。前においた値が true であれば、次に続く値が評価される。これは論理演算の書き方と同じ」

「はいはーい、先生! else を使いたいときはどうするんですか?」

「そういう場合は三項演算子を使うといいね。{value ? <foo /> : <bar />} のように書ける」

「なるほど」

「じゃ次は繰り返し処理について」

「targets.map(target => <...>) のとこですよね。ふむふむ、コレクションの繰り返し処理を使えば

いいのか」

「そう。応用として、一定の条件にマッチしたものだけを表示したい場合は、たとえば targets.filter(t

=> t.length > 5).map(t => <...>) みたいな感じに書くことができる」

「おお―――っ」

「次、Spread Attributes の説明。ちょっと <img> タグのとこを見てくれる?」

「関数のアタマで logoOptions というオブジェクトが定義されて、<img> タグ内にスプレッド演算子

『...』で展開されてますね」

「こうすることで、オブジェクトに格納されたタグ属性値をまとめて渡すことができるの」

「へえー、なんかオシャレな書き方ですね」

「こういうのがさらっと使いこなせるようになると、中上級者と言えるかな」

「早くそのレベルになれるよう、がんばります!」

「最後、JSX リテラル内でのコメント文だけど、HTML のように <!-- コメントです --> といった記

法はできないから気をつけて。{} でくくった上で JavaScript のコメント記法である // や /*〜*/ を

69
第5章 拡張記法JSX

使うようにしましょう」

「なんで上の例では閉じカッコを改行してるんですか? 一行にしちゃいましょう……って、あれ?

エラーになっちゃった」

「// が閉じカッコまでをコメントアウトしちゃうからね。完全に一行にしたいときは {/* コメント

はこう書く */} みたいにできるよ。

補足として忘れちゃいけないポイントを挙げておくと、JSX でタグを階層化して書くときは、ツリ

ー階層のトップレベルはひとつにしないといけないから気をつけて。上のコードでは階層の一番上は

ひとつの <div></div> でくくられてるよね。たとえばその同じ階層に並べて <p>foo</p> を書くこと

はできないの」

「あっ、やってみたらアンダーラインで画面が真っ赤になりました。なになに? 『 [ts] JSX

expressions must have one parent element.』だって。ほんとだ」

「私もよく忘れてこれやっちゃうので、気をつけましょう」

「はい!」

70
6-1. ESLint

第 6 章 Lint と Prettier でコードをクリーンに

6-1. ESLint

「一般的には Linter というのかな。コードが任意のコーディング規約通りに書かれているかどうかを

チェックする静的コード解析ツールのことなんだけど、それの JavaScript 版が ESLint なの」

「Ruby にも RuboCop というのがあります。あれ? でも使うのは TypeScript ですよね。TypeScript

の Linter じゃないんですか?」

「TSLint というのはあるんだけど、ほら TypeScript って型の記述部分以外は JavaScript だったでしょ。

だから ESLint と機能的にかぶる部分が多くて開発者泣かせだった。そんななか 2019 年 2 月に作者が

ESLint への統合プランを発表して、TSLint は近い将来に非推奨になるってアナウンスがあった の。


*1

だからこれからは ESLint だけを使いましょう」

「はー、それにしても移り変わりの激しい世界ですねえ……」

「まあ今の状況じゃ、しょうがないよね。でも一から学ぶにはシンプルでわかりやすい方向に行って

るんだから、拒否するべき変化じゃないよ」

「確かに」

「じゃあ勉強のため、手動で既存のプロジェクトに ESLint を導入していこうか。さっきの『Hello,

World』プロジェクトをまるごとコピってきて作業するよ。そのルートディレクトリでこのコマンド

を実行しましょう」

yarn add -D eslint-plugin-react @typescript-eslint/eslint-plugin @typescript-eslint/parser

「あれ? ESLint 本体はインストールしなくていいんですか?」

「Create React App で生成されたプロジェクトはには便利なスクリプト群がまとまったパッケージの

*1 TSLint in 2019 https://medium.com/palantir/tslint-in-2019-1a144c2317a9

71
第6章 LintとPrettierでコードをクリーンに

react-scripts というものが最初から入ってるんだけど、その中にすでに ESLint が含まれてるんだよ。

react-scripts の ESLint と二重インストールになる上、バージョン不整合の問題もあるので、別途 ESLint

をインストールするのはやめたほうがいい」

「なるほど」

「次に ESLint の設定ファイルを作るよ。設定ファイルは yarn eslint --init コマンドで対話的に作る

こともできるんだけど、現状そのままでは使いものにならないので、あらかじめ私が調整しておいた

設定ファイル .eslintrc.js がこちら*2 になりまーす」

「……料理番組メソッドですね。どれどれ? あ、この rules の中に有効にしたいルールを書いてい

くんですね」

「 う ん 、 そ の 通 り 。 主 要 な ル ー ル は 最 初 の ほ う で 読 み 込 んで る eslint:recommended と

plugin:@typescript-eslint/recommended に定義されてるので、ここでは最低限必要そうなものだけを

追加・上書きしてる。各ルールの意味は公式サイト 等で確認して。
*3

あと、実際に ESLint を動かすのは VSCode 上がほとんどなので、VSCode のほうにも設定が必要

になるの。もう ESLint プラグインはインストールしてあるよね?」

「はい、最初に VSCode をインストールしたときに言われましたから」

「じゃ、次の設定を VSCode メニューの Code > Preferences > Settings で開いたタブの右上『{ }』ア

イコン『Open Settings (JSON)』から設定ファイル settings.json を開いて、以下の内容を追加して

おいて」

"eslint.autoFixOnSave": true,
"eslint.enable": true,
"eslint.validate": [
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],

*2 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/06-lint/01-eslint/.eslintrc.js

*3 https://eslint.org/docs/rules/

72
6-1. ESLint

「ちなみに VSCode の設定は、プロジェクトルートに .vscode/ というディレクトリを作って、そこ

に settings を置けば、開発チーム内で同じ設定内容を共有できるよ、というのは前にも言ったね。こ

のサンプルコードでもそうなってるので確認しておいて」

「わかりました」

「一通り設定が終わったら、ちょっと VSCode で src/App.tsx の const を var に書き換えてみて」

「あっ、書き換えた部分に赤の破線が表示されて、そこにマウスカーソルをホバーさせると『Unexpected

var, use let or const instead.eslint(no-var) 』とポップアップが出ました」

「よしよし、ESLint が VSCode 上で正常に動いてるね。あと、すでに共有の .vscode/settings.json

には設定済みだけど、VSCode の Preference で『Eslint: Auto Fix On Save』にチェックを入れておく

と、ファイル保存時にルールに沿った簡単な整形をしてくれるようになるよ」

73
第6章 LintとPrettierでコードをクリーンに

6-2. Prettier

「よく Linter と混同されがちなんだけど、Code Formatter というものがあります。Code Formatter

とは、問答無用でコードを自動整形してくれるツールのこと。ESLint でも yarn eslint --fix <ファイ

ル名> のようなコマンド実行や VSCode で eslint.autoFixOnSave を有効にしたときはある程度、整形

してくれるんだけど、そっちのはただのオマケみたいなもの。正統派 Code Formatter である Prettier

は、インデントや改行箇所といったところにまで介入して、より積極的に書き換えてくれるの」

「えっ、なんか強引……」

「ふふっ、すぐに慣れて気持ちよくなってくるからたいじょうぶ」

「ええ――――っ?」

「私も昔は、より見やすい改行とかにこだわったりしたけど、Prettier を使うようになってからは『コ

ードの整形なんか、わざわざ人間がやるような仕事じゃない』と考えるようになったよ。どんなにこ

だわって書いても cmd + S 一発で書き換えられてしまうんだから。Prettier の前では新人だろうがシニ

アエンジニアだろうが CTO だろうが関係なく平等に書いたコードを一律に直され、みんな平等に屈

服させられるのは逆に気が楽なんだよね」

「……はあ、そういうものですか」

「使っていくうちにわかるよ。それで Prettier、すごく便利なんだけど ESLint とバッティングするこ

ともあるから、その調整はけっこう面倒なんだよね。Prettier 本体に加えて eslint-config-prettier や

eslint-plugin-prettier といったモジュールをインストールして、.eslintrc.js に正しく設定を書き加え

る必要がある」

「大変そうですね」

「でもだいじょうぶ。後で私の設定を丸ごとコピらせてあげるから」

「先輩! ありがとうございます!!」

74
6-3. 組み合わせとカスタマイズ

6-3. 組み合わせとカスタマイズ

「私のカスタマイズした設定 なんだけど、ESLint と Prettier に加えて、CSS の Linter である Stylelint


*4

も入れてます。あと ESLint はデフォルトよりもさらに踏み込んだ細かいルールを追加してあるよ。

具体的には Airbnb の JavaScript Style Guide をベースにしたルールとか、React A11y というアクセシ

ビリティを考慮したルールとか」

「へえ――」

「あと、コミット前に自動的に Lint や Prettier が走るようにするために、lint-staged や husky という

モジュールもインストールしてある。今回、追加したモジュールのリストはざっとこんな感じね」

・ eslint

・ eslint-config-airbnb

・ eslint-config-prettier

・ eslint-plugin-import

・ eslint-plugin-jest

・ eslint-plugin-jsx-a11y

・ eslint-plugin-prettier

・ eslint-plugin-react

・ eslint-plugin-react-hooks

・ @typescript-eslint/eslint-plugin

・ @typescript-eslint/parser

・ stylelint

・ stylelint-config-prettier

・ stylelint-config-standard

・ stylelint-order

・ prettier

*4 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/06-lint/03-mysetting

75
第6章 LintとPrettierでコードをクリーンに

・ prettier-stylelint

・ husky

・ lint-staged

「それに合わせて .eslintrc.js と .vscode/settings.json に手を入れて、さらに Stylelint の設定フ

ァイル .stylelintrc.js も追加してあるから」

「ひいっ、何ですかこれ。複雑すぎじゃないですか? こんなの自分で設定できる気がしません……」

「うーん、私でもこの設定の作り込みに公式サイトや国内外のブログ記事を読み込んで、試行錯誤し

ながら半日はかかったからね。過渡期だからしょうがない」

「……そんなこと言われても」

「文句言わない。私が作り込んであげたんだから、あとは yarn コマンド一発でしょ。じゃ、適当に

インデントに余分なスペースを入れてみたり、行末のセミコロンを省略してみたり、改行場所をぐち

ゃぐちゃにしてみたり、ダブルクォートをシングルクォートに変更してみたりしてから、を command

+ S を押してみてくれる?」

「あっ、すごい! 保存と同時にぐちゃぐちゃにしたコードが一瞬で手直しされた!」

「そう、ちょっと感動ものでしょ? TypeScript や JavaScript のファイルの他に、JSON や CSS ファ

イルだって直してくれるよ。あと、git commit 時にも ESLint と Stylelint が走るようになってるから」

「これなら、この先気持ちよくコーディングできそうです!」

76
7-1. Reactの基本思想

第 7 章 何はなくともコンポーネント

7-1. React の基本思想

「React にはその設計を支えている重要な概念がいくつかあります。公式が自己定義しているわけじ

ゃないけど、よく取り上げられるのが以下の3つね。聞いたことはある?」

・ 仮想 DOM(Virtual DOM)

・ コンポーネント指向

・ 単方向データフロー


『仮想 DOM』はときどき耳にしますね。意味はあんまりわかってないですけど。
『コンポーネント

指向』はコンポーネントをベースに開発するってことでしょうか? 『単方向データフロー』は……。

すいません、さっぱり見当がつきません」

「HTML が DOM によるノードツリーで構成されてるのは知ってるよね? リアルな生 DOM の更

新はブラウザにとって各種オーバーヘッドが大きくて、それを最適化しようとすると非常に高いコス

トがかかるの。だから React では JavaScript オブジェクトで DOM と同じ構造のノードツリーを再現

しておき、一連の処理結果の最終的な差分だけを元の DOM に書き戻すようにしたことで、フロン

トエンド開発者が何も考えなくてもそれらのオーバーヘッドを最小限に抑えてくれるようになった。

これが仮想 DOM」

「なるほど」

「コンポーネント指向の概念は React のオリジナルじゃなく、Web Components から借り受けたもの

だね。WebComponents とは端的に言うと、Web アプリケーションを構築するための再利用可能なカ

プセル化された独自の HTML タグを Web 標準の技術だけで作成できる技術のことね。レゴブロック

を組み合わせるように、用意されたものやカスタマイズされた UI タグを HTML の中で組み合わせ

ていくことでアプリケーションを作ることを目指してる」


『目指してる』というのは、つまりまだ実現されてない技術ってことですか?」

77
第7章 何はなくともコンポーネント

「Google が Web コンポーネントを発表したのが 2011 年のこと。それからかなり経っていてフォロ

ワーな人たちはすでに実戦投入可能な技術だと主張するかもしれないけど、私はなかなかその評価は

一般的ではないと思う。そもそも Web 標準を拡張していこうという技術で Microsoft のブラウザは対

応してないとか、それだけでアプリを組み上げるのは難しいので現実的には React とか既存のフレー

ムワークを組み合わせる必要があるの。でもそうすると、冗長な抽象階層が増えるメリットがあまり

感じられなくなるといった問題もあって」

「そんな Web Components が志向した『カスタマイズ可能なタグでアプリを組み上げることができる』

技術を、未来の Web 標準ではなく今ある JavaScript で手っ取り早く実現したのが React だと言えなく

もない。Web Components 陣営からすれば、こんな JavaScript で何でもかんでも作ろうとするやり方

は邪道だと言うかもしれないけどね。

ちなみにこの仮想 DOM とコンポーネント志向を現実的な技術に落とし込んだ React は 2011 年か

ら Facebook の自社プロダクトに使われていたんだけど、それがオープンソースソフトウェアとして

2013 年に公開された事件は、当時の Web フロントエンド界隈に激震を走らせた。サーバーサイド Web

アプリケーションフレームワーク界に Rails が登場したときのように、他の Web フロントエンドフレ

ームワークの開発者たちは先を争うようにその仮想 DOM とコンポーネント志向を採り入れたの。


Vue

や Angular がバージョン 1 系とそれ以降で互換性がほぼないも同然なのはそのせいだと言えるね」

「はー、そんな歴史があったんですか」

「うん。で、最後の単方向データフローなんだけど、これはあまり他のフレームワークに真似されな

かった。だから今となっては逆に、React を特徴づける概念と言えるね。データの受け渡しについて

のやり方の違いなんだけど、Vue や Angular はデータバインディングという方法を採用してる。これ

は簡単に言うと、テンプレートに埋め込まれた任意の変数が別のところに紐付けられていて、その変

更がリアルタイムに反映されるというもの」

「なるほど、わかりやすいです」


『わかりやすい』か……。たぶん初学者にとってはそうなんだろうね。でもこのやり方はコンポーネ

ントの独立性という側面から見るとあまりよろしくない。たとえばアプリが複雑になって、色んな方

向にバインドしてる変数がそこらじゅうに散りばめられ、コンポーネントの階層がどんどん深くなっ

ていくと、人間の頭では何が起こるか予測がつきづらくなってくる。思わぬところで思わぬ値が書き

換わって思わぬ事態を引き起こしかねない」

「うーん、設計しだいな気もしなくもないですけど……」

78
7-2. Propsをコンポーネントに受け渡す

「でもコンポーネント志向というのは独立性の高いコンポーネントを組み合わせてアプリを構成する

考え方なわけで、コンポーネントの中身が知らないところで勝手に書き換わるのは、React の開発者

としては許せなかったのね。だから React では、データは必ず親コンポーネントから子コンポーネン

トへ一方通行で渡されます。下の階層から上の階層にデータの変更を反映させることはできないの」

「すごくストイックな考え方のように思えますけど、それで不都合はないんですか? たとえばユー

ザーのフォーム操作によって、上の階層のコンポーネントの中身を書き換える必要があるときとか」

「うん、なかなか鋭い質問だね。それにはいくつか方法はあるんだけど、たとえばそのひとつは、親

コンポーネントが自身の状態を変更する関数を子コンポーネントに渡して、フォーム入力時に発火さ

れるイベントにその関数を仕込んでおくというやり方だったりする」

「えっ、えっ?」

「んー、まあ今の段階ではまだ理解はむずかしいかな。React は特にそうなんだけど、まず基本の思

想を把握して、多方面の基礎知識を積み上げたうえで実際のコードに立ち会わないと、なかなか応用

問題が解けるようにならない」

「……うう、早くわかるようになりたいです」

「まあまあ、そう焦らずに。今は単方向データフローとは、親コンポーネントから子コンポーネント

に一方向でしかデータを受け渡せないやり方ということを知ってればいいから。

話を戻すけど、React ではコンポーネントは JavaScript で記述されると言ったよね。正確には、マ

ウントする場所では JSX のタグとして記述され、コンポーネントそれ自身は JavaScript のクラスや関

数として定義される」

「はい」

「その親コンポーネントから子コンポーネントのデータの受け渡しに使われるのが Props というもの

ね。これについて、くわしく説明していこう」

7-2. Props をコンポーネントに受け渡す

「Props とは関数に対する引数のようなもの、と考えてもらえればいいかな。マウント時のタグの中

79
第7章 何はなくともコンポーネント

では、そのタグの属性値として表現され、コンポーネント自身の定義の中ではそれがクラスコンポー

ネントの場合は props という名前のメンバー変数、関数コンポーネントの場合はその関数の引数とし

て表現される」

「クラスコンポーネント? 関数コンポーネント?」

「あー、ごめんごめん。まだその話をしてなかったね。それについてはまたあらためて説明するけど、

今はクラスコンポーネントだけで Props の説明をしていこう。まず用意したサンプルコード を実行し


*1

てみて」

「……これは?」

「高校バドミントンのスポ根漫画『はねバド!』のキャラ一覧だね」

「柴咲さんって、意外にこういうのが好きなんですね……。でもかくいう私も漫画、大好きですよ。

気が合いますね!」

「はいはい、そういった話は休憩時間にね」

「はーい。それじゃあえっと画面なんですけど、なんか見た目がちょっとリッチになってるみたいで

*1 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/02-props

80
7-2. Propsをコンポーネントに受け渡す

すよね?」

「Semantic UI React って React 向けの UI フレームワークを入れたからね。インストール方法や使い

方は公式サイト 2 を見てもらえばいいけど、yarn add semantic-ui-react semantic-ui-css を実行して、

src/index.tsx で semantic.min.css を読み込むようにしただけ。簡単でしょ?」

「Twitter Bootstrap みたいのは知ってましたけど、これは UI 要素を React のコンポーネントで提供し

てくれてるんですね」

「他にも Material-UI とかいろいろあるので、デザインや使い勝手を考えて好きに選ぶといいよ。じ

ゃ、中身のコードを見ていこう。src/App.tsx はどうなってるかな?」

import React, { Component } from 'react';


import CharacterList, { Character } from './CharacterList';

import './App.css';

class App extends Component {


render() {
const characters: Character[] = [
{
id: 1,
name: ' 羽 咲 綾 乃 ',
age: 16,
height: 151,
},
{
id: 2,
name: ' 荒 垣 な ぎ さ ',
age: 18,
height: 174,
},
{
id: 3,
name: ' 泉 理 子 ',
age: 18,
},
];

return (
<div className="container">
<header>
<h1> は ね バ ド ! キ ャ ラ ク タ ー 一 覧 </h1>
</header>
<CharacterList school=" 北 小 町 高 校 " characters={characters} />
</div>

81
第7章 何はなくともコンポーネント

);
}
}

export default App;

「えーっと、あらためてアタマから確認させてください。そもそも import って?」

「他のファイルでエクスポートされたオブジェクトや関数、型インターフェースもろもろを読み込んで

使うための文だね。最終行にあるように export default とデフォルトエクスポートされたものは、好

きな名前をつけて読み込めるけど、普通にエクスポートされたものは { Character } のようにその名

前を指定しないとインポートできないよ」

「ところで1行めでインポートしてある React って、使われてないように見えるんですけど?」

「これは読み込んでおかないと以下の行で JSX リテラルが使えないので、まあお約束と考えておいて」

「はい」

「インポートは import { Character as Player } from './CharacterList'; みたいに、as で別の名前を

つけることもできるよ。モジュール同士で名前がバッティングしちゃったときとかに重宝する」

「この Character というのは CharacterList.tsx で定義された型インターフェースなんですね。それを

使って characters 変数を定義してる」

「そう。で、いちばん大事なのはインポートしてきた CharacterList コンポーネントを JSX でタグと

してマウントしてる <CharacterList ... /> のところだけど、そこに属性値が指定されてるのに注目し

て」

「school 属性に '北小町高校' という文字列、characters 属性に上で定義した同じ名前の Character

オブジェクト配列が設定されてますね」

「これが Props として子コンポーネントである CharacterList に渡されてるわけ。それじゃ、

CharacterList.tsx のほうのコードを見てみようか」

import React, { Component } from 'react';


import { Header, Icon, Item } from 'semantic-ui-react';

export interface Character {


id: number;
name: string;
age: number;

82
7-2. Propsをコンポーネントに受け渡す

height?: number;
}

interface CharacterListProps {
school: string;
characters: Character[];
}

class CharacterList extends Component<CharacterListProps> {


render() {
const { school, characters } = this.props;

return (
<>
<Header as="h2">{school}</Header>
<Item.Group>
{characters.map(c => (
<Item>
<Icon name="user circle" size="huge" />
<Item.Content>
<Item.Header>{c.name}</Item.Header>
<Item.Meta>{c.age} 歳 </Item.Meta>
<Item.Meta>
{c.height ? c.height : '???'}
cm
</Item.Meta>
</Item.Content>
</Item>
))}
</Item.Group>
</>
);
}
}

export default CharacterList;

「まず見てほしいのは CharacterListProps という型インターフェースが定義されているところ。この

インターフェースはコンポーネントを定義するクラス宣言のところで呼ばれてる」

「クラス宣言のとこ、Component<CharacterListProps> と今まで Component クラスを継承するときにな

かったジェネリクスみたいなのがあります。これは何ですか?」

「その通りジェネリクスだよ。この CharacterList クラスに対する型引数で、こうやって渡すことで

そのコンポーネントの Props の型を指定してるの。Component クラスの型引数にはデフォルト値として

83
第7章 何はなくともコンポーネント

{} という空オブジェクトが設定されているので、Props が必要なかったこれまでは省略してただけ」

「あ、そうだったんですね」

「こうすることで、そのコンポーネントをタグとしてマウントするときに必要な属性値とその型が決

まるの。だから App.tsx で <CharacterList /> をマウントするとき、school と chracter 属性値をそれ

ぞれの適正な型で記述しないと VSCode に怒られてしまう」

「ほんとだ。属性値を型違いで書き換えたり、school="..." の部分を削除したり、foo="bar" みたい

に適当な属性値を追加したりすると、赤の波線アンダーラインが出て『型が違う』って指摘されます

ね」

「そうやって自分でいろいろ試してみて、実際にエラーを出して確認するのはいいことだね。いっぽ

う、コンポーネント自身ではそうやってタグ属性として渡された Props の値を、クラスのメンバー変

数 props としてアクセスできる。render() メソッドの最初のところでは、その props から school と

characters の要素をローカル変数として抽出してるわけだけど」

「へー、こういう書き方ができるんですね」

「もちろん、こうやって抽出しなくても this.props.school のように書けばアクセスできるけどね」

「return されてる JSX タグツリーのトップレベル階層ですけど、<>...</> という見慣れないタグが

あります」

「これはフラグメントといって、React.Fragment のシンタックスシュガーだね。class 指定のない <div>

でくくっても表示は同じなんだけど、それだと HTML ソースに不要な <div> 階層ができちゃうので、

こう記述しておくとそれが避けられるの。ただ、<div> とちがって必ず中身のノードが必要なので、

処理の結果、中身がなくなる可能性のあるときは使っちゃダメだよ」

「わかりました」

「で、その下の抽出した school と characters を使ってレンダリングしてるところはだいじょうぶ?」

「えーっと、<Header>, <Item>, <Icon> は Semantic UI React のコンポーネントですよね。配列の

characters を map() で繰り返し処理してると……。

あれ? {c.height ? c.height : '???'} のとこってどうなってるんだろう。よく見たら Character イ

ンターフェースの定義で height? と要素名にクエスチョンマークがついてますけど」

「これは、
『※その要素は省略できます』ってことだね。だから泉理子さんの height 値は設定されて

ないけど型チェックで怒られてない。設定されてなければ参照値は undefined で false なので、三項

演算で '???' という文字列が返ってるというわけ」

84
7-2. Propsをコンポーネントに受け渡す

「なるほどー。こうして説明されると、最初とっつきづらそうでわけわかんなかったコードが、すご

くキレイに見えてきました」

「お、いいねいいねー。その調子。ちなみにこの書き方は Props のインターフェースにも応用できる。

ここだと CharacterListProps インターフェースで school? と要素をオプショナルに定義すると、

<CharacterList /> タグをマウントする際に school 属性値を書いても書かなくてもどちらでもよくな

る。もちろん、省略された場合の処理はちゃんと考えておく必要があるけどね」

「へー、了解です!」

「よし。ところでこのコード、一見正しく動いてるようだけど、実は不具合があるの」

「えっ、そうなんですか?」

「ちょっとブラウザの JavaScript コンソールを開いてみて」

「あ、なんか赤く Warning が出てますね。


『Each child in a list should have a unique "key" prop.』

って怒られてます」

「うん、じゃ CharacterList.tsx をこんな感じに書き換えてみようか」

{characters.map(c => (
- <Item>
+ <Item key={c.id}>

「あっ、コンソールの Warning が消えました! map() 関数から繰り返しのインデックスを抽出して、

Item コンポーネントの key という Props に渡してあげてるようですけど、これって?」

「React でループ処理で複数のコンポーネントを生成するとき、その要素に一意な key 属性値を必要

とするの。仮想 DOM が変更を検知して、最小限の変更で実際の DOM に反映するのに使われるの

ね。
なくても動きはするんだけど、
パフォーマンスのために大事だから、
ないと指定するように Warning

が表示される。私もよく忘れがちなので気をつけて」

「なるほど、おぼえておきます!」

「よし、じゃ次は Local State に行ってみよう」

85
第7章 何はなくともコンポーネント

7-3. コンポーネント内部の状態を規定する Local State

「コンポーネントで最も大事なのは以下の 3 つの要素ね。ここ、テストに出るからねー」

・ Props

・ Local State

・ ライフサイクル

「Props はさっきやりましたね。親コンポーネントから受け取る値のこと。2 つめは『Local State』と

なってまけど、じゃ『Global State』もあるってことですか?」

「うん、あいかわらずいい質問をしてくれるねー。厳密には『Global』とはいわないんだけど、どの

コンポーネントからもアクセスできる State は、この先出てくるよ。といっても、React のライブラリ

それ自体がその機能を持ってるわけじゃなくて、別のライブラリをインストールする必要があるんだ

けど。

で、それと区別するために Local State と呼んでるんだけど、これはその名の通りコンポーネント自

身が内部に持つ状態のこと。じゃ、サンプルコード を実行してみようか」
*2

*2 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/03-state

86
7-3. コンポーネント内部の状態を規定するLocal State

「カウンターですね。
『+1』を押したら 1 が加算されて、
『-1』を押したら 1 を減算されていきます」

「Local State を説明するには、このカウンターがいちばん簡単で理解しやすいからね。では src/App.tsx

のコードを見ていこう」

import React, { Component } from 'react';


import { Button, Card, Statistic } from 'semantic-ui-react';

import './App.css';

interface AppState {
count: number;
}

class App extends Component<{}, AppState> {


constructor(props: {}) {
super(props);
this.state = { count: 0 };
}

increment() {
this.setState(prevState => ({
count: prevState.count + 1,
}));
}

decrement() {
this.setState(prevState => ({
count: prevState.count - 1,
}));
}

render() {
const { count } = this.state;
return (
<div className="container">
<header>
<h1> カ ウ ン タ ー </h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>

87
第7章 何はなくともコンポーネント

<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={() => this.decrement()}>
-1
</Button>
<Button color="green" onClick={() => this.increment()}>
+1
</Button>
</div>
</Card.Content>
</Card>
</div>
);
}
}

export default App;

「まず注目してほしいのが、AppState という型インターフェース」

「クラス宣言で Component クラスを拡張するときに型引数で渡されてますね。Props のときと違って、

Component<{}, AppState> とふたつ型引数があります」

「うん、ひとつめは Props だけど、このコンポーネントには Props が必要ないので空オブジェクトを

渡してます。で、ふたつめが Local State の型になるの」

「なるほど、Local State として AppSate インターフェースの型を設定して、そこで count 情報を持たせ

ているんですね。あと、今回はクラスの中にコンストラクタがありますね」

「Local State の初期化に必要だからね。ここでの props 周りの記述はお約束ごとなので毎回こんな感

じに書く必要があります。大事なのは this.state に値を設定しているところ」

「カウンター値を0に初期化してるんですね」

「そう。で、increment() メソッドと decrement() メソッドでその値を変化させてる。気をつけなき

ゃいけないのは、this.state には直接値が導入できないこと。値の設定には必ず setState() メソッ

ドを使うの」

「ほんとだ。this.state.count に値を導入してみたら、
『[ts] Cannot assign to 'count' because it is

a constant or a read-only property.』と怒られました」

「参照だけなら普通にできるんだけどね。でね、setState() の引数には、以下の2種類が設定できる

の」

88
7-3. コンポーネント内部の状態を規定するLocal State

1. 設定したい値の State オブジェクト。

2. (prevState, props) => newState の形式の、State と Props を引数として受け取り新しい State

を返す関数。

「ここでは2を使ってるわけだけど、Props は使わないので省略してる。どちらを使うかは、State に

固定値を設定するときは1、動的に変更したい場合は2を選びましょう」

「わかりました」

「次、<Button> タグのところを見てみよう。onClick 属性値には何が入ってるかわかる?」

「中の値は () => this.increment() となってますけど……。すいません、意味がわからないので教え

てください」

「これは、引数なしにメンバーメソッドの increment() を実行する関数をアロー関数リテラルで表現

したものだね。Button コンポーネントに対して、属性値としてその関数が渡されてる。前に単方向デ

ータフローの説明をしたとき、秋谷さんから『ユーザーのフォーム操作によって、親コンポーネント

の中身を書き換える必要があるときはどうするんですか?』って質問されたよね」

「……そういえば」

「それに対して私は『親コンポーネントが自身の状態を変更する関数を子コンポーネントに渡して、

フォーム入力時に発火されるイベントにその関数を仕込んでおく』って答えたと思うんだけど、これ

がまさにそれなんだよね。 increment() は Local State の counter を1加算する関数で、それが子コ

ンポーネントの Button に Props として渡され、コンポーネント内部でそのフォームボタンのクリック

イベントに仕込まれてるの」

「あっ、ああ――――! 今、納得いきました。そうか、そういうことなんですね」

「うん、わかってくれて私もうれしいよ。じゃ、もうひとつ過去のすっきりしない疑問を解消しとこ

うか。() => this.increment() なんてわざわざしなくても、this.increment ってメソッドをそのまま

渡してやれば簡単だと思わない?」

「そう言えばそうですね。どうしてそうしないんでしょうか?」

「じゃ、そう書き換えてみようか」

- <Button color="red" onClick={() => this.decrement()}>


+ <Button color="red" onClick={this.decrement}>

89
第7章 何はなくともコンポーネント

- <Button color="green" onClick={() => this.increment()}>


+ <Button color="green" onClick={this.increment}>

「あれ? 『+1』ボタンを押したら、実行時エラーになっちゃいましたよ? えーっと、


『TypeError:

this.setState is not a function』と怒られてます」

「これがアロー関数と従来の関数定義の話をしたときに説明した、this の挙動のちがいなんだよね。

increment() はアロー関数ではなく従来型の関数定義なので、this は実行時のオブジェクトになっち

ゃうの。Button コンポーネントの中には setState() なんて関数はないから、それで実行時エラーに

なる」

「おおお――。なるほど―――」

「だからメンバーメソッドの定義のほうをアロー関数に書き換えてみよう」

- decrement() {
- this.setState(prevState => ({
- count: prevState.count - 1,
- }));
- }
+ decrement = () =>
+ this.setState(prevState => ({
+ count: prevState.count - 1,
+ }));
+ }

- increment() {
- this.setState(prevState => ({
- count: prevState.count + 1,
- }));
- }
+ increment = () =>
+ this.setState(prevState => ({
+ count: prevState.count + 1,
+ }));
+ }

90
7-3. コンポーネント内部の状態を規定するLocal State

「あ、これで『+1』ボタンがさっきまでと同じように正常に動くようになりました!」

「うん。this の挙動のちがい、これで理解できたかな?」

「あ――、なんかずっと残ってたモヤモヤが晴れました! 本当はあのときは『うーん、なんだか煙

に巻かれたなあ』と思ってたんですけど、ごめんなさい!」

「あはは、こっちは素直な反応が見れて教えがいがあるけどね。でもあと最後にもう一点。increment

と decrement は実はこんなふうにも書けることをおぼえておいて」

increment = (e: SyntheticEvent) => {


e.preventDefault();
this.setState(prevState => ({
count: prevState.count + 1,
}));
};

decrement = (e: SyntheticEvent) => {


e.preventDefault();
this.setState(prevState => ({
count: prevState.count - 1,
}));
};

「メソッドに e という引数が設定されてますね。これって何ですか?」

「うん。これは SyntheticEvent という型で定義される、イベントハンドラのオブジェクトなの。イベ

ン ト ハ ン ド ラ を 使 っ て メ ソッ ド 内 部 で 何 か 操 作 し た い 場 合 、 引 数 と して 受 け 取 れ る 。

e.preventDefault() は該当要素のオリジナルな onClick の挙動を抑制するための記述だね。今回は空

のボタンでオリジナルの挙動がないので省略してたんだけど、たとえばこれが <a> タグだったりする

とクリックでページ移動が起きてしまうので、それをキャンセルするためにこういう記述が必要にな

るんだよ」

「へー、なるほど」

「他にも例えば、<select> タグで選択した値を受け取りたい場合は、その onChange 値に設定した関

数内で同様にイベントハンドラ e を引数で定義すれば、e.target.value から参照できたりするわけ。

ちょっと Local State から話が脱線しちゃったけど、説明はこんなところかな。では、三大要素の残

りのライフサイクルの話をしようか」

91
第7章 何はなくともコンポーネント

7-4. コンポーネントのライフサイクル


『ライフサイクル』とは、たとえばマーケティング分野では『ある製品が開発され、市場に投入され、

発展普及し、やがて廃れて姿を消すまで』の過程を示す用語のことね。コンポーネントのライフサイ

クルとは、初期化されてマウントされレンダリングされ、何らかの処理が行われて再レンダリングさ

れたりして、最後にアンマウントされるまでの過程。途中、何度も再レンダリングされることがある

ので、
『サイクル』という言葉がピッタリだと思う。

このライフサイクルには、大きく分けて以下の4つのフェーズがあります」

1. Mounting …… コンポーネントが生成され DOM ノードに挿入されるフェーズ

2. Updating …… 変更を検知してコンポーネントが再レンダリングされるフェーズ

3. Unmounting …… コンポーネントが DOM ノードから削除されるフェーズ

4. Error Handling …… そのコンポーネント自身および子コンポーネントのエラーを捕捉す

「すいません、2の『変更を検知して再レンダリング』のところなんですが、何の変更についてなん

でしょうか?」

「うん、いい質問だね。コンポーネントが再レンダリングされるのは、基本的に2つの場合のみなの。

そのコンポーネントに渡されている Props か、または自身の Local State の値に変更があったときがそ

のタイミング。ただしそこに介入して、任意の条件で再レンダリングを阻止することはできるけどね」

「さっきのカウンターアプリでは、ボタンを押したタイミングで Local State に規定された count の値

が変更されたから、コンポーネントが再レンダリングされたんですね」

「その通り。それで、このライフサイクルの各フェーズに介入して任意の処理を差し込むことができる

メソッドが、React のコンポーネントには用意されてるの。これをライフサイクルメソッドといいます。

それらをフェーズごとに表にまとめてみたのがこれね」

92
7-4. コンポーネントのライフサイクル

1. Mounting フェーズ

メソッド 戻り値 説明
constructor(props) void コンストラクタ
static getDerivedStateFromProps(props, State | null レンダリングの直前に呼ばれ、戻り値で
state) Local State を変更することができる
render() React.ReactNode レンダリングを行う
componentDidMount() void コンポーネントがマウントされた直後に
呼ばれる

2. Updating フェーズ

メソッド 戻り値 説明
static getDerivedStateFromProps(props, State | null レンダリングの直前に呼ばれ、戻り値で
state) Local State を変更することができる
shouldComponentUpdate(nextProps, boolean 再レンダリングの直前に呼ばれ、false
nextState) を返せば再レンダリングを中止できる
render() React.ReactNode レンダリングを行う
getSnapshotBeforeUpdate(prevProps, Snapshot | null コンポーネントが変更される直前に呼ば
prevStat) れ、戻り値でスナップショットを取って
おける
c o m p o n e n t D i d U p d a t e ( p r e v P r o p s , void コンポーネントが変更された直後に呼ば
prevState, snapshot?) れる

3. Unmounting フェーズ

メソッド 戻り値 説明
componentWillUnmount() void コンポーネントがアンマウントされる直
前に呼ばれる

4. Error Handling フェーズ

93
第7章 何はなくともコンポーネント

メソッド 戻り値 説明
componentDidCatch(error, info) void 子孫コンポーネントで例外が起きたとき
に呼ばれる
static getDerivedStateFromError(error) State 子孫コンポーネントで例外が起きたとき
に呼ばれ、State を更新する

「おおー、なんかたくさんありますねえ」

「まあ全部暗記する必要はないけど、なんとなくあんなことができるメソッドがあったなレベルでお

ぼえておいてくれたらいいよ。太字で書いた componentDidMount(), shouldComponentUpdate(),

componentDidUpdate(), componentWillUnmount() の 4 つだけは忘れてもらったら困るけど」

「わかりました! 最低その 4 つだけはおぼえておきます!」

「コンポーネントのライフサイクルに対して、各フェーズとそれぞれのメソッドを当てはめた図がこれ

ね」

「おおー。この図、わかりやすいですねー」

94
7-4. コンポーネントのライフサイクル

「他に注意点として、ネットの古い記事に注意してほしいかな。React コンポーネントのライフサイク

ルって、バージョン 16.3 から 17 にかけて大きな変更が予定されててね」

「えっ。今私たちが使ってるのは 16.5 だから、まさにその変更のまっ最中ってことですよね」

「うん。16.2 系までは componentWillMount(), componentWillReceiveProps(), componentWillUpdate()

というライフサイクルメソッドがあったの。これらはレンダリングの直前に実行されるメソッドで、け

っこう多用されてたんだけどバージョン 17 から有効化される、最適化された完全非同期なレンダリ

ングと相性が悪くて副作用を生みやすいので、16.3 より後からは公式から非推奨になってるのね。そ

してバージョン 17 からは、これら 3 つのメソッドは完全に削除される予定」

「へー。そうか、だから古い記事を見ると、それら非推奨のメソッドがバリバリ使われてたりするこ

とがあるわけで、それを参考にしちゃうと痛い目をみることがあると……」

「そういうこと。気をつけてね。

じゃ、実際にコードでライフサイクルメソッドの使い方を見ていこうか。まずは yarn と yarn start

コマンドでサンプルコード*3 を実行してみて」

「60 秒でリセットされるカウントダウンタイマーですね。途中、ボタンでもリセットできるようにな

ってます」

「うん。じゃ、いつものごとく src/App.tsx のコードを見ていこう」

*3 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/04-lifecycle

95
第7章 何はなくともコンポーネント

import React, { Component } from 'react';


import { Button, Card, Icon, Statistic } from 'semantic-ui-react';

import './App.css';

const LIMIT = 60;

interface AppState {
timeLeft: number;
}

class App extends Component<{}, AppState> {


constructor(props: {}) {
super(props);
this.state = { timeLeft: LIMIT };
}

reset = () => {
this.setState({ timeLeft: LIMIT });
};

tick = () => {
this.setState(prevState => ({ timeLeft: prevState.timeLeft - 1 }));
};

componentDidMount = () => {
this.timerId = setInterval(this.tick, 1000);
};

componentDidUpdate = () => {
const { timeLeft } = this.state;
if (timeLeft === 0) {
this.reset();
}
};

componentWillUnmount = () => {
clearInterval(this.timerId as NodeJS.Timer);
};

timerId?: NodeJS.Timer;

render() {
const { timeLeft } = this.state;

return (
<div className="container">

96
7-4. コンポーネントのライフサイクル

<header>
<h1> タ イ マ ー </h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value>{timeLeft}</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={this.reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
</div>
);
}
}

export default App;

「うわ長い!」

「これまでと比べればね。実際の業務コードではこんなの序の口だよ。ざっと見たところ、どう?

読める?」

「えっと、コンストラクタで Local State の timeLeft を 60 に初期化してて、メンバーメソッド reset()

が実行されると同じく 60 に再設定されます。その下のメンバーメソッド tick() が実行されると1減

算されますね。で、その次からがライフサイクルメソッドですね。コンポーネントが最初にマウント

された直後に呼ばれる compnentDidMount() の中で……。うーん、これは何をやってるんでしょうか?」

「setInterval() というのは JavaScript の組み込み関数で、第一引数の関数を第二引数のミリ秒ごと

に延々と実行し続けるようにするものだね」

「なるほど。だからコンポーネントがマウントされた直後から、1秒ごとに tick() メソッドが実行

さ れ る よ う に な る わ け で す ね 。 そ れ か ら 、 コ ン ポーネ ン ト が 変 更 さ れ た 直 後 に 呼 ば れ る

componentDidUpdate() の中で、
Local State の timeLeft の値が 0 になったら reset() メソッドを呼ぶと。

コンポーネントがアンマウントされる直前に、えーっと……」

「setInterval() 関数は戻り値にその繰り返し処理タスクの識別 ID を返すのね。放っとくとずっとそ

のタスクが生き続けてしまうから、コンポーネントがアンマウントされるときに clearInterval() で

97
第7章 何はなくともコンポーネント

その ID を指定してタスクを止めてあげるの」

「そうなんですね。あとはボタンコンポーネントの onClick 属性値に reset()メソッドを渡してあげて

ると。はい、だいたいわかりました!」

「うん、よくできました」

7-5. 関数コンポーネント

「コンポーネントの説明に関してはこれが最後。関数コンポーネントについて学んでいきましょう」

「そういえば最初の Props の説明のところで、クラスコンポーネントと関数コンポーネントがどうっ

て、柴咲さん説明されかけてましたよね。でもこれまで見たコンポーネントは全部クラスで定義され

てましたけど」

「うん。コンポーネントはクラスだけじゃなく、関数としても定義できるの。というより、正しい React

Way に則せばコンポーネントは関数で定義できるときは関数で定義するべきだね。ここにいたるまで

さんざんクラスコンポーネントの説明をしてきたわけだけど、現在 Facebook の React 開発チームの公

式的な見解として、その5年以上の開発の歴史を経てクラスコンポーネントではなく、関数コンポー

ネントで書くことを優先するよう開発者にアナウンスしているの。理由としては、コードがシンプル

に書けるというのはもちろんだけど、他にも以下のようなことが挙げられてる」

・ クラス内の this の挙動が難解

・ 記述が冗長になりがちで、時系列が複雑なライフサイクルメソッドの挙動

・ 今後導入予定の各種最適化がクラスだと難しい

「ええー? クラスコンポーネント、がんばって勉強してきたのに使われなくなっちゃうんですか?」

「すぐになくなるわけじゃないし、今のところクラスコンポーネントを廃止する計画はないって言っ

てるけど、新しく作るコンポーネントは関数で書くように推奨されてるね」

「……そうなんですね」

98
7-5. 関数コンポーネント

「でも関数コンポーネントで、クラスコンポーネントを全て置き換えることはできるんですか?」

「鋭い質問だねー。これまでの関数コンポーネントには大きな制約があった。まず Local State を持つ

ことができない。そしてライフサイクルメソッドを備えられない。これらはクラスコンポーネントでは

そのクラスのメンバーとして実装されていたものだからね」

「うーん。じゃあ関数コンポーネントって、あんまり使い途がないのでは……」


『これまでは』って言ったでしょ。事情が変わったの。React は 2019 年 2 月にリリースされたバー

ジョン 16.8.0 以降、関数コンポーネントにも Local State に準じる機能や、ライフサイクルに処理を割

り込ませる機能が追加されたの。これについてはこの次でじっくり説明する予定。今はシンプルな関

数コンポーネントについて学びましょう」

「はーい」

「じゃあ以前使ったサンプルコードの『はねバド!』のキャラ一覧を、関数コンポーネントを使って

書き直してみよう 。この src/CharacterList.tsx を開いてみて」


*4

import React, { FC } from 'react';


import { Header, Icon, Item } from 'semantic-ui-react';

export interface Character {


id: number;
name: string;
age: number;
height?: number;
}

interface CharacterListProps {
school: string;
characters: Character[];
}

const CharacterList: FC<CharacterListProps> = ({


school = ' 校 名 不 明 ',
characters,
}) => (
<>
<Header as="h2">{school}</Header>
<Item.Group>
{characters.map(c => (

*4 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/05-functiona

99
第7章 何はなくともコンポーネント

<Item>
<Icon name="user circle" size="huge" />
<Item.Content>
<Item.Header>{c.name}</Item.Header>
<Item.Meta>{c.age} 歳 </Item.Meta>
<Item.Meta>
{c.height ? c.height : '???'}
cm
</Item.Meta>
</Item.Content>
</Item>
))}
</Item.Group>
</>
);

export default CharacterList;

「関数コンポーネント、たしかにスッキリした感じですね。えーっと、書き換わった箇所はと……。

この FC というのは?」

「これが React が用意している関数コンポーネントの型だよ。ちょっと前までは『SFC』つまり『Stateless

Functional Component』って名前だったんだけど、最近のアップデートで関数コンポーネントでも状

態が持てるようになったので『FC』
、『Function Component』に改名されたの」

「あらら、スーパーファミコン(SFC)からファミコン(FC)にダウングレードしちゃったんですか

……」

「いや、秋谷さん? あなたいくつ?」

「えっ、24 ですけど……」

「……まあいいや。インポートするときは FC でも FunctionComponent でもどちらでもいいよ。FC は

FunctionComponent の Type Alias だから。私は FC のほうが簡潔なので好きかな。

「わかりました」

「じゃ、続きを見ていこうか」

「はい。最初の CharacterList の関数定義では、引数のカッコ内でもう要素を抽出しちゃってますね。

こんなシンプルに書けるんだ。でもこの school = '校名不明 ' ってなんか変なの代入しちゃってます

けど?」

「これは引数抽出時のデフォルト値を指定してるんだよ。外から与えられた引数が Falsy な値だった

100
7-6. Presentational ComponentとContainer Component

らこうやって指定されたデフォルト値で上書きされるけど、Truthy な値だったら引数値がそのまま表

示される」

「なるほど。で、本文は return すらなくなっちゃってますね」

「このコンポーネントでは他にやることがないからね。return 文しかない場合は JSX ノードを () で

囲っちゃえば return も書く必要がなくなるの」

「だからスッキリ書けるんですね。わかりました。関数コンポーネントで書くときは私もこういうふ

うに書きます」

「うん、src/App.tsx のほうはとりたてて変わったところがないので説明を省略するけど、自分で見

ておいてね。

7-6. Presentational Component と Container Component

「コンポーネントにはクラスコンポーネント(Class Component)と関数コンポーネント(Function

Component)の2種類があるってことだっただけど、それとは別の側面からコンポーネントをまた2

種類に分類することができるの。それが Presentational Component と Container Component。ふだん

の会話の中では、ただ『コンポーネント』と『コンテナ』と略して呼ぶことが多いかな」

「クラスと関数じゃない、別の側面での分類ってどういうことですか?」

「Presentational Component はその名の通り、主に見た目を担うコンポーネントのことね。もう一方

の Container Component は処理を担うコンポーネント。Facebook で Redux や Create React App を開

発している Dan Abramov さんの記事 を参考にすると、それぞれの特徴はこんな感じになる」


*5

Presentational Component Container Component


「どのように見えるか」に関心を持つ 「どのように機能するか」に関心を持つ

*5 Presentational and Container Components


https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

101
第7章 何はなくともコンポーネント

内部に DOM マークアップをふんだんに持つ DOM マークアップを可能な限り持たない


データやふるまいを Props として一方的に受け取る データやふるまいを他のコンポーネントに受け渡

Flux の Store 等に依存しない Flux の Action を実行したり、Flux の Store に依
存する
自身の状態を持たない(UI の状態は除く) しばしばデータの状態を持つ
データの変更に介入しない しばしばデータの変更に介入して、任意の処理を
行う
関数コンポーネントで表現されることが多い HOC や Render Props、Hooks が使われることが
多い

「ちょっとこの先学ぶことになる概念が先取りして入っちゃってるけど、それらはおいおい理解でき

ればいいとして、だいたいのニュアンスは通じるかな?」

「そうですね。この定義に基づけば、さっきの『はねバド!』キャラ一覧を表示したサンプルコード

の関数コンポーネントは、この Presentational Component に相当するわけですね」

「うん、正解。いっぽうで Local State を持ち、ライフサイクルメソッドを利用しているタイマーのサ

ンプルコードのコンポーネントは Container Component ということになる」

「じゃあ関数コンポーネントはほぼ Presentational Component で、Container Component になること

はまずないわけですね?」

「いや、それは違うんだな。関数コンポーネントも Container Component になることができるよ。そ

れを実現するのが、上記表の最後の行の Container Component の欄に書いてある HOC とか Render

Props、Hooks といった機能だね」

「いっぺんに新しい機能が3つも出てきた……」

「……あー、いや。今はわからなくていいよ。あと Flux についても後で改めて説明するので、そう

いう機能があるってことだけ認識してくれれば。

それらを踏まえて、React らしくて一番きれいなコンポーネントの作り方をここで説明しておくね。

まず関数コンポーネントで見た目だけを整えた Presentational Component を作ます。それをインポー

トして Hooks や HOC で必要な機能を追加していって、別途 Container Component を作る、というの

がスマートなやり方。最初から状態や機能を盛り込んだ Container Component を作るのはあまり得策

じゃない」

「それはどうしてですか?」

「そのほうがコンポーネントの再利用性やテスタビリティが高くなるからだよ。Presentational

102
7-6. Presentational ComponentとContainer Component

Component だけを集めてコンポーネントのスタイルガイドを作ったり、Container Component にする

際に追加した機能だけをテストする、みたいなことがやりやすいの。これもそのうち実践するから」

「わかりました」

「はい、じゃあ長くなったけどコンポーネントの説明はこれで終了。おつかれさまー」

「……ふー、いっぱい勉強しました。今日だけでかなり React 力がついた気がします!」

103
第8章 Hooksで関数コンポーネントを強化する

第 8 章 Hooks で関数コンポーネントを強化
する

8-1. Hooks 登場以前の話

「最近まで既存のコンポーネントに機能を追加するには、おおまかに2つのやり方があった。ひとつ

が HOC(Higher Order Component)


。日本語では高階コンポーネントと呼ばれる関数を使う方法」

「以前、関数型言語について学んだとき、関数を引数にとったり関数を戻り値として返す高階関数が

出てきたのをおぼえてます」

「そう、HOC は高階関数の親戚みたいなものだね。コンポーネントを引数にとり、戻り値としてコ

ンポーネントを返す関数のこと。HOC がどんなふうに使われるか、ごく簡単なサンプルを作ってみ

たので見てみよう」

import React, { Component, FC } from 'react';

const Hello: FC = () => <div>Hello!</div>;

const logProps = (WrappedComponent: FC) => {


return class extends Component {
componentDidMount() {
console.log('Component is rendered.');
}

render() {
return <WrappedComponent />;
}
};
};

export default logProps(Hello);

104
8-1. Hooks登場以前の話

「なるほど、この logProps() というのが HOC なんですね。Hello コンポーネントを引数にとって、

マウント時に『Component is rendered.』というテキストをコンソールに出力する機能が付与されてい

るわけですか」

「そう、便利でしょ。Redux を始めとするメジャーなライブラリは、たいていがこの HOC のインタ

ーフェースを備えてる。でも公式の React チームにはどうにも嫌われててねえ……」

「え、そうなんですか? どうして?」

「親コンポーネントと子コンポーネントの間で Props の名前の衝突が起きるとバグるとか、JSX の内

部で使うことができないので柔軟性に欠けるとか、そんな理由だったと思う。そこで公式が HOC に

代わるものとして出してきたのが Render Props と呼ばれる方法」

「れんだーぷろっぷす? render() メソッドとは違うんですか?」

「いやそれとは全くの別物。render という名前の Props の値に関数を設定して使うの。こんな感じに

書くんだけど……」

<DataProvider render={data => (


<h1>Hello {data.target}</h1>
)}/>

「……すいません、意味不明です……」

「だよねー、私も理解するのに苦労したよ。Render Props を使う際には親・子・孫の3階層のコンポ

ーネントを意識する必要があって、肝は真ん中の子コンポーネント。親から渡されるの孫コンポーネ

ントが Presentational Component なんだけど、それが決め打ちじゃなく任意のコンポーネントにする

ことが可能で、その子コンポーネントの中で自身の Local State とかを使ったりしながら孫コンポーネ

ントに機能を追加していく感じ」

「うーん、言葉だけではなかなか難しいです……」

「サンプルを用意しようかと思ったけどすごく長くなってしまうので、どうしても理解しておきたい

なら公式ドキュメント*1 を読んでおいて」

「えええ―――、そんな投げやりでいいんですか?」

*1 Render Props https://ja.reactjs.org/docs/render-props.html

105
第8章 Hooksで関数コンポーネントを強化する

「うん、だってすでにほとんど使う場面がないからねー。開発者の中には『Render Props はもう死ん

だ』とまで言う人もいるし」

「死んじゃったんだ……」

「Wrapper Hell と形容される複雑にネストしたコードになりやすいとか、TypeScript での型の定義が

難しいとか、そもそも概念の理解のための障壁が高くコードも読みづらいとかあってね。HOC に代

わるものとして公式から提案されたものだけど、そのメリットに共感できる開発者がついに多数派に

なることはなかったと言えるね。以下は Andrew Clark が自身のツイート*2 に掲載した、架空の UI テ

ーマライブラリを Rendrer Props と HOC の両者のインターフェースで使った場合のコードを比較した

ものなんだけど……」

「やっぱり Render Props のほうが、ネストが入り組んでてわかりづらいですね」

「そう思うよね。で、公式が推しているのになかなか普及しない中、これから説明する Hooks の登

場によって、Render Props はほぼその将来を断たれてしまった状況かな。サードパーティのライブラ

*2 https://twitter.com/acdlite/status/971605795501613056

106
8-2. Hooksとは何か

リで Render Props のインターフェースを提供しているものもまだあるけど、これからはまちがいなく

Hooks に移行していくだろうね」

「そんなに Hooks の登場ってインパクトがあったんですか?」

「うん。じゃ、これからその説明をしていこう」

8-2. Hooks とは何か

「React Way 的にはクラスコンポーネントよりも関数コンポーネントのほうが望ましいけど、関数コ

ンポーネントは Local State もライフサイクルメソッドも持てないという話はしたね。それで HOC を

使って関数コンポーネントに Local State やライフサイクルメソッドといったクラスコンポーネントが

持っている機能を始めとする便利な機能を付与できる Recompose*3 というライブラリが、意識高い開

発者のあいだでよく使われていたの」

「へー、そうだったんですね」

「その作者 Andrew Clark が本家 Facebook の React 開発チームにスカウトされて、彼が中心的な役割

を果たして開発されたのが Hooks というわけ。Hooks は様々な React の機能をクラスを使わずに利用

できるようにするもの。関数コンポーネントに Local State やライフサイクルといった React の機能を

『接続する(hook into)
』から Hooks と名付けられたの。

React Hooks が最初に発表されたのは 2018 年 10 月に開催された ReactConf 2018 の基調講演の中

*3 https://github.com/acdlite/recompose

107
第8章 Hooksで関数コンポーネントを強化する

だったんだけど、そのときはまだα版だったというのにコミュニティの反響が大きくてね。Hooks を

使ったライブラリが雨後の竹の子のように出てきて、さらにメジャーなライブラリも将来的に Hooks

に対応することを次々に表明しだした。その影響を受けていくつかの技術が終わりに追いやられたの」

「さっき教えてもらった Render Props もその内のひとつですか?」

「そう。まあこれは Andrew Clark が積極的に殺しにいった感もあるわけだけど。こんなツイート*4 も

してるくらいだし」

「あはは。彼、相当 Render Props が嫌いだったんですね」

「みたいだね。あと当然ながら、Recompose も作者自身が鞍替えしたわけだから、早々に開発中止と

Hooks への移行を促すアナウンスが出された。Recompose は私もかなりヘビーに使ってたので、寝耳

に水でショックだったな……」

「ええー、それは地味につらいですね」

「まあ、Web フロントエンドはまだまだ過渡期で移り変わりの激しい世界だから。さらに React は

外部のコミュニティから人や技術を採り入れて進化を続けている技術だからね。

それで話を戻して Hooks の何がそんなに革新的だったかなんだけど、端的に言ってさっき説明した

ような HOC や Render Props の弱点とされたもの、たとえば Wrapper Hell になりやすい、可読性が

*4 https://twitter.com/acdlite/status/1032363809430626309

108
8-3. State HookでLocal Stateの管理

低いといった点がほぼクリアされていて、コードが読みやすくシンプルになるところだね。さらに state

やライフサイクルを使うといったコンポーネントに付与したい機能をそこだけ切り出すことも簡単な

ので、再利用しやすくテストしやすいといったメリットもある」

「なるほど。ところで Hooks を使うと、クラスコンポーネントでできることが全て関数コンポーネン

トでもできるようになるんですか?」

「2019 年 3 月現在、getSnapshotBeforeUpdate() と componentDidCatch() のふたつのライフサイクルメ

ソッドに相当する機能は Hooks にはないけど、将来的には対応される予定だそうなので、採用するの

に大きな問題はないと思う」

「じゃ、将来的にはクラスコンポーネントは使われなくなる方向なんでしょうか?」

「長いスパンで見ればそうだろうね。ただ公式は React からクラスコンポーネントをなくす計画はな

いと言うし、何より過去の膨大な資産のほとんどがクラスコンポーネントで作られているだろうから、

なくなったらかなりの混乱を招いてしまう。でも新しく作るコンポーネントは Hooks と関数コンポー

ネントで作ることを推奨しているので、ウチのチームもその方針で行くよ」

「ええー? じゃ、なんかクラスコンポーネントを延々勉強してきたのが無駄だったような……」

「いや、でもね。既存のコードはクラスコンポーネントで書かれてることが多いんだから、それが読

めないと困るでしょ? 公式も既存のクラスコンポーネントをわざわざ Hooks を使って書き換える必

要はないって言ってるし。それにライフサイクルの概念も、クラスコンポーネントのライフサイクルメ

ソッドを先に知っていたほうが理解しやすいんだよ」

「……なるほど」

「じゃ、これから Hooks の各機能を実際にコードを見ながら学んでいこう」

8-3. State Hook で Local State の管理

「まずは State Hook。これはクラスコンポーネントの Local State に相当するものを関数コンポーネン

トでも使えるようにする機能だね。使い方はこんな感じ」

109
第8章 Hooksで関数コンポーネントを強化する

const [count, setCount] = useState(0);


setCount(100);
setCount(prevCount => prevCount + 1);

「useState() は戻り値として state 変数とそのセッター関数を返すのね。だから上のように分割代入

で受け取る。useState() の引数には、その state 変数の初期値を設定してあげる。もちろん、state 変

数とセッター変数の名前は好きに設定できるよ」

「クラスコンポーネントの this.setState() に似てますね」

「 そ う だ ね 。 違 う 点 は 、 setState で は そ の 引 数 に this.setState({ count: 0 }) や

this.setState(prevCount => ({ count: prevCount + 1 })) のようにオブジェクトを使う必要があったん

だけど、State Hook の場合はその必要がないところ」

「確かにわかりやすいですね」

「じゃ、次は実際のコードで見ていこう。前にクラスコンポーネントで実装したカウンターのサンプ

ル を、State Hook を使って関数コンポーネントに書き直したものがこれ ね。他は全部同じだから、


*5 *6

src/App.tsx だけ開いて確認すればいいかな」

import React, { FC, useState } from 'react';


import { Button, Card, Statistic } from 'semantic-ui-react';

import './App.css';

const App: FC = () => {


const [count, setCount] = useState(0);

const increment = () => {


setCount(count + 1);
};

const decrement = () => {


setCount(count - 1);
};

*5 7-3. コンポーネント内部の状態を規定する Local State


https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/03-state

*6 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/08-hooks/03-state

110
8-3. State HookでLocal Stateの管理

return (
<div className="container">
<header>
<h1> カ ウ ン タ ー </h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={decrement}>
-1
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
</Card.Content>
</Card>
</div>
);
};

export default App;

「useState() を使っているところのコードは、さっきのと全く同じですね」

「うん。で、どう? 全体的に読んでみて」

「すごくシンプルにまとまってて、読みやすいですね。最初のクラスで作ったものと比べて、行数も 57

行→ 42 行と 15 行も減ってますし」

「そう、それが Hooks の強力なところなんだよね。積極的に使っていきたくなるよね」

「でもなんだか魔法みたいで気持ち悪いような気がしないでもないです。クラスならインスタンスの

中でメンバー変数が状態を記憶しているのはわかりますけど……」

「実際は、React モジュールのグローバル空間にそれぞれのコンポーネントに関連付けられる形で配

列として格納されてるっぽいので、まあ黒魔術的なものではあるよね」

「……えっ?」

「useState() を極端に単純化すると、おおむねこんなコードになるはず」

111
第8章 Hooksで関数コンポーネントを強化する

let currentIndex = 0;
const states = [];

const useState = (initialState) => {


const index = currentIndex;
currentIndex++;

return [
states[index] || initialState,
newState => { states[index] = newState; }
];
}

「ふおお、グローバルな配列に state 値を突っ込んでる……」

「だから userState() を使って複数の Local State を設定するとき、条件文とかでくるんでしまうと配

列の順番がおかしくなるので、それはタブーになってるの。関数定義の最初のほうにこんなふうに

userState() をプレーンに連ねて書くこと」

const [foo, setFoo] = useState(100);


const [bar, setBar] = useState('Inital Bar');
const [buz, setBuz] = useState(true);

「わかりました。でもなんか、さらっとすごいことを流された気もしますが、Hooks 確かに便利だし、

あまり気にせず使うことにします」

「ふふ。そう、それが賢い選択よね」

8-4. Effect Hook でライフサイクルを扱う

「じゃあ次は Effect Hook。これは副作用を扱う Hooks で、クラスコンポーネントのライフサイクルメ

112
8-4. Effect Hookでライフサイクルを扱う

ソッド componentDidMount(), componentDidUpdate(), componentWillUnmount() に相当する機能を実現す

るものだね。副作用とは具体的には、データの取得、手動での DOM の改変、ログの記録といったも

のになる。使い方はこんな感じ」

useEffect(() => {
doSomething();

return clearSomething();
}, [watchVar]);

「useEffect() は第一引数に、引数なしの関数を設定します。その渡した関数の中身、ここでは

doSomething() が コ ン ポ ー ネ ン ト の レ ンダ リ ング の 直 前 に 実 行 さ れ る こ と に な る の 。

componentDidMount()や componentDidUpdate() といったメソッド内に書くのと同じことね。関数は必

ずしも戻り値を必要としないけど、戻り値に関数を設定すると、それはコンポーネントのアンマウン

ト直前に実行されることになる。これは componentWillUnmount() に書くのと同じことね。

useEffect() の第二引数は配列で指定する必要がある。これは省略可能なんだけど、その配列の中

に任意の変数を入れておくと、その値が前回のレンダリング時と変わらなければ第一引数で渡された

関数の中身の副作用処理実行がキャンセルされることになるの」

「……えーっと。つまり、この useEffect() 文が記述されたコンポーネントでは、初回のレンダリン

グの直後に doSomething() が実行され、再レンダリング時には watchVar という変数の中身が変更され

ていれば doSomething() が実行されるけど、watchVar が変わっていなければ doSomething() は実行さ

れない。そしてコンポーネントのアンマウント時前には clearSomething() が実行される、ということ

で合ってますか?」

「うん、100 点満点」

「ちなみにですけど、ここで第二引数を省略したり、または空の配列を渡したりしたらどうなるんで

すか?」

「お、いい質問だ。第二引数を省略した場合は問答無用でレンダリングの毎回に doSomething() が実

行される。空の配列を渡してあげると、doSomething() は初回のレンダリングでしか実行されなくな

る」

「なるほど、componentDidMount() と componentDidUpdate()のふたつあったものがまとめられちゃっ

113
第8章 Hooksで関数コンポーネントを強化する

て、初回のレンダリングしか実行したくない処理があったときはどうするんだろうと思ってましたけ

ど、空の配列を第二引数に渡してあげればいいんですね」

「せっかくおぼえたコンポーネントのライフサイクルの考え方だけど、Hooks ではそのパラダイムを

少し変換する必要があるんだよね。クラスコンポーネントで書いていたときは『このライフサイクル

のタイミングでこの処理とこの処理を実行する』と考える必要があったんだけど、Effect Hook で書

くときは『この処理を実行したいのはこれとこれのライフサイクルのタイミングだ』と発想が逆にな

るの」

「お――、なるほどー。確かにこっちのほうが綺麗に書けそうですね」

「useEffect() 文は useState() 文と同じように、ひとつの関数コンポーネントの中で何回でも書ける

ので、実行したい処理ごとに useEffect() でまとめて書いてあげるといいよ」

「わかりました」

「じゃ、実際のコードで見ていこうか。今回も前に使ったサンプルコードを Effect Hook を使って書

き直してみるよ。クラスコンポーネントで実装した減算タイマーのサンプル*7 を書き直したもの*8 を用

意したから、src/App.tsx を開いてみよう」

import React, { FC, useEffect, useState } from 'react';


import { Button, Card, Icon, Statistic } from 'semantic-ui-react';

import './App.css';

const LIMIT = 60;

const App: FC = () => {


const [timeLeft, setTimeLeft] = useState(LIMIT);

const reset = () => {


setTimeLeft(LIMIT);
};

const tick = () => {


setTimeLeft(prevTime => (prevTime === 0 ? LIMIT : prevTime - 1));
};

*7 7-4. コンポーネントのライフサイクル
https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/07-component/04-lifecycle

*8 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/08-hooks/04-effect

114
8-4. Effect Hookでライフサイクルを扱う

useEffect(() => {
const timerId = setInterval(tick, 1000);

return () => clearInterval(timerId);


}, []);

return (
<div className="container">
<header>
<h1> タ イ マ ー </h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value>{timeLeft}</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
</div>
);
};

export default App;

「今度もクラスコンポーネントのときの 68 行から 46 行に、22 行も減ってますね」

「うん。三つあったライフサイクルメソッドがひとつの useEffect() 文にまとまっちゃったのが大きい

ね。じゃ、頭から順番に説明していくよ。まず useState() で state 変数 timerLeft とそのセッター関

数 setTimerLeft() を定義して、timerLeft を LIMIT で初期化してる。

その下いったん飛ばして useEffect() 文では第二引数に空の配列を渡してあげているので、

setInterval() の と こ ろ の 処 理 は 初 回 の レ ンダ リ ング で し か 実 行 さ れ な い 。 戻 り 値 と して

clearInterval() の実行関数を設定しているので、コンポーネントがアンマウントされる際にそれが

実行される」

「あれ? ComponentDidUpdate() に記述してた、カウントゼロになったら自動的に 60 にリセットす

る処理はどこに行ったんですか?」

「あー、それね。timerLeft の値の判定を、useEffect() の第一引数の関数は初回レンダリングでしか

115
第8章 Hooksで関数コンポーネントを強化する

呼ばれないのでそこには置けないし、関数 tick() は setInterval() で非同期に実行されるためにそ

の中では古い値しか参照できなくてそこにも置けなかったので、setTimerLeft() の引数に渡す関数の

中に入れちゃったんだよね」

「prevTime => (prevTime === 0 ? LIMIT : prevTime - 1) の、三項演算子によって処理を振り分けてい

るところですね。そうか、こうすればいいんですね」

「まあ、苦しまぎれの気もしなくないけど、他に書きようがなかったので」

「Hooks、すっきり短く書けるというメリットばかりじゃなく、ライフサイクルメソッドが使えないた

めにそういうところもちゃんと考えて設計する必要もあるんですね」

「そうだね、こうやって思わぬ挙動になることがあるから。その必要もないのに既存のクラスコンポ

ーネントを Hooks で書き換えるのはおすすめしない、って公式も言ってるくらいだし」

8-5. Custom Hook で独自の Hook を作る

「こうやって書いてきた Hooks の処理だけど、これらをまとめてひとつの関数にして Custom Hook

として使うことができるの。この方法を使えば、まず見た目だけを備えた Presentational Component

を作り、それをインポートしてきて Custom Hook で必要な機能を追加して Container Component に

することが簡単にできるようになる。さっきの減算タイマーをそのやり方で書き換えてみよう 」
*9

「 src/ ディ レ ク ト リ の 下 に あ っ た App.tsx が な く な っ て 、 src/components/App.tsx と

src/containers/App.tsx のふたつに分割されていますね」

「コンポーネント
(Presentational Component)
とコンテナ
(Container Component)
に分けたからね。
React

アプリケーション開発でのディレクトリの切り方についてはいろいろ流派があるみたいだけど、

components/と containers/ に分けて、対応するファイルを同じ名前・同じ階層に置く、というのが多

数派のようなので、ウチでもそれを採用してるの。

*9 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/08-hooks/05-custom

116
8-5. Custom Hookで独自のHookを作る

ではまず、src/components/App.tsx のほうから見ていこうか」

import React, { FC } from 'react';


import { Button, Card, Icon, Statistic } from 'semantic-ui-react';

import './App.css';

interface AppProps {
timeLeft: number;
reset: () => void;
}

const AppComponent: FC<AppProps> = ({ timeLeft, reset }) => (


<div className="container">
<header>
<h1> タ イ マ ー </h1>
</header>
<Card>
<Statistic className="number-board">
<Statistic.Label>time</Statistic.Label>
<Statistic.Value>{timeLeft}</Statistic.Value>
</Statistic>
<Card.Content>
<Button color="red" fluid onClick={reset}>
<Icon name="redo" />
Reset
</Button>
</Card.Content>
</Card>
</div>
);

export default AppComponent;

「Local State だった timeLeft とリセット関数 reset() をコンポーネントの Props として外から渡すよ

うにしてるの。これをこのままマウントすれば、見た目だけ整ってるけどタイマーが全く機能しない

コンポーネントが表示される。スタイルガイドに載せるのはこういったコンポーネントだね」

「Props の型をインターフェースで切り出して AppProps として別途定義してますね」

「これだけシンプルなコンポーネントなら関数定義の中でついでに定義してもいいんだけど、複雑な

アプリになってくるとこうやって切り出した方がいいだろうね。HOC を使うシーンだと、コンテナ

のほうでこれをインポートして拡張することも多いし」

117
第8章 Hooksで関数コンポーネントを強化する

「へー、そうなんですね」

「じゃ、次にコンテナのほうのファイル src/containers/App.tsx のファイルを見てみよう」

import React, { FC, useEffect, useState } from 'react';

import AppComponent from '../components/App';

const useTimer = (limitSec: number): [number, () => void] => {


const [timeLeft, setTimeLeft] = useState(limitSec);

const reset = () => {


setTimeLeft(limitSec);
};

const tick = () => {


setTimeLeft(prevTime => (prevTime === 0 ? limitSec : prevTime - 1));
};

useEffect(() => {
const timerId = setInterval(tick, 1000);

return () => clearInterval(timerId);


}, []);

return [timeLeft, reset];


};

const AppContainer: FC = () => {


const LIMIT = 60;
const [timeLeft, reset] = useTimer(LIMIT);

return <AppComponent timeLeft={timeLeft} reset={reset} />;


};

export default AppContainer;

「この useTimer() というのが自分で作った Custom Hook だね。変数 timeLeft と関数 reset() を返す

ようになってる。中身の処理は、さっきのサンプルコードと全く同じなんだけど」

「へ―――、綺麗にカプセル化されてますね」

「うん、そのおかげで AppContainer の中身は実質2行で済んでる。Custom Hook を汎用的に作れば、

複数のコンポーネントで使い回すことも可能だよ。ここでもある程度はそうしてるわけだけど。

118
8-6. その他のHooks

useTimer() に渡してあげる引数 limitSec によって、1分タイマーにも5分タイマーにもなる」

「お、すごい」

「あと補足だけど、Custom Hook の関数名は『use』で始める決まりになってるので忘れないで」

「えっ、じゃあ『use』意外に変更したらどうなるんですか? おっ、VSCode に怒られた。これって

『use』が関数名の頭にないと useState()と useEffect()が使えないってことですか?」

「eslint-plugin-react-hooks という Hooks のための ESLint プラグインを入れてあるからね。規約でそ

うなっているだけで、ESLint を外せば動くんだけど」

「そうなんですね。それにしても、Presentational Component と Container Component を分けて作る

ってピンとこなかったんですけど、こんなふうに書くんですね」

「分離することで全体のコード量は多少増えてしまうけど、それよりもコンポーネントの独立性を高

めて再利用性やテスタビリティを担保することのほうが、長い目で見れば断然重要だからね。秋谷さ

んも早くこのやり方に慣れてほしいかな」

「わかりました! がんばります!」

8-6. その他の Hooks

「Hooks を使う際の注意点のおさらいをしておこうか」

・ Hooks を呼べるのは関数コンポーネントか Custom Hook の中のみ。クラスコンポーネント

や React モジュール管轄外での使用は不可。

・ Hooks 文を記述するのはその関数のトップレベルで行う。条件分岐やループ、ネストした関

数内に記述するのは不可。

・ Custom Hook の関数名は、useXxx のように必ず「use」で始める。

「はい、だいじょうぶそうです」

「ここまで useState()、useEffect() と Custom Hook について見てきたけど、他にも Hooks の API

119
第8章 Hooksで関数コンポーネントを強化する

はたくさんある。

・ useState

・ useEffect

・ useContext

・ useReducer

・ useCallback

・ useMemo

・ useRef

・ useImperativeHandle

・ useLayoutEffect

・ useDebugValue

「えっ、こんなにあるんですか?」

「うん、でもこれ全て使う必要はなくて、useState() と useEffect() と比べれば他の Hooks の出番

はあんまりない。あるとしても普通、使う場面がありそうなのはせいぜい useRef() と useMemo() く

らいかな。

useRef() は本来は DOM への参照のための ref オブジェクトを取得するためのものなんだけど、そ

の current プロパティの値は変更可能でどんな値でも保持することができるので、インスタンス変数

のように使えるの。たとえば任意の Local State の前回レンダリング時の値を保持しておきたい場合と

かは、こんなふうに書ける」

const Counter: FC = () => {


const [count, setCount] = useState(0);

const prevCountRef = useRef(0);


const prevCount = prevCountRef.current;

useEffect(() => {
prevCountRef.current = count;
});

return <div>Now: {count}, before: {prevCount}</div>;

120
8-6. その他のHooks

};

「ふむふむ。これも useRef() の引数の中身で current プロパティの初期化を行う感じですか?」

「うん、そうだね。常に current プロパティを扱うというのがちょっとわかりにくいけど、まあこう

いうものだと思っておぼえましょう。

それから useMemo()。 これは useEffect() によく似ているけど、副作用を伴う処理を行うのではな

く、任意の計算結果を保持しておきたいときに使う。こんな感じ」

const memoValue = useMemo(() => calculateSomething(), [watchVar]);

「 第二引数の配列に渡された変数が前回のレンダリング時と差分があれば、第一引数の実行結果が戻

り値として返されるの。

また useMemo() はパフォーマンス最適化のためによく使われることがあるんだけど、たとえば特定

の Props が変更されたときだけ任意の子コンポーネントの再レンダリングを行いたい場合はこんなふ

うに書ける」

const Parent: FC<{ a: string, b: string }> = ({ a, b }) => {


const child1 = useMemo(() => <Child1 a={a} />, [a]);
const child2 = useMemo(() => <Child2 b={b} />, [b]);

return (
<>
{child1}
{child2}
</>
)
};

「これ、クラスコンポーネントのライフサイクルメソッド shouldComponentUpdate() に相当するもので

すよね?」

「そう、その通り。shouldComponentUpdate() とちがって、マウントする親のコンポーネントにロジ

ックを書かないといけないのが面倒だけどね。ちなみにパフォーマンスの最適化については、コンポ

121
第8章 Hooksで関数コンポーネントを強化する

ーネント設計の初期段階で行うのはタブーなので、完成したもののパフォーマンスがどうしても出な

かったときに改めて検討するようにしましょう。

はーい、それじゃあこれで Hooks についての説明はおしまい。Hooks API の残りのものについて

は適宜、公式ドキュメント*1 を参照しておいてね。これまでの説明で Hooks の表現力の高さはわかっ

てもらえたかな?」

「そうですね。すごく便利そうということはわかりました。でもこれ、まさにかなり頭のいい人が考

えた機能って感じですよね。はまれば超シンプルで綺麗に書けるのはわかるんですけど、凡人の私が

使いこなせるようになるには時間がかかりそうです……」

「まあそのへんは、何ごとも最初は実践による試行錯誤を繰り返していくしかないから。基本的な考

え方はインストールしてあげたつもりなので、あとは自分でコードを書きながら、また綺麗に書いて

る他の人のコードを読んだりしてスキルを上げていくしかないね」

*1 Hooks API Reference https://ja.reactjs.org/docs/hooks-reference.html

122
9-1. SPAのルーティング

第 9 章 ルーティングで URL を制御する

9-1. SPA のルーティング

「じゃあ秋谷さん、Web アプリケーションにおける『ルーティング』の定義を述べてください」

「……いきなりですね。えーっと、
『アプリケーションサーバがリクエストされた URL に対して、そ

れに紐付けられたページを生成し、レスポンスとして返すこと』でいいでしょうか?」

「うん、Rails で作られたようなサーバーサイド Web アプリケーションであれば、それは正解だね。

でも最近の高機能な SPA のケースでは、それは適切とは言えない。典型的な SPA では、アプリケー

ションサーバの役割は最初のリクエストに対してアプリ全体が記述された JavaScript のコードのかた

まりを返すだけで終わる。アプリ内のページ遷移でブラウザのアドレスバーの URL が書き換わるこ

とがあっても、サーバにリクエストが飛ぶことは原則的にないの」

「そうなんですね。そのへんの理解はあいまいでした」

「アプリケーションサーバが API サーバと統合されてたり、サーバーサイドレンダリングを行ってい

たりする場合はその限りではないけどね。ちなみにブラウザでこんなことができるようになったのは

2010 年ごろからなんだよね。秋谷さんは 昔の Google や Twitter とかで #! という記号が入った URL

を見たことはない?」

「うーん、見た記憶はないですね……」

「む……、最近の若い子は見たことないか。JavaScript にはブラウザ履歴を管理するための History API

というものがあって、これには以前は back() や forward() って『戻る』


『進む』くらいのメソッドし

かなかったの。だからサーバにいちいちリクエストを送ることなく Ajax なアプリでルーティングを実

現するために、URL に #! を挿入する一種の裏技がいっとき流行ったんだよ。URL に # ハッシュを

つけるとページ内リンクアンカーになって、そこ以降が変わってもブラウザの履歴は変わるけどサー

バにリクエストは飛ばないでしょ。それを利用したわけ」

「へえー、豆知識ですね」

「いや私にとっては実体験なんだけど……。それでその History API が、HTML5 になって pushState()

と replaceState() というメソッドが実装されたの。これは任意の URL をブラウザ履歴に追加したり

123
第9章 ルーティングでURLを制御する

修正したりできるもので、これによって JavaScript で URL が完全に制御可能になったわけ。今ある

ほとんど全てのフロントエンドフレームワークは、軒並みこの API を使ってルーティングを実現して

るんだよ」

「なるほど。そのおかげで URL が変わってるのに Web サーバにリクエストが飛んでないんですね。

じゃあ逆に、サーバーサイドフレームワークでのルーティングとの違いって他にありますか? 開発

する上で気をつける点とか」

「サーバにリクエストが行かないということは、サーバ側からはクライアントがどんなページを見て

るかとか、ページをどう移動したかとかがわからないわけだよね。これはアクセス解析を行う上でネ

ックになる。でも Google Analytics 用に react-ga というモジュールがあるので、それを組み込むこと


*1

で対処できるよ。

あとサーバが HTTP ステータスコードを返せないことも注意すべき点かな。既存のページを削除

したときにサーバが 404 を返すようにすれば、検索エンジンがそれを検知してインデックスから削除

してくれるけど、SPA だとそうはいかない。サイトマップや Google Analytics の管理機能を使うこと

でフォローする必要がある。

他には、ルーティングの適用単位がコンポーネントだというのも前提知識として知っておく必要が

あるね。Rails だと URL に対応するのは HTML のページ全体なわけだけど、React に組み込むルー

ターは個々のコンポーネントが URL に紐付く形になる。Rails の Controller でも URL から判断して

パーツテンプレートの出し分けはできるけど、React に対応したルーターでは親コンポーネントのあ

ずかり知らぬところで子コンポーネントが自分で URL を見て振る舞いを変えたりできるの」

「なるほど。
『React はコンポーネント指向』って、そういうことでもあるんですね」

9-2. React Router にまつわるあれこれ

*1 https://github.com/react-ga/react-ga

124
9-2. React Routerにまつわるあれこれ

「React 向けのルーティングモジュールはいくつかあるようだけど、React Router*2 が圧倒的なデファ

クトスタンダードだね。二番手は Universal Router*3 だろうけど、2019 年 2 月現在の統計で React Router

が週間約 300 万ダウンロードなのに対して、こちらは 12,000 ダウンロードほど。だから普通は React

Router 一択だろうね。

ただ気をつけてほしいのは、今の最新版はバージョン4系なんだけど3系のほうもまだ開発が続け

られてること。4.0.0 の正式版がリリースされたのが 2017 年 3 月で、それから1年のあいだに3系も

3.0.2 → 3.2.1 と版を重ねてる」

「えっ、それどういう状態なんですか?」

「React Router は3系から4系に移ったときに大きな破壊的変更があったの。それを嫌って3系をず

っと使い続けたい人たちがまだいるってことでしょうね。具体的には、たとえば <Route> に onEnter

というルーティングから描画コンポーネントに処理を渡す直前に任意の関数を実行させる属性値があ

ったり、Nested Routing といって <Route> を階層化させて記述すると、子のルーティングの前に親の

レンダリングが実行されたりといった強力な機能があったんだけど、それらが4系になって根こそぎ

削除された」

「一見便利そうですけど、どうしてなくしちゃったんでしょう?」

「React 本体と関係ないところでレンダリングをブロックしたり別の処理やレンダリングを差し込む

のは、そのライブラリとして行儀がいい振る舞いとは言えないからね。これらの機能は使う人によっ

てはありがたいのかもしれないけど、そういった処理はライフサイクルメソッドやコンポーネントの

階層化で行うのが正しい形の React 開発であって、ルーティングの中でやるべきじゃない。書くほう

は便利かもしれないけど、そのコードを読まされるほうは追わなきゃならない流れが幾重にも複雑に

なって、たまったものじゃないよ。React Router の開発者たちもそう思ったからこそ、4系でそれら

の機能をごっそり削ったんだと思う」

「なるほど。じゃあ迷わず最新版の4系を使うべきなんですね?」

「そうだね。あともうひとつ、使うのを避けたほうがいいモジュールを紹介しておきます。

react-router-redux*4 という React Router が公式のパッケージとして提供しているものなんだけど」

*2 https://reacttraining.com/react-router/

*3 https://www.kriasoft.com/universal-router/

*4 https://github.com/reacttraining/react-router/tree/master/packages/react-router-redux

125
第9章 ルーティングでURLを制御する

「公式なのに使わないほうがいいんですか?」

「Redux についての説明は後でくわしくやる予定だけど、必要なのでちょっとさわりだけ説明してお

くね。Redux ではアプリケーションの状態を変異させるための処理を Action Creator 関数というとこ

ろに書けるんだけど、react-router-redux はその処理中にリダイレクト処理とかを差し込むことができ

るものなの。でも React や Redux の思想からは、Action Creator がそのような副作用を持つことは好

ましくない。React アプリは全てがコンポーネントで構築されているはずなので、リダイレクトが必

要ならコンポーネント自身がその Props や Local State から判断して実行するのがあるべき姿だと私は

思うよ」

「ちょっと今の私には難しいですけど、ルーティングにおいてもコンポーネント指向を徹底するべき

ということですよね」

「端的に言えばそういうことだね。

それじゃ、React Router の使い方を見ていこうか。React Router は React Training という会社が開

発していて、自社のサイトでいくつかのサンプルを含んだ詳しいドキュメント*5 を提供しているのでそ

れを見といてね、で終わるんだけど……」

「えええー、そんな投げやりな。ちゃんと教えてくださいよー」

「はいはい、ちゃんと基本的な使い方は教えます。でも全てはカバーできないので、ふだんから自分

で公式ドキュメントを読んでおく習慣をつけておくこと。いいエンジニアになるために大事なことだ

からね」

「……はーい、わかりました。あとで余裕のあるときに目を通しておきます」

「なお React Router は 2019 年 3 月現在、Hooks には未対応。withRouter() という HOC を使うこと

になるけど、コンポーネントで Hooks を使うときは HOC と混在してモヤモヤすることになるけど、

現状は仕方ないね。useRouter() という Custom Hook が提供されるのは次のバージョン 4.4 になる予

定 なので、それが出たら
*6

*5 https://reacttraining.com/react-router/web/guides

*6 Add useRouter Hook #6430 https://github.com/ReactTraining/react-router/issues/6430

126
9-3. React Routerの使い方

9-3. React Router の使い方

「今回のサンプル*7 は、関数コンポーネントの説明のときに使った『はねバド!』のキャラ紹介のコ

ードに大幅に手を加えたものになります。ちなみに新しくインストールしたライブラリは以下の4つ

ね」

・ react-router

・ react-rouer-dom

・ @types/react-router

・ @types/react-router-dom

「じゃ、さっそく yarn と yarn start で実行してみようか」

*7 https://github.com/oukayuka/ReactBeginnersBook/tree/master/09-routing/03-react-router

127
第9章 ルーティングでURLを制御する

「ホームページと高校別のキャラクター一覧ページがあって、それぞれリンクされてますね」

「うん。まずはコンポーネントの最上階層の src/index.tsx のコードから見ていこう」

import React from 'react';


import ReactDOM from 'react-dom';
import { BrowserRouter } from 'react-router-dom';

import App from './App';


import * as serviceWorker from './serviceWorker';

import './index.css';

128
9-3. React Routerの使い方

import './styles/semantic.min.css';

ReactDOM.render(
<BrowserRouter>
<App />
</BrowserRouter>,
document.getElementById('root'),
);

serviceWorker.unregister();

「これまでこのファイルは中身が <App /> だけだったのが、<BrowserRouter> というコンポーネント

でラップされてる。こうすると下の階層のコンポーネントで HTML5 の History API を利用した各種

の機能が使えるようになるわけ」

「ふむふむ」

「次はお同じ階層の src/App.tsx を開いてみよう」

import React, { FC } from 'react';


import { Redirect, Route, Switch } from 'react-router';

import Home from './components/Home';


import Characters from './components/Characters';

import './App.css';

const App: FC<{}> = () => (


<div className="container">
<Switch>
<Route path="/characters/:code" component={Characters} />
<Route path="/" component={Home} />
<Redirect to="/" />;
</Switch>
</div>
);

export default App;

「このファイルが実質、URL のルーティングマップになってるの。React Router は本来、コンポーネ

ントごとにルーティングできるんだけど、単純化のために今回はほぼページ全体をひとつのルーティ

129
第9章 ルーティングでURLを制御する

ングの対象にしてる」

「switch 文みたいにルーティングが書けるんですね。path に URL がマッチすると、component に指定

したコンポーネントがレンダリングされるわけですか」

「その通り。どのエントリにもマッチしなかったら、最後の <Redirect /> に行き当たってホームに

リダイレクトされる。じゃあ今度は、ホームに相当する src/components/Home/index.tsx を見てみよう」

import React, { FC } from 'react';


import Helmet from 'react-helmet';
import { Link } from 'react-router-dom';
import { Container, List } from 'semantic-ui-react';

import { characterData } from '../../characterData';


import './index.css';

const codes = Object.keys(characterData);

const Home: FC<{}> = () => (


<>
<Helmet>
<title> 作 品 紹 介 | は ね バ ド ! </title>
</Helmet>
<header>
<h1> 『 は ね バ ド ! 』 作 品 紹 介 </h1>
</header>
<Container className="summary">
<p>
『はねバド!』は、濱田浩輔による日本の漫画。高校女子バドミントンを題材
に し た 作 品 で 、『 good! ア フ タ ヌ ー ン 』( 講 談 社 ) に て 2013 年 32 号 よ り 現 在 も 連 載 中 。
</p>
<p>
舞台は神奈川県にある北小町高校バドミントン部。
自身の母校を強くしたいと新しくコーチに就任した立花健太郎だったが、練習
が厳しすぎると部員が続々とやめていき、キャプテンの荒垣なぎさも彼に反抗的で部は
分裂寸前。
そんなとき、運動神経抜群でバドミントン経験者の 1 年生「羽咲綾乃」を見か
けた立花は、彼女を部にスカウトしようとするが……。
</p>
</Container>
<h2> 登 場 人 物 </h2>
<List as="ul">
{codes.map(code => (
<List.Item as="li" key={code}>
<Link to={`/characters/${code}`}>{characterData[code].school}</Link>
</List.Item>
))}

130
9-3. React Routerの使い方

</List>
</>
);

export default Home;

「登場人物のデータは src/characterData.ts に移動させたんだけど、型もちょっと変えてるので注意

しておいて。characerData.kitakomachi.players[0] みたいにアクセスできるようになってる」

interface Character {
id: number;
name: string;
age: number;
height?: number;
}

export interface Characters {


[code: string]: {
school: string;
players: Character[];
};
}

export const characterData: Characters = {


kitakomachi: {
school: ' 北 小 町 高 校 ',
players: [
{
id: 1,
name: ' 羽 咲 綾 乃 ',
age: 16,
height: 151,
},
《以下略》

「コンポーネントの最初の <Helmet> って何ですか?」

「これは React Helmet1 ってモジュールで、こうやって書くことでどこからでも HTML ドキュメン

トヘッダが上書きできるようになるの。こうしないとどのページに行っても public/index.html に書

かれた <title> の中身がページタイトルになってしまうからね」

「なるほどー、これも便利なモジュールですね」

131
第9章 ルーティングでURLを制御する

「その下では Object.keys(characterData) で characterData のオブジェクトキー ['kitakomachi',

'furejo'] を抽出してあったものを、map() で 繰り返し処理を行ってる。その中の <Link to=...> がそ

の名の通りリンクを表示するコンポーネントだね」

「これって <a> タグで書いちゃダメなんですか?」

「<a> タグで書くと、そのクリックを踏んだ時点で React Router の管轄外となって、管理していた履

歴が全て消えてしまうよ。ふつうに Web サーバにリクエストが行って、SPA のコード全体がリロー

ドされるわけだから」

「あっ、そうか。そうなるんですね……」

「じゃ、最後。src/components/Characters/index.tsx を開いて」

import React, { FC } from 'react';


import { RouteComponentProps, withRouter } from 'react-router';
import { Redirect } from 'react-router-dom';
import { parse } from 'query-string';
import Helmet from 'react-helmet';
import { Button, Divider, Icon } from 'semantic-ui-react';

import { characterData } from '../../characterData';


import Spinner from '../common/Spinner';
import CharacterList from './CharacterList';

import './index.css';

type CharactersProps = {} & RouteComponentProps<{ code: string }>;

const Characters: FC<CharactersProps> = ({ history, location, match }) => {


const codes = Object.keys(characterData);
const targetCode = match.params.code;
const isLoading = parse(location.search).loading === 'true';

return codes.includes(targetCode) ? (
<>
<Helmet>
<title> キ ャ ラ ク タ ー 一 覧 | は ね バ ド ! </title>
</Helmet>
<header>
<h1> は ね バ ド ! キ ャ ラ ク タ ー 一 覧 </h1>
</header>
{isLoading ? (
<Spinner />
):(
<CharacterList

132
9-3. React Routerの使い方

school={characterData[targetCode].school}
characters={characterData[targetCode].players}
/>
)}
<Divider hidden />
<Button basic color="grey" onClick={() => { history.push('/'); }}>
<Icon name="home" />
ホームへ
</Button>
</>
):(
<Redirect to="/" />
);
};

export default withRouter(Characters);

「ここでまずやりたかったのは URL が http://localhost:3000/characters/kitacomachi のときに北

小町高校の、http://localhost:3000/characters/furejo のときにフレ女の登場人物一覧に振り分け、

それ以外のときはホームにリダイレクトすること。そんなふうに URL からマッチする要素を抽出した

り、リダイレクトのために、React Router が提供する history や location や match といったオブジェ

クトをコンポーネント内部で使いたい。コンポーネントに withRouter() という HOC を適用すること

でそれが可能になるの。最終行でエクスポートしてるのは withRouter() でラップした Characters コ

ンポーネントになってるでしょ?」

「たしかに」

「history や location や match は withRouter() を適用したコンポーネント内の Props として渡される。

react-router モ ジ ュ ー ル が 提 供 す る そ の 型 が RouteComponentProps だ ね 。 VSCode 上 で

RouteComponentProps に左クリックして『Go to Definition』で定義ファイルを開いてみて」

export interface RouteComponentProps<Params extends { [K in keyof Params]?: string } = {}, C extends


StaticContext = StaticContext, S = H.LocationState> {
history: H.History;
location: H.Location<S>;
match: match<Params>;
staticContext?: C;
}

133
第9章 ルーティングでURLを制御する

「この中で history と location と match が定義されてるよね。だからまず Characters コンポーネント

の Props を元々持っていた Props 要素、ここではないので {} になっているけど、それに

RouteComponentProps を合成することで、あとで withRouter() を適用する受け皿を作っておくの」

type CharactersProps = {} & RouteComponentProps<{ code: string }>;

「ここ、RouteComponentProps<{ code: string }> と型引数を渡してあげてるのはどういうことなんで

しょうか?」

「それはね、ちょっと src/App.tsx の該当するルーティングエントリのところに戻ってみて」

<Route path="/characters/:code" component={Characters} />

注目してほしいのは :code のところ。RouteComponentProps の型引数に与えてるのと同じ code だね。

こうすることでコンポーネント内部で Props から match.params.code のように URL の :code の部分

が文字列として抽出できるの。ちなみに型は string でしか定義できないから気をつけてね」

「な るほど。 そうやって抽 出した code が ['kitakomachi', 'furejo'] に存在する場合は、

<CharacterList> コンポーネントが描画され、存在しない場合は <Redirect> コンポーネントに渡され

て ホ ー ム に リ ダ イ レ ク ト さ れ る ん で す ね 。 試 し に ア ド レ ス バ ー の URL を

http://localhost:3000/characters/konan に書き換えたら、やっぱりリダイレクトされました!」

「うんうん。じゃ、ちょっとそれを http://localhost:3000/characters/furejo?loading=true に書き

換えてみて」

「あれ? 『読み込み中...』ってぐるぐるローダーが回って中身が表示されなくなりました」

「URL から任意のクエリーパラメータを抽出して、その値によって部分的に DOM を入れ替えてるの」

const isLoading = parse(location.search).loading === 'true';

「こうやることで location オブジェクトから loading という URL のクエリーパラメータを抜き出して

134
9-3. React Routerの使い方

るわけ。query-string というのはクエリーパラメータを扱うためのモジュールね。

それじゃ、次が最後。<Button> コンポーネントの onClick 属性値に () => { history.push('/'); }

という無名関数が渡されてるよね」

「<Redirect> コンポーネントの描画でもリダイレクトができてましたけど、こう書く方法もあるんで

すね。これなら Effect Hook の useEffect() の中でもリダイレクトができますね」

「そうだね。ちなみに同じリダイレクトでも history.replace() というメソッドもある。違いは push()

はその URL にいた履歴が残るけど、replace() はその過去が抹消されること。ブラウザの『戻る』

ボタンで戻って来られると困るときとかは replace() を使うといいよ」

「React Router の使い方、だいたい理解できたと思います。基本的な概念はサーバーサイドと変わら

ないので、違いさえ認識していれば頭に入りやすかったです!」

「そう、よかった。でもサーバーサイドと違ってあと一点、注意が必要なことがあるのでそれについ

て言及しておくね。スクロール位置なんだけど、React Router ではルーティング遷移時にスクロール

位置が変わらないの。というか、React Router に限らずたいていの SPA 用のルーティングライブラリ

の挙動はそう」

「?? ちょっとピンとこないです」

「そうだね。ちょっとブラウザでこの技術書典5のサークル紹介ページ*8 を開いてみて。それからペ

ージの一番下の『次のサークル』ってリンクをクリック」

「えっ? 一番下にスクロールされた状態のまま、新しいページに飛んじゃいました。一瞬、何が起

こったのかわからなかったです」

「このサイトは React で作られてるわけじゃないけど、まあこういうこと。サーバーサイドアプリケ

ーションなら、ページをまたぐと当然のように毎回トップ位置から始まるわけだけど、History API

の pushState() を使ったルーティングでは履歴が変わって新しいページがレンダリングされても、ス

クロール位置は遷移前のままになる。これは一般ユーザーの期待する挙動じゃなくて、かなり違和感

を抱かれるよね」

「それはもう、そうですね」

「ちゃんとユーザーが期待する挙動にするには componentDidMount() に window.scrollTo(0, 0) を仕込

んだコンポーネントを作って、DOM ツリーの上のほうでマウントさせるみたいな処理が必要になる。

*8 https://techbookfest.org/event/tbf05/circle/27720001

135
第9章 ルーティングでURLを制御する

とりあえずおぼえておいて」

「は――、やっぱりサーバサイドアプリケーションとは勝手がけっこう違うんですね」

136
10-1. Fluxアーキテクチャ

第 10 章 Redux でアプリの状態を管理する

10-1. Flux アーキテクチャ

「React ではコンポーネントを組み合わせてアプリケーションを作っていくわけだけれども、コンポ

ーネントには状態を持たないステートレスなコンポーネントと、状態を持つステートフルなコンポー

ネントがあるってことがここまででわかったよね? ただ実際のアプリケーションには、コンポーネ

ントをまたいで保持したい状態が存在することがよくある。たとえばユーザーのログイン・非ログイ

ン状態やアカウント情報なんかがその最たるものだね」

「はい、単方向データフローの説明のときにも出た話ですよね。React ではどうやって管理してるん

でしょうか」

「ひとつ考えられる方法としては、上位のコンポーネントに必要な状態を全て持たせておいて、それ

らを子や孫のコンポーネントに Props でバケツリレーしていく、なんてのがある」

「いやそれ、めちゃくちゃ大変じゃないですか……?」

「あはは。まあ、そうだね。さすがに今はそんなことをしてる人はいないだろうね。もうひとつは React

の公式が提供している Context API を使う方法。これについての詳細な説明は省くけど、使い勝手が

あまりよろしくないので、今のところ UI テーマライブラリや i18n 対応ライブラリといった限定的な

用途でしか使われてるのを見ないかな」

「……またですか。それにしても React 公式が出してくる技術の打率、低すぎじゃないですか?

Flowtype とか Render Props とか」

「し―――っ! Hooks みたいな例もあるので、一概にはそう言えないから! それに今は Context

API、Hooks からも useContext() で使えるようになって多少は使いやすくなったし、何より最新の

React Redux モジュールはその Context API を使って作られてるからね」

「へー、そうなんですね」

「それで話を戻すけど、React は公式がそう説明しているようにもともとただの View のライブラリで

しかないので、そういったアプリ全体の状態管理を含めたアプリケーションのアーキテクチャをどう

するのかは、各々の開発者に委ねられていたわけ。最初のほうはみんながんばって Props のバケツリ

137
第10章 Reduxでアプリの状態を管理する

レーをしたり、他のフレームワークを組み合わせて使っていたみたいだけど、React の急速な普及に

より、その性質に合った新しい設計指針が求められるようになってきたの。

そして 2014 年に Facebook が自社のカンファレンスで発表したのが Flux というアーキテクチャパタ

ーンだった。そこで Facebook の開発者たちは既存の MVC をかなりディスって『MVC はスケールし

ない。だから私たちは Flux でやる!』*1 と宣言したので、当時はかなり議論を巻き起こして結構な騒

ぎになったらしいね」

「でも Rails も MVC アーキテクチャですけど、MVC だと何がダメなんでしょう?」

「Facebook が言ったのは、あくまでフロントエンド開発では MVC はスケールしないということだっ

たんだけどね。
『Model と View の間に双方向のデータフローが作られるため、新しい機能を追加しよ

うとするたびに、システムの複雑度はが指数関数的に増大し、そのコードは《壊れやすくて予測不能

なもの》になってしまう』というのが彼らの言い分。一枚岩の静的ページを返せばいいサーバーサイ

ドアプリではデータフローは Model → View の一方向が当たり前だけど、ユーザーの操作をインタラ

クティブに反映させる必要があるフロントエンドではどうしても View → Model のフローも必要な場

面が出てきてしまうからね」

「なるほど」

「そこで提唱されたのが Flux パターンだったわけだけど、これを理解するにはまずこの図を見ても

らおうかな」

*1 Facebook の決断:MVC はスケールしない。ならば Flux だ。


https://www.infoq.com/jp/news/2014/05/facebook-mvc-flux

138
10-2. Reduxの登場

「Store とはとりあえずアプリケーション全体で保持するべき情報を蓄えたものと考えて。View から

はそれを適宜参照することができる。そして何らかのイベントが起こったときに Action という『何を

どうしたいか』という意図を表現したものが View から発行される。投げられた Action は Dispatcher

によって処理され、Store の中身が書き換えられる。そういうアーキテクチャなの。

Store が書き換えられれば、参照していた View にも速やかに反映される。これによってデータフロ

ーが常に単方向になることが保証され、どんな複雑なシステムでも《破綻しにくく予測可能なもの》

にすることができる。そう、単方向データフローだね」

「ここでも単方向データフローですか……。本当に React って原則を徹底させますよね」

「この Flux アーキテクチャの発表に感銘を受けた開発者たちが、こぞって Flux を使ったフレームワ

ークを作り始めたの。Facebook 自身も、そのまんまの『Flux』という名前のモジュール をリリース


*2

した。だから『Flux』っていう場合、ほとんどはアーキテクチャの Flux パターンのことを指すけど、

Facebook による実装のこちらを指すこともあるのでまぎらわしいね」

「はー、そうなんですね」

「それ以外にも、覇権を目指して色んな企業や個人から Flux フレームワークがリリースされた。でも

Facebook の Flux アーキテクチャ提唱からちょうど1年後の 2015 年に、Facebook や同じく開発競争に

参加していた Yahoo!といった大企業でもない、在野の一開発者が公開したあるプロダクトが、登場す

るやいなや注目を集めて、他の多くの Flux 実装の開発者たちを黙らせてしまったの」

「おおお、ドラマチックですね! ……にしても、ここでもまた公式が負けちゃってるんですね」

10-2. Redux の登場

*2 http://facebook.github.io/flux/

139
第10章 Reduxでアプリの状態を管理する

「その登場が大きな衝撃を与えたプロダクトこそが Redux ね。Dan Abramov という人が作者だった

んだけど、彼は文章の才能もあってドキュメントやブログ記事の説得力がすごくて、いっそうその普

及に拍車をかけた。Redux は一気にデファクトスタンダードの座に上り詰めたんだけど、Dan Abramov

はなんとわずかその半年後に Facebook に電撃入社して本家の React 開発チームに参加してしまった」

「あれ? そういえば Hooks のところで同じような話を聞きましたよね?」

「うん、Recompose の作者 Andrew Clark が React 開発チームにジョインして Hooks を開発したって

いうエピソードだよね。時系列的には Dan Abramov のほうが早いわけだけど、開発者コミュニティ

で人気を博したライブラリの作者を内部に取り込むというのが実質的に Facebook の React チームの常

套戦略になってるよね」

「でも彼らのあいだに具体的にどういういきさつがあったか、気になりますね」

「ふふ、ちょっと映画化できそうな話だよね。今となっては React と Redux が開発の現場ではほとん

どセットとして扱われるようになってるけど、そこに至るまでにはこんな経緯があったんだよ。

じゃ、Redux の何がそんなに開発者の心を射止めたのか、説明していこう。Redux には三つの原則

があるの。それがこれらね」

・ Single source of truth(信頼できる唯一の情報源)

・ State is read-only(状態は読み取り専用)

・ Changes are made with pure functions(変更は純粋関数にて行われる)

「ひとつめの『Single source of truth』


、これはアプリケーションの状態がただひとつの Store オブジェ

クトによるツリー構造で表現されるということ。複数の Store が存在すると、Store 間のデータのやり

取りが面倒になるよね。ひとつのオブジェクトに集約されることで、デバッグやテストもやりやすく

なる。わかりやすく他にたとえるなら、どこで定義されているかわからない複数のグローバル変数に

依存したアプリケーションを想像してみて」

「それはふつうに嫌ですね……」

「だから Redux では Store がひとつだけなの。シングルツリーにすることで、たとえばこれまでのア

プローチでは難しかった Undo/Redo といった機能も簡単に実装できたりする。

そしてふたつめの『State is read-only』
、これは View やイベントのコールバックが Store の状態を直

接書き換えることが許されないということ。Store の変更のためには必ず Action を発行する必要があ

140
10-2. Reduxの登場

るの。変更が Action だけに集約されることで、予期せぬところからの書き換えを防いでる。そして

Redux の Action は単なるプレーンオブジェクトなので、ログを取ることも簡単にできる。

そして最後の『Changes are made with pure functions』


。これは状態の変更は Reducer という、あら

かじめ定義された純粋関数によって行われるということ。Reducer は簡易化するとこんな式で表現さ

れる関数になるの」

(prevState, action) => newState

「古い状態を引数に取り、新しい状態を返す関数ですか」

「そう。Action が同じなら、必ず新旧の状態の差分も同じであることが保証される。これを Flux モ

デルに組み込んで表現したのが以下の図ね」

「View から発行された Action は Dispatcher で割り振られて Reducer に渡される。Reducer はその

Action と現在の State を受け取って、新しい State を返す。


『reduce』は英語で『減らす』という意味

だけど、科学の分野では『還元する』
『減数分裂させる』という意味もある。(prevState, action) =>

newState の式って、まさにそのイメージだよね。ちなみに『Redux』って名前もここから由来してる

141
第10章 Reduxでアプリの状態を管理する

んだよ」

「へえ、なるほどー。そうだったんですね」

「また元々の Flux パターンでは、Dispatcher は Action を受け取って自分で処理をしてからその結果

を State に書き込むものだったけど、Redux ではその名の通り Action を振り分けることしかしてない。

だから同じ名前でも意味するところは違ってるの。さらに Store の状態も、Flux ではどうとでも書き

換えられるものだった一方、Redux では書き換えの手順とその結果の State の差分が厳密に定義され

ている。だから動作の信頼性が高いし、テストやデバッグもやりやすい。そんな点が開発者たちの心

をつかんだんだろうね」

「ふむふむ。納得です」

「ちなみに Redux ってよく React とセットで話題にされるけど、何も React 専用ってわけじゃないの

ね。採用されるケースは多くないながらも、Angular や Vue に組み込んで使うこともできる。それだ

け普遍的な思想を持ったプロダクトだってことだね。

そしてこれは他の技術においても言えることだけど、特に Redux は一度は公式サイトのドキュメン

ト を読んでみることをおすすめするよ」
*3

「うう、また公式ドキュメントですか……。英語ですよね……」

「秋谷さん、英語は得意だったでしょ?」

「いや、得意ってわけじゃ……。勉強はしてますけど」

「フロントエンド、特に React はドキュメントの和訳を待ってると技術の進歩についていけないよ。

Redux 作者の Dan Abramov さんは文章力もすごいって言ったけど、Redux の公式ドキュメントには

開発の動機から、さっき説明した三大原則、影響を受けた思想・技術について説明があっておもしろ

いので、それらを知らずに Redux を使うよりも、認識しながら使うほうが絶対いいと思う」

「……わかりました。そこの部分だけでも読んでおきます」

「よろしい。あとは Hooks 対応の話をしておこうか。2019 年 3 月現在、Redux の React バインディン

グである React Redux について、正式にいつの段階で Hooks 対応がなされるかはアナウンスされてい

ません。ただし先日リリースされた 7.0.0 β 0 という開発版では connect 関数の内部で Hooks が使わ

れていて、そのおかげでパフォーマンスが大幅に改善しているみたいね。そのリリースノートの中で、

useRedux() みたいな Hooks インターフェースが React Redux で実装されるのはバージョン 7.x のいつ

*3 https://redux.js.org/

142
10-3. Reduxの使い方

かになるって言及してたので、直近ではなくてもそう遠くない将来では使えるようになってそう。で

もそれまでは、従来通り HOC のインターフェースで使いつづけることになります。

じゃあ今から、実際のコードの中で Redux を使っていこうか」

10-3. Redux の使い方

「Redux の使い方を学ぶサンプルコードとして、Local State を学んだときに使ったカウンターアプリ

を改造してみたんだけど 、インストールして実行してくれる?」
*4

「はい、実行……と。わぁ、今回のはカウント数に合わせて色とりどりのビーズが表示されるんです

ね。キレイ」

「うん。カウンターのボックスとその下のビーズ表示枠は対応するコンポーネントが並列の階層にあ

るんだけど、どちらもカウント数を参照する必要があるので、そこに Redux を使ったわけ」

*4 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/10-redux/03-redux

143
第10章 Reduxでアプリの状態を管理する

「コードはトップ階層の src/index.tsx からよりも、できるだけ参照エラーが起きない順に見ていこ

うか。これは実際に開発する際の順番にも沿ってるしね。まずは Presentational Component である

src/components/Counter.tsx を開いてみよう」

import React, { FC } from 'react';


import { Button, Card, Statistic } from 'semantic-ui-react';

import './Counter.css';

export interface CounterProps {


count?: number;
add?: (amount: number) => void;
decrement?: () => void;
increment?: () => void;
}

const Counter: FC<CounterProps> = ({


count = 0,
add = () => {},
decrement = () => {},
increment = () => {},
}) => (
<Card>
<Statistic className="number-board">
<Statistic.Label>count</Statistic.Label>
<Statistic.Value>{count}</Statistic.Value>
</Statistic>
<Card.Content>
<div className="ui two buttons">
<Button color="red" onClick={decrement}>
-1
</Button>
<Button color="green" onClick={increment}>
+1
</Button>
</div>
<div className="fluid-button">
<Button fluid color="grey" onClick={() => add(10)}>
+10
</Button>
</div>
</Card.Content>
</Card>

144
10-3. Reduxの使い方

);

export default Counter;

「今回は任意の数をカウンターに追加する add() 関数を Props に追加してる。上から三つめの Button

コンポーネントの onClick 属性値には、add(10) が実行される無名関数を渡してるよね」

「なるほど。ところで CounterProps の要素が全て省略可能になってるのはなぜですか?」

「これはスタイルガイドとかに載せるとき、Props をいちいち指定しなくてもマウントできるようにし

ておいてるんだよ。ただの変数だけならともかく、マウントするために毎回ダミーの関数を渡してや

る必要があるのもわずらわしいし」

「なるほど」

「あとはだいじょうぶそうね。色とりどりのビーズが表示される部分に対応してるコンポーネントは

src/components/ColorfulBeads.tsx なんだけど、いちおうそこも見ておこうか」

import React, { FC } from 'react';


import { Container, Label, SemanticCOLORS } from 'semantic-ui-react';

import './ColorfulBeads.css';

const range = (n: number) => (n < 0 ? [] : Array.from(Array(n), (_, i) => i));
const colors: SemanticCOLORS[] = [
'red', 'orange', 'yellow', 'olive', 'green', 'teal', 'blue',
'violet', 'purple', 'pink', 'brown', 'grey', 'black',
];

export interface ColorfulBeadsProps {


count?: number;
}

const ColorfulBeads: FC<ColorfulBeadsProps> = ({ count = 0 }) => (


<Container className="beads-box">
{range(count).map((i: number) => (
<Label circular color={colors[i % colors.length]} key={i} />
))}
</Container>
);

export default ColorfulBeads;

145
第10章 Reduxでアプリの状態を管理する

「ビーズを表示するところで配列の繰り返し処理を行うので、たとえば長さが 4 なら [0, 1, 2, 3] と

いう配列がほしいのね。JavaScript の便利なユーティリティ関数を集めたライブラリの Lodash*5 にも

range() というメソッドがあるけど、わざわざこのためにインストールするまでもなくこうやって一

行で書けるので、頭のところで定義してるの」

「JavaScript では Ruby のような range() がなくて、こんなふうに書くんですね。引数がマイナスのと

きは空オブジェクトを返してるのか」

「あくまで簡易的なものだけどね。他は大丈夫なようなら次行こうか。ここからが Redux 特有のコ

ードになる。src/actions/counter.ts を開いてみて」

export enum CounterActionType {


ADD = 'COUNTER/ADD',
DECREMENT = 'COUNTER/DECREMENT',
INCREMENT = 'COUNTER/INCREMENT',
}

export interface CounterAction {


type: CounterActionType;
amount?: number;
}

export const add = (amount: number): CounterAction => ({


amount,
type: CounterActionType.ADD,
});

export const decrement = (): CounterAction => ({


type: CounterActionType.DECREMENT,
});

export const increment = (): CounterAction => ({


type: CounterActionType.INCREMENT,
});

「ここでは、Redux の Action とそれを生成する Action Creator と呼ばれる関数群を定義してる。見れ

ばわかるように、Action というのはそのやらせたいことの種類と Action 実行に必要なデータを内包

*5 https://lodash.com/

146
10-3. Reduxの使い方

した、ただの JavaScript オブジェクトなのね」

「CounterAction の type プロパティ値が enum で型定義されてますけど、これはどういうことです

か?」

「うん。それはね、発行された Action の type を見て処理のケース振り分けをする部分をこのあと見

ていくんだけど、type が文字列だったりしたらうっかりタイプミスとかで想定の動きと違ってしまう

ことがあるので、その予防措置だね。ではその振り分けの部分を次に見てみよう。src/reducer.ts ね」

import { Reducer } from 'redux';


import { CounterAction, CounterActionType } from './actions/counter';

export interface CounterState {


count: number;
}

export const initialState: CounterState = { count: 0 };

const counterReducer: Reducer<CounterState, CounterAction> = (


state: CounterState = initialState,
action: CounterAction,
): CounterState => {
switch (action.type) {
case CounterActionType.ADD:
return {
...state,
count: state.count + (action.amount || 0),
};
case CounterActionType.DECREMENT:
return {
...state,
count: state.count - 1,
};
case CounterActionType.INCREMENT:
return {
...state,
count: state.count + 1,
};
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = action.type;

return state;
}
}
};

147
第10章 Reduxでアプリの状態を管理する

export default counterReducer;

「お、switch-case 文ですね」

「そう、ここで発行された Action の type を判定して戻り値を switch 文で振り分けてる。これがさっ

き紹介した (prevState, action) => newState の純粋関数で表現される Reducer だね」

「ふむふむ。でもこれ、State 型の中には count の一要素しかないわけだから、...state のスプレッ

ド構文の部分って不要じゃないですか? State に二つ以上の要素がある場合は、他の要素を上書き

しないように必要だけど」

「おっ、いいところに気がついたね。まさにその通りなんだけどね。ただ実際の業務開発では State

の中身が一要素だけってことはほとんどないし、後から要素を追加したときに書き忘れて他の要素が

上書きされるってケースもないわけじゃないから、あえてこうしてあるの。

あと、initialState を定義してるところも見ておいて。ここで State の counter の値を0に設定して

る。その他に聞いておきたいところはあるかな?」

「最後の default のところで never 型を使って漏れをチェックするやり方は、以前 に教わりましたよ


*6

ね。CounterActionType.ADD のケースの count: state.count + (action.amount || 0) となってるところ

なんですけど、これってどういうことですか?」

「CounterAction 型において amount プロパティは省略可能なので値に undefined が入ってる可能性が

あって、そのままだと型エラーでコンパイルが通らないの。undefined は真偽値で言えば偽だから、

こう書いておけば || の論理演算式でうしろの値が評価されて0が代入されるってわけ」

「なるほど―――」

「それじゃ、次は src/index.tsx を見ていこうか」

import React from 'react';


import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore } from 'redux';

import App from './App';

*6 4-2. 型のバリエーション

148
10-3. Reduxの使い方

import counterReducer, { initialState } from './reducer';


import * as serviceWorker from './serviceWorker';

import './index.css';
import './styles/semantic.min.css';

const store = createStore(counterReducer, initialState);

ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root') as HTMLElement,
);

serviceWorker.unregister();

「ふつう、Redux を使う場合は DOM のトップレベルで入れ子にしておくの。まず、さっき

src/reducer.ts で定義した Reducer 関数および State の初期値を関数 createStore() に渡し Stor を作

る。そして Redux を React で使うためのモジュール react-redux からインポートしてきた <Provider>

コンポーネントに Props としてその Store を渡すと、下の階層のコンポーネントで Store にアクセスで

きるようになるわけ」

「ふ――ん、そうなってるんですね」

「これでようやく Redux の機能が使える準備が整った。では実際に Action を発行したり Store にアク

セスしてる Container Component である src/containers/Counter.tsx を見てみよう」

import { connect } from 'react-redux';


import { bindActionCreators, Dispatch } from 'redux';

import { add, decrement, increment } from '../actions/counter';


import Counter from '../components/Counter';
import { CounterState } from '../reducer';

interface StateProps {
count: number;
}

interface DispatchProps {
add: (amount: number) => void;
decrement: () => void;
increment: () => void;

149
第10章 Reduxでアプリの状態を管理する

const mapStateToProps = (state: CounterState): StateProps => ({


count: state.count,
});

const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({


add: amount => dispatch(add(amount)),
decrement: () => dispatch(decrement()),
increment: () => dispatch(increment()),
});

export default connect(mapStateToProps, mapDispatchToProps)(Counter);

「雰囲気的に、HOC を使って Redux とコンポーネントをつないでるんだろうなってのはわかります」

「そうだね。ここでまずやりたいことは、
『参照したい Store State の値をコンポーネントの Props に

マッピングする』こと。加えて『発行したい Action を生成する Action Creator 関数を Props にマッ

ピングする』こと。そのために最初のほうで前者のための型を StateProps として、後者のための型を

DispatchProps として定義してるの」

「このあたりは React Router で出てきましたね。HOC の withRouter() をコンポーネントに適用して

Props と して history や match を 使 う た め の 準 備 と して 、 そ の コ ン ポーネ ン ト の Props に

RouteComponentProps を合成してました」

「うん、そのあたりは基本的には同じことだね。TypeScript で型の整合性を取りながら HOC を使う

ときはそうやるのが定番かな。それから、その下の mapStateToProps() という関数だけど。これは Store

State を受け取って Props を返す関数として定義される。Store State 内の counter プロパティ値を

StateProps の counter にマッピングしてるのね。

次の mapDispatchToProps()。これは Dispatcher を受け取って Props を返す関数として定義される。

ここで DispatchProps 内の各関数を、dispatch() に渡して対応させたい Action Creator 関数にリンク

させてるの。これは bindActionCreators()というユーティリティ関数を用いてこう書くこともできる

よ」

- import { Dispatch } from 'redux';


+ import { bindActionCreators, Dispatch } from 'redux';

- const mapDispatchToProps = (dispatch: Dispatch): DispatchProps => ({

150
10-3. Reduxの使い方

- add: amount => dispatch(add(amount)),


- decrement: () => dispatch(decrement()),
- increment: () => dispatch(increment()),
- });
+ const mapDispatchToProps = (dispatch: Dispatch): DispatchProps =>
+ bindActionCreators(
+ { add, decrement, increment },
+ dispatch,
+ );

「ちなみに Props の関数名と Action Creator の関数名がそれぞれ同じになるように定義してあるので、

プロパティ名をショートハンドで省略できるの」

「こっちのほうがシンプルに書けていいですね」

「うん。それでもこのへんの書き方はややこしいので、お約束と思って丸暗記しちゃったほうがいい

かも。

こ れ ら ふ た つ の 関 数 の 名 前 は 本 当 は 何 で も い いん だ け ど 、 慣 習 的 に mapStateToProps と

mapDispatchToProps と書かれることになってるね。まあ、そのまんまの意味でわかりやすいからね。

そして最終行で、connect() という HOC にそのふたつの関数とマッピング対象のコンポーネント

を引数に渡すと、定義した Props へのマッピングが有効になった新しいコンポーネントが返されるわ

け」

「connect、引数のカッコがふたつあるということは、これカリー化された関数なんですね」

「そう、これもややこしいのでそういうものとしておぼえときましょう。そしてここでエクスポート

したコンポーネントを src/App.tsx でインポートしてマウントすればようやく完成! ColofuleBeads

コンテナのほうもほぼ同様で、こっちは count の値をマッピングしてるだけなので省略するけど、自

分で見ておいてね。

はい、これで Redux の使い方についてのひと通りの説明は終わりかな」

「ふ―――、やっと終わったー! ありがとうございました。でも、柴咲さんの順を追っての解説付

きだったから何とかわかった気になりましたけど、自分でこれを一から書けるようになる自信があり

ません……」

「あはは。そうかな、そうかも。でもそれを言うなら、私だって難しいよ。だいたいどこかのボイラ

ープレートを使わせてもらったりとか、他のプロジェクトで書いたコードを再利用してるもの」

「えっ、そうなんですか?」

151
第10章 Reduxでアプリの状態を管理する

「バックボーンを理解していれば書かれたものは読みやすいし、テストやデバッグも容易だけど、自

分で一から書くのは難儀する、それが React や Redux だから。React 本体は Create React App のおか

げでだいぶ手軽になったけど、Redux にはそういうのがないからねー。誰か作ってくれないかな」

「それを聞いて安心……しちゃいけないんでしょうが、ちょっと気持ちが楽になりました」

「記述が冗長なのは Rails のように規約をバーンと作って、そこに沿った部分はフレームワークが提

供してあげるというやり方をしてないからだけども。React がそれをやらなかったおかげで Redux を

始めとする様々な技術が生まれて、激しい競争の中で優れたものが残り、今も進化し続けてるとも言

えるわけだしね」

「なるほど、一長一短なんですね。でも技術トレンドの先端でバリバリ活躍してるハッカーたちなら

心惹かれるものなんでしょうけど、私みたいな初心者にはやさしくないです……」

「まあ、特に Redux は概念を理解するのが簡単じゃないし、学習コストも決して低くないと私も思う

よ。でも一度理解してやり方をおぼえてしまえばメリットのほうが全然上回るんだけど、初見でひる

んじゃってそれがヘイトな意見につながってる側面もあるよね。私も前の会社で Redux を導入する際

に、他のメンバーに反発された経験があるもの。ちなみに Dan Abramov もこんなツイートをしている

くらいだし」

「あらら、Redux の作者がこんなこと言っちゃっていいんですか?」

「実は、彼は Facebook の React 開発チームにジョインしてから、もうほとんど Recux の開発に関わっ

てないんだよね。むしろ積極的に Redux を滅ぼそうとする側に回ってる感がある」

「ええっ?!」

「まあ Recompose の作者 Andrew Clark が公式に引き抜かれて Hooks のリリースに貢献したのと同じ

152
10-3. Reduxの使い方

構図だと思うけどね。でも今のところ、開発者コミュニティの Redux への圧倒的な支持は変わってい

なくて、公式による Redux 絶滅計画は成就してない」


『絶滅計画』って……」

「確かに全ての場面において Redux が必要になることはないだろうね。Dan Abramov も『You Might

Not Need Redux』*7 というブログ記事を書いて、Local State で十分なときは Redux を使わなくていい、

と言ってる。

でも業務で開発するアプリに、コンポーネントをまたいで共有したいデータがないなんてことはま

ずないから、最初から Redux を入れておいたほうが後からの軌道修正コストも少ない。それに一度

入れてしまえば『このドメインのデータ値は Store State で管理するけど、このドメインのデータ値は

他の方法で管理する』みたいに分けるのは Single source of truth に反していて、読みづらく破綻しや

すくなることまちがいなしなので、一貫して始めから Redux に任せるべきだと思う」

「確かにそうかもしれませんね」

「背景となった歴史やその思想、そしてその技術がもたらすメリットをきちんと理解することもない

まま、短絡的に特定の技術をディスる開発者が多くて、私はつねづね憤慨しているのですよ」

「……なんだかすみません。でも雪菜さんにここまでていねいに教わったので、だいじょうぶです!

私も React と Redux のこと、だんだん好きになってきました!」


『雪菜さん』?!」

「いえ、もうこれだけ毎日顔を合わせてお話ししてるわけですから、そろそろ名前で呼ばせてもらっ

てもいいころかなーって、ダメでしたか?」

「……いや、別にダメってことはないけど……」

「やった! じゃあこれからは『雪菜さん』で。あ、私のことも名前で呼んでいいですよ?」

「ん――、遠慮しとくわ。私は仕事とプライベートはきちんと分けたいので」

「えええー? でも私、プライベートでも雪菜さんと仲良くなりたいと思ってるんですけど……」

「………………」

「そんなあからさまに引かなくても。じゃあこの先の課題と言うことで。考えといてくださいね」

*7 https://medium.com/@dan_abramov/you-might-not-need-redux-be46360cf367

153
第10章 Reduxでアプリの状態を管理する

10-4. Flux Standard Action

「さて、Redux には原則はあっても規約がない、という話をしたよね。Redux はすごくシンプルなモ

ジュールでサイズも 2KB くらいしかなくて、単純な状態管理しか行わない。その代わりに拡張性に優

れていて、Redux の動作に介入して様々なことをさせることができるミドルウェアが手軽に作れる仕

組みを提供しているの。

ただ、明確な決まりごとがない中で開発者がそれぞれのマイルールで思うままに開発すると、こち

らのミドルウェアで動いていたものがあちらのミドルウェアでは動かない、なんてことが起こったり

する。そんな中で非公式だけど Action のフォーマットをきちんと定義しようと提唱した人がいて、そ

れが多くの賛同を集めたの。それが『Flux Standard Action』*7、略して FSA と呼ばれるもの」

「さっきのサンプルコードでも、いちおう Action の型を決めてましたよね。たしかこんな感じの」

interface CounterAction {
type: CounterActionType;
amount?: number;
}

「そうだね。実は Redux モジュール自身が定義している TypeScript 用の型定義でも、一部が FSA と

共通しているの。たとえばこの type プロパティのキー名を actionType とかに書き換えると、

mapDispatchToProps() で型エラーが起きる」

「あ、ほんとだ」

「これは公式が提供している TypeScript の型定義ファイルの中で、Action インターフェース型に type

という名前のプロパティを強制しているからなの。この下の AnyAction というのがその型ね」

*7 https://github.com/redux-utilities/flux-standard-action

154
10-4. Flux Standard Action

export interface Action<T = any> {


type: T
}

export interface AnyAction extends Action {


[extraProps: string]: any
}

「つまり FSA はなかば Redux 公式も認めているデファクトスタンダードなので、これから導入する

ミドルウェアの動作保証のためにもこれに準拠した書き方にしといたほうがいいということ。それに

Action の型のフォーマットをあらかじめ決めておくことは、開発チームのメンバーが増えたときに混

乱を防ぐことにもつながるし」

「ふむふむ、なるほど」

「FSA に準拠した Action のフォーマットを、TypeScript で厳密に記述するとこうなる」

interface Action {
type: string;
payload?: Object;
error?: boolean;
meta?: Object;
}

「各プロパティについて説明するとこんな感じ」

・ type …… Action の種類を一意に示す文字列、enum、またはストリングリテラル。省略で

きない必須プロパティ。

・ payload …… 主に正常時には Action の実行に必要なデータ、異常時にはエラー情報を格納

する。オブジェクトで表現される。省略可能なプロパティ。

・ error …… 真偽値で表現されるプロパティで、true の場合はこれがエラーの Action である

ことを意味する。省略可能なプロパティ。

・ meta …… その他追加で必要な情報を格納する。オブジェクトで表現される。省略可能なプ

ロパティ。

155
第10章 Reduxでアプリの状態を管理する

「じゃあさっきのビーズカウンターのコードで定義されていた CounterAction インターフェース*8 は FSA

に準拠してないってことですね。amount というプロパティは定義されてませんから。この場合は

payload に入れるのが適切なんでしょうか?」

「うん、その通りだよ。あの型を FSA に準拠させるとこうなるね」

interface CounterAction {
type: CounterActionType;
payload?: { amount: number };
error?: boolean;
meta?: object;
}

「さて、ここから戦略を検討する必要に迫られるんだけど、Action の型に厳密な FSA ベースのイン

ターフェースを用意して、それを Action Creator 関数の戻り値に設定するという方法がひとつある。

ただしこれを実践するのは煩雑でけっこうな手間を要するので FSA 準拠の型安全なユーティリティを

提供してくれる typesafe-actions*9 や Typescript FSA*1 といった外部ライブラリを導入するケースが多い

の。実際、私も Typescript FSA を積極的に使ってました。TypeScript FSA を使うと Action の定義は

もちろん、Reducer 周りでの複雑な型解決も任せることができたので重宝してたな。Action によって

Payload の形が変わるから、Reducer 内で action.payload.foo みたいなアクセスをするにはこういった

ユーティリティなしでは難しくて、結局(action.payload as any).foo といった禁断の記述をする羽目

になったりしたんだよね」

「へえー、そんな苦労があったんですね」

「かといって TypeScript FSA を使うと全て幸せかというと、これを使うための学習コストも馬鹿に

できないし、またどうしてもスタンダードな Redux の記法から離れてしまってユーティリティが提供

する独特の書き方になってしまって気持ち悪い。つまりそれは、そのライブラリを外すためのコスト

*8 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/10-redux/03-redux/src/actions/counter.ts

*9 https://github.com/piotrwitek/typesafe-actions

*1 https://github.com/aikoven/typescript-fsa

156
10-4. Flux Standard Action

も高いということでもあるよね」

「ふむふむ、確かに」

「そしてとうとう恐れてた事態が起こった。TypeScript FSA の更新が止まったの。2018 年 5 月に出

たバージョン 3.0.0 β 2 を最後に、GitHub のリポジトリは更新されてない」

「あらら……」

「仕方なく、メンテナンスが必要なコードは全て書き直しになった。この痛い経験から、改めて

typesafe-actions のような別のユーティリティを使う気にはなれなかったな。そしてその間にも

TypeScript の機能は上がり続けて、面倒だった Reducer 周りの型解決とかは型推論でうまく処理でき

るようになってたの。開発者コミュニティのトレンド的にも、ユーティリティを使わずに最新の

TypeScript の高度な型推論を使って素直な Redux の記法で書く人が増えてるように見える」

「ほほぅ、そのへんのお話は参考になります」

「ただこのやり方は、最初に Action に厳密な FSA 準拠インターフェースを適用して、Action Creator

の戻り値に設定するやり方とは相性が悪いんだよね。何をするかというと、Action Creator の戻り値

を ReturnType による型推論で取ってきて、それを共用体型に落とし込んで Action の型として流用す

るという逆のアプローチになるので。具体的にはこんなコードになる」

export const ADD = 'ADD';


export const DECREMENT = 'DECREMENT';
export const INCREMENT = 'INCREMENT';

export const add = (amount: number) => ({


type: ADD as typeof ADD,
payload: { amount },
});

export const decrement = () => ({


type: DECREMENT as typeof DECREMENT,
});

export const increment = () => ({


type: INCREMENT as typeof INCREMENT,
});

export type CounterAction =


| ReturnType<typeof add>
| ReturnType<typeof decrement>

157
第10章 Reduxでアプリの状態を管理する

| ReturnType<typeof increment>;

「なるほどー。ところで ADD as typeof ADD とか DECREMENT as typeof DECREMENT みたいな冗長ぽく見

えるこの書き方って何なんですか?」

「ああ、これはオブジェクトプロパティの定義って、そのままでは type が文字列 'ADD' として認識

されてしまうので、そうじゃなくてここは共用型に使われる文字列リテラルなんだよって注釈だね。

こうすることで Reducer の switch-case 文内で型推論が効くようになる。ちなみに TypeScript 3.4 から

はこんな書き方もできるようになるよ」

export const increment = () => ({


type: INCREMENT as typeof INCREMENT,
} as const);

「話を戻すけど、このやり方だとすっきり見えるけど、厳密な FSA 準拠インターフェースを Action

に強制するのは難しい。ためしにやってみたんだけど、型推論を有効にしたまま実行させるのは難し

かったし、できたとしても一見さんにはひどく難解な型プロレスになりそうだった。それを避けるた

めには『FSA 準拠をやりたいから気をつけて書いてね』とチームメンバーにお願いする、という性善

説に則るしかなくなるわけだけど、前述したような状況を踏まえてメリットとデメリットを考えると

現状はこの形で妥協しておくのがベターかなというのが私見だね」

「うーん、難しいところですね……」

「ちなみに、実際に適用することはないけどいちおう Action の型を用意してあるので、Action Creator

を書くときはこの型を意識して書いてね、ということで」

export interface Action<Payload> {


type: string;
payload?: Payload;
error?: boolean;
meta?: { [key: string]: any } | null;
}

export interface ActionStart<ActionType, Params> {


type: ActionType;
payload?: Params;

158
10-4. Flux Standard Action

meta?: { [key: string]: any } | null;


}

export interface ActionSucceed<ActionType, Params, Result> {


type: ActionType;
payload: {
params: Params;
result: Result;
};
meta?: { [key: string]: any } | null;
}

export interface ActionFail<ActionType, Params, AnyError> {


type: ActionType;
payload: {
params: Params;
error: AnyError;
};
error: boolean;
meta?: { [key: string]: any } | null;
}

「あれ? Action の型が4つに増えてますけど?」

「うん。最初のはノーマルな Action。あとの3つは、非同期処理を行うときの「開始/成功/失敗」

のそれぞれの状態に対応した Action だね。状態によって特に payload の中身が変わってくるのがポイ

ントね。これらの型はこの後、Redux-Saga を用いた Redux による非同期 API 通信処理を実装すると

きに参照することになるよ。ちなみにこれらの型は TypeScript FSA で使われてたものをほぼそのま

ま持ってきたので、汎用性は高いと思う」

「なるほど、わかりました! Action Creator の戻り値は、この形を書くようにします」

「うん。これでビーズカウンターの FSA 対応についての説明は終わり。サンプルコード のほうで、


*11

Reducer とかもどう変わったか見ておいてね」

「はい、わかりました。でもそれにしても、今回に限らず横で雪菜さんがコーディング中に素早く型

を解決していくのを見て、
『えええー? 私にはこんなの無理ー!』と気後れしちゃいます……」

「まあこのへんの型の整合性のとり方は、単純に経験というか慣れかな。赤の破線アンダーラインが

出てる部分にカーソルを合わせると VSCode がどういうふうに型の不整合が起きてるのかちゃんとエ

*11 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/10-redux/04-fsa

159
第10章 Reduxでアプリの状態を管理する

ラーメッセージで教えてくれるから、それを最後まで読んで理解しようと努力する。それから指摘さ

れた型の定義元のファイルを開けば、型や引数の名前でだいたいの目星がつくから、それに合わせて

コードを修正していく、みたいにトライ&エラーをやっていくわけ」

「お、おう……」

「あとはそのライブラリの GitHub 公式リポジトリや Definitly Typed のリポジトリに、型のテストを

してるコードがあるはずだから、それを見れば正しい引数や型引数の渡し方の参考になるよ。私はこ

れでかなり助かってる。でも、私も最初のころは型の不整合にハマってうなりながら半日つぶすとか

ザラだったし、まあそんなもんだよ。秋谷さんの場合は私があげたサンプルコードがあるんだから、

ずっと楽なはずだよ」

「……そうですね。言われてみればエラーメッセージとか、ちゃんと読んでなかった気がします。今

言われたこと、次からやってみます!」

160
11-1. Reactで非同期通信を扱ういくつかの方法

第 11 章 Redux で非同期処理を扱う

11-1. React で非同期通信を扱ういくつかの方法

「ここまで React についてかなり勉強してきたと思うけど、肝心な課題がまだ残ってる。それは外部

のサービス API との通信をどうするかということ」

「そうですよね。今のままではローカルなデータしか持てないので、Web アプリケーションとして一

般ユーザーの使用に耐えられるようなものが作れません」

「うん。React で外部との通信処理を行うときは JavaScript なので必然的に非同期処理になるわけだ

けど、通信を含むそういった副作用を生む非同期処理をどうやって書くかというのは React が公開さ

れたときから様々な方法が試されてきた。まずは大きく分けてふたつの方針に則った方法がある。

React のコンポーネント内で処理するもの方法、もうひとつは Redux のミドルウェアを利用する方法

だね」

「ふむふむ」

「コンポーネント内で処理するというのは、async/await などを用いて非同期通信処理を行う関数をま

ず作って、それをライフサイクルメソッドや Effect Hook、イベントハンドラなどの中で実行して、そ

の結果を Local State に格納して取り回すというのが代表的なやり方になるね」

「なるほど。それならこれまで勉強してきたことで想像つきますし、実際のコードもすぐに読み解け

そうです」

「うん、でもこの方法を採用してるケースは実際には少ないの」

「えっ、どうしてですか? 考え方が素直だし、シンプルに書けそうなのに」

「コンポーネントと通信処理が密結合になってるっていうことは、コンポーネントのコードが複雑に

入り組んでしまい、それが可読性やテスタビリティの低下につながりやすい。React 開発者は特にそ

のふたつを嫌うからね。さら他のコンポーネントからそのデータを取り回して再利用するのが難しか

ったり、アプリ全体で見たときにどこで通信が発生しているのか把握しづらかったり、といった問題

もある」

「……うーん、そう言われると問題な気もしてきました」

161
第11章 Reduxで非同期処理を扱う

「でも実はこの戦略の延長線上に、React 公式がロードマップに出している Suspense for Data Fetching*1

というものがあるんだよね。2019 年の中期にリリースされる予定になっていてまだ詳細は不明なんだ

けど、今挙げたようなポイントをクリアできないようなら、この技術も不発に終わりそうな気がして

る」

「例の、React 公式が出してくる技術の打率の低さですね……」

「いっぽうで開発コミュニティの多数派が今、支持しているのが Redux の非同期処理ミドルウェアを

使う方法なの。以前、Redux は単純な状態管理しか行わないけども拡張性に優れていて、その動作に

介入して様々なことをさせることができるミドルウェアが手軽に作れる仕組みがあるって話したよね」

「はい、おぼえてます」

「Redux 開発チームを含む複数の開発者から、副作用をともなう非同期処理を行うための Redux ミド

ルウェアがいくつもリリースされてるの。主なもので言うと Redux Thunk*2、Redux-Saga*3、

redux-observable 、Redux Promise Middleware といったところかな」


*4 *5

「わ、たくさんありますね」

「マイナーなものも含めるともっとあるよ。ちなみに4つのミドルウェアのダウンロード数を npm

trends で比較したグラフがこれね 」
*6

*1 https://reactjs.org/blog/2018/11/27/react-16-roadmap.html

*2 https://github.com/reduxjs/redux-thunk

*3 https://redux-saga.js.org/

*4 https://redux-observable.js.org/

*5 https://github.com/pburtchaell/redux-promise-middleware

*6 https://www.npmtrends.com/redux-thunk-vs-redux-saga-vs-redux-observable-vs-redux-promise-middleware

162
11-2. Redux Thunk vs. Redux-Saga

「おー、Redux Thunk が他を圧倒してますね」

「まあその理由についてはあとで説明するけど、メジャー度としては Thunk が一番でそれに Saga が

続き、Observable と Promise Middleware はその他大勢という感じだね」

「雪菜さんが使ってるのはどれなんですか?」

「最初は Thunk を使ってたけど、今は Saga を使ってる」

「へ―――、どうして乗り換えたんですか?」

「うん。じゃあ、Redux Thunk と Redux-Saga について、それぞれくわしく見ていこうか」

11-2. Redux Thunk vs. Redux-Saga

「Thunk と Saga について説明する前に、Redux の仕組みをおさらいしよう。下の図は Redux のワー

クフローモデルを表したものね。今回は Action と Action Creator をあえて分けて描いてみたよ」

163
第11章 Reduxで非同期処理を扱う

「View から Action Creator 経由で Action が発行され、それが Dispatcher によって割り振られ、

(prevState, action) => newState の純粋関数で表される Reducer によって Store の State が更新される

というのが、この図が示す内容になるわけだけど」

「はい、ここは一度やったのでだいじょうぶです」

「ここにミドルウェアである Redux Thunk を組み込むと、この図はこんなふうに変貌する」

164
11-2. Redux Thunk vs. Redux-Saga

「Redux においては Action Creator の役割というのは、単に Action を発行して Dispatcher に渡すこと

だけだった。でも Redux Thunk を使うと Action Creator の中に任意の処理、副作用をともなう非同

期通信などを描くことができるようになるの。さらに Action Creator がその中で別の Action Creator

をコールして他の Action を dispatch することもできる。それを表したのが上の図だね」

「前の図と比べて Action Creator が巨大化してますね。そこから API にアクセスしてる」

「Action Creator で何でもできるようになる、というのが Thunk の本質だからね。Redux Thunk の長

所と短所を列挙してみるとこんな感じになる」

Pros

・ シンプルなライブラリで、サイズも minify してわずか 352 バイトと小さい。

・ 概念が理解しやすく、学習コストも低い。

・ コード記述量も比較的少ない。

・ Redux 公式チームが開発しているため、メジャー感があって使用実績が圧倒的に多い。

165
第11章 Reduxで非同期処理を扱う

Cons

・ Action Creator が副作用を持ったり他の Action Creator をコールするなど、本来の Redux で

はひとつの Action Creator がひとつの Action を発行するだけだったあり方から大きく離れて

しまう。

・ Action Creator の実行中にリダイレクトしたり例外を投げるといったことができてしまう。

これは Props と Local State の変化のみが振る舞いを定義するという React コンポーネント

の本来のあり方を変えてしまう。

・ Action Creator が入れ子になるため、コールバック地獄に陥りコードの可読性が低くなりや

すい。

「私が Saga 派なので、ちょっとフェアな意見じゃないかもしれないけど、大きくまちがったことは言

ってないと思う。Redux Thunk の総評は、


『理解しやすく導入しやすいけど、放っておくとどこまで

もコードが汚くなる』と言ったところじゃないかな。そういえば前にも少しふれたよね。私の前の職

場で Redux Thunk を採用してたんだけど、チームメンバーの間の力量にばらつきがあって、気がつい

たら Action Creator がとんでもなくカオスになってたの」

「ひいっ」

「Thunk 派の人たち的には、
『気をつけてればそんなにコード汚くなんてならないよ』って言うんだ

けど、普通の人たちが集うチームだと『なるよ! なるから!』としか私は言えない。経験上ね。

あとこれはさらに意見が分かれそうだけど、React Way や Redux Way から大きく外れることにな

るのも、私としては嫌なところかな。Action Creator は純粋に Action を発行するだけでいてほしいし、

コンポーネントが Props や Local State の変化以外で挙動が変わるのも耐えがたい」

「あはは、そのへんはさすが雪菜さんらしいですね」

「だから Redux-Saga を理解したとき、


『これ! これを求めてたの!』って思ったよ。理解できるよ

うになるまでのハードルは多少高かったけど、その学習コストを払うだけの価値はあった」

「そんな雪菜さんがおすすめする Redux-Saga ってどんなライブラリなんですか?」

「Saga は既存の Redux の仕組みから独立したところで動くものなの。まず実行させたい副作用をと

もなう非同期処理を『タスク』として登録しておく。アプリが起動するとタスクの数だけ独立したス

レッドのようなものが立ち上がってスタンバイする。Action が発行されると Dispatcher は Store だけ

166
11-2. Redux Thunk vs. Redux-Saga

でなく Saga のスレッドにも Action を渡す。タスクは任意の Action が渡されるのをずっと待っていて、

該当する Action が渡されてきたら、タスクが起動してあらかじめ登録してあった処理を行い、その結

果を Action Creator に渡すというのが全体の流れになる」

「……うーん、ちょっとわからなかったです」

「さっきの図を Redux-Saga 用に書き換えると、こんな感じになるよ」

「えっ、登場人物多くないですか?」

「既存の Redux の仕組みからある程度独立して動くものだからね。でも右側 2/3 は元々の Redux の

図とほとんど同じでしょ?」

「確かに。ふーむ、こっちでは Dispatcher は受け取った Action を Reducer だけでなく、Saga の Watch

Task にもパススルーしてるんですね」

「そう。Watch Task はアプリ起動時からずっと Redux の仕組みとは独立したところで、お目当ての

Action が来ないかぐるぐる待ち構えてる。そしてお目当ての Action が来たら、該当するタスクを起こ

167
第11章 Reduxで非同期処理を扱う

して任意の処理を実行させるの」

「……なんとなくイメージがつかめてきました」

「この Redux の仕組みと独立して動いてる、っていうのは Redux のロゴと Redux-Saga のロゴを見比

べると、それが象徴されているのがよくわかるよ」

「ほんとだ。Redux のシステムと色分けされたところに Saga があって、独立した Saga が Redux に部

分的に絡んでるように見えますね」

「ちなみに Saga の考え方は Redux-Saga のオリジナルというわけではなくて、もともと『Saga パター

ン』というものがあって、Redux-Saga はそれを持ってきて応用したものなのね。Saga パターンとは、

1987 年に発表された論文 をもとに、主にマイクロサービスアーキテクチャの文脈で分散トランザク


*7

ションを実現するための解決策として提案されたデザインパターンのこと。複数のサービスにまたが

るリソースに対するローカルなトランザクション処理を、イベントをトリガーにして複数連ねて実践

していくものなの」

「さっきのタスクの話ですね」

*7 SAGAS https://www.cs.cornell.edu/andru/cs711/2002fa/reading/sagas.pdf

168
11-2. Redux Thunk vs. Redux-Saga

「この一連のワークフローのことを『Saga(物語、神話)
』と呼ぶ。また Saga は処理の進行状況を表

す状態と、各サービスが返すドメインのデータのふたつの状態を持つことも特徴になってる。後者は

Store State に相当するわけだけど、前者については Redux-Saga の実践の場面では Action Type に「開

始/成功/失敗」の3つの状態を持たせてこれを表現することになる」

「んんー、難しいですね」

「まあこのへんの説明は全部は理解する必要はなくて、バックグラウンドとして理解の助けになれば

いいかな程度だから。マイクロサービスの文脈から提案されただけあって、ドメインをまたがって複

数のサービス API を利用する SPA と親和性が高いっていうこと。

とりあえず今回も、さっきと同じように Saga の長所と短所をリストとして挙げておこうか」

Pros

・ 独立性が高く、既存の Redux の仕組みを崩さない。つまり書いたコードの可読性やテスタ

ビリティが高くなる。

・ 非同期処理を同期的に書くことができ、コールバック地獄に陥りにくい。

・ Redux DevTools との相性がよい。

・ 開発が活発。

Cons

・ 概念が理解しづらく、学習コストが高い。

・ 必要なコード記述量がどうしても多くなる。

・ タスクを表現するのに、ジェネレータや独特の DSL ぽい Effect という記述を使う必要があ

る。

・ ライブラリが巨大(Thunk の 352B に比べ、Saga は 14KB)


「Thunk の総評が『理解しやすく導入しやすいけど、放っておくとどこまでもコードが汚くなる』だ

ったけど、Saga は『綺麗に書けるけど、学習コストが高くてコード量も増える』といったところかな」

「まさに雪菜さんが好きそうな技術ですけど、難しいのは私は苦手です……」

「だーいじょうぶ。まーかせて。ちゃんと今からわかるように説明してあげるから」

「不安だなあ……。あ、ちょっと気になったのが『開発が活発』ってところなんですが」

169
第11章 Reduxで非同期処理を扱う

「Thunk は中身がシンプルだからその必要がないのかもしれないけど、中身は3年前からほぼ変更な

しでコントリビュータの数も 30 人ほど。いっぽうで Saga のコントリビュータは 270 人以上いて、コ

ードの更新も頻繁に行われてる。ちなみに GitHub スターの数では Thunk が 11,600、Saga が 17,000

(2019 年 3 月現在)と、Saga のほうが断然多いの。Issue や Fork の数だともっと差がついてる」

「へー、npm ダウンロード数では Redux Thunk が圧倒してるのにですか?」

「Saga は玄人受けするからなんだろうね。それだけ乗りこなし甲斐のある技術ってことだよ」

「うう、難しそうだけど、がんばってチャレンジしてみます!」

11-3. Redux-Saga を使いこなす

「まず Redux-Saga を使うために必要なライブラリはひとつだけ。TypeScript 用の型も含まれてるから

Definitly Types を別途入れる必要はないよ。既存の Redux を使ったアプリに導入したいときは yarn

add redux-saga 一発で OK。

じゃあ、サンプルコード をローカルに落として VSCode で開いておいて。それでターミナルから


*8

yarn と yarn start で実行」

「これは何でしょうか?」

「GitHub API から任意の Organization の公開メンバーを取得してきて、それを顔写真入りでリスト

表示しているものだね。API の制限によって1回のリクエストで 30 人までしか取得できないけど、

ページネーションとかにまで話が広がると混乱するので、とりあえずそこで区切ってる」

*8 https://github.com/oukayuka/ReactBeginnersBook-2.0/tree/master/11-async/03-saga

170
11-3. Redux-Sagaを使いこなす

「おお―――。データロード中のぐるぐるまで実装されていて、けっこう本格的ですね」

「ちなみに Organization 名は URL の http://localhost:3000/:companyName/members から取得してい

るので、そこだけ書き換えればどんな組織のメンバーも表示されるよ」

「ほんとだ! ウチの会社の人たちも表示されますね。雪菜さん、その中でもひときわ目を惹いてて

さすがです!」

「はいはい、そういうのは置いといてね。実際に開発する順番でファイルを見ていこうか。まずは

Presentational Component ね。src/components/Members.tsx を開いて」

171
第11章 Reduxで非同期処理を扱う

import React, { FC } from 'react';


import { Helmet } from 'react-helmet';
import { Card, Header, Image } from 'semantic-ui-react';
import capitalize from 'lodash/capitalize';

import { User } from '../services/github/models';


import Spinner from './Spinner';

import './Members.css';

export interface MembersProps {


companyName: string;
users: User[];
isLoading?: boolean;
}

const Member: FC<MembersProps> = ({


companyName = '< 会 社 名 >',
users = [],
isLoading = false,
}) => {
const title = `${capitalize(companyName)} の 開 発 メ ン バ ー `;

return (
<>
<Helmet>
<title>{title}</title>
</Helmet>
<div className="Members" data-test="users">
<Header as="h2">{title}</Header>
{isLoading ? (
<Spinner />
):(
<Card.Group>
{users.map(user => (
<Card
key={user.id}
href={`https://github.com/${user.login}`}
target="_blank"
>
<Card.Content>
<Image floated="right" size="mini" src={user.avatar_url} />
<Card.Header data-test="card-header">
{user.login}
</Card.Header>
<Card.Meta>GitHub ID: {user.id}</Card.Meta>
</Card.Content>
</Card>
))}

172
11-3. Redux-Sagaを使いこなす

</Card.Group>
)}
</div>
</>
);
};

export default Member;

「おさらいだけど、大事なのはこの時点で何を Props として定義するかということ。companyName と

users と isLoading を MembersProps として定義して、コンポーネント内部で動的に使ってるね」

「はい。このあとこのコンポーネントを Container Component でインポートして、これらの Props を

Redux の Store にマッピングするんでしたよね」

「そうそう、ちゃんと理解できてるね。今回は省くけど実際の開発では、ここの Props にダミーの値

を直書きで渡してあげて画面がちゃんと表示されるか確認するの。CSS とか見た目の調整もこのとき

にやるわけだけど、この段階ではまだ Container Component ができてないはずだから、コンポーネン

トをマウントしてる親コンポーネントの src/App.tsx では、こうしておく必要があるの」

- import Members from './containers/Members';


+ import Members from './components/Members';
《中略》
<Route path="/:companyName/members" component={Members} />

「なるほど、へ―――」

「じゃあ次は素の Redux 部分。Action の定義からいこうか。src/actions/githubConstants.ts を見て

みて」

export const GET_MEMBERS_START = 'GITHUB/GET_MEMBERS_START';


export const GET_MEMBERS_SUCCEED = 'GITHUB/GET_MEMBERS_SUCCEED';
export const GET_MEMBERS_FAIL = 'GITHUB/GET_MEMBERS_FAIL';

「これまでは Action Creator と同じファイル内で Action Type を定義してたけど、別ファイルに切り

173
第11章 Reduxで非同期処理を扱う

出してます。こうしておくと import * as ActionType from './actions/githubConstants'; みたいにま

とめてインポートできて扱いやすいからね。なお今回は、将来的に異なるドメインの複数のサービス

API を使うことを考慮して、値の頭に GITHUB/ ってプレフィックスをつけてます。

次は Action Creator 関数の定義にいこうか。src/actions/github.ts を開いて」

import { AxiosError } from 'axios';

import { User } from '../services/github/models';


import * as ActionType from './githubConstants';

interface GetMembersParams {
companyName: string;
}
interface GetMembersResult {
users: User[];
}

export const getMembers = {


start: (params: GetMembersParams) => ({
type: ActionType.GET_MEMBERS_START as typeof ActionType.GET_MEMBERS_START,
payload: params,
}),

succeed: (params: GetMembersParams, result: GetMembersResult) => ({


type: ActionType.GET_MEMBERS_SUCCEED as typeof ActionType.GET_MEMBERS_SUCCEED,
payload: { params, result },
}),

fail: (params: GetMembersParams, error: AxiosError) => ({


type: ActionType.GET_MEMBERS_FAIL as typeof ActionType.GET_MEMBERS_FAIL,
payload: { params, error },
error: true,
}),
};

export type GithubAction =


| ReturnType<typeof getMembers.start>
| ReturnType<typeof getMembers.succeed>
| ReturnType<typeof getMembers.fail>;

「ここでは Saga で非同期を扱うために、getMembers というひとつの仕事について「開始/成功/失

敗」の三つの状態を持った Action を返す、それぞれの Action Creator 関数を定義してる。わかりやす

174
11-3. Redux-Sagaを使いこなす

くするためにひとつのオブジェクトにまとめてて、実際にコールするときは getMembers.start()、

getMembers.succeed()、getMembers.fail() のように書くようになってる。これも typscript-fsa から借

用した書き方なんだけど」

「なるほど」

「 Action Creator 関 数 が 返 す Action の 型 は 、 最 後 に 型 推 論 で ま と め ち ゃ っ て る け ど 、

src/actions/index.d.ts でサジェストしているインターフェースを意識しといてね。で、次はこれに対

応する Reducer だね。src/reducer.ts を開いてみようか」

import { Reducer } from 'redux';


import { AxiosError } from 'axios';

import { GithubAction } from './actions/github';


import * as ActionType from './actions/githubConstants';
import { User } from './services/github/models';

export interface GithubState {


users: User[];
isLoading: boolean;
error?: AxiosError | null;
}

export const initialState: GithubState = {


users: [],
isLoading: false,
};

const githubReducer: Reducer<GithubState, GithubAction> = (


state: GithubState = initialState,
action: GithubAction,
): GithubState => {
switch (action.type) {
case ActionType.GET_MEMBERS_START:
return {
...state,
users: [],
isLoading: true,
};
case ActionType.GET_MEMBERS_SUCCEED:
return {
...state,
users: action.payload.result.users,
isLoading: false,
};

175
第11章 Reduxで非同期処理を扱う

case ActionType.GET_MEMBERS_FAIL:
return {
...state,
isLoading: false,
error: action.payload.error,
};
default: {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _: never = action;

return state;
}
}
};

export default githubReducer;

「ここはとりたてて説明しておかなきゃいけない点はないけど、Action Type の「開始/成功/失敗」

に合わせて返却する State のどこをどう変更しているかは注意して見ておいてね。

ここまでで素の Redux 部分については説明完了。次は Saga 特有の部分にいきたいけど、その前に

非同期通信処理を行っているところを見ておく必要があるね。src/services/github/api.ts を開いて」

import axios from 'axios';

import { User } from './models';

interface ApiConfig {
baseURL: string;
timeout: number;
}

const DEFAULT_API_CONFIG: ApiConfig = {


baseURL: 'https://api.github.com',
timeout: 7000,
};

export const getMembersFactory = (optionConfig?: ApiConfig) => {


const config = {
...DEFAULT_API_CONFIG,
...optionConfig,
};
const instance = axios.create(config);

const getMembers = async (organizationName: string) => {

176
11-3. Redux-Sagaを使いこなす

try {
const response = await instance.get(`/orgs/${organizationName}/members`);

if (response.status !== 200) {


throw new Error('Server Error');
}
const members: User[] = response.data;

return members;
} catch (err) {
throw err;
}
};

return getMembers;
};

「……こ、これは。以前教わった『クロージャ 』ってやつじゃないでしょうか?」
*9

「そうそう、よくおぼえてたね。今回はほぼ使わないんだけど、外から共通設定を与えた状態で API

リクエストを実行するという汎用的な作りにしたかったの。従来ならクラスを使ってコンストラクタ

の引数でやるところだけど、せっかくなのでクロージャで書いてみました。

あと、通信処理は JavaScript 標準の Fetch API*10 でもよかったんだけど、外部ライブラリの axios*11

が使い勝手がよさげでよく使われてるのを見るので、そっちを採用してます」

「getMembers() で実際にリクエストしてる URL は https://api.github.com/orgs/facebook/members

とかですね」

「うん、GET メソッドなのでブラウザで直接アクセスすれば、返ってくる JSON が見れるよ。ユー

ザーオブジェクトの配列が直に返ってきてるよね。あらかじめこの型を User インターフェースとして

services/github/models.ts で定義しておいて、axios のレスポンスデータから抽出したものをその配

列に格納、最終的にそれをリターンする関数を返してるの。実際の使い方はこんなふうになるかな」

const getMembers = getMembersFactory({ timeout: 3000 });

*9 「3-5. クロージャ」

*10 https://developer.mozilla.org/ja/docs/Web/API/Fetch_API/Using_Fetch

*11 https://github.com/axios/axios

177
第11章 Reduxで非同期処理を扱う

try {
const users = await getMembers('facebook');
} catch(err) {
console.log(err);
}

「ふーん、なるほど。さすが雪菜さん、綺麗な設計ですね!」

「まあ、このくらいはね。ちなみに async/await の使い方があやふやな場合は、ちゃんと復習しとい

てね。

それじゃ次、実際にこれを利用する Saga のタスクを見ていこうか。src/sagas/github.ts を開いて」

import { all, call, fork, put, takeLatest } from 'redux-saga/effects';

import * as Action from '../actions/githubConstants';


import { getMembers } from '../actions/github';
import { getMembersFactory } from '../services/github/api';

function* runGetMembers(action: ReturnType<typeof getMembers.start>) {


const { companyName } = action.payload;

try {
const api = getMembersFactory();
const users = yield call(api, companyName);

yield put(getMembers.succeed({ companyName }, { users }));


} catch (error) {
yield put(getMembers.fail({ companyName }, error));
}
}

export function* watchGetMembers() {


yield takeLatest(Action.GET_MEMBERS_START, runGetMembers);
}

export default function* rootSaga() {


yield all([fork(watchGetMembers)]);
}

きも
「ここが今回の一番の肝だね。ジェネレータと Effect と呼ばれる Saga 特有の DSL っぽい API を使

った書き方。でもとりあえず、その説明は後回しにしよう。

178
11-3. Redux-Sagaを使いこなす

上のファイルでは三つの関数が定義されてるよね。上から runGetMembers、 watchGetMembers、

rootSaga。rootSaga は最上位のタスクとなるもので、これを Saga ミドルウェアに渡すとアプリ起動時

に同時に起動される。そしてここで fork された分だけ別のタスクも立ち上がってスタンバイするの。

ここで起動されるのは watchGetMembers ひとつだけだけど、これが Action.GET_MEMBERS_START という

Action Type の Action を Dispatcher から渡されてこないか監視し続けることになる。そしてお目当て

の Action を受け取ったら runGetMembers を実行する、という手順になってるわけ」

「ふむふむ、流れはわかりました」

「そして各タスクが定義されている関数だけど。これが async/await ではなくジェネレータを使って書

くようになってるのは開発チームの信条 らしいんだけど、まあそういうもんだと思っておいて。あ
*12

と Effect API を使った書き方ね」

・ select …… Store State から必要なデータを取得する。

・ put …… Action Creator を実行して Action を dispatch する。

・ take …… 特定の Action を待ち受ける。

・ call …… 外部の非同期処理関数をコールする。

・ fork …… 自身とは別のスレッドを起動し、そこで特定のタスクを実行する。Task オブジェ

クトを返す。

・ join …… fork の戻り値の Task オブジェクトを指定して、そのタスクが完了するのを待つ

「もっとたくさんの API が用意されてるんだけど、よく使うのはこれくらいかな。くわしくは公式ド

キュメント を参照してね。これらを使う場合は必ず yield 文の中で書く必要があるのも気をつけて。


*13

take については実際に使うのは takeEvery か takeLatest のことが多いね。前者は渡された Action

の数だけ律儀にタスクを実行する。後者は以前に渡された Action のタスクが終了してなくて滞ってる

場合は、最新のものだけを実行するという違いね。今回は takeLatest を使ってる」

「なるほど、今回は F5 連打のリクエストに全て律儀に返す必要はないですからね」

*12 Redux Saga が async/await でなく generators を採択した理由


https://github.com/redux-saga/redux-saga/blob/master/README_ja.md

*13 https://redux-saga.js.org/docs/api/

179
第11章 Reduxで非同期処理を扱う

「うん。じゃあ肝心な処理を行っている runGetMembers の中身を順に見ていこうか。タスク関数には

引数として Action が渡されてくる。その型はここでも ReturnType で Actrion Creator の戻り値からの

型推論で設定しておこう。そうすることで Payload の中身から companyName が抽出できる。

そしてさっき見た API ハンドラを使用する部分ね。getMembersFactory() の戻り値関数を call で

companyName を引数として渡して実行すると、API リクエストが実行されて User オブジェクト配列が

取得できる。call で非同期処理関数を実行する場合、そのふたつめ以降の引数がその関数を実行する

際の引数になるの。

API リクエストが成功したら、最後に Action Creator の関数 getMembers.succeed() に必要なデータ

を引き渡して実行、Action を dispatch して完了ね」

「ちょっと独特な書き方ですけど、読めないほどじゃないですね」

「そうね。迷ったら API リファレンスを参照すればなんてことはないはず。それじゃ、Saga の仕上

げの部分。src/index.tsx を見てみよう」

import React from 'react';


import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { BrowserRouter } from 'react-router-dom';
import createSagaMiddleware from 'redux-saga';

import App from './App';


import reducer from './reducer';
import rootSaga from './sagas/github';
import * as serviceWorker from './serviceWorker';

import './index.css';
import './styles/semantic.min.css';

const sagaMiddleWare = createSagaMiddleware();


const store = createStore(reducer, applyMiddleware(sagaMiddleWare));

ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root') as HTMLElement,
);

180
11-3. Redux-Sagaを使いこなす

serviceWorker.unregister();
sagaMiddleWare.run(rootSaga);

「Redux の createStore() を実行するときのふたつめの引数に、applyMiddleware() に Saga のミドル

ウェアを渡してあげたものを設定すると、Saga ミドルウェアが有効になる。もちろん複数のミドルウ

ェアを組み込むこともできるよ。生成された Store を DOM ルートの <Provider> に Props として渡し

てあげると、Saga ミドルウェアが有効になった Redux の機能が使えるようになるわけ。

あと見逃しちゃいけないのが最後の行。sagaMiddleWare.run(rootSaga);。ここでさっき定義したル

ートタスクを渡してミドルウェアの run() を実行することで、Saga の監視スレッドが起動するの」

「あ、そういうことですね。だんだん見えてきました」

「よし次で最後、Container Component の説明に移ろう。src/containers/Members.tsx を開いて」

import React, { FC, useEffect } from 'react';


import { connect } from 'react-redux';
import { bindActionCreators, Dispatch } from 'redux';
import { RouteComponentProps, withRouter } from 'react-router';

import Members, { MembersProps } from '../components/Members';


import { User } from '../services/github/models';
import { GithubState } from '../reducer';
import { getMembers } from '../actions/github';

interface StateProps {
users: User[];
isLoading?: boolean;
}

interface DispatchProps {
getMembersStart: (companyName: string) => void;
}

type EnhancedMembersProps = MembersProps &


StateProps &
DispatchProps &
RouteComponentProps<{ companyName: string }>;

const mapStateToProps = (state: GithubState): StateProps => ({


users: state.users,
isLoading: state.isLoading,
});

181
第11章 Reduxで非同期処理を扱う

const mapDispatchToProps = (dispatch: Dispatch): DispatchProps =>


bindActionCreators(
{
getMembersStart: (companyName: string) =>
getMembers.start({ companyName }),
},
dispatch,
);

const MembersContainer: FC<EnhancedMembersProps> = ({


users,
isLoading,
getMembersStart,
match,
}) => {
const { companyName } = match.params;

useEffect(() => {
getMembersStart(companyName);
}, []);

return (
<Members companyName={companyName} users={users} isLoading={isLoading} />
);
};

export default withRouter(


connect(
mapStateToProps,
mapDispatchToProps,
)(MembersContainer),
);

「ここはもう、Saga だからどうというところはないよね。Props の users と isLoading にそれぞれの

Store State をマッピングして、getMembersStart() に Action Creator 関数の getMembers.start() を対

応させてる。そして Effect Hook でコンポーネントの初回のレンダリング時に getMembersStart()を実

行させてるわけ」

「……えーっと、withRouter と connect というふたつの HOC を利用するために、最初に Props の型

合成をしてるんですよね。そのとき、URL から companyName を抽出するために、RouteComponentProps

に型引数を渡してあげてる。それで最後にふたつの HOC を適用したコンポーネントをエクスポート

してると」

「うん、ちゃんと理解できてるね。よろしいよろしい。で、実際の開発のときに忘れるといけないの

182
11-3. Redux-Sagaを使いこなす

が 親 コ ン ポーネ ン ト で マ ウ ン ト してる の を Presentational Component か ら 今 見 た Container

Component に書き換えること」

- import Members from './components/Members';


+ import Members from './containers/Members';
《中略》
<Route path="/:companyName/members" component={Members} />

「私、よくこれを忘れがちで。全部適切に作ったはずなのに機能が動かなくて『何でかなー? 何で

かな−?」とおかしいと思われるところをずっと探し続ける羽目になるんだよね……」

「ふふふー、かわいい――」

「はい。んじゃ、これで Redux-Saga を使ったサンプルコードの説明は終わり。どうだった?」

「うーん、そうですねー。最初に散々おどされて覚悟してたんですが、やっぱり難しかったですねー。

でも雪菜さんの解説付きだったので、なんかわかったような気になっちゃってるのが自分でも怖いん

ですけど……」

「まあそれはそうだろうね。私だって最初は Saga、ちんぷんかんぷんだったし。検証と称して2日ほ

ど集中して公式のドキュメントやサンプルコードを読み込み、それでも TypeScript のサンプルはほと

んど見当たらなかったので型解決に悩まされながら、悪戦苦闘してようやく簡単なコードが書けるよ

うになったくらいだから」

「えっ、雪菜さんでもそんなだったんですね。じゃ、いきなり私が使いこなすなんて無理じゃないで

すか」

「あなたの場合はこうやって私がお膳立てしたものがあるんだから、話が別でしょ。いいなー、私も

こんな優しい先輩に教えてもらいたかったなー」

「……いや自分で『優しい先輩』って言っちゃってますよ? いや私も思ってますけどね、
『優しい

先輩』って」

「うふふー、ありがとう」

183
第11章 Reduxで非同期処理を扱う

184
エピローグ

「今日が約束の一週間の最終日だったね。おめでとう、予定してたカリキュラムを終えることができ

ました。感想はどう?」

「あっという間の一週間でしたけど、まさか本当に一週間でここまで来られるとは思ってませんでし

た。雪菜さんのおかげです。ありがとうございます!」

「ふふふ、言ったでしょ? 私、教えるのうまいって」

「本当そうですよね。それに単なるコードの読み書きのコツだけじゃなく、React を支える思想や歴

史とか、どんな周辺技術がどういう経緯で出現してコミュニティに受け入れられてきたのかとか、ど

んなライブラリを選定するべきかみたいなお話が聞けたのもよかったです」

「React は Angular や Vue みたいなオールインワンのフレームワークじゃないからね。本体は最小限

の View ライブラリでしかなくて、ちゃんとしたアプリを開発するためにはいくつもの周辺技術を組

み合わせる必要があるけど、最適解は日々移り変わっていてすぐ陳腐化してしまうから。それが React

の進化のダイナミズムを生む源泉になってるんだけど、初学者には優しくないよね」

「でも React の宣言的で関数型プログラミング主体なところ、ちゃんと理解できるとかっこよくシン

プルに書けるし、かなり大規模で複雑なアプリでもぐちゃぐちゃにならずに書けそうで、すごく気に

入りました」

「うん、秋谷さんが React のことを好きになってくれたようで何より。私もうれしいよ」

「えへへ。それで、来週からどうするんですか? もう私、実戦投入ですか?」

「そうだねー。でも実は、次のプロジェクト開始までに、まだ少し時間的に余裕があるんだよね」

「えっ、じゃあなんで最初にわざわざ1週間でって脅されたんですか私?」

「脅すなんて人聞きが悪い。まあ多少は危機感を持ってもらったほうが身が入るかなと思って、ちょ

っとはっぱをかけただけじゃない」

「ええ――、なんかだまされた気がします……」

「まあまあ。それで来週からだけど、もうちょっと現場の開発寄りに踏み込んだ内容のことをおぼえ

てもらおうかなって思ってるの。そのほうが実際の業務で開発に入ったときにスムーズだろうし。具

体的には、コンポーネントと CSS の折り合いの付け方とか、スタイルガイドの作り方とか、ユニット

185
エピローグ

テストや E2E テストといったあたりかな」

「そういう情報、あまり本とかに載ってないので、ぜひ教わりたいです!」

「うん、まあ楽しみにしといて。それより、ここまで一週間、本当におつかれさま。よくがんばった

ね。そのがんばりをねぎらってあげるから、いっしょに飲みに行かない? もちろん私のおごり」

「えっ、本当ですか?! 嬉しい―――! 雪菜さんのプライベートの話をいろいろ聞いちゃおうか

な」

「……秋谷さん、ほんとぐいぐい来るよね。まあ特に隠すようなこともないので、聞きたいことは話

してあげるよ」

「この業界、女性エンジニアが少ないじゃないですか。活躍してる人となるとなおさら。だから雪菜

さんがどうやって今みたいになれたのかとか、すごく興味があるんですよね」

「わかったわかった。就業時間もちょうどさっき終わったところだし、話の続きは会社を出てからね」

「はい、じゃあ行きましょう!」

186
あとがき

芝崎さんと秋谷さんのふたりの会話による React 解説、楽しんでいただけたでしょうか? 実はふ

たりにはストーリー小説が書けるくらい詳細なキャラクター設定があるのですが、基本的には芝崎さ

んは著者の分身、秋谷さんは React の初学者代表として書き分けているつもりです。ですが初版では、

「秋谷さん頭よすぎ。初学者なのにそんなにすんなり理解できるもの?」という声があったのも事実。

それについては、逐一さかのぼりすぎて説明すると話の流れを分断してしまったり、ある程度の経験

者にとってはそれが煩わしく感じられるだろうと思い、意図的に物わかりのいいキャラにして話をは

しょってしまっている自覚はありました。

第2版で書き直すに当たり、そういった部分もできるだけフォローしたつもりですが、それでも同

様の感想を持たれる方がいらっしゃるかもしれません。Web アプリケーション開発の経験のある方

でしたら、最初から通して読めば理解するために最低限必要な要素を網羅されており、また都度、外

部資料へのリンクを紹介しているので、理解が難しいと感じられたらぜひ最初のほうから戻って読み

直してください。

初版ではその思想への理解の少なさと敷居の高さからか国内で今ひとつコミュニティの盛り上がり

を見せない React の普及に貢献したいというのが執筆の一番の動機でした。反響の大きさから、その

目論見はある程度成功を収めた思います。そして第2版では、React のすぐれた最新の技術を現場に

スムーズに採り入れやすくするということも念頭に置いて書いてみました。特にリリースされたばか

りの Hooks とまだ Hooks インターフェースが未対応な Redux や React Router といったメジャーなライ

ブラリとの共存について、まだまだ採用例が少ない中で参考になる情報を提供できたのではないかと

思っています。

なお著者として今回の改版の一押しポイントは、表紙に芝崎さんのイラストが掲載されたことです。

イラストレーターの黒木めぐみ様とのご縁があり、著者の要求を幾重にも上回るすばらしい出来の芝

崎さんを描いていただけました。芝崎さんの理知的で、確固たる自信を感じさせる微笑みをたたえた

姿。ああ、なんて麗しい……。

内容についてのご意見・ご感想、またはご指摘などがあれば Twitter やブログなどで言及していた

187
あとがき

だけると、著者としてはとても励みになります。また、Twitter ハッシュタグ #りあクト をつけてツ

イートしていただくと、著者が必ず目を通します。イラストについてのご感想もお待ちしています。

それではまた、次の機会に。

188
著者紹介

大岡由佳(おおおか・ゆか)
フリーランスで現在、React を専門にするフロントエンドエンジニア。過去には PHP や Rails のサー

バーサイドエンジニアや、人材系サービスのプロダクトマネージャーなどの経歴を持つ。直近でのお

気に入りの漫画は『かげきしょうじょ!!』と『ブルーピリオド』
。二年ほど毎日続けているストレッチ

の成果で最近、360 °開脚と I 字バランスができるようになったのが自慢。Twitter アカウントは

@oukayuka。

黒木めぐみ(くろき・めぐみ) ◎表紙イラスト
漫画家、イラストレーター。

189
りあクト! TypeScript で始めるつらくない React 開発 第2版

2019 年 4 月 14 日 第 2 版第 1 刷発行

著者 大岡由佳

印刷・製本 日光企画

©くるみ割り書房 2019

You might also like