初めてReactをプロダクションで採用した時に役立ったこと、情報収集についての思い出


React を使ったプロダクトが形になるまで、知識不足やメンタル面でぶつかったこと、解決方法などを覚書として記事にしました。新規プロダクトで使用したので過去のしがらみや技術的な負債はなく、技術選定の自由度は高い状態です。

この記事では当時ぶつかったこと、悩んだことや行ったことを極力そのまま書いておこうと思います。解説記事ではなく思い出要素の強い記事です。

フロントを動作させるサーバーに node.js は必要ない

開発に node.js を使用しているだけで出来上がったファイルの動作に node.js は必要ありません。ビルドした時に作成される JavaScript はブラウザで動く JavaScript ファイルなので、サーバーサイドレンダリングしないならサーバー側に node.js は必要ありません。普通の JavaScript や jQuery が動いているなら同じ環境で動きます。

html と JavaScript さえ動けば良いので普通のレンタルサーバーでも動きます。プロダクション環境に必要なのは build したファイルのみで、開発時に使用している jsxやpackage.jsonといった ファイルは実環境には必要ありません。

パッケージマネージャー

npm と yarn の選択肢がありどちらを使っても問題ありません。npmは古いバージョンの時に速度面で yarn と比較して遅いというデメリットがありましたが、現在では速度改善されているらしくyarn と遜色ないという人もいます。yarn には 固有の機能としてworkspace という機能があります。

他の人が npm 使っているプロジェクトなら npm、特に決まっていないならわたしは yarn を使います。プロジェクトですでに使用されている方を使います。

React の選択肢

自身で環境設定を作り開発を進めていくのもひとつの手段ですが、Webpack関連の知識を持った人がいないと茨の道です。また、ReactはSPA意外にも選択肢があります。大きな選定基準として SEO が必要かどうかで、SEO が必要なら静的ファイルを生成する Gatsby や、サーバーサイドレンダリングを可能にする Next.js が候補になります。

  • Next.js
  • Gatsby

SEO が必要ないかそれほど重要ではなく、SPA で問題ないなら CreateReactApp をベースにしてSPA開発ができます。

SPA と SEO

Google のクローラーが最新の chrome になり、SPA 環境でも Google では SEO の問題は解決されているらしいです。しかし、通常の静的サイトと同じタイミングでインデックスされるかどうか、Google 以外の検索エンジンはどうかが分かりません。

なので、高い更新性と SEO の強さを求められるなら SPA の採用はベストではなく、現状はサーバーサイドレンダリングするか Gatsby のような静的なファイルを生成する方式をとります。SEO の専門ではないので断定はできませんが、SPA での SEO を完全に信頼するのはまだ速いという印象です。

ベースに CreateReactApp を使う

自身のプロジェクトでは SEO が必要なかったのでベースは CreateReactApp で作成しました。TypeScript や storybook など人気ライブラリと簡単に連携可能。開発環境について考える手間が大きく減ります。

build 設定など変更する時は eject せずにreact-app-rewiredを使い変更しました。ejectするならCreateReactApp使わない方が後の人のためにも良いかもしれません。

Typescript 採用

勢いのある JavaScript のスーパーセット。最初とても難しく感じますが、型がわからないところはとりあえず anyで使っても十分恩恵があり、使っているうちに any は減ってきます。変数を扱う時にでる型情報を元にしたヒント、将来的に API とやりとりするが、すぐには用意できない時のモック作成など、型情報に助けられる部分は大きかったです。

現状気になっているのは、ファイル数の増加とともにリロード時の読み込みやエラーの表示に時間がかかるようになりました。これは設定で解消できるのか、PC の性能なのか分かりません。

import SampleLibrary from 'sample-library'; // 型情報求められる
const SampleLibrary = require('sample-library'); //求められない

ライブラリ使用するかの検討で、モックレベルでライブラリを試すことがあると思います。その時に@types に型がない場合は import ではなく require でまず試すとスムーズでした。型のことは実際に使うことになってから考えます。

TypeScript はどうしてもコード量が増えてしまうものの、それ以上のメリットはありました。

storybook の難点

開発初期に導入したもののうまく廻らず、保守が面倒になって放置されるコンポネントが増え、ストーリーを作るコストとメリットが開発に与えるメリットを超えていないと判断して今回の開発では途中で外しました。

storybookが採用できるかは、取れる工数と開発のフェーズによって違いがありそうです。少人数でとにかく開発スピード優先で実装している時はあまり重要視されずメリットも少なく、規模が大きくなり人数が増えてくるあたりでは威力を発揮するように感じます。

React, Redux に翻弄される日々

