Webエンジニアの備忘録

およそ自己の作業メモとして残しております。

お手軽React開発のテンプレを作りました

SPAを最速で作り始めたい!

結果的にはこちらを作りました。 よければ使ってください。

react-template

github.com

セールスポイント

  • 取り込んで2コマンドで動きます。
npm i
npm run start
  • typescript / eslint / prettier とオススメの作法を詰めておきました。
  • importパスのエイリアスがすでに設定してあります。
  • CSSも読めるようにしておきました。
  • キャッシュバスターも入れておきました。

なぜ作ったか?

前回に続き、初学者向けの内容です。

▼前回はこちら

tak-taniguchi.hatenablog.com

Reactと言えば、SPA(だと思っている)

静的アセットのホスティングのみで動作しPWAへの展開も迅速におこなえる、何より開発がシンプルで、BFFによる債務分割でUI/UXに集中できるフロントエンドエンジニアとして最高の開発環境がSPAではないでしょうか?

開発着手に時間がかかるReact SPA

前述の通り、SPA自体は作業が始まれば非常に手が早く動く開発手法かと思いますが、Reactにおけるその環境構築はなかなか手がかかります。

よいフレームワークが見当たらない

React自体はレンダリング部に注力したパッケージであることが利点ですが、書き始めにWebpack等トランスパイルの環境を整えていく必要があるため、初学者はまずここで心が折れます。

最近ではNext.jsなども流行っており、もはやReact公式のチュートリアルもこちらに頼ってきているのですが、こちらはSPAを書くのには正直適していませんでした。

Next.jsの強みはSPAではない

  • 静的コンテンツの生成が可能
    • 例えばブログなど、ビルドタイミングでAPIを実行し、JSファイルにコンテンツごと書き出すことができます。
  • SSRに向いている
    • サーバーサイドから情報を取得してレンダリングに反映させる、といった挙動が可能です。
    • Next.js自体がホスティングサービスであるVercelへの誘導ビジネスのツールであるがゆえの特性と思われます。
  • Amazon Amplifyでも動きますが、Next.jsだと「サーバーサイドレンダリング」というオプションが勝手に付きます。

Amplify Hosting を使用したサーバーサイドレンダリングアプリをデプロイ - AWS Amplifyホスティング

今回のテンプレについて

今回は書き始めるときの作法だけ統一できればと思い、React開発でよく使う多くのパッケージを割愛しました。なので、おまけとしてこちらでいくつかのパッケージを紹介しておこうと思います。

今回含まれないメジャーどころ

自身がReact開発をおこなう際、ほぼ以下のようなパッケージを用いています。用途次第ですが、知れば開発しやすさが変わるものばかりなので、列挙しておきます。

styled-components

導入難易度: 低

styled-components.com

ReactのJSX構文でのスタイル指定はlowerCamel化したオブジェクト書式に書き換えが必要です。こちらを取り込むと、コンポーネント内にCSS表記をそのままおこなえるので、ブラウザやデザインツールからのCSSコピーが捗ります。

// style="color: #333333; font-size: 16px; font-weight: bold"

const style = {
  color: '#333333',
  fontSize: 16,
  fontWeight: 'bold',
}

react-router

導入難易度: 中

reactrouter.com

画面遷移を再現するパッケージです。 内部的にはコンポーネントの表示・非表示をURLと紐つけて管理してくれるような仕組みになっており、ブラウザヒストリーの管理も併せて行ってくれます。

react-redux / redux-saga

導入難易度: 高

react-redux.js.org

Reactのstateはコンポーネント内で完結してしまいますが、react-reduxは横断で参照できるstoreを用意し、ページを跨いで状態管理を可能にすることができます。

グローバルなstateを持てる、くらいの感覚です。

redux-saga.js.org

上記とredux-sagaを併せて用いることで、state更新に関わるアクションを非同期、または並列に実行します。

例えば、以下のような連続的な作業を簡単に書くことができます。

