私がReactを始める前に知っておけば良かった事

Hiroki Kaneko
19 min readDec 2, 2018

--

Photo by Andrea Reiman on Unsplash

11月はReact漬けの一ヶ月だった。アプリのすべての処理をApp.jsに書いて、とりあえず動きますのバージョンを作った後に、全てを一つのコンポーネントに詰め込んでいるのはReactの流儀的には間違っているのでは、と感じたので、画面を分割してコンポーネントごとに分け、クラスごとのファイルにした。これが大変で、まず本人がReactの流儀というものが分かっていなかったから試行錯誤だったし、私は2015年頃にReactが流行りだした頃にブログなどで概要を一瞥して、翌年ぐらいにオライリーから出版された「Reactビギナーズガイド」のチュートリアルを8割程度やってみただけで、それでも当初からの疑問であった「上位コンポーネントからデータが流れてくるだけの一方通行で、本当に大規模アプリケーションなど作れるのだろうか?」は解消しなかった。

というか、自分のWebアプリ開発に対する考えというのが従来のサーバー側で全てを用意するものだ、という考えがこびりついており、「Webアプリ開発とは、DBからデータを取ってきてHTMLタグ内に入れ込むだけの簡単なお仕事だ」の考えを止めないとReactの仕組みは理解できないと感じた。

“ReactがJavaScriptでDOMと画面を生成するのは分かったけど、HTMLはどのサーバサイド言語が動的に生成するの?”

これは誤解であり、まさかUIの描画はすべてWebブラウザ側のJavaScript(React)でやってしまい、HTMLなんてindex.htmlのみ、サーバ側といえばJSファイルのホスティングと、あとは表示するデータなどは動的に取ってくるでしょうから、それはその都度、どこか同じか別のサーバに問い合わせる、という事に気づくのにずいぶん時間がかかった。上記「Reactビギナーズガイド」は、サーバとの通信に関して何も記述がないので困ってしまう。
フレームワークの流儀に関しては、従来の「動的HTMLを生成するためのサーバサイド言語」の考えから脱却しなければならない、というのは上記の本には載っていなくて苦労した。クライアントだけで動くからサーバーレスです、みたいな事を言われても、いやいや、ファイルはどこかしらに置かなければならないでしょう、と疑問に思っていたのですが、昔「CGIスクリプト」と漠然と呼んでいた、サーバーサイドのビジネスロジック…ああいうのを指して「サーバー(サイドのビジネスロジック)レス」と読んでいるのだな、と気づいた。サーバー上のデータを読み書きするのなら、REST APIサーバなりmBaaSなりがあればよいと。

しかしチュートリアルを消化した程度では自分はReactを使ってWebアプリを作れるぞという気がせず、血肉化していないというか、やはり自分である程度オリジナルのWebアプリを作ってみないと本当の理解は出来ないと思うし、アインシュタインだったか、「自分は天才ではなく人より多くの時間、同じ事について考えていただけだ」というような意味の事を言っていたけど、一つの技能を習得するという事は、本を読んだりチュートリアルをこなして「ふーん」と思っただけでは身につかず、少なくとも私は、一つの事柄について、疑問があり、それについて長い間考え続けて、試行錯誤、その上の成功体験がないと身に付いた気にならない。それが「勉強」というやつなのだろうと、この年になって実感する。

「勉強」とは、就業時間でも電車の中でも歩いているときも家に居ても、ずっと設計・実装上の問題について考えることである、とそのように思える。悩み続ける。ジタバタする。あがき続ける。
考え続けていれば、コンピューターが手元にはない電車の中で急に答えが出ることもある:「ああ、{this.props.children}はRuby on Railsで言えばyieldか!」など。それは電車の中で下らないスマホゲームなどせず、参考書もなしに、頭の中だけで考え続けて自力で答えに辿り着いた快感は、電車で横に座っているネーチャンが遊んでいるツムツムなんて問題にならない程の達成感なのだけど。

Reactを企業で導入するなら、まずReactの流儀を理解しているコンサルタントを招いて設計段階の打ち合わせをするべきだ。画面を紙に印刷してからコンポーネントをペンで囲みながら要素を分解する。最小単位までコンポーネントに分ける、という工程が必要だと思っている。従来式のWebアプリ開発に慣れた人だけでReactアプリ開発チームを作ったとしたら、最初の数ヶ月は相当苦労するのでは。

