Webアプリケーションを開発する際に、「今ならSPA(Single Page Application)っしょ」となり、フレームワークの選定からやらせてもらう機会があったので、そのときにやったことを共有します。
前提
本記事作成時の筆者のスキル
- Webアプリケーションは作ったことがあるが、言語はJava
- SPA is 何?
- Javascriptはかじった程度の知識
フレームワークの選定
Googleトレンドや、実績からSPAのフレームワークを以下に絞りました。
- React
- AngularJS
- Vue.js
その時人気だったReactにしておけばいいだろと、Reactにしました。
※近年、Vue.jsが勢力を伸ばしてきているらしいです。
React + α
Reactは様々なライブラリと組み合わせるのが普通のようです。
npm installでどんどんライブラリを追加していくことになりますが、ここでは開発手法に大きく関わるライブラリだけ。
- TypeScript:Javascriptに静的型付けをしてくれる、AltJS。正直これ初心者には辛い。
- Redux:stateの管理方法に大きく関わるライブラリ
参考記事:https://qiita.com/mpyw/items/a816c6380219b1d5a3bf - @reduxjs/toolkit:ReduxのActinとReducerをまとめて記述できるツール。Reduxチーム公式。
参考記事:https://qiita.com/Ouvill/items/a76e9cbce569d01f2931 - redux-saga:タスクをうまいこと処理してくれる"ミドルウェア"という概念。APIリクエストなどの非同期処理で使用する。
参考記事:https://qiita.com/kuy/items/716affc808ebb3e1e8ac
※while(true)よりも、takeLatestをよく使いました。 - Material-UI:見た目をいい感じにしてくれるライブラリ。これは無くてもよかったかも。
- react-intl:メッセージ管理や多言語対応をできるようにするライブラリ
公式ドキュメント:https://github.com/formatjs/react-intl/blob/master/docs/README.md
執筆時点での最新バージョンはそれぞれ以下の通りです。あまり古くなっているようなら、参考にならないかもしれません。
プロダクト | version |
---|---|
React | 16.12.0 |
Redux | 4.0.5 |
TypeScript | 3.7.5 |
redux-saga | 1.1.3 |
Material-UI | 4.9.2 |
react-intl | 3.12.0 |
エディター
これも流行りなのでVSCodeを使用しました。
結果的に、nodeやgitのコマンドもターミナルのペインで実行できるので、かなり使いやすかったです。
設定と拡張機能をチーム内で共有する
エディタの設定は、プロジェクト直下の.vscode/settings.jsonというファイルで共有できます。
拡張機能は、.vscode/extensions.jsonというファイルを置くことで、VSCode起動時にポップアップが出るようになります。
フォーマッター、保存時の自動フォーマットなどの設定は共有しておくと便利です。
プロジェクトの作成
create-react-appというコマンドを使います。これを使うと起動スクリプトやwebpackのコンフィグなどが隠された状態で、カスタマイズできないので、すぐにejectというコマンドで素のnodeで扱えるようにします。
npx create-react-app my-app
cd my-app
npm run eject
プロジェクトの設定
ディレクトリ構成を考える
こんな感じになりしました。「これが正解!」というパターンは無いようなので、あくまで一例として。
ディレクトリ構成(クリックで開く)
<root>
├─public 画像ファイルなど。srcから見たとき、"/"がpublic直下
├─build ビルドされたファイルが作成される先
├─config create-react-appで勝手の作成される
├─scripts create-react-appで勝手の作成される+自作のnpmスクリプトを作る場合はここ
├─node_modules
└─src src以外のファイルはほとんど触らない
├─i18n メッセージのyamlと、それを管理するJS
├─modules actionとreducerをまとめたもの
├─containers 1画面1コンテナという考え方で、コンテナコンポーネントを作成する。
├─comopnents コンポーネント。画面ごとにサブディレクトリを切る
├─sagas 非同期処理を扱う処理
├─scss .scssファイル
└─utils 共通で使う処理。Validatorなど
importを絶対パス指定でできるようにする
これデフォルトじゃないんかい。。。と思いながら設定しました。
方法はいくつかあるようですが、TypeScriptを使っているのでtsconfigで設定できる方法を採用しました。
これをやらないと、"../../../../"地獄になります。
npm i --save tsconfig-paths
{
"compilerOptions": {
"baseUrl": "src"
~~~後略~~~
}
メッセージ管理方法を考える
yamlでメッセージ管理できるようにする
yaml-flat-loaderというライブラリを使用して、yamlをjsonとして読み込めるようにします。
npm i --save-dev yaml-flat-loader
{
test: /\.yml$/,
use: [{ loader: 'json-loader' }, { loader: 'yaml-flat-loader' }]
},
メッセージのキーを補完できるようにする
yamlでメッセージ管理する弊害として、コピペがし難いこともあり、補完できるように一手間加えました。
メッセージのキーをオブジェクトとして参照できるようにすることで、メッセージキーの変更やキーの指定ミスがコンパイルエラーになるというメリットもあります。
react-intlの正しい使い方をしているなら、こちらの記事が参考になりますが、JSでデフォルトメッセージを定義して、各言語はyamlというのは面倒です。
メッセージはyamlファイルだけで完結したいので、yamlファイルのキーをjsonにします。
スクリプトを作成し、messageKeysというオブジェクトが各ファイルから参照できるようにします。
ja.ymlに存在するキーをJSONにしています。
スクリプト詳細(クリックで開く)
npm install --save-dev js-yaml
'use strict';
var yaml = require('js-yaml');
var fs = require('fs');
// Get document, or throw exception on error
try {
let doc = yaml.safeLoad(fs.readFileSync('src/i18n/ja.yml', 'utf8'));
let str = "export const messageKeys =";
str += JSON.stringify(keyToValue("", doc));
str += ';\n';
str += "export default messageKeys;"
fs.writeFile("src/i18n/messageKeys.tsx", str, (err) => {
if (err) {
throw err;
}
});
} catch (e) {
console.log(e);
}
function keyToValue(parentKey, param) {
let ret = param;
for (let [key, value] of Object.entries(param)) {
if (typeof value === 'object') {
keyToValue(parentKey + key.toString() + ".", value);
} else if (typeof value === 'string') {
ret[key] = parentKey + key;
}
}
return ret;
}
"scripts": {
- "start": "node scripts/start.js",
+ "start": "node scripts/messageGen.js && node scripts/start.js",
+ "message": "node scripts/messageGen.js"
},
import messageKeys from "src/i18n/messageKeys";
~~~中略(コンポーネント内)~~~
const intl = useIntl();
intl.formattedMassage({ id: messageKeys.some.message.id });
"npm run message"でsrc/i18n/ja.ymlに存在するキーをjsonにしてくれます。
※エディタ上で補完できるようにするには、messageKeys.jsを一度開く必要がありました。
スタイリング方法を考える
こちらの記事を参考に、"1. クラス名によるスタイリング"と"3. CSS Modules"で悩みましたが、CSS(SCSS)によるスタイリングを採用しました。
理由は、
- 業務ロジックやイベント処理を記述しているコンポーネントファイルに、見た目のことまで書きたくない
- 普通のSCSSの方が書ける人が多い
- ぶっちゃけCSS Moduleよくわからなかった
あたりです。
ただし、これには以下のデメリットがあります。
- 普通にscssをimportすると、Reactはstyleタグを生成するので影響範囲は全体ということになる
- 上記動作により、複数回importされると無駄なstyleタグが増えることになる
よって、コンポーネントのrootとなる要素にclassを設定し、その下にネストしたSCSSを書くようにしました。
(結局は開発者の運用努力なので、改善すべき部分ではあります。)
結び
まだまだ色々なことをやった(ような気がします)が、以上が開発開始時の"進め方"を決める段階で時間をかけて考えた部分になります。
これからReactの開発を始める際の参考になれば幸いです。