- ローダーを表示する
- API①を実行する
- API②を実行する
- API①、②ともに成功したらstoreを更新する
- ローダーを閉じる

総括

Reactはフレームワークではありません。Next.jsのような一揃いのフレームワークに比べて初期導入が困難になりがちではあると思います。

ただし債務が明瞭なおかげで、ルーティングやグローバルの状態管理、それぞれのアップデートやテストについても個別で検討することができ、慣れれば技術追従が非常に楽になってくると思います。

今回は最低限の雛形を用意することで唯一の難点であった導入ハードルを下げることができれば、と思い記事を書きました。

またアップデート要望や記事への指摘などあれば、コメントいただければと思います。

javascript初学者向けメモ(2022年)

今回の記事について

私の周囲で他レイヤーからフロント開発へ幅を広げる方が増えていることもあり、こちらの記事を残しておこうと思います。

自身もサーバーサイドからフロントエンドへ転身した経験を持っており、javascriptを触れ始めたとき特異に感じた箇所がいくつかありました。

今回は他言語経験者が学ぶ上での差異をポイントにまとめておこうと思います。

書き方

構文についての知識は体系的に得る以外ではググりづらいものが多いので、その点も踏まえて要点をまとめておきます。

アロー変数

  • functionを書かずに即時実行しないfunc型を定義する。
    • 最近は他の言語でもよく目にするかもしれません。
const getSum = (a, b) => {
  return a + b;
};
const c = getSum(1, 2);
console.log(c);
// 3
// 上の省略形、暗黙のreturn
const getSum = (a, b) => a + b;
  • 関数型を即時実行することもできる。
// 関数型の即時実行
const c = ((a, b) => a + b)(1, 2);

分割代入

  • オブジェクトをキー別変数に直接定義する。
    • 関数内で引数の展開に使われることが多い。
const func = ({ name, age }) => `${name} is ${age} years old.`;

const user = {
  name: 'Taro',
  age: 18,
}
func(user);
// 'Taro is 18 years old.'

ショートハンド

  • オブジェクトへの代入の際、キー名と同じ変数名を省略できる。
    • 初見でびっくりするが、慣れると読み書きしやすいので推奨。
const name = 'Taro';
const age = 18;

const user = {
   name,
   age,
}
console.log(user);
// { name: 'Taro', age: 18 }

スプレッド構文

  • 配列やオブジェクトを上の階層に展開する。
    • オブジェクトのマージや値更新に利用すると便利です。
    • 配列をメソッド引数として並べることも可能。
const user = {
  name: 'Taro',
  age: 18,
}
const contacts = {
  email: 'taro@example.com',
  tel: '080-0000-0000',
}

const newAge = 19;

const updatedUser = {
  ...user,
  ...contacts,
  age: newAge,
}
console.log(updatedUser);
// {name: 'Taro', age: 19, email: 'taro@example.com', tel: '080-0000-0000'}

オプショナルチェーン

  • オブジェクトの展開時、値の有無を検証する。
    • 型の検証に便利です。
const contacts = {
  email: 'taro@example.com',
  tel: '080-0000-0000',
}
console.log(contacts.address.prefecture);
// TypeError

console.log(contacts.address?.prefecture);
// undefined(オプショナルチェーンによる参照)

静的解析・自動整形

静的解析や自動整形は初学者こそ導入すべきと感じています。

動作の正誤はプログラムの実行で確認できますが、作法や品質は学びづらいものです。それを正してくれるのが静的解析や自動整形になります。

eslint

eslint.org

Javascriptの静的解析ツールで、ルールに従って構文が書かれているかを確認してくれます。ルールについては個々に手動設定もできますが、プラグインで広まっているものを導入するのが良いと思います。

例えば、Reactを用いたプロダクトであれば eslint-plugin-react などを用いるのが通例です。

github.com

例えば、以下のようなことは書き始める前に気になることかと思います。

  • インデントはスペース2文字で揃える。
  • オブジェクトキー名はキャメルで定義する。
  • 型一致まで確認する、厳密等価演算子(===)が推奨される。