私の試行錯誤は以下のようなものだった:

  • まずは全部App.jsに書いて、画面に表示されるところまで実装する。動的に表示する部分は、その都度ReactDOM.render()で描画する。このやり方は良くないのだけど、コンサルタントもいないし自分はReactの流儀を知る前なのだから仕方がない。顧客はとりあえず動くMVP(最低限の機能実装)を見せろと言っているのだ。時間がない。
  • 一枚岩のApp.jsからコンポーネントを切り出す。クラス単位にまとめて別ファイルに保存し、import文で読み込む。この辺りのコンポーネント化は、Reactの機能というよりモダンJavaScriptのおかげで部品化される。一枚岩Web Appでは、コンポーネント間のI/Fなぞ全く考えていなかったので、この工程に時間が掛かる。
  • 孫世代以上の階層になってくると、親であるAppから情報を引数のバケツリレー式で渡すことに疲れてくるので、別の方法を探す。引数の書き方は{…this.props}としてすべての引数を省略できる事を知り、記述の煩雑さが少しマシになる。React v16.3からのComponent APIを使えばグローバルストアが実現すると知るが、コンポーネントをReactDOM.render()で動的に表示していたため、情報を受け取ることが出来ない。Componetn APIを使うには、情報を渡したい先の子コンポーネントがrender()メソッド内に静的に書かれている必要があるようだ。親コンポーネントのrender()メソッド内に<Provider>タグで情報を書き、このタグ内に子がいなければ、子コンポーネントのrender()メソッド内の<Consumer>タグから情報を取り出す(macOSだったか、Producer/Consumerモデルというのがあってずっと’Producer’と書いて意味不明のエラーが出たりした)。
    ここでReactDOM.render()を廃止するリファクタリングを行う。
  • JavaScriptを使って非同期にデータを取得する時に起こるコールバック地獄は、Promiseを経てES2017規格のasync/awaitを使うことで一応は解決する。const response = await fetch(...);これで非同期コードを入れ子なしに書けている。ここでfetch()は非同期関数だが、戻り値がresponseに代入されるまで処理が次の行へ行かない。

この程度の事を理解するだけで一ヶ月掛かってしまった。要領の良い人なら、二週間ぐらいかもしれない。この辺りの、実際にコーディングしてみて浮かび上がる疑問とその答えというのは本には書かれておらず、日本語で書かれている記事もネット上にはあまりなく、Reactは日本で流行っていないような気がしてくる。上記「Reactビギナーズガイド」では解決しなかった疑問については、以下のようなものがあった。

  • 静的にコンポーネントを配置する、といってもフォームの項目が動的に生成される必要がある時はどうするの?
    →この辺が参考になるかと。
  • stateのメンバーが単なる文字列や数値ではなく、もっと複雑なデータ構造、たとえば配列でその中の1要素を更新したい場合はどうするの?

→配列をシャローコピー。i番目の要素を編集してからsetState()であらためて再代入。

const arrayCopy = this.state.array;
arrayCopy[i] = "newValue";
this.setState({array: arrayCopy});

Reactは恐らく、「普段はPHPを使ってWordPressのカスタマイズしかしないデザイン系で、ちょっとJavaScriptもいじります」みたいな人は使いこなすことは無理だと感じた。Reactを習得すること…関数型の考え方と、リアクティブ・プログラミングに慣れること…は困難だがチャンスでもある。このフレームワークの使い方を試行錯誤して、自分の中で理解が深まればこの先5年、10年単位での飯のタネになるかもしれない。Reactを習得することは、AIだブロックチェーンだと、よく知りもしない癖に詐欺師たちが騒ぎ立てるバズワードよりも余程「地に足の着いた」技術だと感じる。5,10年後の主流Web AppフレームワークがReactかどうかは分からないけど、それは関数型やリアクティブ・プログラミングの影響を受けた何者かであろう可能性が高いと思うからだ。その頃AIは「第三次AIの冬」になって予算も大幅に削られていると思うし(自動運転技術は、”画像認識”とかもう少し具体的な名前になって続くと思う)、ブロックチェーンは、水道メーター調査員の職を奪うだけの技術になっていると私は予測する。「バンドで一発当てる」なんて夢見てるんじゃねえ、自動車整備士の資格を取れ、というわけである。
更に、React NativeによるiOS/Androidアプリ開発では、この2つのプラットフォーム向けアプリケーションを別々に作っていたのではコストが2倍掛ると思っているクライアントには訴求できるかもしれない。もっとも、最近ではReact Nativeの限界、みたいなものが明るみに出ていて、大手IT企業の中には、React Native辞めます、みたいなところも出てきているけど、まあいい。そういう所にはFlutterじゃ駄目なのか。2012年のDart言語登場からずっと、どこかのタイミングでDartを使ってみたいと思っているのだけど…。