Redux は React を使う上で非常に便利なライブラリであり、非常に頭を悩ませるライブラリでもあります。というのも、みんな使ってるからという理由で採用されることが増えた結果、「採用の前に、Redux が必要かをまず冷静に考えよう!」という注意書きが生まれるくらいで、Redux 不要論を唱える人もいます。また、React に hooks が正式導入されたことでまた混乱がありました。

Redux の導入にはかなり悩み、色々な記事や公式を読み直しました。

Dan Abramov 氏の解説記事で React 自体の理解も深めつつ、Redux 公式の Motivation と Core Concepts、Basic Tutorial をなんども読みました。Redux の動画もあります

Getting Started with Redux

検討を続けても Redux のふわつきは大きくなる一方で悩みがちな日々となりました。悩ぶ度、Redux 公式の Basic Tutorial をなぞりました。

情報を日々集めるていると、自分以外もReact や Redux に混乱している人は多いと感じるようになりました。本質的に正解かも分からない多くの React, Redux 記事に翻弄されるのは良くないので、信頼できる情報源を絞り込むのが重要だと考えました。色々なところから情報を集めるのをやめて、React や Redux 公式、そしてそのメンテナ達が流してくれる情報だけを信頼して採用するかを決めることにしました。

情報の集め方

公式のドキュメントは最重要です。これはどこにでも書いてあることですが実践するのはとても難しく油断するとすぐに読まなくなります。難しい…Qiita の方がわかりやすい気がする、目的の情報に辿り着けない、などなど公式を読み続ける精神的なハードルは高めです。

しかし、結果的には公式に書いてあることで解決することがたくさんありました。また、React, Redux 公式の他に、下記の人の情報は信頼して追いかけていました。

Dan Abramov 氏は React チームの1人で質が高く分かりやすい情報を積極的に流してくれます。React の情報を追いかける時外すことはできません。

Mark Erikson 氏は Redux のメンテナで、やはり質の高い解説を色々な場所で流し込んでくれます。github issue でのドキュメント整備関連でも積極的に活動されているため、まだドキュメントが少ない Redux + React hooksの方向性をみたいときに特に助かりました。

Kent C.Dodds 氏は主にテスト関連で情報を多く出してくれます。テスト以外でも React の使い方を色々と解説されているので参考になります。

他にも素晴らしい情報を出してくれている人はたくさんいるとは思いますが、情報過多になるといけないのでまずは上記3人と公式ドキュメントに絞りました。信頼する情報をベースにして他の情報を読むと混乱は小さくなります。これら以外は絶対に信じない!ということではありません。stackoverflowは普通に使います。

折り合いをつける

質の高い情報をたくさん仕入れたらベストなコードを書けるかといえば書けません。逆に、良いものに触れ過ぎて自分のコードに悲観してしまったり、多すぎるリファクタリング、思い悩んで手が止まる原因になることもありました

この対策は、今の実力を認めて期日内に必要な機能をどうすれば実装できるかを考えてコード書くしかありません。もっとも重要なのは必要な機能を期日までに実装して製品にすることです、ベストプラクティスから外れていたとしても正しく動くならひとまず良しです。少なくとも公式をなぞっていれば大きく道を外れていることはないはずで、その自信を作るためにも公式ドキュメントを読み、信頼できる情報ソースを持っておくのは大切だと思います。

また、解決が難しく、動いてはいるが後々問題が出るのでは?と感じる箇所は出てきます。それらは隠さずに issue でもプロジェクト管理系の何かでも良いので社内に情報を共有。将来経緯を追いかけやすく、誰かが解決策を持っているかもしれません。

結果的にRedux 導入

上記を経て 最終的に Redux 導入しました。どうしても結論ありきなところは否めませんが、色々試作している過程で以下の状況で大きなメリットを感じました。

  • データの加工が多い
  • データ構造が複雑になりがち
  • データの反映、保存、破棄操作がある

例えば、データを画面上で加工して保存する処理があるとします。Redux を使わない場合、データを加工するロジック、データを保存するロジックを親のコンポネントで定義し、それを props で子のコンポネントに渡します。この段階だと問題ありません。後々、保存したときに画面の右下にお知らせがポップアップする、失敗したときに別のポップアップが発生する、別画面で共通するデータの一部が保存されるといった機能など、後付の改良で親子関係が苦しくなるケースがありました。

UI の親子関係とデータ関連のロジックを Redux で分離できるとそれぞれ独立して機能を付与できるので親子関係に悩まされにくくなります。特に、React hooks 対応後の React-Redux なら従来の mapStateToProps や mapDispatchToProps を作らずに直接 import して Redux 管理のデータの読み込み、アクションの dispatch が可能です。これはとにかく便利でした。