VSCode等でリアルタイム解析をおこなえば、コーディングしながら学べます。

prettier

prettier.io

自動整形をおこなってくれます。

こちらはかなり恣意的な作法を強いられますが、prettierが作法であると定めることによって、コーディング規約における派閥論理を抑え込む作用があります。

例えば改行ルールなど、人間がおこなうと可読性を考えてルールがおろそかになりがちですが、prettierを用いるとガッチリと揃えられます。

フロントエンドエンジニアの中でも好き嫌いが分かれるかと思いますが、導入することでメンバー間の規約差異を仲裁してもらえると思えば有益です。

総括

ここに記載した内容は私自身が業務経験で培ってきたものなので、人によっては異なる作法を持っている場合もあるかと思います。

ただ、私の薦める方向性は一貫して 「マジョリティを見つけてそれに倣う」 ということです。

前半に記載した「書き方」については多くのコードを読めるための技術であり、後半の「静的解析・自動整形」は他人と同じように書く技術になります。

javascriptはおそらく最もコードに溢れた言語なので、良否を見極めるために多く読み、多く書くことが上達の秘訣だと感じています。

React v18への更新を試みる

React v18が正式リリース!

React v18がnpmで更新できるようになったようです。

reactjs.org

手元でv17の環境を持っていたので、こちらを最新のものに更新してみました。

何も考えずに更新してみる

普段、パッケージ更新に関しては npm-check-updates という更新確認ライブラリを用いています。

今回も慣例に従ってこちらを実施してみました。

% ncu -u // 最新パッケージの確認
Using yarn
Upgrading /Users/ttaniguchi/product/package.json
[====================] 48/48 100%

 react        ^17.0.2  →  ^18.0.0     
 react-dom    ^17.0.2  →  ^18.0.0     

Run yarn install to install new versions.

% yarn install

   :

success Saved lockfile.
✨  Done in 2.79s.

そのままビルドしたところ問題なく動きました。

v17への更新時、いくつかのライフサイクルが廃止となりましたが、v16からの更新過程にwarningで通知してくれていました。おそらく今回も、日頃から警告に準じた対応をしていれば極小の修正で済むようになっていたのではないかと思います。

(そういえば、いつの間にかライフサイクルなんて使わなくなってました…)

一点、更新後に以下の警告が表示されていました。

f:id:tak_taniguchi:20220331235850j:plain

Warning: ReactDOM.render is no longer supported in React 18. Use createRoot instead. Until you switch to the new API, your app will behave as if it's running React 17. Learn more https://reactjs.org/link/switch-to-createroot

ReactDOM.renderはReact18では推奨されなくなり、代わりに createRoot を使用するようになったようです。 とはいえ、新しいAPIに切り替えるまではReact17と同様に振る舞います。

廃位互換も素晴らしいです。

警告箇所の対応

こちらを修正しないと更新の恩恵にはあずかれないようなので対応します。警告に出たとおり、こちらに書かれている内容を取り込めば大丈夫です。

reactjs.org

修正前

import { render } from 'react-dom'; // 廃止になりました

  :

render(
  <Component />
  document.getElementById('app'),
);

修正後

import { createRoot } from 'react-dom/client'; // 今後はこちらを使う

  :

const container = document.getElementById('app') as HTMLElement; // tsの場合は型を断言しておく
const root = createRoot(container);
root.render(
  <Component />
);

以上の対処で警告は消えました。

GitHubの記述にもありますが、こちらは平行モード実装のためのリプレイスであるとのこと。 rootという概念でDOMを掴み続け、改めて呼び出すことなく再描画等の処理が実施できるようです。

今回の更新では createRoot と併せて hydrateRoot が描画コンテナを再利用するために提供されています。

総括

今回はアップデートまでやってみましたが、また別で新しい機能を調べてみたいと思います。