つまり、ここ40年間のプログラミングの方法論の変化、構造化→オブジェクト指向→関数型→リアクティブ・プログラミングの流れは、CがC++になった時にオブジェクト指向プログラミングの考えについて行けずに多くの人が脱落したのと同じくらいの技術の断絶があるのではないかと思っている。昔、アセンブラ言語に親しんだエンジニアは、Cぐらいまでは「高級アセンブラ」として付いてこられたが、C++で導入されたオブジェクト指向の考え方を理解できなかった。同様に、21世紀初頭のJava guyとして生きてきたソフトウェア・エンジニア(私じゃないか)は、Java 8から導入された関数型の考え方に苦戦しているのだろうか。

この10年の間の関数型言語ブームで、どんなものかと関数型言語に触れた方もいると思われるが、私などは「手続きが無く、全てが関数だけでどうやってコンピュータに実用的な仕事をさせるのだろうか?」と不思議に思ったし、この疑問はReactに対する疑問「コンポーネントの情報が一方通行で実用的なWebアプリが作れるのだろうか?」と同じである。
今まで染み付いた手続き型の考え方を変えるのは容易なことではない。外国語を習得するようなものだ。普段使わない脳の部分を使うわけで、とても疲れる。

ここまで読んで、私が「関数型は手続き型よりも優れたアイディアである」と言いたいかのような感想を持たれた方もいるだろうが、逆である。実装に不必要な複雑化を持ち込んでいるだけのような気がする。少なくとも、小規模開発の場合は。

上記のコミックでは、棒人間が「関数型の悟り」を開いて「これ(Lisp)こそ神が宇宙を記述した言語だ」と思うのだが、神は「表向きはそうだが、途中から面倒になってPerlで書いた」と言っている。優れたアイディアであっても、それのみを強制されるのは窮屈である事をこのコミックは示唆している。
関数型の開発を強制するReactに対し、関数型のアイディアを持ち込みつつもそれを無理強いしないVueがGitHubのスター数でReactを追い越したのは偶然ではないと感じる。

昔、JavaでDBMSを扱うアプリケーションを書いていた時、ビジネスロジックはというと、DBを操作する生のSQLがあり、それに変数を代入するテンプレート言語があり、更にはDBのレコードをJavaの世界で表現するDTO、DBを操作するDAO、更にそれらを利用するビジネスロジックが…それが嫌ならO/Rマッパーを使うか、それとも10年以上前に流行ったDIか?
これら技術は簡単な事をわざわざ難しくしようとして、非効率極まりない。RDBMS/SQLだって、この方が効率が良いんだ、数学なのだ。1+1=2に疑念を抱くとは何と馬鹿なのか(※2)、と世の常識人達は言うのだろうけど、Javaで非効率なシステムを高コストで作っている一方、スタートアップ企業たちはスクリプト言語を使用して、遥かに効率の良いシステムを作ってきた姿を横目で見つつ、しかし私はその非効率な設計、実装、商習慣、制度にオマンマを食わせてもらって来たという負い目がある。年度末に予算を消化するためだけに無駄に道路を掘り起こす公共工事だって、それで首の皮一枚つながって来年度も生き延びられる工事業者だってあるだろう(※1)。だから私はReactを支持する。この非効率的な複雑さで、私に明日の飯を食わせてほしい。「SES滅ぶべし」と自社ブログで声高に叫ぶ、元大阪府知事のような、「今でしょ!」という講師のような、ズルそうな奴らに共通の顔立ちをしたアイテー社長もいるが、仮に「SESが滅んだ」として、その後で私を雇用していただけるのですかね?綺麗事だけで世の中回ると思っているのだったら、とんだ世間知らずだ。これこそが地球温暖化対策が進まない理由だというのに、まだ分からないのか。
関数型の理論的な背景は、頭の良い方に任せる。

(※1)それが税金の無駄遣いだ、というのはまた別の話で、富の再配分とは、という政治哲学の問題になる。

(※2)RDBMSの数学的根拠は集合論なのでベン図が分かれば計算の話にはならない。ここで1+1=2を出したのは例として不適当

Reactは、その名前からしてリアクティブ・プログラミングと関係があるのだろうかと思ってリアクティブ・プログラミングを調べたけど、具体的にはこのようなものらしい:例えばA→B→Cというグラフがあり、A,Bそれぞれが値を持ち、Cで合計値を表示する時、A,Bの値が変わると自動的にCに合計値が「描画」される。プログラマーは描画のタイミングに関しては感知せず、値を変更することだけ考えれば良いと。