import React from "react";
import { useDispatch, useSelector } from "react-redux";

const Sample = () => {
  const dispatch = useDispatch();
  const sampleState = useSelector(state => state.sample);

  // 省略
};

hooks の動向とベストプラクティスの充実次第では Redux なしの方が良くなる可能性はありますが、現状はあった方が便利で後々の改良にも見通しが良かったです。また、redux-devtools を導入することで

  • データ操作の失敗がわかりやすい
  • 想定したタイミングで正しい回数 API 叩けているかを確認しやすい
  • 想定外の action が起きていないかわかる
  • 不具合時の状況再現がしやすい

など、データ操作や意図した操作をきっちり行えているか確かめるのに重宝しました。問題が起きたとき、問題が表示ロジックにあるのか、データ操作にあるのかを切り分けやすく、不具合を起こした時の state を読み込めば同じ状況を再現できます。検証中はローカルにエラー時の state を json に保存して読み込む形にしていました。

また、後日談ですがReduxを導入したことでstateの更新ロジックがチームで揃いやすくなったり、state関連の理解が浅い段階でもdispatchすれば上手く行くということで開発参加のハードルが下がったように思いました。個々のコンポネントでstateロジックを書くのが中心だと実装者毎に書き方に癖があり、UIとロジックが密になってあとで困るケースはありました。これもhooksが上手く使えていれば問題なかったのかもしれませんけど。

Redux のフォルダ構成

通常の action,reducer などでフォルダきるパターンや、一箇所に集める duck パターン、それを改良した re-duck など色々な方式があります。今回は TypeScript 導入していたので公式の下記プロジェクトの構成を参考にしました。

Usage with TypeScript

このフォルダの切り方で引っかかっているのは、action と reducer が 1 対 1 になりやすく、共通の action が書きにくいということでした。1 つの action で複数の reducer 動かす時に action をどこに置くかが難しく、結局雰囲気で一番わかりやすそうなところの action に書くことにしましたが明確にルール化できてない部分です。このあたりは勉強が必要な課題になりました。

ライブラリの選定

機能を自分で作るか、ライブラリを使うかは悩ましい問題です。React, Redux 以外のものは以下の基準で選びました。大抵のライブラリはgithubで管理されているのでgithub上で情報を集めます。

  • ライブラリの枠組みが大きすぎないこと(学習コスト高めのものは極力避ける)
  • 企業が保守あるいは使用しているなど長期的に保守される可能性が高いもの
  • release や update が 1 年以内にどれくらいされているか、issue や pull request が停滞していないか

star の数は重要な指標ではありますが公開日が古いほど有利な要素でもあります。star 数だけでなく、React のバージョンアップに合わせて動きがあるかも重視しました。また、メインのロジックに絡む部分は可能なら極力ライブラリを使わないようにしました。

バックエンドとの連携

API 先が準備できていない時や設計中の時は 返答 される(はず)の json を用意してローカルから読み込んだりしました。また、開発と本番でAPI先を切り分けたり、エラーの実装を変えたい時は NODE_ENV が便利です。

process.env.NODE_ENV === "development";

ただし、上手く細かく切り分けないと開発、あるいは本番でしか起きない問題に困ることがありました。

IEの対応

CreateReactAppを使用している場合、初期の状態ではIE や Edge のpolyfillは読み込まれていません。これらブラウザは polyfill が必要になります。CreateReactApp なら独自の polyfil がありますのでそちらをまずは検討します。react-app-polyfillなど。

polyfilは最終のビルドファイルのサイズに大きく影響を与えるので、とりあえず読み込んでおくという考えは良くありません。対応するブラウザによりけりです。

build した先が相対パスかどうか

開発がひと段落して yarn build などした後。設置場所によってはファイルの読み込みを相対パスにしたいことがあります。その時は package.json に homepage を追加します

  "version": "0.1.0",
  "homepage": ".",

サーバーのgzip対応

buildするとターミナルにビルドファイルのサイズが表示されますが、実際に出力されたファイルはそれよりもかなり大きいファイルサイズのはずです。表示されたサイズは、サーバーからgzipという圧縮形式で配信された時のサイズを想定しています。

gzipはサーバー側の設定が必要なのでフロントエンドではどうこうできません。実際に設置してみてResponse Headersに

content-encoding: gzip

となっていれば読み込みサイズがbuild時に表示されたものと同じになっているはずです。SSL環境は必須。圧縮配信されないとReactのプロジェクトは厳しいのでサーバー設定は忘れずに。一番初期のHello worldでも130kbくらいあります、gzipで40kbくらい。これよりも容量を下げるならpreactなどより軽量なライブラリを使うことになります。