v18では平行モードにレンダリングや、SSRを視野に入れた更新が含まれるそうです。(サーバーコンポーネントは開発中とありますが) html上はSPAとしてはエントリーのrenderを書いたらおおよそ更新しない〜というのがザラでしたが、描画要素の分散やMPAによる構成管理など視野に入り、今後はhtmlが主戦場になっていくかもしれませんね。(原点回帰感ある)

いずれにしてもv18の敷居は低いので、早々に更新しておくと良さそうです!

トラックボールマウス「ロジクール Logicool M575」を購入しました

トラックボールマウスを買い替えました

長らく愛用していたM570ですが最近ボールの反応が鈍くなってきており、ついに後継のM575に乗り換えました。

左がM570、右がM575でサイズや重量はほとんど変わりません。 f:id:tak_taniguchi:20220312232411j:plain

使用感は上々

ボールの操作感ですが、M570は経年劣化もあり、ボールがツルツルしてうまくカーソルが留まらないことがおおかったのですが、M575は実に的確にブレーキが働く、止めたいところに吸い付くような感じで、早くも手放せない、やみつきになりました。

唯一、最悪だった点(克服済み)

ここだけメモっておきたくてブログを更新しています。 Logi Options に1点だけ機能欠如がありました…

M570 M575
f:id:tak_taniguchi:20220312233154j:plain f:id:tak_taniguchi:20220312233157j:plain

おわかりいただけただろうか?

そう、なぜかスクロール方向オプションが消えていました。

個人的に、トラックパッドは「ナチュラル(iPhoneなどと同じ)」、マウスホイールは「スタンダード(Windowsなどと同じ)」にスクロール方向をカスタマイズして使っています。

これがMacだとパッド・マススホイールで区別なく同じ方向にしか設定できません。 Logi Optionsで切り替えることで今までストレスなく利用していたのにあんまりです。

ググりまくって見つけた解決策

pilotmoon.com

Scroll ReverserというMac向けアプリを導入することで対応できるという記事を見つけました。

幸いにも無料です。(寄付を受け付けているので、利用される方は検討ください!)

f:id:tak_taniguchi:20220312234510j:plain

設定はこんな感じで思い通りのスクロール方向を設定できました。

最高だった点

f:id:tak_taniguchi:20220312234855j:plain

カスタマイズ可能なボタン数は同じものの、設定値にジェスチャーボタン」が追加されていました。 ボタンとボールとの動作を合わせて、ひとつのボタンに4つの操作をあてがうことができる新機能になります。

垂直・水平スクロールをボールでできるのが最高!

ホイールを左右に倒すことで水平スクロールができる「チルト操作」については上位機である「MX ERGO」のみとなっており諦めていましたが、「ジェスチャーボタン」>「パン」をあてがうことで、この操作をよりよい形で実現することができました!

ハードウェアの品質に感動しながらも、最も喜憂させられたのはソフトウェア側だったな、というオチです。

総括

M570はロングセラーだったため、後継機がデグレしていないか心配でしたが、更に上を行くデバイスに進化していました。 今回記載したスクロール方向の設定については公式にも改善をお願いしたい点ではありますが、それを補っても買い替えメリットは大きいかと思いました。

フロントエンドエンジニアがCloudFrontを構築してみた

SPAを自分でホスティングしたい

AWSはまだ初学レベルですが、Amplifyによるホスティングを通してSPAを簡単に公開できることに味をしめていました。

次のステップとして、もう少しコストを抑えた一般的な構成を学びたいと思っていましたが、ちょうど業務でS3・CloudFrontを用いての実装を試すことができたので、備忘録がてら記録しておきます。

使用したAWSサービス

  • S3
  • CloudFront

CloudFrontの疎通

S3バケットを作成する

まずはSPAをホスティングするバケットを用意します。

公開用バケットということで、ここでパブリックアクセスを触ってしまいそうになりますが、今回はCloudFrontで配信を行うため、まだ権限はいじりません。

f:id:tak_taniguchi:20220226011127j:plain
S3バケット作成