これはExcel等スプレッドシートの関数=SUM(A1..A3)の時、A1からA3のいずれかのセルの値が変更されたら自動的にSUM()を定義しているセルに計算結果が出るという仕組みに似ている。互換性のためにReactDOM.render()関数はあるものの、プログラマーは描画のタイミングを自分で決めてはいけない。コンポーネントを動的に配置してしまうと継承関係が分からなくなってしまうので値の変更が伝播されなくなるからだ。

最初に、広く認知されていて、解決すべき問題として、「Webブラウザの表示速度が遅い」という現状があり、10年くらい前はAjaxで画面の部分リロードがその解決策、とされていた。今は更にDOMコンポーネント単位で描画の更新をしたい、という時に、Facebookの優秀なエンジニアはアメリカのトップ大学の更に大学院だかで関数型言語の教育を受けており、いや「教育」というと受け身な感じがするので、「Haskellのコードを血反吐を吐くほど読み込んだ末に手にした博士号を持ってFacebookへ入社しており(※2)」その素養を以て問題に取り組めば、このような解決法へたどり着くのは不自然ではないと。

(※2)私の勝手な想像です。Reactの作者はElm界の有名人なのでしたっけ?

更に大規模アプリケーション開発において、構造化の各種仕組みを持たなかったJavaScriptでは、すぐにコードがメンテナンス不可能になってしまう問題もあり、これはES2015以降の、class構文や、import文によって部品化をしつつ、更にこれ以上分解できないくらい小さなReact Componentに分けて、そのクラス、関数単位でテストコードも用意すれば、ポール・グレアムの言う「レンガの一つ一つがしっかりしていれば、壁もしっかり立つだろう」というわけだ。そこで役立つのが冪等性(べきとうせい)というやつで、例えば関数の中に、実行するたびに異なる値があってはいけないのだ。例えば日付を取得するnew Date() などがそうだ。これをやってしまうとまず、テストコードが書けない。期待する値(日付)が、テストを実施した日時によって変わってしまうからだ。これを防ぐには、日付データは関数の外に追い出して、引数として関数内に取り込むようにすればテストコードから期待値を制御できる。こんな事、「冪等性」などという読み方すら分からない難解な言葉を持ち出さなくても、関数型などという概念がなくても、勘の良い人は昔からやっていたことだ。

「業務アプリケーションの目標は、昔も今も<コードの交換可能な部品化(※3)>」であり、その目標のための手段が構造化だろうがオブジェクト指向だろうが関数型だろうがリアクティブだろうが大した違いはないし、関数型に関して言えば半世紀前から存在するアイディアが、ここ10年で「再発見」されただけであり、オブジェクト指向も未来のいつか、誰かによって「再発見」されるだろう(※4)。そのような概念を意識せずとも、優れたソフトウェア・エンジニアならCで書いてもオブジェクト指向的であったし、Java 8以下で書いても関数型的であった。

(※3)これもポール・グレアムの言葉

(※4)ドメイン駆動設計(DDD)はオブジェクト指向の再発見と言えないだろうか?

しかしここ10年の関数型に対する「リバイバル・ブーム」は、以前話題になった”How it feels to learn JavaScript in 2016”からも分かる:

-No one does at the beginning. Look, you just need to know that functional programming is better than OOP and that’s what we should be using in 2016.

初心者はみんなそんなもんだよ。なぁ、君は関数型プログラミングがOOPよりも優れていることを知っておく必要があるし、それを2016年では使うべきなんだよ。

日本語訳は【翻訳】 2016年にJavaScriptを学んでどう感じたか

たとえこの記事が冗談だったとしても笑えない。というのは、オブジェクト指向なるものが流行り始めた1980年代、それは関数型より新しい、関数型では解けない問題を解決する「より優れたもの」として期待されていたからだ。
もし本当に“関数型プログラミングがOOPよりも優れている”のだとしたら、関数型言語であるCommon Lispにオブジェクト指向プログラミングフレームワークであるCommon Lisp Object Systemが後付けで標準化された理由を説明できない。尚、Lispは関数型言語ではないという純血主義者の戯言は無視する。1990年頃も「純粋オブジェクト指向設計とはどういうものか」を巡って下らない論争があったけど、また同じような事を言っているのか。「土地の古老」は当時のことを覚えているから、「純粋関数型」とは何かについての下らない論争を見ると、1990年当時のデジャヴュだと感じる。

論争したいなら勝手にどうぞ。私はただ、「ナンボ儲かンねんソレで!?」と言うだけなので…。

--

--

Hiroki Kaneko

底辺ソフトウェア・エンジニア。私はわたしの考えていることを誰かに知ってほしいと思っている。