ファイルをアップしておきます。 (適当な index.html ファイルでOK)

<html>
<head>
  <meta charset="utf-8"/>
</head>  

<body>
テストページ
</body>

</html>

f:id:tak_taniguchi:20220226012920j:plain
SPAファイルのアップロード

CloudFrontの設定

次は本丸のCloudFrontの設定です。

S3を配信するための設定をおこなっていきます。

ディストリビューションの設定には大まかに以下の項目が含まれます。

オリジン

呼び元となるリソース、今回はS3となる。

  • 「オリジンドメイン」にS3を選択する。(プルダウンで選択可)
    • S3内のディレクトリ下を指定する場合は「オリジンパス」も指定する。こちらは空白可。
  • 任意の「名前」を入力。(プルダウン選択時に自動入力される)
  • S3バケットアクセスにて、OAIを使用する方を選択する。
    • こちらを選択した上で表示される、「新しいOAIを作成」ボタンを押下します。
    • また、バケットポリシーを自動で更新するようチェックを入れておきます。
      • 先ほどバケットの権限設定を飛ばしましたが、ここで自動的に設定が可能です。

f:id:tak_taniguchi:20220226020103j:plain

ビヘイビア

CloudFrontにアクセスがあった際のルーティングごとの振る舞い。 ここで指定のルートにS3を割り当てる。

  • 設定は任意ですが、HTTPへのアクセスをHTTPSに転送してくれるのが便利なので設定しました。
  • また、S3へのアクセスはGET、HEADに限定するのが安全です。

f:id:tak_taniguchi:20220226014820j:plain

キャッシュキーとオリジンリクエス

キャッシュを有効にさせる設定や、ルーティング解決時の関数設定などがおこなえる。

  • 今回はキャッシュの設定のみおこないます。
  • ChachingOptimaizedがS3では推奨されています。
    • ファイル更新時にキャッシュクリアの処理が必要になります。

f:id:tak_taniguchi:20220226014822j:plain

設定

最後に、設定という項目下を確認します。

  • 「デフォルトルートオブジェクト」にindex.htmlを設定します。
  • こちらを忘れると、ドメイン直下が疎通しません。

f:id:tak_taniguchi:20220226020742j:plain

動作確認

作成が完了すると、以下のようなメニューが表示されます。

f:id:tak_taniguchi:20220226021712j:plain

こちらが実際のURLの実行結果になります。

疎通成功です!

f:id:tak_taniguchi:20220226022103j:plain

おまけ

キャッシュ削除

S3ファイルを更新した際、「キャッシュ削除」の実施により迅速な確認がおこなえます。

  • 全部のキャッシュをクリアしたい場合は /* を入力。
    • 実行後、若干の待ち時間があります。

f:id:tak_taniguchi:20220226021714j:plain

所感

意外にも、CloudFrontを用いることでバケット設定をいじるよりもシンプルに公開ができたように思えます。

AWSへの接し方としては単独のサービスで解決しようとせず、適切な組み合わせを把握した上で実装することが重要に感じました。

独自ドメインの紐付け、NotFoundルーティングへの対応などもここでおこなえるので、そちらも追々書き留めていこうと思います。

StorybookをWebpack5で使う

StorybookにDefinePluginを導入、すると…?

webpack では DefinePluginをプラグインを誰しも使っていると思います。

真新しい環境にStorybookを取り込んだ際、これが動かない事態に直面しました。
.storybook/main.js の中身はこんな感じ。

module.exports = {
  "stories": [
    :
  ],

    :

  webpackFinal: async config => {
    config.plugins.push(

        :

      new webpack.DefinePlugin({
        NODE_ENV: JSON.stringify('test'),
      }),
    );

    return config;
  },
};

Storybookで見つけた不穏なエラー

問題のエラーがこちら。

yarn storybook
yarn run v1.22.10
$ start-storybook -p 6006
info @storybook/react v6.3.7
info
info => Loading presets
info => Loading 1 config file in "/Users/taniguchi/project/.storybook"
info => Loading 9 other files in "/Users/taniguchi/project/.storybook"
info => Adding stories defined in "/Users/taniguchi/project/.storybook/main.js"
info => Using PostCSS preset with postcss@7.0.36
info => Using default Webpack4 setup
0% compilingERR! TypeError: Cannot read property 'get' of undefined

ちなみに、記事を書き始めるまで気づかなかったのですが(汗)、この時点で不具合の答えが出ていました…

info => Using default Webpack4 setup

構築環境は最新のWebpack5を導入していました。

Webpack5向け対応が必要

Storybookは基底でWebpack4を想定したビルドシステムを保持しており、これが直近バージョンでは刷新されていませんでした。
その解決に、Add-onを利用しており、つまるところ以下のパッケージを取り込み main.js に設定を加筆すればなんとか動くということがわかりました。

storybook.js.org

Storybook Webpack 5 · GitHub

対応

以下のパッケージを追加

yarn add -D @storybook/builder-webpack5 @storybook/manager-webpack5

main.js を修正

module.exports = {
    "stories": [
      :
    ],
  
      :
  
+   core: {
+     builder: 'webpack5',
+   },
    webpackFinal: async config => {
      config.plugins.push(
  
          :
  
        new webpack.DefinePlugin({
          NODE_ENV: JSON.stringify('test'),
        }),
      );
  
      return config;
    },
};

総括

今回のミスはもともと知見のあったパッケージをメジャーアップデートの際、ドキュメントを見忘れていた点でした。新規プロダクトで更新の意識も薄く、さらに悪いことにStorybookがなんとなく動いていたことも災いしました。

公式ドキュメントの確認、大切ですね〜

componentWillMountをReactフックで書く方法を考えた

componentWillMountの今…

v16以前、componentWillMountというライフサイクルイベントがありました。(過去形…)
v17以降は非推奨となり、UNSAFE_componentWillMount と名称変更されています。

嫌われるcomponentWillMount

v16以前からReactを利用しているプロジェクトではしばしばReactフックへの移行の話が上がり、componentWillMountの対応方法について求めるコメントが上がっています。ただし、解決方法については有識者からは冴えない返答が散見されます。

  • Reactフックを使えばconstructorはいらないよ!
  • useEffectを使えばcomponentDidMount相応の挙動だ。(ただしレンダー後に実行)
  • そもそもcomponentWillMountを使うな。

言い回しは変わっていますが、すべて3つ目の「使うな」の言い換えになっています。
有識者側の言い分としては、実装を無くすよう検討してくれということなのです。

非推奨な理由を考察

そもそもComponentのレンダー前にやっている処理はComponentに関係ない処理である可能性が高いです。
何かしら計算が必要であれば計算後の値をpropsに送ればよいし、レンダーに関わる処理はComponent呼び出し前に済まされているのが理想的です。

おそらく、v16以前はすべてをComponentで行う志向が強く、ライフサイクルにすべての処理を内包するのが作法だったかのように思えます。
(本当はもっと前からですが)v17ではパラダイムシフトが起きており、債務を絞った、より軽微なComponentを目指すのがトレンドになってきました。

非推奨でも使いたい

設計思想の変化は理解しつつも、すでに動いているサービスの改修は別問題です。 useMemoを利用するのがシンプルかと思います。

const Componnet = ({ updateTrigger }) => {
  const key = useMemo(() => {
    // ここに前処理を書ける
    return updateTrigger;
  }, [updateTrigger]);

  return (
    <div key={key} />
  );
};

値を返す特性上、レンダリングより早く動作し、propsを外部更新のトリガーにすればcomponentWillUpdateの代替としても利用できます。 本当に初期実行時にしか動作しないのであれば、useStateを実行済みフラグなどとして利用する手段も浮かびましたが、こちらのほうがキレイかと思いました。


フィードバックなどあれば、コメントいただければ嬉しいです!