<?xml version="1.0" encoding="UTF-8"?><?xml-stylesheet href="/pretty-feed-v3.xsl" type="text/xsl"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>開発関連技術 | Change Of Pace</title><description>気分転換の遊び場</description><link>https://changeofpace.site</link><item><title>ポートフォリオサイトをTanStack Startでリプレースしたので振り返ってみよう</title><link>https://changeofpace.site/posts/2026-02-23-portfolio-replace-tanstack</link><guid isPermaLink="true">https://changeofpace.site/posts/2026-02-23-portfolio-replace-tanstack</guid><description>Docusaurus -&gt; TanStack Startにリプレースしたので、これまでを振り返って感想などをつぶやいてみる</description><pubDate>Mon, 23 Feb 2026 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前からリプレースしたいと言っていたポートフォリオサイトをようやくリプレースできたので、振り返りや感想などを軽くつぶやいてみます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;ポートフォリオサイトの履歴&lt;/h2&gt;
&lt;p&gt;ざっとポートフォリオサイトの履歴を振り返ってみます。&lt;/p&gt;
&lt;h3&gt;起源&lt;/h3&gt;
&lt;p&gt;はじめてポートフォリオサイトを作ったのが2020年頃。&lt;/p&gt;
&lt;p&gt;当時、日々の勉強メモを記録する TIL リポジトリを更新していっており、その派生でポートフォリオサイトを作ったのでした。
採用していた Docusaurus だとドキュメントを書くのに適していたため、TIL の記録をドキュメントのように公開してみようと思っていたのです。
そうすれば勉強の状況も他者に示せたりするかなぁと考えたこともあり。&lt;/p&gt;
&lt;h3&gt;TIL の移行&lt;/h3&gt;
&lt;p&gt;TIL 活動を続けているうちに、だんだんと Notion で書いていった方が気軽に書きやすいのでは？という考えが浮かび。
リポジトリで管理となるとどうしても都度コミットする必要があるので、その手間がだんだんと気になってきたのでした。&lt;/p&gt;
&lt;p&gt;それもあって、ポートフォリオサイトからは TIL の記録を公開するのはやめていました。
ちなみに現在は Obsidian で記録を書いていくようにしています。&lt;/p&gt;
&lt;h3&gt;リプレースへ&lt;/h3&gt;
&lt;p&gt;TIL の記録を公開するのをやめたことで Docusaurus を採用した意味がなくなってしまったな...と感じるようになりました。
また、サイトのテーマは既存のものを使っていたこともあり、なんだかオリジナリティというか面白味も特にないなぁと。&lt;/p&gt;
&lt;p&gt;ブログも独自デザインでリプレースしたこともあり、ポートフォリオサイトもいずれリプレースしたいと思うようになっていました。
どうも &lt;a href=&quot;/posts/2024-12-31-looking-back-2024-tech&quot;&gt;2024年振り返り ～技術活動、ブログ、キャリア編～&lt;/a&gt; の頃にはすでにそう思っていたようです。&lt;/p&gt;
&lt;h2&gt;リプレースしたもの&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/portfolio-tanstack&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;レイアウト比較&lt;/h3&gt;
&lt;p&gt;大まかなレイアウト比較として、トップページとスキルページを載せてみます。&lt;/p&gt;
&lt;p&gt;ページ構成自体はそれほど変えていないので真新しさはないですが、リプレース前より少しすっきりしたような気がしています。&lt;/p&gt;
&lt;h4&gt;デスクトップ&lt;/h4&gt;
&lt;p&gt;トップページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docusaurus（リプレース前）&lt;/th&gt;
&lt;th&gt;TanStack Start（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-docusaurus-desktop.png&quot; alt=&quot;ポートフォリオリプレース前のトップページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-tanstack-desktop.png&quot; alt=&quot;ポートフォリオリプレース後のトップページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;スキルページ.
（リプレース前は経歴も載せていましたが、全公開するのはやめたのでスクショも経歴の前までにしてます。本来はフッターまであります）
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docusaurus（リプレース前）&lt;/th&gt;
&lt;th&gt;TanStack Start（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-skill-docusaurus-desktop.png&quot; alt=&quot;ポートフォリオリプレース前のスキルページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-skill-tanstack-desktop.png&quot; alt=&quot;ポートフォリオリプレース後のスキルページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;モバイル&lt;/h4&gt;
&lt;p&gt;トップページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docusaurus（リプレース前）&lt;/th&gt;
&lt;th&gt;TanStack Start（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-docusaurus-mobile.png&quot; alt=&quot;ポートフォリオリプレース前のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-tanstack-mobile.png&quot; alt=&quot;ポートフォリオリプレース後のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;スキルページ.
（リプレース前は経歴も載せていましたが、全公開するのはやめたのでスクショも経歴の前までにしてます。本来はフッターまであります）
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Docusaurus（リプレース前）&lt;/th&gt;
&lt;th&gt;TanStack Start（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-skill-docusaurus-mobile.png&quot; alt=&quot;ポートフォリオリプレース前のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2026/portfolio-replace-tanstack/portfolio-skill-tanstack-mobile.png&quot; alt=&quot;ポートフォリオリプレース後のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;リプレースで採用してみた主な技術&lt;/h2&gt;
&lt;h3&gt;TanStack Start&lt;/h3&gt;
&lt;p&gt;主となるフレームワークに選定したのは TanStack Start。
&amp;lt;OG url=&quot;https://tanstack.com/start/latest&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/max-comma --&amp;gt; */}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Full-stack Framework powered by TanStack Router for React and Solid&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;Full-document SSR, Streaming, Server Functions, bundling and more, powered by TanStack Router and Vite - Ready to deploy to your favorite hosting provider.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/max-comma --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;TanStack シリーズでいろんなパッケージが提供されているうち、React, TanStack Router ベースで作られているフルスタックフレームワークです。&lt;/p&gt;
&lt;p&gt;ブログリプレースの時と同様に、ポートフォリオサイトも最初は Next.js でリプレースしようかと思い、実際に Next.js で着手していたのですが...。
Next.js だとややオーバースペック気味に感じていたのと、メジャーすぎてなんだか面白みがないなになり。&lt;/p&gt;
&lt;p&gt;TanStack Start は比較的新しい技術なことと、React ベースなのでとっきやすいこと。SSG もできるらしいということで、今回初めて触って採用してみたのでした。
まぁ、仕事だったらこんな技術選定すると怒られそうではありますが、個人開発なので自由が利くということで。&lt;/p&gt;
&lt;h3&gt;Tailwind CSS&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://tailwindcss.com/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;スタイリングは Tailwind CSS を採用しました。
TanStack Start のテンプレで採用されていた流れで採用しましたね。&lt;/p&gt;
&lt;p&gt;CSS in JS を久しぶりに書くことも考えたのですが、サッと作りやすいというと Tailwind CSS の方があるかなぁと。
（そもそも TanStack Start で CSS in JS って動くのかな？）
普段、仕事でよく書いていることもあり、なじみ深いです。&lt;/p&gt;
&lt;p&gt;デザインは本ブログのリプレース時と同様に、結局作りながら雰囲気で構築していきました。モノクロ基調にしたいというのはあったので、そこから膨らませた感じでしたね。
Tailwind だとこういう雰囲気でスタイルガチャガチャするのも少し楽な気がしています。&lt;/p&gt;
&lt;h3&gt;React Bits&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://reactbits.dev/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これは技術というかコンポーネント集ですね。
React をベースとして、アニメーションが設定されているコンポーネントが Shadcn や jsrepo 経由で提供されているものです。
Tailwind CSS 版のコードが提供されていることもちょうどよかったですね。&lt;/p&gt;
&lt;p&gt;アニメーションは入れすぎるとくどい印象を与えるため、扱いが少し難しいものではありますが、せっかくリプレースするので遊び心的な観点で少し入れてみたいなと思ったのでした。
現時点では以下の3つを採用しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Text Type&lt;/li&gt;
&lt;li&gt;Circular Text&lt;/li&gt;
&lt;li&gt;Fade Content&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;リプレースした感想など&lt;/h2&gt;
&lt;p&gt;今年入ってからリプレース着手しまして、主に週末細々と進めていました。
かかった時間としてはざっくり30時間ほど。ブログの時は150時間くらいかかっていたので、それと比べればぱっと作れた方でしょうか（記事データの移行がないからそれはそう）&lt;/p&gt;
&lt;p&gt;シンプルな構成ではあるので TanStack Start の機能をそれほど使えてはいませんが...。公式で Next.js からの移行ガイドが用意されていて、それを見た感じ必要十分な機能はある程度そろっているような印象を受けましたね。
今後は TanStack Start の採用事例の話も流れてきたりするんだろうか。&lt;/p&gt;
&lt;p&gt;ちなみに今回 AI は使いませんでした。小規模で使うまでもなかったというか、自分でガチャガチャしながら作りたかったので使わなかったです。&lt;/p&gt;
&lt;p&gt;前からリプレースしたいな～と思っていたので、ようやくリプレース出来てよかったです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;元々は年を越すタイミングでリリースできたらと考えていました。
着手がずれこんでしまいましたが、とりあえず無事にリリースまでいけたので一安心しています。&lt;/p&gt;
&lt;p&gt;今度はポートフォリオの内容が充実できるよう、何か作ったり、技術の知識を深めたりしていきたいですね。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/start/latest&quot;&gt;TanStack Start&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tailwindcss.com/&quot;&gt;Tailwind CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://reactbits.dev/&quot;&gt;React Bits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2025年振り返り ～技術活動、ブログ、キャリア編～</title><link>https://changeofpace.site/posts/2025-12-30-looking-back-2025-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2025-12-30-looking-back-2025-tech</guid><description>2025年のGitのコミット履歴から、記事執筆など技術活動、キャリアの振り返り</description><pubDate>Tue, 30 Dec 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;年末恒例の振り返りのうち、技術活動、ブログ、キャリア編やっていきます～。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2025年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;今年の Git の Contributions 履歴から。&lt;/p&gt;
&lt;h4&gt;GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2025/looking-back-2025-tech/github-contribute.png&quot; alt=&quot;2025年のGitHub Contributionsの履歴画像 1279Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年は1279でした。&lt;/p&gt;
&lt;p&gt;このアカウントを使った活動は個人活動のみでしたね。
ほぼ土日メインの活動だったかなと思います。色がついている日こそ少ないですが、1日あたりの履歴数がそこそこあったみたいです。&lt;/p&gt;
&lt;h4&gt;案件 - GitHub&lt;/h4&gt;
&lt;p&gt;個人アカウントとは別アカウントの活動になるため、別途記載しています。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2025/looking-back-2025-tech/github-contribute-pj.png&quot; alt=&quot;2025年の仕事用GitHub Contributionsの履歴画像 1417Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年は1417でした。&lt;/p&gt;
&lt;p&gt;平日はほぼ毎日何かしらコード書いてたと思うので、積み重ねでこれくらいになったようです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;例年と比較するとこんな感じ。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2025&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2022&lt;/th&gt;
&lt;th&gt;2021&lt;/th&gt;
&lt;th&gt;2020&lt;/th&gt;
&lt;th&gt;2019&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(個人)&lt;/td&gt;
&lt;td&gt;1,279&lt;/td&gt;
&lt;td&gt;858&lt;/td&gt;
&lt;td&gt;1,712&lt;/td&gt;
&lt;td&gt;657&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;920&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(仕事)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;541&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab(案件)&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;179&lt;/td&gt;
&lt;td&gt;1,699&lt;/td&gt;
&lt;td&gt;1,448&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(案件)&lt;/td&gt;
&lt;td&gt;1,417&lt;/td&gt;
&lt;td&gt;1,707&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合計&lt;/td&gt;
&lt;td&gt;2,696&lt;/td&gt;
&lt;td&gt;2,763&lt;/td&gt;
&lt;td&gt;3,617&lt;/td&gt;
&lt;td&gt;2,304&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;1,461&lt;/td&gt;
&lt;td&gt;307 + ?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;昨年と比べるとビミョーに減っていたようです。
個人活動は増えていたようですが、案件活動が少し減っていました。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;h4&gt;アクセス履歴&lt;/h4&gt;
&lt;p&gt;今年の履歴はこんな感じです。
例年通り、Google Analytics のデータ部分のみ抜粋。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;142&lt;/td&gt;
&lt;td&gt;216&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;187&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;242&lt;/td&gt;
&lt;td&gt;294&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;165&lt;/td&gt;
&lt;td&gt;223&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;164&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;252&lt;/td&gt;
&lt;td&gt;292&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;175&lt;/td&gt;
&lt;td&gt;202&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;td&gt;223&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;188&lt;/td&gt;
&lt;td&gt;234&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;234&lt;/td&gt;
&lt;td&gt;281&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;td&gt;269&lt;/td&gt;
&lt;td&gt;329&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;97&lt;/td&gt;
&lt;td&gt;217&lt;/td&gt;
&lt;td&gt;253&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：2,431&lt;/li&gt;
&lt;li&gt;ページビュー数計：3,011&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年との比較.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数：2,868 -&amp;gt; 437減&lt;/li&gt;
&lt;li&gt;ページビュー数：4,006 -&amp;gt; 995減&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年からさらに記事書けなかったこともあり、今年もまぁ減るよね...という感じでした。&lt;/p&gt;
&lt;h4&gt;記事執筆について&lt;/h4&gt;
&lt;p&gt;さらに全然書けてないですね...。
脱・資料まとめの人ならず。&lt;/p&gt;
&lt;p&gt;今年投稿した記事.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2025-02-16-developers-summit-2025&quot;&gt;Developers Summit 2025 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2025-11-25-jsconf-jp-2025&quot;&gt;JSConf JP 2025 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2025-11-30-windows-mise-safe-chain&quot;&gt;Windows + Git Bash環境でmiseとSafe Chainを共存させる&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2025-12-28-looking-back-2025-mental-illness&quot;&gt;2025年振り返り 〜精神疾患との生活編〜&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2025年振り返り ～技術活動、ブログ、キャリア編～ ※当記事&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ブログ以外の記事執筆活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn に投稿した記事は、基本的には当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;ブログ + Zenn で活動しているのは継続中。当ブログ関連以外の記事はクロス投稿しているのは変わりません。&lt;/p&gt;
&lt;p&gt;今年 Zenn からいただいた振り返りはこんな感じでした。
（12/24までのもの）
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2025/looking-back-2025-tech/zenn-recap.png&quot; alt=&quot;Zenn 2025 Recap - 2025年の活動振り返りデータ - 29,923字の執筆、3記事、23,175ビュー、58Likes&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;新規活動は特になしでした。&lt;/p&gt;
&lt;p&gt;引き続き、ブログや過去に作ったもののメンテナンスをしていたくらいです。
脆弱性対応のためのアプデとか。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;前半に devChallenges の課題を少しこなした程度ですね。
Front-end 編の課題をやる気でいたものの、結局着手できなかったな🙄
App Router の試し書きをしたかったやつ。
（仕事では普段書いてますが）&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;今年は書く機会がなかったですね。
書くとしたら Hono で書くような気はします。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;結局、昨年の振り返りで言っていた HTML プロフェッショナル レベル2は受験できてないです。&lt;/p&gt;
&lt;p&gt;ただ、会社の都合で TOEIC を受験しに行きました。
とりあえずスコアの登録するためというので、ほとんど勉強できずで受験したのでひどいスコアでしたが...😇
試験自体はちょっと面白かったので、ちゃんと勉強して再チャレンジしたいところ。&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;今年参加したイベントはこちら。
オンライン参加のみです。&lt;/p&gt;
&lt;p&gt;※資料まとめのみ
&amp;lt;OG url=&quot;https://event.shoeisha.jp/devsumi/20250717&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://jsconf.jp/2025/ja&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;オンライン視聴参加
&amp;lt;OG url=&quot;https://findy-tools.connpass.com/event/347251/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;習慣づいてないからか、全然参加できなかったですね。
業後はゆっくりしたいな...という思いが先行してしまう問題。&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;今年は特に目立った大きな成果がなかったような。&lt;/p&gt;
&lt;p&gt;案件でのチーム開発や調整が多い対応などは引き続きこなしていましたが、いつもの業務という感じです。&lt;/p&gt;
&lt;h2&gt;2026年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;ブログ自体のメンテナンスは引き続き行うとして、記事執筆が問題ですね。
なかなか昔のペースに戻せない問題。&lt;/p&gt;
&lt;p&gt;今年の mise の記事を書いたときは、記事にするモチベが湧いて書いてたのでそういうモチベが戻ってくれるといいのですが。
小ネタ記事からでいいのでリハビリしたいところ。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;ポートフォリオサイトのリプレースをいい加減やりたいですね。
Next.js(App Router) でのリプレースを考えていたものの、静的サイトで使うにはオーバースペックな気もするので、他の技術を使うかな～と考えています。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;devChallenges の課題の Front-end Developer 編やあるいは他の個人活動で何か作りたいですね。&lt;/p&gt;
&lt;p&gt;今年は npm にランサムウェアの攻撃があったり、React, Next.js に深刻な脆弱性が見つかったりなど色々波乱だったこともあり、脆弱性にも気を付けておきたいところ。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;そろそろ Hono 使ってみたいですね。
Kotlin を書く機会もあると良さそうですが、気軽に試せそうなのって Kotlin のフレームワークよりは Hono かなぁと。&lt;/p&gt;
&lt;h4&gt;ドキュメント活動&lt;/h4&gt;
&lt;p&gt;いい加減、DocDD を試さねば。
新しい仕事の環境で何か関われる機会があるといいのですが。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;今年もテクニカルライティング試験の教本の第4版出なかったなぁ...。
出版されるのを待っていたのですが、いい加減第3版で勉強して受験した方がいいかも？&lt;/p&gt;
&lt;p&gt;それと情報処理試験が CBT 形式に変わることで、受験しやすくなるようなので、チャレンジするのもありですね。
いつも会場が遠くて起床試験が大変だったのです...。
応用情報のリベンジするかなぁ。&lt;/p&gt;
&lt;p&gt;それ以外でチャレンジするとしたら、HTML プロフェッショナル レベル2や期限が切れてしまった AWS SAA の更新もしくは SAP かなと思います。&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;フロントエンド、ドキュメント系はできれば参加したいところです。
業後に参加するモチベが湧くかがちょっと微妙なところですが...。&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;来年は仕事の環境の変化が本格的に始まるので、それ次第なところがあります。
変わった先で求められるレベル感にうまく適応できるといいのですが、どうなるかがちょっと読めないですね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以上、今年の技術活動、ブログ、キャリア編の振り返りでした。&lt;/p&gt;
&lt;p&gt;年々書くことが抽象的でふわっとしている気がする...。
精神疾患との生活編でも書いたのですが、環境の変化に伴い来年はどうなるのか読めないところがあるので、プラスに働いてくれるといいなと思うばかりです。&lt;/p&gt;
&lt;p&gt;皆様よいお年をお迎えください～。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>Windows + Git Bash環境でmiseとSafe Chainを共存させる</title><link>https://changeofpace.site/posts/2025-11-30-windows-mise-safe-chain</link><guid isPermaLink="true">https://changeofpace.site/posts/2025-11-30-windows-mise-safe-chain</guid><description>Windows + Git Bash環境でもエイリアスを利用して、miseとSafe Chainを共存させて、安全かつ扱いやすくする方法</description><pubDate>Sun, 30 Nov 2025 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;めちゃ久しぶりの小ネタ記事です。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;筆者の個人の開発では Windows + Git Bash 環境を使っています。
Node.js のバージョン管理ではしばらく Volta を使っていました。&lt;/p&gt;
&lt;p&gt;この Volta をより汎用性の高い mise に移行したかったのですが、Safe Chain と共存させるのに少しだけ工夫が必要だったので、備忘録として残しておきます。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;mise は言語や CLI ツールのバージョン管理、環境変数やタスク管理の機能を提供するツールです。
&amp;lt;OG url=&quot;https://github.com/jdx/mise&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Safe Chain は、マルウェアに汚染されているパッケージをブロックしてくれるツールで、最近流行っている Shai Hulud による攻撃に対する防御策の1つです。
&amp;lt;OG url=&quot;https://github.com/AikidoSec/safe-chain&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;詳細は各公式情報を参照してください。&lt;/p&gt;
&lt;h2&gt;環境のつくり方&lt;/h2&gt;
&lt;h3&gt;mise のインストール&lt;/h3&gt;
&lt;p&gt;mise の公式ドキュメントにもある通り、winget での導入に対応しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;winget install jdx.mise
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;winget で導入した場合は、パスを通す部分も自動で行ってくれるので楽ちんですね。&lt;/p&gt;
&lt;h3&gt;Node.js のインストール&lt;/h3&gt;
&lt;p&gt;mise 経由で任意のバージョンの Node.js をインストールして、とりあえずグローバル使用設定にしておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise install node@24.11.1
mise use --global node@24.11.1
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Safe Chain のインストール&lt;/h3&gt;
&lt;p&gt;mise 経由で Safe Chain を導入します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise exec node -- npm install -g @aikidosec/safe-chain
mise exec node -- safe-chain setup
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この後にターミナルを再起動することで、Safe Chain が使えるようになるのですが...
以下の動作確認用パッケージをインストールしようとしても、ブロックしてくれず🙄&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mise exec node -- npm install safe-chain-test
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを使うにあたり、毎回&lt;code&gt;mise exec node&lt;/code&gt;を書くのがめんどくさいという課題もありました。
Volta では直接&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを使っても、カレントディレクトリにおける設定バージョンを自動的に適用してくれていたため、これを mise でもやりたかったのです。&lt;/p&gt;
&lt;h3&gt;エイリアスの活用&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;mise 経由でインストールした Safe Chain のマルウェア検知がうまく動作しない&lt;/li&gt;
&lt;li&gt;mise でバージョン管理している&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを実行するには、&lt;code&gt;mise exec node&lt;/code&gt;をつける必要がある&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;この2つの課題に対して、筆者の場合はエイリアスで解決しました。&lt;/p&gt;
&lt;p&gt;Bash の環境で読み込まれるファイルにエイリアスを設定しておきます。
筆者の場合は、エイリアス用の別ファイルを用意して、それを&lt;code&gt;.bashrc&lt;/code&gt;から読み込むようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# mise
# 本来は mise activate を実行すれば、直接 node や npm コマンドが実行できるようになるが
# Windows + Git Bash で mise activate すると git や vi などの PATH が通らなくなってしまう。
# 環境変数の PATH に &amp;lt;homedir&amp;gt;\AppData\Local\mise\shims 通すことでも直接 node, npm コマンドが使えるようになるが、
# Safe Chain がうまく動作してくれないため、エイリアスで対応する。
if command -v mise &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
  alias node=&apos;mise exec node -- node&apos;
  alias npm=&apos;mise exec node -- aikido-npm&apos;
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;if [ -f ~/.bash_aliases ]; then
  . ~/.bash_aliases
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで直接&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを書いても、実質的には mise を通したコマンド実行をしてくれるのでいい感じになりました。
&lt;code&gt;npm&lt;/code&gt;の方は&lt;code&gt;aikido-npm&lt;/code&gt;にしているのがポイントです。これは通常の&lt;code&gt;npm&lt;/code&gt;コマンドではなく、Safe Chain が提供している&lt;code&gt;npm&lt;/code&gt;コマンドのラッパーにあたるものとなるため、Safe Chain が動作してくれるようになりました。
今回は&lt;code&gt;npm&lt;/code&gt;コマンドのみの対応ですが、他のパッケージマネージャーのコマンドでも同様にエイリアスで対応できそうです（筆者は未確認です）&lt;/p&gt;
&lt;p&gt;ちなみに直接&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを書けるようにする方法として、mise の公式ドキュメントに記載があった方法も試しました。
しかし、筆者の環境では2つの課題の解決ができなかったため、今回の方法をとりました。&lt;/p&gt;
&lt;p&gt;その1：&lt;code&gt;.bashrc&lt;/code&gt;に&lt;code&gt;mise activate bash&lt;/code&gt;を実行する処理を記載しておく方法
-&amp;gt; vi や git などほかのコマンドのパスが通らなくなってしまった。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;その2：Windows の環境変数の PATH に&lt;code&gt;&amp;lt;homedir&amp;gt;\AppData\Local\mise\shims&lt;/code&gt;を追加する
-&amp;gt; 直接&lt;code&gt;node&lt;/code&gt;や&lt;code&gt;npm&lt;/code&gt;コマンドを実行できるようにはなったが、Safe Chain のマルウェア検知がうまく動作しなかった。
ただし、もし Git Hook で任意の npm パッケージを動作させたい時は、この PATH 追加が必要なようだった（セキュリティ的には Lifecycle Scripts の取り扱いは要注意...！）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;Safe Chain のマルウェア検知の動作確認&lt;/h3&gt;
&lt;p&gt;マルウェア検知の動作確認用のパッケージをインストールしようとして、ブロックされていれば Safe Chain が動作しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; npm install safe-chain-test
✖ Safe-chain: Malicious changes detected:
 - safe-chain-test@0.0.1-security

Safe-chain: Exiting without installing malicious packages.
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;他にもよりよい方法はあるかもですが、とりあえず mise と Safe Chain の共存ができたので満足してます。
Shai Hulud による攻撃怖いですしね...。
皆様もご安全に開発を～。&lt;/p&gt;
</content:encoded></item><item><title>2024年振り返り ～技術活動、ブログ、キャリア編～</title><link>https://changeofpace.site/posts/2024-12-31-looking-back-2024-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2024-12-31-looking-back-2024-tech</guid><description>2024年のGitのコミット履歴から、記事執筆など技術活動、キャリアの振り返り</description><pubDate>Tue, 31 Dec 2024 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;一人暮らし後、初めての年末年始を迎える、よしです。
年末恒例の振り返りのうち、技術活動、ブログ、キャリア編やっていきます～。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2024年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;恒例の Git の Contributions 履歴から。&lt;/p&gt;
&lt;h4&gt;GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2024/looking-back-2024-tech/github-contribute.png&quot; alt=&quot;2024年のGitHub Contributionsの履歴画像 877Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年は 858（個人） + 19（案件外の仕事） = 877 でした。&lt;/p&gt;
&lt;p&gt;個人活動としては、devChallenges の課題をいくつかこなしたくらいだったかな。ほぼ土日メインの活動でした。&lt;/p&gt;
&lt;p&gt;（案件外の）仕事は、業務改善活動で作った Bot のメンテナンスを少しだけやった程度だったため少な目。&lt;/p&gt;
&lt;h4&gt;案件 - GitLab・GitHub&lt;/h4&gt;
&lt;p&gt;参加している案件では、セルフホスティングの GitLab, GitHub が共存しているため、両方載せておきます。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2024/looking-back-2024-tech/gitlab-contribute-pj.png&quot; alt=&quot;2024年の仕事用GitLab Contributionsの履歴画像 179Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;あくまで今のメインは GitHub 側を使っているため、ほとんど contributions 数がないですね。手動で数えたところ 179 contributions で、一番濃いところは 30 contributions でした。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2024/looking-back-2024-tech/github-contribute-pj.png&quot; alt=&quot;2024年の仕事用GitHub Contributionsの履歴画像 1707Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;GitHub の方は、ほとんどの日はコード書いてたので、平日はほぼ履歴がありますね。&lt;/p&gt;
&lt;p&gt;合わせると、案件活動としては 179 + 1707 = 1886 でした。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;例年と比較するとこんな感じ。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2024&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2022&lt;/th&gt;
&lt;th&gt;2021&lt;/th&gt;
&lt;th&gt;2020&lt;/th&gt;
&lt;th&gt;2019&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(個人)&lt;/td&gt;
&lt;td&gt;858&lt;/td&gt;
&lt;td&gt;1,712&lt;/td&gt;
&lt;td&gt;657&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;920&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(仕事)&lt;/td&gt;
&lt;td&gt;19&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;541&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab(案件)&lt;/td&gt;
&lt;td&gt;179&lt;/td&gt;
&lt;td&gt;1,699&lt;/td&gt;
&lt;td&gt;1,448&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(案件)&lt;/td&gt;
&lt;td&gt;1,707&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合計&lt;/td&gt;
&lt;td&gt;2,763&lt;/td&gt;
&lt;td&gt;3,617&lt;/td&gt;
&lt;td&gt;2,304&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;1,461&lt;/td&gt;
&lt;td&gt;307 + ?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;うーん、昨年と比べ活動量減りましたねぇ。
2022年よりは多いものの、個人活動の減少が響いていたようです。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;h4&gt;アクセス履歴&lt;/h4&gt;
&lt;p&gt;今年の履歴はこんな感じです。
例年通り、Google Analytics のデータ部分のみ抜粋。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;250&lt;/td&gt;
&lt;td&gt;317&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;88&lt;/td&gt;
&lt;td&gt;837&lt;/td&gt;
&lt;td&gt;999&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;337&lt;/td&gt;
&lt;td&gt;542&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;279&lt;/td&gt;
&lt;td&gt;408&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;218&lt;/td&gt;
&lt;td&gt;266&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;220&lt;/td&gt;
&lt;td&gt;266&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;89&lt;/td&gt;
&lt;td&gt;106&lt;/td&gt;
&lt;td&gt;134&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;（計測できていない）&lt;/td&gt;
&lt;td&gt;（計測できていない）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;113&lt;/td&gt;
&lt;td&gt;182&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;90&lt;/td&gt;
&lt;td&gt;158&lt;/td&gt;
&lt;td&gt;209&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;91&lt;/td&gt;
&lt;td&gt;159&lt;/td&gt;
&lt;td&gt;201&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;93&lt;/td&gt;
&lt;td&gt;181&lt;/td&gt;
&lt;td&gt;218&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：2,858&lt;/li&gt;
&lt;li&gt;ページビュー数計：3,742&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年との比較.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数：3,358 -&amp;gt; 500減&lt;/li&gt;
&lt;li&gt;ページビュー数：5,683 -&amp;gt; 1,941減&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今年は全然記事書けてなかったこともあり、まぁ減るよね...という。
それと GA4 での計測が正しく行えてない時もあったのでした。&lt;/p&gt;
&lt;p&gt;2月にポーランドからのアクセスが謎に急増するという現象があり。
どうもこの時期、同じ現象に遭遇した方が多数見られて不正アクセスのようでした。
以下の記事を参考に WAF で IP 制限を入れて対処したのが効いたのか、一応アクセスは収まってくれたようです。
&amp;lt;OG url=&quot;https://note.com/taiwanwalking/n/naa162cfd47f2&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;7/16～9/7 のあたりでは、GA4 が謎に計測できてない現象が起きていました。
その頃にライブラリのアプデ等はしていなかったこともあり、気づくのが遅れてしまったやつ...。
もしかしたら、7月に UA が完全に廃止になった影響かもしれません。
GA4 の設定が出来ているように見えて、実は不完全なところがあり。UA 経由で計測できていただけで、UA 完全廃止に伴い計測不調がおきたみたいな。
（でも、UA の計測自体は昨年の7月に停止しているはずなので、それも違うのかな...？）&lt;/p&gt;
&lt;h4&gt;記事執筆について&lt;/h4&gt;
&lt;p&gt;今年は全然記事書けてなかったですね...。
ほぼ資料まとめの人になってしまっていた🙄&lt;/p&gt;
&lt;p&gt;今年投稿した記事.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2024-02-17-developers-summit-2024&quot;&gt;Developers Summit 2024 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2024-03-30-freelance-hub-introduced-html-level1&quot;&gt;HTML5プロフェッショナル認定試験 レベル1受験レポート記事が、freelance hubで紹介されました&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2024-08-25-frontend-conf-hokkaido-2024&quot;&gt;フロントエンドカンファレンス北海道 2024 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2024-11-24-jsconf-jp-2024&quot;&gt;JSConf JP 2024 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2024-12-29-looking-back-2024-mental-illness&quot;&gt;2024年振り返り 〜精神疾患との生活編〜&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2024年振り返り ～技術活動、ブログ、キャリア編～ ※当記事&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年もこのままでは資料まとめの人になってまう...と嘆いていたのに、そうなってしまっているや～ん。&lt;/p&gt;
&lt;p&gt;ただ、3月に過去記事が freelance hub 様で紹介されるという希少な体験もしたりしていました。
技術記事の執筆活動復活したら、またそういう機会があったりするんだろうか...？&lt;/p&gt;
&lt;h3&gt;ブログ以外の記事執筆活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn および Qiita に投稿した記事は、基本的には当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;ブログ + Zenn で活動しているのは継続中。当ブログ関連以外の記事はクロス投稿していました。&lt;/p&gt;
&lt;p&gt;今年 Zenn からいただいた振り返りはこんな感じでした。
（12/22までのものです）&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2024/looking-back-2024-tech/zenn-recap.png&quot; alt=&quot;Zenn 2024 Recap - 2024年の活動振り返りデータ - 32,090字の執筆、3記事、32,876ビュー、307Likes&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Qiita, ~note~ は今年活動なしです。&lt;/p&gt;
&lt;p&gt;※2025/09/15...note のリンクは除去しました。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;今年も新規活動は特になし。&lt;/p&gt;
&lt;p&gt;ブログをメンテナンスしていたくらいでした。
Astro の画像最適化に対応するとかもやってましたね。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;昨年、React と Next.js の最新バージョンのキャッチアップしなきゃと言っていた気がしますが...。
今年は React 19, Next.js 15 まで来ましたね。やべー追いつけてねぇよぉ...になってます😇&lt;/p&gt;
&lt;p&gt;まぁ、案件では Next.js の App Router 使っているので自然と触れる機会は増えてはいます。
ただ、個人で使うまでには至ってない状態ですね。&lt;/p&gt;
&lt;p&gt;React 19 も uhyo さんの本をチームで読み合わせたりしつつも、実際に書く機会がまだなくて慣れていない感じ。
&amp;lt;OG url=&quot;https://zenn.dev/uhyo/books/react-19-new&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;あとは過去に作った devChallenges の課題メンテナンス + 新規課題をいくつかこなしました。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;案件で Kotlin を書く機会は今年もほとんどなかったですね。
昨年使ってみたいと言っていた Hono も結局使えてなかったな...。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;昨年少し勉強していた HTML プロフェッショナル レベル2の試験は結局受けてなかったです。&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;技術メモの Notion から Obsidian に移行は一段落ついていたものの、まだそこまで活用できてないかな。&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;今年参加したイベントはこちら。
オンライン参加のみです。&lt;/p&gt;
&lt;p&gt;※資料まとめのみ
&amp;lt;OG url=&quot;https://event.shoeisha.jp/devsumi/20240215&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://www.frontend-conf.jp/2024&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://jsconf.jp/2024/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;オンライン視聴参加
&amp;lt;OG url=&quot;https://tw-meetup.connpass.com/event/306213/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://findy.connpass.com/event/306774/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://tw-meetup.connpass.com/event/309169/&quot;/&amp;gt;
&amp;lt;OG url=&quot;https://offers.connpass.com/event/310086/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://findy.connpass.com/event/318090/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://tw-meetup.connpass.com/event/320894/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年前半止まりで後半は参加できてないなぁ。
ドキュメント周りにも関心があることもあり、ドキュメント系のイベントも参加していました。&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;年始から、案件で先方エンジニアチームに1人で先行して合流・キャッチアップし、他メンバー合流の橋渡しをする。という役割を担っていました。
他のタスクとの優先度の関係で、3月にはその役割を離れることにはなってしまったのですが、ドキュメントに書いていた情報が他メンバー合流時に役に立ったとの声をもらうことができました。&lt;/p&gt;
&lt;p&gt;なお、今回は既存案件内の別プロジェクトという感じだったので、ドメイン知識等は備えている状態ではありました。
新規契約の案件で、リードが先行して入ってキャッチアップして他メンバーとの橋渡しをする。というのはリードの仕事でよくあると思いますし、今後そういう機会があった時に活かせるといいなと。&lt;/p&gt;
&lt;p&gt;その他は、案件内の GraphQL - Fragment Colocation から派生した設計をチームで協力しながら進めたことがありました。
アプリケーション全体に影響する設計だったこともあり、結構時間かけて悩んだ記憶。こういうのも自らすすんでこなせていけると強いんだろうなぁ。&lt;/p&gt;
&lt;h2&gt;2025年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;来年は1年で12記事書けるといいなぁ。もしくは、12記事までいかなかったとして、技術の話的な記事をまた書けるようにしたいところ。
資料まとめの記事は多分まだ続けると思います。&lt;/p&gt;
&lt;p&gt;ブログ自体のメンテナンスも合間見てやっていきます。
今も Astro 5系へのアプデがまだなので、近々やらねば。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;ポートフォリオサイトの Next.js(App Router) リプレースを進めるかも？
デザインどうしようかな...があるんで、結局実作業は進まない気もしつつ、まぁいずれはやりたいということで。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;案件で React 19, Next.js 15 アプデの話が出ているので、新しい機能も使える機会ありそうな気がしています。
ただ、やはり個人でも素振りはしておきたいですねー。&lt;/p&gt;
&lt;p&gt;進めていた devChallenges の課題で、次の Front-end Developer 編からはフロントエンドのフレームワークが使用可能になるため、そこで素振りしようかなと考えています。
旧課題のメンテナンスで Next.js の Pages Router から App Router 移行も考えたものの、使用していた emotion 移行が少し大変そうだったので新課題でやろうかなと。
スタイリング系のライブラリも新しめのやつ使ってみたい。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;何かの機会に Hono を使ってみたいところ。
そういえば、Next.js の Route Handlers を Hono に差し替えるみたいな面白い記事を見かけたので、これをちょっとやってみたい気もしました。
&amp;lt;OG url=&quot;https://zenn.dev/chot/articles/e109287414eb8c&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;ドキュメント活動&lt;/h4&gt;
&lt;p&gt;元々、自分が関心を持っている領域なのと。
弊社内で代表自ら DocDD（ドキュメント駆動開発）を浸透させるという流れができているので、そこに何かしら関わっていけるといいのかなぁと漠然と考えていたりします。
（ちなみに社内のフロントエンド職能グループ内で、ドキュメント整備・管理の役割のチームにも所属していたりします）&lt;/p&gt;
&lt;p&gt;その過程で AI との関わり方も変わってきそう。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;HTML プロフェッショナル レベル2、いい加減挑戦するかなぁ。&lt;/p&gt;
&lt;p&gt;あとテクニカルライティング試験も気になっているのですが、公式の教本の第4版が出そうで出ないという状態で、教本買うタイミングを悩んでいます。
来年には出版されてほしいなぁ。
&amp;lt;OG url=&quot;https://jtca.org/learn-tc/certificate-exam/writing_b/&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;Obsidian のナレッジベース構築ガシガシ進めたい。
自分はテキストに起こして情報を整理して理解するタイプなので、Obsidian に書いていったら自然と良い方向に行きそうな気がします...！&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;引き続き、無理のない範囲でフロントエンド、ドキュメント、決済系のイベントは参加してみようかなと。
でも、優先度としては前者2つが高いですね。&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;精神疾患との生活編でも以下のように書いていました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;仕事面では、来年何か変化の起こりそうな気がしています。
案件が1つの佳境を迎えるのと、案件外の新しい仕事にチャレンジすることがありまして。&lt;/p&gt;
&lt;p&gt;それと占い的な話で、来年の運勢は
「目立つポジションやリーダー的な立場といった、これまで避けてきた仕事を任されることがあるが、毛嫌いして断るな。」
「苦手な分野で周囲から指摘されることがあるが、これまでその分野をやってこなかったツケを払う時がきたと思え。」
的なことが書いてあったので、なんかそういう変化があるんだろうなぁと漠然に思っているのもあります。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;何かしら変化は起きるのかなと。
変化に振り回されてあーだこーだ言ってる未来も予想されますが、可能な範囲で迎え撃って、自分の糧にしていきたいところです。
まぁ、必要に応じて誰かに相談しながらなら、なんとかなるはず？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以上、技術活動、ブログ、キャリア編の振り返りでした。&lt;/p&gt;
&lt;p&gt;今年の振り返りやってて、あれこれできてねぇなぁ...とややしょんぼりしつつ。仕事・プライベートともにちょっとしたチャレンジできたものもあったので、まぁよしとしようかと思います。&lt;/p&gt;
&lt;p&gt;では、皆様よいお年をお迎えください～。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>2023年振り返り ～技術活動、ブログ、キャリア編～</title><link>https://changeofpace.site/posts/2023-12-31-looking-back-2023-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2023-12-31-looking-back-2023-tech</guid><description>2023年のGitのコミット履歴から、記事執筆など技術活動、キャリアの振り返り</description><pubDate>Sun, 31 Dec 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;今年はキャリアに伸び悩んでクヨクヨしたりが多かったなぁと思っている、よしです。
今年の振り返り、技術活動・キャリア編です～。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2023年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;恒例の Git の Contributions 履歴から。&lt;/p&gt;
&lt;h4&gt;GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/looking-back-2023-tech/github-contribute.png&quot; alt=&quot;2023年のGitHub Contributionsの履歴画像 1712Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年は 1712（個人） + 82（仕事） = 1794 でした。&lt;/p&gt;
&lt;p&gt;個人活動としては、当ブログリプレースや、過去に作ったアプリや課題作品のメンテナンスをしていました。まぁ、ぼちぼち？&lt;/p&gt;
&lt;p&gt;仕事は会社の業務改善活動で作った Bot のメンテナンスを少しやっていたくらいでした。
昨年同様、案件の活動は別アカウントの方です。&lt;/p&gt;
&lt;h4&gt;案件 - GitLab・GitHub&lt;/h4&gt;
&lt;p&gt;参加している案件では、セルフホスティングの GitLab -&amp;gt; GitHub へ移行中のため、両方載せておきます。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/looking-back-2023-tech/gitlab-contribute-pj.png&quot; alt=&quot;2023年の仕事用GitLab Contributionsの履歴画像 1699Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;GitLab の方は Contributions の合計を計算したところ 1699 でした。
一番多い濃い日は、多いところで 32 contributions ほど。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/looking-back-2023-tech/github-contribute-pj.png&quot; alt=&quot;2023年の仕事用GitHub Contributionsの履歴画像 785Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;GitLab の方を見てもらえるとわかるように、大体9月くらいから移行しています。
GitHub 側の9月より前の活動は重複カウントとみなしてカウントせず。
（恐らくコミット数のみ重複カウントされる形になる）
9月以降の GitHub 活動からも重複カウントを除外すると、124 ほどでした。&lt;/p&gt;
&lt;p&gt;なので、案件活動としては 1,699 + 124 = 1,823 くらいのようです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;数年比較するとこんな感じ。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2023&lt;/th&gt;
&lt;th&gt;2022&lt;/th&gt;
&lt;th&gt;2021&lt;/th&gt;
&lt;th&gt;2020&lt;/th&gt;
&lt;th&gt;2019&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(個人)&lt;/td&gt;
&lt;td&gt;1,712&lt;/td&gt;
&lt;td&gt;657&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;920&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(仕事)&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;541&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab(案件)&lt;/td&gt;
&lt;td&gt;1,699&lt;/td&gt;
&lt;td&gt;1,448&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(案件)&lt;/td&gt;
&lt;td&gt;124&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合計&lt;/td&gt;
&lt;td&gt;3,617&lt;/td&gt;
&lt;td&gt;2,304&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;1,461&lt;/td&gt;
&lt;td&gt;307 + ?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;今年は個人活動がグッと活動量が増えましたね。
案件活動は少しだけ。
（2019年が極端に少ないのは、年始から休職〜時短勤務で復職とかだったため）&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;h4&gt;アクセス履歴&lt;/h4&gt;
&lt;p&gt;今年の履歴はこんな感じです。
例年通り、Google Analytics のデータ部分のみ抜粋。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;440&lt;/td&gt;
&lt;td&gt;627&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;305&lt;/td&gt;
&lt;td&gt;414&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;312&lt;/td&gt;
&lt;td&gt;405&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;319&lt;/td&gt;
&lt;td&gt;410&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;232&lt;/td&gt;
&lt;td&gt;323&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;81&lt;/td&gt;
&lt;td&gt;296&lt;/td&gt;
&lt;td&gt;402&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;272&lt;/td&gt;
&lt;td&gt;856&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;82&lt;/td&gt;
&lt;td&gt;156&lt;/td&gt;
&lt;td&gt;214&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;188&lt;/td&gt;
&lt;td&gt;482&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;83&lt;/td&gt;
&lt;td&gt;241&lt;/td&gt;
&lt;td&gt;377&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;85&lt;/td&gt;
&lt;td&gt;294&lt;/td&gt;
&lt;td&gt;766&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;87&lt;/td&gt;
&lt;td&gt;283&lt;/td&gt;
&lt;td&gt;407&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：3,338&lt;/li&gt;
&lt;li&gt;ページビュー数計：5,683&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年との比較.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数：7,896 -&amp;gt; 4,558減&lt;/li&gt;
&lt;li&gt;ページビュー数：10,644 -&amp;gt; 4,961減&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;年々減っていっていますね...🙄
7月にページビューが増えたのはリプレースしたからかなと思うのですが、そのあとはまた戻った感じでした。
今年も大して技術記事書けてなかったしなぁ...という所感ではあります。&lt;/p&gt;
&lt;p&gt;技術記事ガリガリ書いて、ブログ改善をしていけば、またアクセス数戻ったりするだろうか？&lt;/p&gt;
&lt;h4&gt;記事執筆について&lt;/h4&gt;
&lt;p&gt;今年はついに年間12記事投稿の目標を達成できなかったです...。
技術記事という記事全然かけてないじゃん。&lt;/p&gt;
&lt;p&gt;今年投稿した記事.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-02-11-developers-summit-2023&quot;&gt;Developers Summit 2023 公開資料・Twitterリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-03-25-book-technical-writing-primer&quot;&gt;技術文書を書く方に送る、書籍「技術者のためのテクニカルライティング入門講座」&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-07-30-blog-replace-astro&quot;&gt;当ブログサイトをAstroでリプレースしたので、リプレース前後を振り返ってみよう！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-09-23-stylelint-postcss-preset-env&quot;&gt;StyleLintアプデ対応とご一緒にpostcss-preset-envはいかが？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-11-18-frontend-conf-okinawa-2023&quot;&gt;フロントエンドカンファレンス沖縄 2023 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-11-19-jsconf-jp-2023&quot;&gt;JSConf JP 2023 公開資料・Xアカウントリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-12-30-looking-back-2023-mental-illness&quot;&gt;2023年振り返り ～精神疾患、体調編～&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2023年振り返り ～技術活動、ブログ、キャリア編～ ※当記事&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このままでは資料まとめ記事の人になってしまうので、エンジニアとして尊厳を復活させなければ。&lt;/p&gt;
&lt;h4&gt;リプレースについて&lt;/h4&gt;
&lt;p&gt;2021年くらいから当ブログをリプレースする構想があったけれど、なかなか手が付けられていなかったというやつ。
今年はついに Astro でリプレースしました！🎉&lt;/p&gt;
&lt;p&gt;5～7月の平日夜や休日を使ってコツコツ作業して進めました。
7月にリリースして、リプレースしました記事も書いたので、詳細はそちらで書いています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2023-07-30-blog-replace-astro&quot;&gt;当ブログサイトをAstroでリプレースしたので、リプレース前後を振り返ってみよう！&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;何より大変だったのは記事データの移行でしたね。
リプレースやりきることができてよかったー。&lt;/p&gt;
&lt;p&gt;ちなみにリプレースしました記事を書いた後に、ドメインおよびホスティング環境を Cloudflare にお引越ししていたりします。ポートフォリオサイトも同様にお引越し。
ドメイン、ホスティング環境ともにまとめて管理できるの嬉しい。&lt;/p&gt;
&lt;h3&gt;ブログ以外の記事執筆活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn および Qiita に投稿した記事は、基本的には当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;ブログ + Zenn で活動しているのは継続中で、年末振り返り記事以外はクロス投稿していました。
ただ、ブログリプレースの記事は技術中心の内容になるように少し編集しています。
（Zenn で自ブログの宣伝をしたいわけではなかったので）&lt;/p&gt;
&lt;p&gt;今年 Zenn からいただいた振り返りはこんな感じでした。
（12/20までのものです）&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2023/looking-back-2023-tech/zenn-recap.png&quot; alt=&quot;Zenn 2023 Recap - 2023年の活動振り返りデータ - 64,912字の執筆、6記事、1スクラップ、63,246ビュー、402Likes&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;やはり文字数は少なめ（昨年は13万文字くらい）&lt;/p&gt;
&lt;p&gt;Qiita, Crieit, ~note~ は今年活動なしです。&lt;/p&gt;
&lt;p&gt;※2024/09/14追記：Crieit はサービスクローズされました。
※2025/09/15...note のリンクは除去しました。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;新規活動は特になし。
web1week で過去に作ったアプリのメンテナンスをしていたくらい。&lt;/p&gt;
&lt;p&gt;以前はよくお世話になっていた CRA 構成から脱却して Vite に移行する対応をしました。
eject して、こんなにいろんなパッケージを隠蔽して管理してくれていたんだなぁと思ったとともに、それゆえにメンテナンス大変だったのかな...とも思ったり。
技術の栄枯盛衰を感じた出来事でしたね。&lt;/p&gt;
&lt;p&gt;Vite の爆速環境になって快適になりました。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;昨年、React 18 とか Next.js 13 とか大きなアプデがあったわけですが、今年もそんなにドキュメント読み込めてないかも...。
そうこうしているうちに Next.js 14 がリリースされたりして。&lt;/p&gt;
&lt;p&gt;App Router のハンズオン教材や、Next.js 14 リリース時に公開された Learn Next.js はやりました。
あとは App Router を実際に開発で使う機会があると、より理解が深められそうだなと。&lt;/p&gt;
&lt;p&gt;それと、過去に作った devChallenges の課題のメンテナンスをしていました。
ちなみに devChallenges は9月にリニューアルしたので、新しい課題もいくつかこなしたりしていましたね。&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;
&amp;lt;p lang=&quot;en&quot; dir=&quot;ltr&quot;&amp;gt;🎉 BIG ANNOUNCEMENT:&amp;lt;br /&amp;gt;- &amp;lt;a href=&quot;https://t.co/FrvrS6Ugkr&quot;&amp;gt;https://t.co/FrvrS6Ugkr&amp;lt;/a&amp;gt; is officially upgraded.&amp;lt;br /&amp;gt;- Totally new designs and challenges.&amp;lt;br /&amp;gt;- More updates to come in the future.&amp;lt;br /&amp;gt;- The old version is still accessible at &amp;lt;a href=&quot;https://t.co/HOApmv1MeL&quot;&amp;gt;https://t.co/HOApmv1MeL&amp;lt;/a&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;This marks a new beginning after 2 years inactive. 🐰🎊 &amp;lt;a href=&quot;https://t.co/rKZ1FF3e56&quot;&amp;gt;pic.twitter.com/rKZ1FF3e56&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;— devchallenges.io (@devchallengesio) &amp;lt;a href=&quot;https://twitter.com/devchallengesio/status/1706023452182790334?ref_src=twsrc%5Etfw&quot;&amp;gt;September 24, 2023&amp;lt;/a&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;今年は案件でも Kotlin を書く機会がほとんどなかったですね。&lt;/p&gt;
&lt;p&gt;案件チームで取り組んでいた、書籍「実践 Node.js 入門」を使った Node.js 勉強会が一巡したくらい？&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;HTML プロフェッショナル レベル2を受験してみようかと書籍も購入していたのですが、勉強に飽きてそのままになっています...。
自分の場合、ただ本を読むだけだと飽きてしまって身につくこともない傾向があるので、何か良い勉強法を見つけたい。&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;2021年から Notion で活動していましたが、Obsidian の方が良いのでは？と考えて移行途中です。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://obsidian.md/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Obsidian はマークダウンベースのエディタで、ローカルファイルベースになっているのが特徴です。
拡張性に優れていて、コミュニティプラグインなんかも多く公開されています。
書いていてちょっと楽しくもあるので、今後 Obsidian でナレッジベースを作ろうかなと構想中。&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;今年参加したイベントはこちら。
オンラインは気軽に参加できて、ありがたいです🙏&lt;/p&gt;
&lt;p&gt;※資料まとめのみ
&amp;lt;OG url=&quot;https://event.shoeisha.jp/devsumi/20230209&quot; /&amp;gt;
※会社でアーカイブ視聴会に一部参加
&amp;lt;OG url=&quot;https://cssnite.doorkeeper.jp/events/163736&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;オンライン視聴参加
&amp;lt;OG url=&quot;https://offers.connpass.com/event/298939/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://front-okinawa.connpass.com/event/299738/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://jsconf.jp/2023/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://offers.connpass.com/event/302376/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://findy.connpass.com/event/302508/&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;案件業務ではエピックリードを複数担当。
仕様がかなり複雑なものだったり、関係者の多い中規模程度のものもありました。&lt;/p&gt;
&lt;p&gt;今年の前半はよかったのですが、後半くらいからキャリアに悩むことが多かったです。
チームメンバーがガシガシキャリアを前に進んでいる感じで、それに対して自分は何ができるのか？と思ったり。&lt;/p&gt;
&lt;p&gt;自分の能力向上やキャリアの悩みに対して、能力検査や研修を受けたりしました。&lt;/p&gt;
&lt;h4&gt;EQ 検査・研修&lt;/h4&gt;
&lt;p&gt;EQ とは Emotional Intelligence Quotient の略で、心の知能指数のこと。
よく言われる IQ とは違い、先天的なものではなく、後天的に伸ばすことができる能力です。&lt;/p&gt;
&lt;p&gt;まず自分の感情を理解しよう、というところから始まり。
感情は自律神経（体調）と深く関係している -&amp;gt; 自分の感情を理解することで自分の体調を理解することにつながる。
というのは、精神疾患との付き合い方にも活かせそうだなと思いました（元々、EQ 研修に関心を持った理由の1つでもある）&lt;/p&gt;
&lt;p&gt;そのほか.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;人との関係性を楽にするコツ&lt;/li&gt;
&lt;li&gt;自分の感情を調整する&lt;/li&gt;
&lt;li&gt;モチベーションと自己成長&lt;/li&gt;
&lt;li&gt;他者理解と自己の大きな目的&lt;/li&gt;
&lt;li&gt;コミュニケーションスタイル（癖）を知る&lt;/li&gt;
&lt;li&gt;プロジェクトマネジメント、脳科学、キャリアと EQ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;といった研修に参加。&lt;/p&gt;
&lt;p&gt;自分自身のことから、リードとしてチームメンバーとの関わり方であったりと幅広く活かせそうな分野だなと。
実用的なことを抜いたとしても、興味深い話が聞けて楽しいです。
また研修の新期が始まったら参加してみようと思っています。&lt;/p&gt;
&lt;p&gt;ちなみに、この研修を受ける前と後とで EQ を測る検査をしました。
劇的な伸びがあったわけではないものの、最初に低かった部分が伸びて全体的に均等に。
均等の方がその後の能力伸ばしやすいそうなので、よい経過だなーという話をしました。&lt;/p&gt;
&lt;p&gt;EQ 検査の内容を踏まえた面談なんかも実施いただいて、とても良い話ができたなと思っています。&lt;/p&gt;
&lt;h4&gt;ジョブ・クラフティング&lt;/h4&gt;
&lt;p&gt;自分の仕事を再定義して認識や行動を変えることで、よりよい仕事ができるようにする。
というワークショップのようなものです。&lt;/p&gt;
&lt;p&gt;具体的には、こんな感じのことをしました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;今自分がやっている仕事を洗い出す
&lt;ul&gt;
&lt;li&gt;どれだけ熱量やコストをかけているかで、大中小にわける&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;自分の価値観を洗い出す&lt;/li&gt;
&lt;li&gt;自分の強みを洗い出す&lt;/li&gt;
&lt;li&gt;自分が熱量を持っていることを洗い出す&lt;/li&gt;
&lt;li&gt;洗い出した今やっている仕事をもとに、今後やっていきたい仕事を考える&lt;/li&gt;
&lt;li&gt;洗い出した仕事・価値観・強み・熱量をグループ化する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;自分自身のことを考える機会でもあるので、事前にクリフストレングスを受けてみたり、身近なメンバーに強みをヒアリングして望みました。それでもなかなか難しかったですね。
皆さん、最後のグループ化のところで一番悩むらしいです。&lt;/p&gt;
&lt;p&gt;案件業務の話も含まれているので、実際にやったもののスクショは貼らないのですが、全体の所感はこんな感じでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;強みの調和がいろんなところに影響している感じがする&lt;/li&gt;
&lt;li&gt;収集・整理が強い&lt;/li&gt;
&lt;li&gt;ドキュメント業務の比率高いので、伸ばしていきたい
&lt;ul&gt;
&lt;li&gt;ドメインエキスパートの情報をドキュメント化するみたいな動きができると良さそう&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;共感力がすべての統率を担っている&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2024年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;いつもの記事を1年で12記事は掲げつつ、半分くらいは技術の話的な記事を書きたいですねー。
せっかくエンジニアをやっているので。&lt;/p&gt;
&lt;p&gt;イベント資料まとめ記事も需要があることではあって続けようとは思っていますが、技術記事執筆と両立したいよね～という話。&lt;/p&gt;
&lt;p&gt;あとは Astro のアプデが日々行われているので、追従してメンテナンスしていきます。
Astro の新しい機能を使って改善なんかもやっていきたいですね。
Lighthouse スコア改善とか、アクセシビリティ改善とか。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;今は Docusaurus を使用しているポートフォリオサイトを、Next.js(App Router) にリプレースする構想を進めるかもしれません。
記事データがない分、ブログより楽できそうではあるのですが、デザインを少し凝ってみるかなぁとかぼんやり考えていたりします。
（デザインは沼なのでまた進まないかも😇）&lt;/p&gt;
&lt;p&gt;あとは、RSS リーダーアプリを作ってみる？という構想があったりします。
普段、RSS リーダーを使っているわけではないのですが...。
いくつか既存のアプリを使おうとしてなんかいまいち続かなかったので、自分で作ったら活用するかなぁとかぼんやり思ったという話。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;来年こそ React と Next.js の公式ドキュメント読み込みたい...。&lt;/p&gt;
&lt;p&gt;Next.js の App Router に関しては、案件業務で触れる予定なので自然とキャッチアップしていけそう。
便利な機能ができた一方で、キャッシュの使い分けとか難しくなった部分もあるので頑張ってついていきたい。&lt;/p&gt;
&lt;p&gt;リニューアルした devChallenges の課題は、引き続き合間を見つけて取り組もうと思っています。
また、似た課題を公開しているところとして、以下のサイトも気になっています。
&amp;lt;OG url=&quot;https://www.frontendmentor.io/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;フロントエンドの学習コンテンツがまとまっているサイトなので、いい感じに活用していきたい。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;Kotlin も関心はあったのですが、直近は Node.js 周りの理解を深めていくといいのかなぁと。&lt;/p&gt;
&lt;p&gt;Hono なんかは、何かの機会に使ってみたいですね。
&amp;lt;OG url=&quot;https://hono.dev/&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;来年こそは、HTML プロフェッショナル レベル2にチャレンジしてみようかな。
名前で紛らわしいですが、実質 JavaScript の試験なので業務にも活きるはず？&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;Notion から Obsidian への移行を終わらせて、Obsidian でガシガシナレッジベースを作る方針にしたいですね。&lt;/p&gt;
&lt;p&gt;プログラミングのことだけでなく、エンジニアとしての仕事に活きそうな知識全般を残していくイメージ。
キャリアに関する深堀とかもやりやすくなる...はず。&lt;/p&gt;
&lt;h3&gt;イベント参加&lt;/h3&gt;
&lt;p&gt;フロントエンド、ドキュメント、決済系のイベントは参加してみようかなと思っています。
時間が合うなら無理のない範囲で参加しようと。&lt;/p&gt;
&lt;p&gt;ただ、オフライン参加はちょっと敷居高い感じがして踏み出せないかも。&lt;/p&gt;
&lt;h3&gt;リードエンジニア的な立ち回り・キャリア&lt;/h3&gt;
&lt;p&gt;来年の案件業務では、切り込み隊長のようなことにチャレンジする予定がありまして。
自分が先行して、後からほかのメンバーが合流できるように横展開していくような感じ。&lt;/p&gt;
&lt;p&gt;あまりやったことがない動きなので少々不安はありつつ。
今年後半から停滞を感じていたキャリアへ、大きくプラスにできそうなチャレンジではあると思っています。
適度に人に頼りながら向き合っていきたいですね。&lt;/p&gt;
&lt;p&gt;また、年末にチームメンバーとの話で面白い発見がありまして。
自分は、前でリードするタイプとは違って、周りに安心感を与える守護者のような印象なのだそうです。
来年の切り込み隊長のような動きも守護範囲を広げる活動なんじゃないかと言われて、いい解釈だなぁと。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;ドメインエキスパートの守護者&lt;/strong&gt;のような動きを意識していこうかなーと思う出来事でした。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今年の振り返りではキャリアのことも触れてみました。
会社の方でも振り返りをやっているので、同じようなことを書くことにはなるのですが、技術活動とも関わることではあるかなと思ったので。&lt;/p&gt;
&lt;p&gt;無理はしないのが前提にありつつ、来年はチャレンジしたなーと思えるような年にしたいですね。&lt;/p&gt;
&lt;p&gt;では、皆様よいお年をお迎えください～。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>StyleLintアプデ対応とご一緒にpostcss-preset-envはいかが？</title><link>https://changeofpace.site/posts/2023-09-23-stylelint-postcss-preset-env</link><guid isPermaLink="true">https://changeofpace.site/posts/2023-09-23-stylelint-postcss-preset-env</guid><description>StyleLintがfixする構文のブラウザサポートが心配な時にも使える、postcss-preset-envの紹介</description><pubDate>Sat, 23 Sep 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは。
前に作ったもののメンテナンスをしていると、いろいろ発見があるねぇとぼんやり思っている、よしです。
今回は StyleLint 15系アプデ時に発見したプラグインを紹介してみます。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;要約&lt;/h2&gt;
&lt;p&gt;さらっと結論だけ知りたい方向けに要約を書いておきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;StyleLint とその基本ルールセットである stylelint-config-standard をアプデすると、fix される CSS 構文が新しくなっていく（それはそう）&lt;/li&gt;
&lt;li&gt;おおよそ各種ブラウザ対応はされているが、場合によってもう少し古いバージョンにも対応させたいことがある&lt;/li&gt;
&lt;li&gt;postcss-preset-env を使用すると、これ1つでほとんどの PostCSS をまとめて適用・管理できて旧構文の CSS を出力できるぞ
&lt;ul&gt;
&lt;li&gt;書くときは新しい構文で、出力は旧構文というのが実現できまっせ&lt;/li&gt;
&lt;li&gt;まだブラウザサポートが不安な新しい構文も書いていけるぞ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;背景&lt;/h2&gt;
&lt;p&gt;過去に作った静的ページの課題のメンテナンスをしていまして。
StyleLint 15系へのアプデ対応をしている中で、基本ルールセットである stylelint-config-standard もアプデしました。
それにより、課題を作った当時は採用を見送った比較的新しめの CSS 構文へ fix されるようになりました。
その課題を作ったのは2年前とかなので、あれから各種ブラウザ対応が進んでいることを考えると、それはそう🙄&lt;/p&gt;
&lt;p&gt;ただ、なかには一部のブラウザで少し古いバージョンだとダメそうなものがあったので、書くときは新しい構文で、出力は旧構文ということができたらいいなーと考えていました。
といいつつ、PostCSS を1つずつ適用するのは少々面倒だな…と思っていたところで見つけたのが postcss-preset-env でした。&lt;/p&gt;
&lt;p&gt;※なお、今回の使用バージョンは以下です.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;StyleLint：15.10.3&lt;/li&gt;
&lt;li&gt;stylelint-config-standard：34.0.0&lt;/li&gt;
&lt;li&gt;PostCSS：8.4.29&lt;/li&gt;
&lt;li&gt;postcss-preset-env：9.1.3&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;postcss-preset-env&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://preset-env.cssdb.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;コードはこちらのリポジトリの一部として管理されています。
&amp;lt;OG url=&quot;https://github.com/csstools/postcss-plugins&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※元々は以下のリポジトリでしたが、2022/12/14にアーカイブされました。
&amp;lt;OG url=&quot;https://github.com/csstools/postcss-preset-env&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;PostCSS Preset Env lets you convert modern CSS into something most browsers can understand, determining the polyfills you need based on your targeted browsers or runtime environments.&lt;/p&gt;
&lt;p&gt;※Google 翻訳
PostCSS Preset Env を使用すると、最新の CSS をほとんどのブラウザーが理解できるものに変換し、対象のブラウザーまたはランタイム環境に基づいて必要なポリフィルを決定できます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;postcss-preset-env は多くの PostCSS プラグインをまとめて管理できるプラグインパックです。
Can I Use、MDN から提供される、各機能のブラウザサポート情報を公開している CSS DB というものがあり。
この CSS DB を参照し、各 PostCSS プラグインを適用するか制御しているとのこと。
&amp;lt;OG url=&quot;https://cssdb.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;まさに「書くときは新しい構文で、出力は旧構文」というのが、これ1つでほとんど対応できるわけです。楽ちん。&lt;/p&gt;
&lt;p&gt;README で紹介されている変換例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@custom-media --viewport-medium (width &amp;lt;= 50rem);
@custom-selector :--heading h1, h2, h3, h4, h5, h6;

:root {
  --mainColor: #12345678;
}

body {
  color: var(--mainColor);
  font-family: system-ui;
  overflow-wrap: break-word;
}

:--heading {
  background-image: image-set(url(img/heading.png) 1x, url(img/heading@2x.png) 2x);

  @media (--viewport-medium) {
    margin-block: 0;
  }
}

a {
  color: rgb(0 0 100% / 90%);

  &amp;amp;:hover {
    color: rebeccapurple;
  }
}

/* becomes */

:root {
  --mainColor: rgba(18, 52, 86, 0.47059);
}

body {
  color: rgba(18, 52, 86, 0.47059);
  color: var(--mainColor);
  font-family: system-ui, -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen, Ubuntu, Cantarell, Droid Sans, Helvetica Neue;
  word-wrap: break-word;
}

h1, h2, h3, h4, h5, h6 {
  background-image: url(img/heading.png);
}

@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) {
  h1, h2, h3, h4, h5, h6 {
    background-image: url(img/heading@2x.png)
  }
}

@media (max-width: 50rem) {
  h1, h2, h3, h4, h5, h6 {
    margin-top: 0;
    margin-bottom: 0;
  }
}

a {
  color: rgba(0, 0, 255, 0.9)
}

a:hover {
  color: #639;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;導入方法&lt;/h3&gt;
&lt;p&gt;インストール.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm i -D postcss postcss-preset-env
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは PostCSS の設定ファイルで、このプラグインを使用するよう設定。
基本的にはこれだけで OK。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  plugins: [require(&apos;postcss-preset-env&apos;)],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;オプションを渡して設定をカスタムすることももちろんできます。&lt;/p&gt;
&lt;h3&gt;対応している PostCSS&lt;/h3&gt;
&lt;p&gt;postcss-preset-env 配下の &lt;a href=&quot;https://github.com/csstools/postcss-plugins/blob/main/plugin-packs/postcss-preset-env/src/plugins/plugins-data.mjs&quot;&gt;src/plugins/plugins-data.mjs&lt;/a&gt; を見てもらえると良さそうです。
ざっと60個以上ありますね…！すごい。
（このファイルには記載がないですが、実は autoprefixer も内包されていたりします）&lt;/p&gt;
&lt;p&gt;このリポジトリ内で管理している PostCSS プラグインも多数参照しています。
&lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugins&quot;&gt;リポジトリルートからの plugins 配下&lt;/a&gt;とそれぞれの README も見てもらえると、よりどんなものがあるかわかりますね。&lt;/p&gt;
&lt;p&gt;なお、postcss-preset-env には W3C に認められた CSS 標準機能のポリフィルとフォールバックのみが含まれているとのこと。
そのため、非標準機能や糖衣構文にあたるものは対応していないことに注意です。
もし非標準機能や糖衣構文の機能を提供する PostCSS と共存させたい場合は、PostCSS 設定にて、postcss-preset-env より先に定義する必要があります。&lt;/p&gt;
&lt;p&gt;公式の例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
	plugins: [
		&quot;postcss-syntactic-sugar&quot;,
		&quot;postcss-non-standard&quot;,
		// ...
		[
			&quot;postcss-preset-env&quot;,
			{
				// plugin options
			},
		],
		// ...
		// maybe a minifier?
	],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、一部の PostCSS はポリフィルを追加で導入 + それを読み込む設定を追加する必要があることも注意です。
詳細は &lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#plugins-that-need-client-library&quot;&gt;README - Plugins list - Plugins that need client library&lt;/a&gt; を参照ください。&lt;/p&gt;
&lt;h3&gt;オプション&lt;/h3&gt;
&lt;p&gt;ここではオプションの一部だけ紹介。&lt;/p&gt;
&lt;h4&gt;stage&lt;/h4&gt;
&lt;p&gt;各 CSS 機能が Web 標準として実装される過程における安定性を示すもの。
どの stage の CSS 機能のポリフィルを有効にするかを指定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postcssPresetEnv({ stage: 2 })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各 stage のざっくり概要.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;stage&lt;/th&gt;
&lt;th&gt;概要&lt;/th&gt;
&lt;th&gt;説明&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;Aspirational（野心的）&lt;/td&gt;
&lt;td&gt;非公式草案または編集者草案。まだまだ下書き段階。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Experimental（実験的）&lt;/td&gt;
&lt;td&gt;編集者草案または初期作業草案。これから仕様検討が進むくらいの温度感。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Allowable（許容可能）&lt;/td&gt;
&lt;td&gt;進行中草案。変更される可能性はありつつ、一部のブラウザで実装される。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Embraced（受け入れられる）&lt;/td&gt;
&lt;td&gt;推奨事項候補。少なくとも2つの一般的なブラウザに実装される。ほとんど変更されることがない。&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Standardized（標準化）&lt;/td&gt;
&lt;td&gt;勧告。一般的なブラウザによって実装される Web 標準。&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;各 stage の説明や、stage ごとの CSS 機能は CSS DB を見ると一目瞭然です。
stage 2の機能多いな👀
&amp;lt;OG url=&quot;https://cssdb.org/#the-staging-process&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;デフォルトでは&lt;code&gt;2&lt;/code&gt;が設定されています。
大体の方は2のままでも、ほとんどの目新しい機能は試せそうですね。&lt;/p&gt;
&lt;h4&gt;minimumVendorImplementations&lt;/h4&gt;
&lt;p&gt;最小ベンダー実装数のこと。
どの実装ステータスに基づいて、CSS 機能のポリフィルを有効にするか指定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postcssPresetEnv({ minimumVendorImplementations: 0 })
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;目安.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;0：どのベンダーも実装していない&lt;/li&gt;
&lt;li&gt;3：すべての主要ベンダーが実装済み&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;デフォルト値は&lt;code&gt;0&lt;/code&gt;。
もし安定している CSS 機能のみにポリフィルを有効にしたい場合は、&lt;code&gt;2&lt;/code&gt;が推奨されているようです。&lt;/p&gt;
&lt;h4&gt;features&lt;/h4&gt;
&lt;p&gt;各 PostCSS の id を指定することで、ポリフィルの有効無効を直接決定するもの。
id は&lt;code&gt;src/plugins/plugins-data.mjs&lt;/code&gt;を直接参照したり、公式ドキュメントから確認可能です。
&amp;lt;OG url=&quot;https://preset-env.cssdb.org/features&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;例えば以下の設定は&lt;code&gt;nesting-rules&lt;/code&gt;を有効にして、それ以外は stage 3 としてポリフィル有効化判定が行われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;postcssPresetEnv({
  /* use stage 3 features + css nesting rules */
  stage: 3,
  features: {
    &apos;nesting-rules&apos;: true
  }
})
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;そのほか、毎度おなじみの browserslist に関する設定や、autoprefixer の設定などがあります。
詳細は &lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env#options&quot;&gt;README - Options&lt;/a&gt; を参照ください。&lt;/p&gt;
&lt;p&gt;これらのオプション設定を踏まえて、どの PostCSS のポリフィルを有効化するか一元管理してくれるわけです。便利。&lt;/p&gt;
&lt;h2&gt;プレイグラウンド&lt;/h2&gt;
&lt;p&gt;PostCSS をまとめて面倒見てくれることは分かったけれど、実際の出力がどうなるか検証したい！
という方は、公式のプレイグラウンドを活用するとサッと検証できます。
オプションもある程度カスタムできるので、stage などの設定を変更して比較なんかも簡単です。やったぜ。
&amp;lt;OG url=&quot;https://preset-env.cssdb.org/playground&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみに StyleLint にも公式のプレイグラウンドがあります。
&amp;lt;OG url=&quot;https://stylelint.io/demo&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;元構文がどう fix されるのか差分を表示してくれるので便利ですね。
パッケージのバージョンも変更できるため、どのバージョンからこのルール適用されるようになったんだ？みたいな検証も楽にできます。やったぜ。&lt;/p&gt;
&lt;p&gt;2つを組み合わせて、StyleLint で fix されるこの構文は PostCSS 対応しているのか？と試していくとよさそうです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;これまで新しい CSS 構文が登場しても、ちょっと前のブラウザバージョンで使えなかったりして、積極的に書こうとしてこれなかったんですよね。
なので新しい構文を情報としては知りつつ、結局書く機会がないから習得できてない状態で。
とはいえ、普段フロントエンドで仕事している人間として、ちゃんと習得できた方がいいよなーと。&lt;/p&gt;
&lt;p&gt;今回知った postcss-preset-env のおかげで、新しい構文を書けるものがわーっと広がりました。
これからは StyleLint の恩恵も受けながら、積極的に新しい構文を採用していきたいですね。&lt;/p&gt;
&lt;p&gt;皆様の良い CSS ライフに繋がりますように～。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://preset-env.cssdb.org&quot;&gt;postcss-preset-env&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/csstools/postcss-plugins/tree/main/plugin-packs/postcss-preset-env&quot;&gt;GitHub - csstools - postcss-preset-env&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/csstools/postcss-preset-env&quot;&gt;GitHub - postcss-preset-env ※アーカイブ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://cssdb.org&quot;&gt;CSS DB&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stylelint.io/demo&quot;&gt;StyleLint Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://creators.bengo4.com/entry/postcss-preset-env&quot;&gt;postcss-preset-envで少し未来のCSSを予習する&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>当ブログサイトをAstroでリプレースしたので、リプレース前後を振り返ってみよう！</title><link>https://changeofpace.site/posts/2023-07-30-blog-replace-astro</link><guid isPermaLink="true">https://changeofpace.site/posts/2023-07-30-blog-replace-astro</guid><description>Jekyll -&gt; Astroリプレースにあたり、パフォーマンスや使用技術を比較しながら振り返り</description><pubDate>Sun, 30 Jul 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、今年の暑さにヘロヘロ気味のよしです。
昨年、全然手を付けられていなかったブログリプレースをやり遂げたので、この機に当ブログの履歴の振り返りをしてみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;当ブログの履歴&lt;/h2&gt;
&lt;p&gt;まずはリプレースまでの当ブログの履歴を軽く振り返ってみます。&lt;/p&gt;
&lt;h3&gt;起源&lt;/h3&gt;
&lt;p&gt;当ブログサイトを Jekyll で作ったのが2018年5月。
この頃の自分というと、エンジニア1年目のペーペーの新人時代。&lt;/p&gt;
&lt;p&gt;特にブログを作りたい意欲があったわけではなく、Jekyll というものでローカルサーバ立ち上げて何か作れるらしいぞ、という謎の意欲で作っていました。
Jekyll が SSG の1つということや、そもそも SSG がなんなのかすらよくわかってなかった気がする…。
（自分で振り返ってもよくわからん起源だ😇）&lt;/p&gt;
&lt;p&gt;当時、ブログを作りたい！ってなると WordPress に行きつく人多そうなのに、自分は全然 WordPress に行き当たらなかったなぁ。
駆け出しエンジニアで Jekyll 使ってた人を観測したことがないので、ちょっと変わったやつだったのかもしれない。&lt;/p&gt;
&lt;p&gt;そんな起源ながらも、密かに記事を書いていっていました。&lt;/p&gt;
&lt;h3&gt;Web 上に公開&lt;/h3&gt;
&lt;p&gt;Web 上に公開したのが2018年の年度末。&lt;/p&gt;
&lt;p&gt;当時、親交があった会社の先輩とブログの話をしているうちに「公開しちゃいなよ」的なことを言われて。
「Netlify で公開すると楽」と教えてもらって、画面共有しながらデプロイした記憶があります。
確かに楽でした。懐かしいですね。&lt;/p&gt;
&lt;p&gt;それから独自ドメイン割り当てたり、各種アクセス解析ツールや広告を導入したり、サイトを運営していくにあたり必要なことを少しずつやっていきました。&lt;/p&gt;
&lt;h3&gt;リプレースへ&lt;/h3&gt;
&lt;p&gt;2020年末の振り返り時には、リプレースしたいなぁって言ってました。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://changeofpace.site/posts/2020-12-31-looking-back-2020-tech&quot;&gt;2020年振り返り ～技術活動、ブログ編～&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Jekyll によるブログサイトでも特に困ったことはなかったのですが、Ruby 製というのが気になってきて。
というのも、この頃は自分のキャリアに悩んだ結果、フロントエンドに転向しようかなと思っていた時期でした。
フロントエンドとなると、TypeScript なり Node.js なりが身近になるなか、ここだけ Ruby というのもどうなのかなぁと。
自分が関心のある言語に Ruby がなくなったのと、普段扱う技術スタックをある程度狭めたかったこともあり、リプレース構想をするようになりました。&lt;/p&gt;
&lt;p&gt;Jekyll の時は公開されているテーマを使用していたので、リプレース時にオリジナルのデザインにしたいなー。モノクロ基調のかっこいいデザインにしたいなーとか。
ただ、そこから実際に着手ができずで、これはデザインを凝りだすと当分はリプレースできないな…というのでデザインに凝るのはあきらめました。&lt;/p&gt;
&lt;h2&gt;リプレースしたもの&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/change-of-pace-astro&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;レイアウト比較&lt;/h3&gt;
&lt;p&gt;大まかなレイアウト比較として、トップページと記事ページを載せてみます。
ちなみに、なんかえらい余白空いているところやぼかしているところは広告の箇所です。&lt;/p&gt;
&lt;h4&gt;デスクトップ&lt;/h4&gt;
&lt;p&gt;トップページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-jekyll-desktop.png&quot; alt=&quot;リプレース前のトップページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-astro-desktop.png&quot; alt=&quot;リプレース後のトップページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;記事ページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-post-jekyll-desktop.png&quot; alt=&quot;リプレース前の記事ページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-post-astro-desktop.png&quot; alt=&quot;リプレース後の記事ページ - デスクトップ&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h4&gt;モバイル&lt;/h4&gt;
&lt;p&gt;（リプレース前に撮っていた Jekyll の頃のスクショがバグっていたので、過去の Deploy Preview で撮りなおした結果、広告が表示されていません）&lt;/p&gt;
&lt;p&gt;トップページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-jekyll-mobile.png&quot; alt=&quot;リプレース前のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-astro-mobile.png&quot; alt=&quot;リプレース後のトップページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;記事ページ.
&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- リプレース前の画像は大きすぎて最適化表示できなかったので、通常の img タグにしてる --&amp;gt; */}&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;img src=&quot;/screenshots/2023/blog-replace-astro/changeofpace-post-jekyll-mobile.png&quot; alt=&quot;リプレース前の記事ページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/changeofpace-post-astro-mobile.png&quot; alt=&quot;リプレース後の記事ページ - モバイル&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h2&gt;リプレースで採用してみた主な技術&lt;/h2&gt;
&lt;p&gt;リプレースにあたり、採用した主な技術を振り返ってみます。&lt;/p&gt;
&lt;h3&gt;Astro&lt;/h3&gt;
&lt;p&gt;主となるフレームワークに選定したのは Astro。
&amp;lt;OG url=&quot;https://astro.build&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;Astro ってどんなもの？&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Astro is the all-in-one web framework designed for speed. Pull your content from anywhere and deploy everywhere, all powered by your favorite UI components and libraries.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Astro は、速度を重視して設計されたオールインワン Web フレームワークです。お気に入りの UI コンポーネントとライブラリを利用して、どこからでもコンテンツを取得し、どこにでも展開できます。&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ざっくり言うと、コンテンツ重視の高速な Web サイトを構築するためのフレームワークです。
ビルド時に不要な JavaScript を取り除き、プレーンな HTML に近い状態となることもあり、高速に動作してくれます。&lt;/p&gt;
&lt;p&gt;元々は SSG に特化していましたが、2系からは特定のページだけ SSR する、なんてこともできるようになりました。
あらゆる形式のデータを提供するエンドポイントという機能があるので、動的に画像や RSS を生成して返したり、API ルートとして使うこともできたりします。&lt;/p&gt;
&lt;p&gt;それと面白い特徴の1つとして、React や Vue、Svelte などのコンポーネントも共存できることがあったりします。
使い慣れたフレームワークのコンポーネントを作るもよし。
JSX ライクにも書ける Astro コンポーネントを作るもよし。&lt;/p&gt;
&lt;h4&gt;採用した背景&lt;/h4&gt;
&lt;p&gt;リプレース構想の最初の頃は Next.js でやろうかなーと思っていました。
移行にあたりライブラリの調査なんかはしていたものの、実作業はなかなか手を付けられないまま2022年後半になり。
いい加減、どうにか進めたいなと考えていた頃に、Astro を知りました。&lt;/p&gt;
&lt;p&gt;Astro は元々 SSG 特化のフレームワークだったこともあってか、すでにブログで使えるような機構が備わっていたんですよね。
Next.js だと自分で機構を用意する必要があるので、Astro の方が移行楽そうだなと。
また、Next.js は色々できる分、ブログにはオーバースペックなのでは？
JavaScript を必要最低限まで減らすアプローチの Astro の方がパフォーマンス良いのでは？
などと思うようになり、Astro を採用することにしました。&lt;/p&gt;
&lt;h3&gt;UnoCSS&lt;/h3&gt;
&lt;p&gt;スタイリングには UnoCSS を採用しました。
（グローバルスタイルや記事コンテンツ部分は Sass も併用）&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://unocss.dev&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;UnoCSS ってどんなもの？&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Instant On-demand Atomic CSS Engine&lt;/strong&gt;
&lt;strong&gt;Customizable · Powerful · Fast · Joyful&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;UnoCSS は Vue.js、Nuxt.js、Vite のコアチームメンバーである、Anthony Fu 氏による OSS です。
Anthony Fu 氏がどのような背景で UnoCSS を開発するに至ったかは、ご本人の以下の記事で語られています。
&amp;lt;OG url=&quot;https://antfu.me/posts/reimagine-atomic-css&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;UnoCSS はフレームワークでなく エンジンとなっており、自分でカスタムルールを定義したり、それをプリセットとして他のユーザと共有したりが可能です。
一方でいくつかのプリセットが同梱されており、デフォルトのプリセットを使用すると、以下のような代表的なフレームワークのスーパーセットを提供してくれます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Tailwind CSS&lt;/li&gt;
&lt;li&gt;Windi CSS&lt;/li&gt;
&lt;li&gt;Bootstrap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;プリセットの中には、Iconify がベースとなった各種アイコンや Web フォントを気軽に使用できるものもあったりします。便利。
ちなみにアイコンの一覧はこちら。
&amp;lt;OG url=&quot;https://icones.js.org&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;採用した背景&lt;/h4&gt;
&lt;p&gt;自分としては独自のカスタムルールを定義したかったわけではなく。&lt;/p&gt;
&lt;p&gt;デザインに凝るのをあきらめた関係上、Tailwind CSS を使うと開発速度早くなるかなぁと思ったのですが。
そのまま使うより UnoCSS を通して使用した方がパフォーマンス良いんじゃないかと考えて採用したのでした。
（なので、デフォルトプリセットを使用しつつ、Tailwind CSS のクラスをメインで使用しています）&lt;/p&gt;
&lt;p&gt;Anthony Fu 氏の記事に記載されていたベンチマークに衝撃を受けたのもありましたね。
&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/unocss-benchmark-2021-10.png&quot; alt=&quot;2021/10/21のベンチマーク UnoCSSを1として、windicssが195.36倍、Tailwind CSSが251.28倍&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;（2022/10のものだと、だいぶ縮まっていましたが）
&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/unocss-benchmark-2022-10.png&quot; alt=&quot;2022/10/25のベンチマーク UnoCSSを1として、Tailwind CSSが6.08倍、windiCSSが8.38倍&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;あとはアイコン気軽に使えるのいいなーと思ったのもあります。&lt;/p&gt;
&lt;h2&gt;リプレースにあたり、機能の変遷&lt;/h2&gt;
&lt;p&gt;Jekyll からリプレースするにあたり、画面一覧や機能を洗い出してリプレース先の技術でどうやって実装できるのか？というのを調査していきました。
加えて、リプレース時に新しく対応したい機能に関しても調査。&lt;/p&gt;
&lt;p&gt;元々、Next.js でリプレース検討していたので、最初は Next.js ベースで調査していました。
その後、Astro 版も調査して比較すると、やはり Astro の方が組み込み機能や Integration で手軽に実現できるものが多い印象でしたね。&lt;/p&gt;
&lt;p&gt;基本部分.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;テンプレートエンジン&lt;/td&gt;
&lt;td&gt;Liquid ベース&lt;/td&gt;
&lt;td&gt;（JSX ライクな Astro 記法）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;テーマ&lt;/td&gt;
&lt;td&gt;jekyll-theme-hydeout&lt;/td&gt;
&lt;td&gt;自作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;フォント&lt;/td&gt;
&lt;td&gt;Abril Fatface, Sawarabi Mincho, HackGen&lt;/td&gt;
&lt;td&gt;Abril Fatface, Sawarabi Mincho, HackGen&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ナビゲーションリンク&lt;/td&gt;
&lt;td&gt;テーマベース&lt;/td&gt;
&lt;td&gt;自作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;カテゴリー&lt;/td&gt;
&lt;td&gt;Jekyll 組み込み&lt;/td&gt;
&lt;td&gt;Astro 組み込み機能を使って自作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;タグ&lt;/td&gt;
&lt;td&gt;Jekyll 組み込み&lt;/td&gt;
&lt;td&gt;Astro 組み込み機能を使って自作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;お問い合わせ&lt;/td&gt;
&lt;td&gt;Google Form 埋め込み&lt;/td&gt;
&lt;td&gt;Google Form 埋め込み&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ページネーション&lt;/td&gt;
&lt;td&gt;jekyll-paginate-v2&lt;/td&gt;
&lt;td&gt;Astro 組み込み&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;記事のもっと見る&lt;/td&gt;
&lt;td&gt;Jekyll  excerpt_separator オプション&lt;/td&gt;
&lt;td&gt;廃止&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;マークダウン周り.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;yaml front matter&lt;/td&gt;
&lt;td&gt;Jekyll 組み込み&lt;/td&gt;
&lt;td&gt;Astro 組み込み&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;マークダウン解析〜変換&lt;/td&gt;
&lt;td&gt;kramdown ベース&lt;/td&gt;
&lt;td&gt;remark, rehype ベース&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;MDX&lt;/td&gt;
&lt;td&gt;（なし）&lt;/td&gt;
&lt;td&gt;@astrojs/mdx&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;改行認識&lt;/td&gt;
&lt;td&gt;kramdown の hard_wrap オプション&lt;/td&gt;
&lt;td&gt;remark-breaks&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;目次&lt;/td&gt;
&lt;td&gt;kramdaown の toc 機能&lt;/td&gt;
&lt;td&gt;（Astro 組み込み機能を使ってメニューにした）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;自動アンカーリンク生成&lt;/td&gt;
&lt;td&gt;jekyll-anchor-headings&lt;/td&gt;
&lt;td&gt;rehype-autolink-headings&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;コードブロックのタイトル&lt;/td&gt;
&lt;td&gt;（なし）&lt;/td&gt;
&lt;td&gt;remark-flexible-code-titles&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;コードブロックのコピーボタン&lt;/td&gt;
&lt;td&gt;（なし）&lt;/td&gt;
&lt;td&gt;自作&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;シンタックスハイライト&lt;/td&gt;
&lt;td&gt;Rouge ベース&lt;/td&gt;
&lt;td&gt;Shiki ベース&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;補助機能.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;リンクカード&lt;/td&gt;
&lt;td&gt;（なし）&lt;/td&gt;
&lt;td&gt;hiroppyさんのやり方を参考に実装&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;RSS&lt;/td&gt;
&lt;td&gt;jekyll-feed&lt;/td&gt;
&lt;td&gt;@astrojs/rss&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;サイトマップ&lt;/td&gt;
&lt;td&gt;jekyll-sitemap&lt;/td&gt;
&lt;td&gt;＠astrojs/sitemap&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SNS シェアボタン&lt;/td&gt;
&lt;td&gt;Twitter、はてブ&lt;/td&gt;
&lt;td&gt;（一旦、廃止）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;関連記事表示&lt;/td&gt;
&lt;td&gt;（デフォルトの最新記事表示のままにしていた）&lt;/td&gt;
&lt;td&gt;（廃止）&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Analytics&lt;/td&gt;
&lt;td&gt;公式のスニペットコード&lt;/td&gt;
&lt;td&gt;公式のスニペットコード + @astrojs/partytown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Search Console&lt;/td&gt;
&lt;td&gt;公式のスニペットコード&lt;/td&gt;
&lt;td&gt;公式のスニペットコード&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Google Adsence&lt;/td&gt;
&lt;td&gt;公式のスニペットコード&lt;/td&gt;
&lt;td&gt;公式のスニペットコードベースで遅延読み込み&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;基本部分&lt;/h3&gt;
&lt;h4&gt;テンプレートエンジン&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://shopify.github.io/liquid&quot; /&amp;gt;
Jekyll では Liquid が採用されていました。
Shopify でも採用されていたりして割とメジャーなのかなと思っていたら、そもそも Shopify が作った言語じゃん…ということを執筆中に気づいたという〜😇
自分はあまり馴染みがなかったので、多少カスタムするのに書いていたくらいでした。&lt;/p&gt;
&lt;p&gt;Astro では JSX ライクに書ける構文となっているため、普段 React や Next.js のコードを書いている自分にとっては扱いやすく、サイトをカスタムしやすくなりました。
リプレースにあたり、もしテンプレートエンジンで書くとしていたら、多分リリースまでモチベーションがもってなかったと思います。型定義がある安心感。&lt;/p&gt;
&lt;p&gt;逆にテンプレートエンジンが慣れている人にとっては、JSX にとっつきにくさがあるかもしれませんね。
といっても、Astro では素の HTML をそのまま Astro ファイルにしても割と動いてくれたりするので、React～Next.js よりは敷居が低そうな印象です。&lt;/p&gt;
&lt;h4&gt;テーマ&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/fongandrew/hydeout&quot; /&amp;gt;
Jekyll の頃は公開されている Hydeout テーマを使用していました。
全体的にモノクロ基調寄りでカッコいいなーと思って採用。&lt;/p&gt;
&lt;p&gt;リプレースにあたっては、モノクロ基調寄りは維持しつつも自作でカッコいいデザインにしたいと構想。
Web 上にあるモノクロ基調のサイトのデザインを参考にしようかなと思っていた頃もありましたが、1からデザインを学ぶとすればいつまでもリプレースできなさそうな気がしまして…。&lt;/p&gt;
&lt;p&gt;あくまでブログサイトなので、見た目に凝ったデザインというより、メインコンテンツである記事が読みやすいことを優先することにしました。
それにあたり、Zenn のような技術記事のプラットフォームや、Astro で作成された技術ブログの構成を参考にさせていただきながらレイアウトを構築していきました。&lt;/p&gt;
&lt;p&gt;自作するとなんだか愛着がわきますね。&lt;/p&gt;
&lt;h4&gt;フォント&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;Abril Fatface：ブログタイトルのみ&lt;/li&gt;
&lt;li&gt;Sawarabi Mincho：テキスト全般&lt;/li&gt;
&lt;li&gt;HackGen：インラインコード、コードブロック&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Abril Fatface は元々使用していた jekyll-theme-hydeout テーマの中で、ブログタイトル部分に使われていて、なんかカッコよかったので続投。
Sawarabi Mincho は日本語フォントの中でお気に入りのフォントです。線の強弱があるので可読性に優れていたり、美しい文字のような印象を受けたので採用しています。
HackGen は VSCode のフォントにも設定しているなど、コードを書くうえでも普段使いしているフォントです。読みやすくて好きです。&lt;/p&gt;
&lt;p&gt;前者2つは Web フォントであるため、UnoCSS の Web Fonts preset を使って利用。
HackGen はリポジトリのリリースからダウンロードして、UnoCSS のテーマに登録したうえで利用。
&amp;lt;OG url=&quot;https://github.com/yuru7/HackGen&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;ナビゲーションリンク&lt;/h4&gt;
&lt;p&gt;Jekyll の頃はテーマベースで、デスクトップ表示では画面左側に配置、モバイル表示ではページ上部に配置のナビゲーションリンクでした。
デスクトップ表示はともかく、モバイル表示でナビゲーションリンクを使いたいときに、いちいちページ上部にスクロールしないといけないのが気になってしまい。&lt;/p&gt;
&lt;p&gt;リプレースにあたり自作。
デスクトップ表示では画面左側にサイドバーとして追従配置、モバイル表示では固定ヘッダーからメニューで使用できるようにしました。
メニューを開いたときに、固定で画面右上に表示だったり、ぼかしをいれるところは Tailwind CSS の公式ドキュメントを参考にしています。&lt;/p&gt;
&lt;h4&gt;カテゴリー&lt;/h4&gt;
&lt;p&gt;Jekyll の頃は元からサポートされており、front matter でカテゴリー指定をすれば、割とスッと使えるようになっていました。
（カテゴリー名の日本語化は別途一工夫したような）&lt;/p&gt;
&lt;p&gt;Astro においては、front matter と動的ルーティングの組み合わせで実現しています。
&lt;code&gt;getStaticPaths&lt;/code&gt;でルーティング生成時に、全記事取得から front matter に指定したカテゴリーで絞り込みをするイメージ。
Next.js の動的ルーティングに慣れている人なら、あっさり対応できると思います。&lt;/p&gt;
&lt;h4&gt;タグ&lt;/h4&gt;
&lt;p&gt;Jekyll の頃については、カテゴリーと同様で元からサポートされていましたが、タグごとの記事一覧ページは特に使用していませんでした。&lt;/p&gt;
&lt;p&gt;Astro においても、カテゴリーと大体やっていることは同じです。
これからはタグごとの記事一覧ページも活用するようにしました。&lt;/p&gt;
&lt;p&gt;それと、タグ一覧ページで新たに技術アイコンを使いたいなーと思いまして、UnoCSS の Icons preset から SVG Logos を使用するようにしました。ちょっとにぎやかにできましたね。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2023/blog-replace-astro/changeofpace-tags.png&quot; alt=&quot;当ブログのタグ一覧表示&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;UnoCSS の Icons presets は Iconfy がベースになっており、この presets  と使いたい Iconfy のデータソースをインストール。
&lt;code&gt;i-logos:react&lt;/code&gt;のように&lt;code&gt;i-&lt;/code&gt;のプレフィックスがついたクラスを当てるだけで使用できます。&lt;/p&gt;
&lt;p&gt;注意点として、UnoCSS はビルド時にクラスの静的抽出を使用して動作します。
その関係上、動的に割り当てているクラスを検出できず、そのクラスのスタイルを生成してくれません（そのクラスが静的にどこからも使われていなければ）&lt;/p&gt;
&lt;p&gt;各タグに応じたアイコンを動的に割り当てるにあたり、Safelist に登録して、強制的にそのクラスのスタイルを生成してくれるように対応しました。
&amp;lt;OG url=&quot;https://unocss.dev/guide/extracting#safelist&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;お問い合わせ&lt;/h4&gt;
&lt;p&gt;Google Form 埋め込みを継続にしました。
リプレースにあたり、Google Form を利用しつつも見た目をカスタムする手もありましたが、今のところ対応していません。&lt;/p&gt;
&lt;p&gt;フリーランスの人が仕事探しにも活用するポートフォリオサイトとかなら対応した方がよいかと思いますが、雑多なこと書いてるブログサイトなので別に優先度落としてよいかな…と。&lt;/p&gt;
&lt;h4&gt;ページネーション&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/jekyll/jekyll-paginate&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/sverrirs/jekyll-paginate-v2&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Jekyll には元々&lt;code&gt;Jekyll-paginate&lt;/code&gt; プラグインが導入されていて、ページネーション機能が備わっていたのですが、カテゴリー記事一覧ページには対応していない課題がありました。
カテゴリー記事一覧ページでも利用したい場合は&lt;code&gt;jekyll-paginate2&lt;/code&gt;を使うよう、公式からも案内されていたのでこちらを利用。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.astro.build/ja/core-concepts/routing/#ページ分割&quot; /&amp;gt;
Astro では組み込みの&lt;code&gt;paginate()&lt;/code&gt;関数を使用することで、ページネーションを実現しています。&lt;/p&gt;
&lt;h4&gt;記事のもっと見る&lt;/h4&gt;
&lt;p&gt;記事一覧ページには冒頭文だけ表示して、もっと見るを押すと記事ページに遷移する、というあれです。
Jekyll の頃は、excerpt_separator オプションを利用することで実現が可能でした。
自分の記事で冒頭挨拶文があるのは、この機能の影響ですね。&lt;/p&gt;
&lt;p&gt;特にこだわりがあったわけではなかったのと、Astro で実現するよさそうな方法がうまく見つけられなかったので、思い切って廃止にしました。&lt;/p&gt;
&lt;h3&gt;マークダウン周り&lt;/h3&gt;
&lt;h4&gt;yaml front matter&lt;/h4&gt;
&lt;p&gt;記事のメタデータを記述するところです。
どちらも組み込みで機能が備わっていたので、特に困らなかったですね。
Astro では、Zod が組み込まれていて front matter の型定義ができたので、より厳密に管理できるようになりました。&lt;/p&gt;
&lt;h4&gt;マークダウン解析〜変換&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://kramdown.gettalong.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Jekyll では kramdown ベースになっており、オプションを設定することでカスタムが可能になっていました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/remarkjs/remark&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/rehypejs/rehype&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Astro では元々組み込まれている &lt;code&gt;@astrojs/markdown-remark&lt;/code&gt; で処理しているようで、remark と rehype がベースになっています。
双方のプラグインによる拡張をサポートしているため、豊富なプラグインで割といろんなカスタムができます。ちょっと楽しい。&lt;/p&gt;
&lt;h4&gt;MDX&lt;/h4&gt;
&lt;p&gt;Jekyll の頃は特に使用しておらず、普通にマークダウンで記事を書いていました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.astro.build/ja/guides/integrations-guide/mdx&quot; /&amp;gt;
Astro では &lt;code&gt;@astrojs/mdx&lt;/code&gt; Integration を使うことでさっくり MDX に対応できました。
コンポーネントが使えるようになるので、表現の幅が広がりますね。
最初はマークダウンでいいかなと思っていたのですが、後述のリンクカードのコンポーネントの都合で MDX を使うようにしています。&lt;/p&gt;
&lt;h4&gt;改行認識&lt;/h4&gt;
&lt;p&gt;マークダウンにある改行をそのまま改行として認識するか、というやつです。
デフォルトでは認識されないので、半角スペース2つ等で改行させる必要があり、これが面倒だったので自分はそのまま認識してくれるようにしています。&lt;/p&gt;
&lt;p&gt;Jekyll の頃は、kramdown の hard_wrap オプションでさっくり対応できました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/remarkjs/remark-breaks&quot; /&amp;gt;
Astro では remark-breaks プラグインを導入して対応しています。
プラグインの適用さえすれば、特に設定は不要なので楽ちんでした。&lt;/p&gt;
&lt;h4&gt;目次&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://kramdown.gettalong.org/converter/html.html#toc&quot; /&amp;gt;
Jekyll の頃は kramdown の機能として備わっていたので、そちらを使って記事ページ冒頭文の後に目次として配置していました。&lt;/p&gt;
&lt;p&gt;Astro では記事データにある見出しを抽出してくれる機能が備わっているため、それで実現しています。
ただ、目次というよりは、ナビゲーションリンクのようにどこからでも使えるようにしたいなーと思い。
デスクトップ表示では、画面左側のサイドバーでナビゲーションリンクと切り替えて利用できるように。
モバイル表示では、固定ヘッダーからメニューとして利用できるようにしました。&lt;/p&gt;
&lt;p&gt;なお、デスクトップ表示において、サイドバーの下部に配置している構成をよく見かけます。
今回はナビゲーションリンクでそこそこ縦幅をとっている + 追従配置にしている関係で、画面の縦幅によっては扱いずらい感じになりそうだったので、ナビゲーションリンクと切り替えできる形をとりました。
（あまり見たことがない構成なので、実はアンチパターンだったりするかもしれない？）&lt;/p&gt;
&lt;h4&gt;自動アンカーリンク生成&lt;/h4&gt;
&lt;p&gt;各見出しのところで # とかリンクアイコンで、その見出しのアンカーリンクが取得できるやつです。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/allejo/jekyll-anchor-headings&quot; /&amp;gt;
Jekyll の頃は jekyll-anchor-headings で対応していました。
Gem として導入するのではなく、最新の該当 HTML ファイルをダウンロードして導入するようになっていますが、比較的楽ちんです。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/rehypejs/rehype-autolink-headings&quot; /&amp;gt;
Astro では rehype-autolink-headings で対応しました。&lt;/p&gt;
&lt;p&gt;注意点として、Astro は rehype プラグインが実行された後に id 属性を注入するようになっています。
そのまま使うと rehype-autolink-headings 実行時に id 属性がない状態になり。うまくリンクが生成されないため、&lt;code&gt;@astrojs/markdown-remark&lt;/code&gt;の&lt;code&gt;rehypeHeadingIds&lt;/code&gt;と組み合わせて使用するようにしています。&lt;/p&gt;
&lt;h4&gt;コードブロックのタイトル&lt;/h4&gt;
&lt;p&gt;Jekyll の頃は対応していませんでしたが、よく対応しているサイトを見かけるのでリプレース時に対応したいなーと思っていました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/mottox2/remark-code-titles&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/ipikuka/remark-flexible-code-titles&quot; /&amp;gt;
リプレースにあたり、最初は remark-code-titles で対応していました。
ただ、タイトルの要素が単純にコードブロックの pre 要素の前に生成されることで、後述のコピーボタンの配置と相性の悪い問題があり。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
&amp;lt;div class=&quot;remark-code-title&quot;&amp;gt;hello.js&amp;lt;/div&amp;gt;
&amp;lt;pre&amp;gt;&amp;lt;code class=&quot;language-js&quot;&amp;gt;console.log(&apos;js&apos;)
&amp;lt;/code&amp;gt;&amp;lt;/pre&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;remark-flexible-code-titles であれば、コンテナとなる要素でラップした中にタイトルの要素が生成される構造になるため、こちらを採用しました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;div class=&quot;remark-code-container&quot;&amp;gt;
  &amp;lt;div class=&quot;remark-code-title&quot;&amp;gt;title.js&amp;lt;/div&amp;gt;
  &amp;lt;pre&amp;gt;
    &amp;lt;code class=&quot;language-javascript&quot;&amp;gt;let me = &quot;ipikuka&quot;;&amp;lt;/code&amp;gt;
  &amp;lt;/pre&amp;gt;
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;コードブロックのコピーボタン&lt;/h4&gt;
&lt;p&gt;これも同様に Jekyll の頃は対応していませんでしたが、リプレース時に対応したいなーと思っていました。
remark-flexible-code-titles で生成される構造に合わせて、自作スクリプトで生成するようにしています。&lt;/p&gt;
&lt;h4&gt;シンタックスハイライト&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/rouge-ruby/rouge&quot; /&amp;gt;
Jekyll の頃は Rouge ベース。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://shiki.matsu.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://prismjs.com&quot; /&amp;gt;
Astro では Shiki と Prism を組み込みサポートしていますが、Prism を使う場合は別途 CSS を用意する必要があるので注意です。
自分はデフォルトの Shiki を使用しています。&lt;/p&gt;
&lt;h3&gt;補助機能&lt;/h3&gt;
&lt;h4&gt;リンクカード&lt;/h4&gt;
&lt;p&gt;Zenn の記事で URL を書くと OGP カード表示に変えてくれる的なやつです。
Zenn であの表示を見てからは自分もこれやりたいなーと思うようになりました。&lt;/p&gt;
&lt;p&gt;Jekyll の頃は対応しておらず、リンクは普通にマークダウンのリンクで記述していました。
自分はブログと Zenn にクロス投稿をしている関係で記事内容を転記しているのですが、リンク部分を書き換えるの地味に面倒で…。
それを解消する意味でもどうにか対応できないかなと調査。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/zenn-dev/zenn-editor&quot; /&amp;gt;
最初に Zenn の zenn-markdown-html のコードを読んでみて、リンクカード変換の部分を参考にできないかなと思ったのですが、どうにも難しそうであきらめました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/gladevise/remark-link-card&quot; /&amp;gt;
remark-link-card でお手軽に対応できそうではあったものの、最終更新が2年前なのと、HTML 構造をカスタムしたかったので見送り。&lt;/p&gt;
&lt;p&gt;結果的に、hiroppy さんがやられていた方法がいいなーと思い、参考にさせていただくことにしました。
（hiroppy さんも Astro でサイトをリプレースされていて、サイト構成から参考にさせていただいていました）
&amp;lt;OG url=&quot;https://hiroppy.me/blog/migrate-blog-from-hatena&quot; /&amp;gt;
具体的にどんなことをやられているかというと、ざっくりこんな感じです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;リンクカード用のコンポーネントを作成
&lt;ul&gt;
&lt;li&gt;props で渡された URL の OGP 情報を fetch して、それで要素を構築
&lt;ul&gt;
&lt;li&gt;本番ビルド時のみ、取得した OGP 情報を JSON にキャッシュ&lt;/li&gt;
&lt;li&gt;キャッシュがある場合はそちらから OGP 情報を表示する&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MDX でそれを利用してリンクを記述するようにする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;OGP 情報をビルド時に取得するので、ビルド後の静的ファイルとして表示時には表示が早くなる。
キャッシュができるので、ビルドも早くできる。
というメリットがありつつ、リンクカードの内容が常に最新になるわけではないデメリットもあります。
そこらへんは定期 JOB などでカバーすればよいかなと思っています。
（今のところまだ作ってないです）&lt;/p&gt;
&lt;p&gt;MDX でリンクカード用コンポーネントを使用することになるので、ブログと Zenn のリンク構文を一致させる目的は達成できていませんが…。
リンクカード用コンポーネント呼出しを記述するスニペットは作ったので、それでいくらかマシにはなりました。&lt;/p&gt;
&lt;p&gt;ファビコン取得部分に関しては、Google の非公開 API を使用するようにしました。
非公開 API ということは急に使えなくなる可能性があるので、最初は meta 情報からファビコン URL を抽出するようにしていたのですが…。
割といろんな URL パターンを観測して分岐処理を書くのがつらくなってきたので、あきらめました。&lt;/p&gt;
&lt;h4&gt;RSS&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/jekyll/jekyll-feed&quot; /&amp;gt;
Jekyll の頃は&lt;code&gt;jekyll-feed&lt;/code&gt;を利用。使用するプラグインに追加するだけなので楽ちん。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.astro.build/ja/guides/rss&quot; /&amp;gt;
Astro では、エンドポイント機能と&lt;code&gt;@astrojs/rss&lt;/code&gt;の組み合わせが公式で紹介されていたので、そちらで対応。
このブログサイトは技術の話だけでなく雑多な話をしているので、カテゴリーごとの RSS も取得できるようにしました。&lt;/p&gt;
&lt;h4&gt;サイトマップ&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/jekyll/jekyll-sitemap&quot; /&amp;gt;
Jekyll の頃は&lt;code&gt;jekyll-sitemap&lt;/code&gt;を利用。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.astro.build/ja/guides/integrations-guide/sitemap&quot; /&amp;gt;
Astro では、&lt;code&gt;@astrojs/sitemap&lt;/code&gt;で対応。本番ビルド時にサイトマップを生成してくれます。
&lt;code&gt;sitemap-0.xml&lt;/code&gt;と&lt;code&gt;sitemap-index.xml&lt;/code&gt;を生成し、後者から前者を参照するような構造になっていたのがちょっと不思議でしたが、特に困ったことはなかったです。&lt;/p&gt;
&lt;h4&gt;SNS シェアボタン&lt;/h4&gt;
&lt;p&gt;Jekyll の頃は、Twitter とはてブのシェアボタンを設置していました。
ただ、使用している人がいなさそうだったのと、最近の Twitter の動向が怪しかったので一旦廃止に。&lt;/p&gt;
&lt;h4&gt;関連記事表示&lt;/h4&gt;
&lt;p&gt;Jekyll では一応この機能が備わってはいたのですが、デフォルトで最新記事を表示するものになっていました。&lt;/p&gt;
&lt;p&gt;どうやらプラグインを追加して、起動時に LSI オプションをつけておくと関連記事のインデックスを生成してくれるようになっていたようですが、自分はここまでやっていなかったです…。
結局は活用していなかった、ということでリプレースでは廃止。&lt;/p&gt;
&lt;h4&gt;Google Analytics&lt;/h4&gt;
&lt;p&gt;引き続き公式のスニペットコードを使用して導入しています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.astro.build/ja/guides/integrations-guide/partytown&quot; /&amp;gt;
ただ、普通に導入すると Lighthouse のスコアに影響が出るということで、Astro では&lt;code&gt;@astrojs/partytown&lt;/code&gt;を使用して影響が出ないようにしました。&lt;/p&gt;
&lt;p&gt;Partytown は遅延読み込みライブラリの一種で、スクリプトをメインスレッドでなく Web ワーカーの方で処理してくれるようです。&lt;/p&gt;
&lt;h4&gt;Google Search Console&lt;/h4&gt;
&lt;p&gt;google-site-verification の meta 情報埋め込みを引き続き行っています。&lt;/p&gt;
&lt;h4&gt;Google Adsense&lt;/h4&gt;
&lt;p&gt;Jekyll の頃は、公式のスニペットコードをパラメータを少し調整した上で導入していました。&lt;/p&gt;
&lt;p&gt;Astro で導入するにあたっては、遅延読み込みで対応しました。
最初は、これも Partytown が使えるかなと思っていたのですが、うまく動作してくれなかったので断念。
普通に読み込むと、Adsense が生成する要素で Lighthouse の減点を食らったのでどうにかしたいが、こちらが手を加えられない箇所だしな…となり。&lt;/p&gt;
&lt;p&gt;調査をすると、どうも遅延読み込みをしてカバーするテクニックがあるとヒットしたので、遅延読み込みで対応することにしました。
主にこちらのサイト様のやり方を参考にさせていただいています。
&amp;lt;OG url=&quot;https://mamenaka.jp/blog/pagespeed-insights-100-astro-blog&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;この遅延読み込みの欠点としては、ファーストビューに広告があると明らかに遅延読み込みしているとわかり、あまりよくないことでしょうか。
（自分がリプレースした）サイトではまさにファーストビューにも広告があったので悩ましかったのですが、まぁ元々オマケ程度につけてるくらいだしな…というので許容としました。&lt;/p&gt;
&lt;h2&gt;パフォーマンス比較&lt;/h2&gt;
&lt;p&gt;Chrome 拡張の干渉を避けるため、シークレットウィンドウの Lighthouse で検証。
一部、リプレース前より悪くなったものもありつつ、全体的にはスコア向上になった印象ですね。&lt;/p&gt;
&lt;p&gt;Astro はその特徴から、そんなに神経使って頑張らなくても高スコアが狙いやすい気はしました。
じゃあ Jekyll は良くなかったのかというと、自分がリプレース前にパフォーマンスをあまり意識していなかったので、ちゃんと意識している人は高スコア出せていると思っています。&lt;/p&gt;
&lt;h3&gt;トップ（/）&lt;/h3&gt;
&lt;p&gt;デスクトップ.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-top-desktop.png&quot; alt=&quot;リプレース前のトップページ・デスクトップスコア - パフォーマンス：98, ユーザー補助：66, おすすめの方法：100, SEO：90&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-top-desktop.png&quot; alt=&quot;リプレース後のトップページ・デスクトップスコア - パフォーマンス：100, ユーザー補助：100, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;モバイル.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-top-mobile.png&quot; alt=&quot;リプレース前のトップページ・モバイルスコア - パフォーマンス：84, ユーザー補助：66, おすすめの方法：92, SEO：89&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-top-mobile.png&quot; alt=&quot;リプレース後のトップページ・モバイルスコア - パフォーマンス：100, ユーザー補助：100, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;about（/about）&lt;/h3&gt;
&lt;p&gt;デスクトップ.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-about-desktop.png&quot; alt=&quot;リプレース前のaboutページ・デスクトップスコア - パフォーマンス：95, ユーザー補助：74, おすすめの方法：92, SEO：82&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-about-desktop.png&quot; alt=&quot;リプレース後のaboutページ・デスクトップスコア - パフォーマンス：98, ユーザー補助：100, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;モバイル.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-about-mobile.png&quot; alt=&quot;リプレース前のaboutページ・モバイルスコア - パフォーマンス：75, ユーザー補助：74, おすすめの方法：92, SEO：81&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-about-mobile.png&quot; alt=&quot;リプレース後のaboutページ・モバイルスコア - パフォーマンス：96, ユーザー補助：100, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;リンク多め（/posts/2023-02-11-developers-summit-2023）&lt;/h3&gt;
&lt;p&gt;リプレース後は、リンクカード関連でいくつかコンソールエラーがでていたので、パフォーマンスに響いてるっぽい。&lt;/p&gt;
&lt;p&gt;デスクトップ.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-link-desktop.png&quot; alt=&quot;リプレース前のリンク多めページ・デスクトップスコア - パフォーマンス：96, ユーザー補助：66, おすすめの方法：92, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-link-desktop.png&quot; alt=&quot;リプレース後のリンク多めページ・デスクトップスコア - パフォーマンス：87, ユーザー補助：100, おすすめの方法：92, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;モバイル.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-link-mobile.png&quot; alt=&quot;リプレース前のリンク多めページ・モバイルスコア - パフォーマンス：57, ユーザー補助：66, おすすめの方法：100, SEO：94&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-link-mobile.png&quot; alt=&quot;リプレース後のリンク多めページ・モバイルスコア - パフォーマンス：64, ユーザー補助：100, おすすめの方法：92, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;画像多め（/posts/2020-04-23-react-material-ui）&lt;/h3&gt;
&lt;p&gt;デスクトップ.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-image-desktop.png&quot; alt=&quot;リプレース前の画像多めページ・デスクトップスコア - パフォーマンス：57, ユーザー補助：71, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-image-desktop.png&quot; alt=&quot;リプレース後の画像多めページ・デスクトップスコア - パフォーマンス：99, ユーザー補助：100, おすすめの方法：92, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;モバイル.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-image-mobile.png&quot; alt=&quot;リプレース前の画像多めページ・モバイルスコア - パフォーマンス：41, ユーザー補助：71, おすすめの方法：100, SEO：94&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-image-mobile.png&quot; alt=&quot;リプレース後の画像多めページ・モバイルスコア - パフォーマンス：85, ユーザー補助：100, おすすめの方法：92, SEO：99&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;ツイート埋め込み多め（/posts/2020-09-20-web1week-2）&lt;/h3&gt;
&lt;p&gt;デスクトップ.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-tweet-desktop.png&quot; alt=&quot;リプレース前のツイート埋め込み多めページ・デスクトップスコア - パフォーマンス：91, ユーザー補助：70, おすすめの方法：100, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-tweet-desktop.png&quot; alt=&quot;リプレース後のツイート埋め込み多めページ・デスクトップスコア - パフォーマンス：99, ユーザー補助：100, おすすめの方法：83, SEO：100&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;p&gt;モバイル.&lt;/p&gt;
&lt;p&gt;&amp;lt;div class=&quot;layout-fixed&quot;&amp;gt;&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Jekyll（リプレース前）&lt;/th&gt;
&lt;th&gt;Astro（リプレース後）&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-jekyll-tweet-mobile.png&quot; alt=&quot;リプレース前のツイート埋め込み多めページ・モバイルスコア - パフォーマンス：80, ユーザー補助：70, おすすめの方法：100, SEO：95&quot; /&amp;gt;&lt;/td&gt;
&lt;td&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2023/blog-replace-astro/lighthouse-astro-tweet-mobile.png&quot; alt=&quot;リプレース後のツイート埋め込み多めページ・モバイルスコア - パフォーマンス：100, ユーザー補助：100, おすすめの方法：83, SEO：98&quot; /&amp;gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;lt;/div&amp;gt;&lt;/p&gt;
&lt;h3&gt;スコアが伸びなかったところ&lt;/h3&gt;
&lt;p&gt;リンクカード周りの取得がうまくいってないところは、その分減点されてましたね。&lt;/p&gt;
&lt;p&gt;Google の API でファビコンが取得できなかったときは、フォールバックのアイコンを返すようになるのですが、404へ行きあたる関係上コンソールにエラーが出てしまうんですよね…。
あとは、OGP 画像として設定されている URL が無効になってしまっているとか。&lt;/p&gt;
&lt;p&gt;この辺は、相手のサイト側の問題の気がしているので、今のところ特に対策等はしていません。
（というか、何か対策できる...？）&lt;/p&gt;
&lt;p&gt;一部はこちら側で改善できそうな点もあったので、今後改善していきたいですね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;ちなみにパフォーマンス改善で行われることの多い、画像最適化については今のところ対応していません。&lt;/p&gt;
&lt;p&gt;現在、Astro で実験的サポートになっていて機能としては一応使えはします。
3系リリースで正式サポートになるっぽかったので、まぁその時に対応でもいいかなぁとなりました。
&amp;lt;OG url=&quot;https://astro.build/blog/images&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;その代わり、記事移行する際にすべての画像を圧縮かけたうえで移行しています。
ほぼ7～8割くらいは削減してくれたので、全体としてはだいぶ軽くなっているはず。
圧縮にはこちらを使わせていただきました。お手軽に圧縮できて良きです。
&amp;lt;OG url=&quot;https://imguma.com&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;リプレースした感想など&lt;/h2&gt;
&lt;p&gt;完全に個人活動なので休日メインで進めていました。
2023/04に本格開始して2023/07/09にリリース。
かかった時間としてはざっくり150時間くらいだったので、もし仕事として平日作業していたなら1か月で終えられたようです。&lt;/p&gt;
&lt;p&gt;数年なかなか進められていなかったものをようやく終えられたので、すっきりしましたね。
Lighthouse のスコアも全体的には改善されたので、とりあえず自己満足しています。&lt;/p&gt;
&lt;p&gt;気になる技術を使って何かを作るのは、キャッチアップの良い機会になりますし、やはり楽しい。
個人開発だと割と自由に技術選定できるので、気兼ねせず試しやすいです。
Astro や UnoCSS と少しだけ仲良くなれた気がしました。
特に Astro は活発にリリースが行われているので、今後の動向が楽しみです。&lt;/p&gt;
&lt;p&gt;ちなみにリプレースで一番大変だったのは、機能実装ではなく記事移行でした。
元のマークダウンをそのまま MDX にすればよいという話ではなくて、微妙に文章調整をしたりしたので。
それを約80記事でやったので、それはまぁ大変だよね…と😱&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;どうせ書くならリプレース前後を比較をするような方式にしたいなーというので書いていたら、すっかり長文になってしまいましたね。
新機能中心にリリースしました記事を書くこともできましたが、同じく移行を考えている方の何かの参考になればいいなと思い、このスタイルにしました。
リプレースにあたっての技術調査って割と大変だと思いますので…。&lt;/p&gt;
&lt;p&gt;少しでも何かの参考になれば幸いです。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;p&gt;Astro 周り.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://astro.build&quot;&gt;Astro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.astro.build/ja/getting-started&quot;&gt;Astro Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://astro.build/blog/images&quot;&gt;Better Images in Astro&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remarkjs/remark&quot;&gt;remarkjs/remark&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rehypejs/rehype&quot;&gt;rehypejs/rehype&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/remarkjs/remark-breaks&quot;&gt;remarkjs/remark-breaks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rehypejs/rehype-autolink-headings&quot;&gt;rehypejs/rehype-autolink-headings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/mottox2/remark-code-titles&quot;&gt;mottox2/remark-code-titles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ipikuka/remark-flexible-code-titles&quot;&gt;ipikuka/remark-flexible-code-titles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://shiki.matsu.io&quot;&gt;Shiki&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prismjs.com&quot;&gt;Prism&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/zenn-dev/zenn-editor&quot;&gt;zenn-dev/zenn-editor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/gladevise/remark-link-card&quot;&gt;gladevise/remark-link-card&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hiroppy.me/blog/migrate-blog-from-hatena&quot;&gt;Astroでサイトを作り直し、ブログをはてなから移管しました&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mamenaka.jp/blog/pagespeed-insights-100-astro-blog&quot;&gt;PageSpeed Insightsでオール100点を取るためにやったこと【Astroブログ】&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;UnoCSS 周り.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://unocss.dev&quot;&gt;UnoCSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://antfu.me/posts/reimagine-atomic-css&quot;&gt;Reimagine Atomic CSS&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://icones.js.org&quot;&gt;Icones&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Jekyll 周り.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://shopify.github.io/liquid&quot;&gt;Liquid&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kramdown.gettalong.org&quot;&gt;kramdown&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fongandrew/hydeout&quot;&gt;fongandrew/hydeout&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jekyll/jekyll-paginate&quot;&gt;jekyll/jekyll-paginate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sverrirs/jekyll-paginate-v2&quot;&gt;sverrirs/jekyll-paginate-v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/allejo/jekyll-anchor-headings&quot;&gt;allejo/jekyll-anchor-headings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/rouge-ruby/rouge&quot;&gt;rouge-ruby/rouge&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jekyll/jekyll-feed&quot;&gt;jekyll/jekyll-feed&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/jekyll/jekyll-sitemap&quot;&gt;jekyll/jekyll-sitemap&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;その他.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/yuru7/HackGen&quot;&gt;yuru7/HackGen&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://imguma.com&quot;&gt;あっしゅくま&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>技術文書を書く方に送る、書籍「技術者のためのテクニカルライティング入門講座」</title><link>https://changeofpace.site/posts/2023-03-25-book-technical-writing-primer</link><guid isPermaLink="true">https://changeofpace.site/posts/2023-03-25-book-technical-writing-primer</guid><description>書籍「技術者のためのテクニカルライティング入門講座」と、さらなるテクニカルライティングの世界の紹介</description><pubDate>Sat, 25 Mar 2023 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、積読の消化が追いついてねぇ…とぼやいてる、よしです。
今回は、読んだ書籍を紹介したい気持ちになりまして、紹介記事に挑戦してみました。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;h3&gt;紹介したい本&lt;/h3&gt;
&lt;p&gt;「技術者のためのテクニカルライティング入門講座」という書籍です。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://www.shoeisha.co.jp/book/detail/9784798157191&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;以下、翔泳社のサイトより引用.&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;日本では、「文書は論理的かつ簡潔に記述する」という、当たり前のテクニックを学ぶ機会があまりありません。
そこで本書では、忙しい技術者の方でも「テクニカルライティング」を通じて、相手に伝わる技術文書を効率よく書けるようになるテクニックを多数紹介していきます。
ユーザーマニュアルや障害報告書、提案書といった実務直結の文例を多数掲載しているので、すぐに業務に役立てることが可能です。
新人～中堅の技術者の方だけでなく、管理職の方も添削指導のお手本としてご利用いただける内容です。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;そもそも「論理的」とはどういうことなのか？というところから始まり。
ロジカルライティングとは？　テクニカルライティングとは？
と基礎的な考え方を提示しつつ、具体的なテクニックを紹介。
さらにそれらのテクニックを組み合わせて、実際の技術文書をどう作っていくのかという実例まで記載されている書籍です。&lt;/p&gt;
&lt;p&gt;書籍が発売されたのは2018年11月19日。
当記事の執筆時点からは約5年前になりますが、今でも十分通用する内容になっていると個人的に思いました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2024/12/31追記&lt;/strong&gt;
2024/12/18に第2版が出版されました！ 主に生成 AI に関する章が増えているようです。
&amp;lt;OG url=&quot;https://www.shoeisha.co.jp/book/detail/9784798188423&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;紹介したいと思った背景&lt;/h3&gt;
&lt;p&gt;（私事なので興味ない方はすっ飛ばしてもらって大丈夫です）&lt;/p&gt;
&lt;p&gt;自分は今の会社に来てから「ドキュメント力が高い」と評価されることが増えました。
（図的なドキュメントは大して描いたことがないので、多くは文章能力のことを指して言われていたのだと思っています）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;それまでは特に自分の長所のような自覚は全くなく。
評価されるようになったことで、これって出来てる方なんだーとやっと自覚するようになり。
「技術記事書いてるから自然と伸びたのかな？」
「Textlint を活用したり、ネットで聞き齧ったテクニックを取り入れたりしてたおかげ？」
と思いつつ、そういえばちゃんと基礎からライティング技術を学んだことなかったなぁと。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分の長所だというなら、ちゃんと基礎から学んでより磨いていきたい。
学んだことを周囲の人にも伝えられるようになれたら、より良いだろうなと思ったこともあり。
それで書籍を読んでみようとまず手に取ったのが、今回の書籍でした。&lt;/p&gt;
&lt;p&gt;読んでいて、技術文書の書き方悩んでる人、みんなこの本読んだらいいんじゃないか？と思ったのがきっかけです。
基礎の基から具体的なテクニック〜実践まで記載されていたので、紹介されているテクニックを取り入れるだけでも十分役に立つと思ったのです。&lt;/p&gt;
&lt;h2&gt;書籍の内容の紹介&lt;/h2&gt;
&lt;p&gt;書籍の目次はこのようになっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1章：ロジカルライティング × テクニカルライティング活用の基礎知識&lt;/li&gt;
&lt;li&gt;第2章：わかりやすく、簡潔な文章を書くテクニック&lt;/li&gt;
&lt;li&gt;第3章：読み手に伝わる文章を書くテクニック&lt;/li&gt;
&lt;li&gt;第4章：読みやすさを高める文書フォーマット〜表現・表記のルール〜&lt;/li&gt;
&lt;li&gt;第5章：実践編　ユーザーマニュアル・取扱説明書&lt;/li&gt;
&lt;li&gt;第6章：実践編　提案書&lt;/li&gt;
&lt;li&gt;第7章：実践編　障害報告書&lt;/li&gt;
&lt;li&gt;第8章：実践編　社外メール文&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;冒頭でも触れた通り、基礎知識〜テクニック・ルール〜実践といった流れですね。
テクニックの章だけでも、それぞれ before・after 例が提示されているためわかりやすいです。
今回は1〜3章の内容について少し紹介してみます。&lt;/p&gt;
&lt;h3&gt;第1章 - ロジカルライティング × テクニカルライティング活用の基礎知識&lt;/h3&gt;
&lt;p&gt;第1章の目次.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1.1：技術者の書く文章がわかりにくい3つの理由&lt;/li&gt;
&lt;li&gt;1.2：そもそも「論理的」とはどのようなことか&lt;/li&gt;
&lt;li&gt;1.3：思考を効果的にアウトプットするための「ロジカルライティング」&lt;/li&gt;
&lt;li&gt;1.4：「読み手」は目の前にいる人だけ？&lt;/li&gt;
&lt;li&gt;1.5：「目的」ありき。読んだ人の行動を想定する&lt;/li&gt;
&lt;li&gt;1.6：書き出す前にロジックを組み立てることが何より重要&lt;/li&gt;
&lt;li&gt;1.7：情報をわかりやすく伝えるための「テクニカルライティング」&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;まずはよくあるアンチパターンに触れつつ、大事な基本的考え方から記載されています。
これまで論理的、ロジカルライティング、テクニカルライティングという言葉はよく聞いていたものの。
その意味がちゃんと説明できるか？と言われると自信がなかったため、改めて認識する良い機会になりました。&lt;/p&gt;
&lt;p&gt;書籍の説明文をざっくり引用すると、こんな感じ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;論理的：わかりやすく伝わり、読み手の次の行動につながるほど筋道が通っていること&lt;/li&gt;
&lt;li&gt;ロジカルシンキング：論理的で筋道の通った思考方法や説明の仕方&lt;/li&gt;
&lt;li&gt;ロジカルライティング：ロジカルシンキングを活用して文書作成する技術&lt;/li&gt;
&lt;li&gt;テクニカルライティング：技術的な情報を利用者にわかりやすく伝えるための文書技術&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;技術文書は、ロジカルライティングとテクニカルライティングを組み合わせて記述していくものと考えると良さそうですね。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;まずは読み手を洗い出し、文書の目的と読み手に期待する次の行動を明確にする。&lt;/strong&gt;
というのが、技術文書を書く上ではとても大事である旨記載されています。
ここがブレてしまうと曖昧な内容になってしまい、結局何が言いたいのかよくわからないものになってしまいがち。
要らぬ不安や混乱を招いてしまうこともある…とあり、心当たりある気がしました😇&lt;/p&gt;
&lt;p&gt;その次に、いきなり文書を書き始める前に、&lt;strong&gt;情報を整理してロジックツリーを組み立てよう&lt;/strong&gt;とありました。
主題を元に整理した情報を階層のツリーにして、全体像のイメージを作成。そこからポイントを設定。
各ポイントには、ポイントを裏付けるような内容を書いていく、といった感じ。
技術記事だったら、最初にタイトルを決めて見出し構成を決めた後に、それぞれの見出しの内容を書いていくといった具合ですね。&lt;/p&gt;
&lt;h3&gt;第2章 - わかりやすく、簡潔な文章を書くテクニック&lt;/h3&gt;
&lt;p&gt;第2章の目次.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;2.1：1文1義で書く&lt;/li&gt;
&lt;li&gt;2.2：文を長くしない。「〜（だ）が」、「〜ので」で文をつながない&lt;/li&gt;
&lt;li&gt;2.3：一文を短く。50字以内で収めるように&lt;/li&gt;
&lt;li&gt;2.4：5W2H を盛り込み、曖昧な文章にしない&lt;/li&gt;
&lt;li&gt;2.5：メールの件名、文書のタイトルから目的が伝わるように書く&lt;/li&gt;
&lt;li&gt;2.6：「話し言葉」と「書き言葉」を使い分ける&lt;/li&gt;
&lt;li&gt;2.7：接続詞の使用は最小限に抑えて、効果的に使う&lt;/li&gt;
&lt;li&gt;2.8：長くなりがちな文章を簡潔に仕上げるコツ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この章では、冗長的で曖昧な文章とならないようにするテクニックが紹介されています。
なんとなく意識できていたものもあれば、ちょっと自信がないものもありましたね😓&lt;/p&gt;
&lt;p&gt;特に「〜（だ）が」、「〜ので」って割と使いがちと言いますか。
これが良くない理由としては、以下のようなことが挙げられていました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;複数の内容が1文に盛り込まれてしまい、1文1義にならない&lt;/li&gt;
&lt;li&gt;文の中で最も伝えたいことが、文の最後にきてしまう&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このせいで、内容が読み手に伝わりにくくなってしまう、というものです。&lt;/p&gt;
&lt;p&gt;とはいえ、文章を書きながら注意するのは難しいこともあり。
書き上げた後の見直しの段階で文を区切ったり、順番を変えたりすると良いとのことでした。
個人的には、完全に使わないようにするというよりは、多用しないように気をつけようかなと思っています。
（当記事でもいくつかは使用していたりします🙄）&lt;/p&gt;
&lt;p&gt;それと「話し言葉」と「書き言葉」を使い分けるというのは、場合にもよりそうかなと。
書き言葉の方が冗長な表現を省いており簡潔に伝わりやすい。
礼節や説得力を高めるためにもビジネス上はこちらを使った方が良い、というのは確かにそうだと思いつつ。
技術記事であれば、きっちり書き言葉でなくても良いかなとは思いました。
きっちりだとなんだかお固い印象を与えてしまいそうな気がしまして。&lt;/p&gt;
&lt;p&gt;ちなみに最初に目次を見た時、目次にあるタイトルだけでも何が言いたいのか率直に伝わってきました。
2.5のテクニックがまさに実施されているんだなぁとちょっと感激したものです。&lt;/p&gt;
&lt;h3&gt;第3章 - 読み手に伝わる文章を書くテクニック&lt;/h3&gt;
&lt;p&gt;第3章の目次.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;3.1：最も重要な内容を先に書く&lt;/li&gt;
&lt;li&gt;3.2：形容詞や名詞句でなく、動詞でズバリと書く&lt;/li&gt;
&lt;li&gt;3.3：否定表現に気をつける&lt;/li&gt;
&lt;li&gt;3.4：受動態と能動態を使い分ける&lt;/li&gt;
&lt;li&gt;3.5：具体的に情報を盛り込んで書く&lt;/li&gt;
&lt;li&gt;3.6：「など」を多用しない&lt;/li&gt;
&lt;li&gt;3.7：レビューを受けて、わかりやすさを高める&lt;/li&gt;
&lt;li&gt;3.8：最後の文でしっかりと締めくくる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この章では、より読み手に内容や目的が伝わるような文章を書くためのテクニックが紹介されています。
うーん、こちらも出来ているものもあれば、自信ないものがある…。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/no-double-negative-ja --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;「〜しないと、〜ない」や「〜しないとは限らない」のような二重否定を使っていませんか？
特に後者の方は「必ずしもそうなるわけではなくて、状況による」と曖昧な表現をしたくなった時に使いたくなったりしますよね。
こういった二重否定は、パッと内容が分かりにくくなってしまって、結局何が言いたいのかストレートに伝わらない問題を抱えているとのこと。
読み手に書き手の意図を考えさせてしまう、迷わせてしまうので避けましょうと。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;「など」を多用しない、というのは特に自信がないやつですね。
「ここに挙げたのは一例で他にもあるよ」という時につい使いがち。
良くない理由として、これも文章を曖昧にしてしまう要因になるためでした。&lt;/p&gt;
&lt;p&gt;対処法としては、「など」を削除してしまう。
もしくは「〜などの工具」のように、「など」の後に例示をまとめるようなものを加えること。
こうすることで「など」が何を示すのかを明確にして曖昧とならないようにすると良いそうです。&lt;/p&gt;
&lt;h2&gt;読んでみてどうだった？&lt;/h2&gt;
&lt;p&gt;冒頭に述べた通り、この書籍の発売日が5年前であるため、古い情報もあるかな？と最初は思っていました。
実際に読んでみると、全部がとまでは言いませんが、十分今でも通用する考え方やテクニックが記載されている印象を受けましたね。
基礎知識の地盤固めにとても良く、読んで良かったなと。&lt;/p&gt;
&lt;p&gt;テクニックがしっかり言語化されているので、これを自分の中で落とし込みつつ。
周りのメンバーにも伝えていけると良いドキュメントを作成する環境にしていけそうです。&lt;/p&gt;
&lt;p&gt;だからこそ紹介したいと思ったというのもあります。&lt;/p&gt;
&lt;h3&gt;テクニカルライティングとシステム開発は似てる？&lt;/h3&gt;
&lt;p&gt;それと読んでいてすごく思ったのは、テクニカルライティングで技術文書を作成するフローやテクニックって、システムを作る時に考えることと似ているなぁと。&lt;/p&gt;
&lt;p&gt;フローで言うとこんな感じ。&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;技術文書&lt;/th&gt;
&lt;th&gt;システム&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;情報収集〜整理&lt;/td&gt;
&lt;td&gt;要件定義&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ロジックツリー作成&lt;/td&gt;
&lt;td&gt;設計&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;執筆&lt;/td&gt;
&lt;td&gt;実装&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;レビュー&lt;/td&gt;
&lt;td&gt;テスト&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;プログラミングにおいても可読性って大事ですよね。
可読性向上のためのテクニックが多く存在するわけで。
それらの基本的な考え方って、分かりやすい技術文書を書くテクニックと同じなんじゃないかなーと思ったのです。&lt;/p&gt;
&lt;p&gt;じゃあ、
優れた技術文書を書ける人 = 優れたシステムを作れる人
なのか...？
そこはどうなんだろうなぁと。&lt;/p&gt;
&lt;p&gt;実装は得意でもドキュメント書くの苦手だと言う人もいるので、一概にそういうわけではなさそう？
ただ、基本的な考え方は理解していて実装には落とし込めるけれど、ドキュメントへの落とし込み方がうまく掴めていない。
ということであれば、その落とし込み方さえわかれば無双できる可能性あるのでは…？🤔
心当たりがある方は、ぜひ意識して伸ばしてみてはいかがでしょう。
自分も設計〜実装力を合わせて磨いていくぞー。&lt;/p&gt;
&lt;h3&gt;さらなるテクニカルライティングの世界へ&lt;/h3&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分はこの書籍をとっかかりに、よりテクニカルライティングへの関心が高まったように思います。
他にもテクニカルライティングを取り扱った文献を読んでみたいなぁと。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-emable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ちなみに現在は、先日発売されたばかりのこちらの書籍を読んでいます。
&amp;lt;OG url=&quot;https://pub.jmam.co.jp/book/b622627.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;架空のサービスのドキュメントを作るような形式で話が進んでいくので、より開発寄りの書籍ですね。
内容が気になる方は、翻訳者の方による紹介スライドがおすすめです。
（プロダクト開発と同じ流れなんだよ、という話があって自分が思ったことは割と合ってたんだーって感激しました）
&amp;lt;OG url=&quot;https://speakerdeck.com/iwashi86/docs-for-developers&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;サイボウズの仲田さんのスライドもおすすめです。
&amp;lt;OG url=&quot;https://speakerdeck.com/naohiro_nakata/technicalwriting&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://speakerdeck.com/naohiro_nakata/technicalwritingfordeveloper&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Google のテクニカルライティングのドキュメントはこれから読みたい。
&amp;lt;OG url=&quot;https://developers.google.com/tech-writing&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;それとテクニカルライティングの資格もあるそうなので、そちらもちょっと気になっています👀
（2023/11/25：URL が変わっていたので差し替え）
&amp;lt;OG url=&quot;https://jtca.org/learn-tc/certificate-exam/&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://speakerdeck.com/line_developers/my-story-about-taking-the-technical-writing-exam&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;世の中にはテクニカルライターという専門職の方もいらっしゃるので、そういった方々には敵わないかもしれませんが…。
テクニカルライティングは、1エンジニアとして身につけて損はない技術だと思っているので、ぜひ伸ばしていこうと思っています。
せっかく周りから評価してもらえた部分ですし、これからも自分の長所とできるようにしたいものです。&lt;/p&gt;
&lt;p&gt;ただ、最近勢いのある AI にとって代わられる可能性のある領域かなとも思いますので、上手い付き合い方も考えていかないとですね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回は初めて書籍の紹介記事を書いてみました。
内容を書きすぎると営業妨害になりそうで、薄すぎても何が良いのか伝わらなくなりそうで。
書くの意外と難しい…。拙い文章で恐縮です。
（この記事でこのテクニック活用されてないじゃんとかツッコミがやや怖いですね）&lt;/p&gt;
&lt;p&gt;今回紹介した書籍であったり、テクニカルライティングに関心を持ってくれる方がいらっしゃれば幸いです。
ドキュメントは軽視しちゃダメですぞ。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.shoeisha.co.jp/book/detail/9784798157191&quot;&gt;翔泳社 - 技術者のためのテクニカルライティング入門講座&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://pub.jmam.co.jp/book/b622627.html&quot;&gt;JMAM - エンジニアのためのドキュメントライティング&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developers.google.com/tech-writing&quot;&gt;Google - Technical Writing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.jtca.org/certificate_exam/exam_writing_b.html&quot;&gt;JTCA - 3級 テクニカルライティング試験[TW]&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2022年振り返り ～技術活動、ブログ編～</title><link>https://changeofpace.site/posts/2022-12-31-looking-back-2022-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2022-12-31-looking-back-2022-tech</guid><description>2022年のGitのコミット履歴から、記事執筆など技術活動の振り返り</description><pubDate>Sat, 31 Dec 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase, ja-technical-writing/ja-no-successive-word --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;今年は学びの多い1年だったなぁとしみじみ思っている、よしです。
例年通り、体調編に引き続き技術活動の振り返りもやっていきますー。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2022年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;恒例の Git の Contributions 履歴から。&lt;/p&gt;
&lt;h4&gt;GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2022/looking-back-2022-tech/github-contribute.png&quot; alt=&quot;2022年のGitHub Contributionsの履歴画像 856Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今年は 657（個人） + 199（仕事）= 856 でした。&lt;/p&gt;
&lt;p&gt;2月に復職して、会社へ慣れることに重きを置いていた1年でもあったので、個人活動は正直あまりできていないですね...。
頭の方に devChallenges の活動と、いくつか書籍の学習のコードを記録していたくらい。&lt;/p&gt;
&lt;p&gt;仕事少なくね？と思われるかもしれませんが、4月から参加している案件がセルフホスティングの GitLab を使用しているので、ここには上がってきていません。
案件外の業務改善活動だったり、内定者アルバイトメンバーの課題レビューとかの履歴ですね。&lt;/p&gt;
&lt;h4&gt;GitLab&lt;/h4&gt;
&lt;p&gt;では、案件での活動はどれくらいだったかというと、こんな感じでした。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2022/looking-back-2022-tech/gitlab-contribute.png&quot; alt=&quot;2022年のGitLab Contributionsの履歴画像 1448Contributions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;GitLab って Contributions の合計出してくれないんだっけ？と思いながら計算してみたところ、1448 でした。&lt;/p&gt;
&lt;p&gt;実装チケットやったり、レビューしたりとでこれくらいになったみたいです。
色が一番濃い日は、多いところだと30 contributions ありました。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;ここ数年を比較するとこんな感じ。
こうしてみると、昨年や前の会社にいた頃より活動できてたんだなぁと。
（2019年が極端に少ないのは、年始から休職〜時短勤務で復職とかだったため）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;2022&lt;/th&gt;
&lt;th&gt;2021&lt;/th&gt;
&lt;th&gt;2020&lt;/th&gt;
&lt;th&gt;2019&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;GitLab&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;?&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(個人)&lt;/td&gt;
&lt;td&gt;657&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;920&lt;/td&gt;
&lt;td&gt;231&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitHub(仕事)&lt;/td&gt;
&lt;td&gt;199&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;td&gt;541&lt;/td&gt;
&lt;td&gt;76&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GitLab(案件)&lt;/td&gt;
&lt;td&gt;1,448&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;合計&lt;/td&gt;
&lt;td&gt;2,304&lt;/td&gt;
&lt;td&gt;2,169&lt;/td&gt;
&lt;td&gt;1,461&lt;/td&gt;
&lt;td&gt;307 + ?&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h3&gt;復職活動&lt;/h3&gt;
&lt;p&gt;昨年5月からやってきた復職活動ですが、0社目もとい7社目の会社の内定が1月に出ました。
その後、2月中旬に入社して今に至るので、無事にフロントエンドエンジニアとして社会復帰できました。
やったーーーーー！！！🎉&lt;/p&gt;
&lt;p&gt;この会社はメンタルヘルスに理解があるだけでなく、相談できる専門チームがあるという〜。自分もよくお世話になっています。
このチームだけでなく、自分が所属しているチームだったり、案件全体のメンバーだったりと相談できる場がいくつかあって助かっています。ありがてぇ。&lt;/p&gt;
&lt;p&gt;復職にあたり体調の波があったりもしましたが、その辺りは振り返り&lt;a href=&quot;/posts/2022-12-30-looking-back-2022-mental-illness&quot;&gt;体調編&lt;/a&gt;の方に書いています。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;h4&gt;アクセス履歴&lt;/h4&gt;
&lt;p&gt;今年の履歴はこんな感じです。
例年通り、Google Analytics のデータ部分のみ抜粋。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;692&lt;/td&gt;
&lt;td&gt;876&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;694&lt;/td&gt;
&lt;td&gt;975&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;818&lt;/td&gt;
&lt;td&gt;1,168&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;72&lt;/td&gt;
&lt;td&gt;691&lt;/td&gt;
&lt;td&gt;893&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;73&lt;/td&gt;
&lt;td&gt;688&lt;/td&gt;
&lt;td&gt;1,080&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;628&lt;/td&gt;
&lt;td&gt;802&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;620&lt;/td&gt;
&lt;td&gt;769&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;612&lt;/td&gt;
&lt;td&gt;809&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;659&lt;/td&gt;
&lt;td&gt;831&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;75&lt;/td&gt;
&lt;td&gt;679&lt;/td&gt;
&lt;td&gt;852&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;77&lt;/td&gt;
&lt;td&gt;633&lt;/td&gt;
&lt;td&gt;927&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;79&lt;/td&gt;
&lt;td&gt;482&lt;/td&gt;
&lt;td&gt;662&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：7,896&lt;/li&gt;
&lt;li&gt;ページビュー数計：10,644&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年との比較.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数：12,002 → 4,106減&lt;/li&gt;
&lt;li&gt;ページビュー数：16,512 → 5,868減&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年と比べて減りましたね。
技術記事は Zenn の方にもクロス投稿しているので、そちらで見ている人が多いのかなというのと。
今年は技術の中身っぽい記事がそんなに書けてなかったのもあるのかなぁと思いました。&lt;/p&gt;
&lt;p&gt;まぁ、アクセス数のために記事書いているわけではないので、あんまり気にしていないです。&lt;/p&gt;
&lt;h4&gt;記事執筆について&lt;/h4&gt;
&lt;p&gt;今年は復職して会社へ慣れることに重きを置いていたこともあり、個人活動はあまりできていませんでした。
技術記事に関しても同様ですね。&lt;/p&gt;
&lt;p&gt;今年投稿した記事.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-02-12-ts-type-safe-request&quot;&gt;TypeScript × Aspida × Zodで型安全なHTTPリクエストについて考えてみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-02-19-developers-summit-2022&quot;&gt;Developers Summit 2022 公開資料・Twitterリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-03-04-devchallenges-front-end&quot;&gt;devChallenges - Front-end 編を完走？したので、さっくり振り返ってみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-05-15-techfeed-conference-2022&quot;&gt;TechFeed Conference 2022 公開資料・Twitterリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-06-11-html-professional-level1&quot;&gt;HTML5プロフェッショナル認定試験 レベル1(ver2.5)に合格したけど、めっちゃギリギリだった話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-09-03-aws-saa-update&quot;&gt;AWS SAAを（SAA-C02バージョンで）更新してきたぞーという話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-11-21-simple-git-hooks-auto-issue-number&quot;&gt;simple-git-hooksでもブランチ名からIssue番号抽出をやりたくて、わちゃわちゃした話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-11-27-jsconf-jp-2022&quot;&gt;JSConf JP 2022 公開資料・Twitterリンクまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-12-30-looking-back-2022-mental-illness&quot;&gt;2022年振り返り ～精神疾患、体調編～&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;2022年振り返り ～技術活動、ブログ編～ ※当記事&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;あれ、12記事目標にしていたのに10記事しかなくね？と言われそうな気がしますが、一応記事としてはもう2記事書いていたりします。
後述しますが、note に1記事と Zenn のみ投稿の1記事がありまして。&lt;/p&gt;
&lt;p&gt;なので、ギリギリ目標達成？&lt;/p&gt;
&lt;h4&gt;リプレースについて&lt;/h4&gt;
&lt;p&gt;昨年の振り返りの時にも、当ブログを Jekyll から Next.js で作り直したいと言っていたやつです。
結論を言うと、結局今年も手を付けないままですね😇&lt;/p&gt;
&lt;p&gt;ただ、今の構想を言うと、Next.js でなく Astro でリプレースすると良いのでは？と思っています。
&amp;lt;OG url=&quot;https://astro.build&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Astro は静的サイトに特化したもので。
Zero JavaScript Runtime で高速に動作すると言うのと。コンポーネント実装に React をはじめ既存のフロントエンドライブラリが使用できるといった、面白い特徴を持っていたりします。
チュートリアルをやってみましたが、なかなか良さそうだったので試したいなぁと。&lt;/p&gt;
&lt;p&gt;ちなみにデザインを少し凝ったものにしたい構想があったのですが、広告との共存を考えると難しいでは？という感じがしてきたので、別に質素なデザインでもええか...という気持ちです。
デザインのことを考えると、いつまでもリプレースできなくね？と思ったというのもありますね。&lt;/p&gt;
&lt;h3&gt;ブログ以外の記事執筆活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn および Qiita に投稿した記事は、基本的には当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;ブログ + Zenn で活動しているのは継続中。
基本的にはクロス投稿していますが、一部会社に紐づくものがあったのでそれは対応していません。
（ちなみに10月に会社の Publication に所属しました）
~入社エントリは note の方で書いています。~&lt;/p&gt;
&lt;p&gt;※2025/09/15...note のリンク、会社記事のリンクは除去しました。&lt;/p&gt;
&lt;p&gt;今年 Zenn からいただいた振り返りはこんな感じでした。
（12/23までのものです）
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2022/looking-back-2022-tech/zenn-recap.png&quot; alt=&quot;Zenn 2022 Recap - 2022年の活動振り返りデータ - 102,201字の執筆、9記事、2スクラップ、83,251ビュー、528Likes、2個のサポートバッジ&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;12/25投稿の記事があるので、それを足すともう少し数値が上がるかなと。
文字数は13万文字くらいに。
それでも昨年は20万文字くらい書いていたので減りましたが、ページビュー的には昨年より多かったです。
（ページビュー的にもやっぱりブログより Zenn の方が圧倒的に見られていますね）&lt;/p&gt;
&lt;p&gt;今年はリンク集が強かったみたいです。&lt;/p&gt;
&lt;p&gt;ちなみに Qiita と Crieit は今年活動なしです。&lt;/p&gt;
&lt;p&gt;※2024/09/14追記：Crieit はサービスクローズされました。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;昨年の春頃やってた OOUI - メモアプリは中断したままですね。
まぁ、今続きをやるにしても諸々のバージョンがだいぶ上がった関係でコードが古くなってしまっているので、作り直した方がいいのかもしれない...。
当時よりもうまく実装できるかもですし。&lt;/p&gt;
&lt;p&gt;来年何かやれたらいいですが、devChallenges の Full-stack 編を優先するとか、なんだかんだ他の学習をやってそうな気がしますね。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;TypeScript × React × Next.js を自分のメイン技術にしていたので、その周辺にはアンテナ貼っていたかなと。
今年は React 18 とか Next.js 13 とか色々ありましたねー。
ただ、正直ちゃんとキャッチアップできてない部分はあります...。&lt;/p&gt;
&lt;p&gt;デザイン、アクセシビリティについてもほとんど何もできてないですね。&lt;/p&gt;
&lt;p&gt;テストに関しては、案件で書くようになったのでいくらか身についたと思います。
とはいえ、E2E テストもわかるようになりたいと言っておきながら、結局やれてない...。
（E2E は今の案件では書いていません）&lt;/p&gt;
&lt;p&gt;あとは、ブログリプレースのところでも書きましたが、Astro が気になってチュートリアルやったりもしていました。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;バックエンドの文脈で Kotlin を少し勉強したりもしていました。
今の案件で使われていることもあり。
ちょっと読み書きできるようにはしておきたいと思いまして。&lt;/p&gt;
&lt;p&gt;自分が昔メイン技術としていた Java の後継的言語なので、気になったというのもありました。
実際に勉強していると、当時の Java のつらみみたいなところが Kotlin で改善されていたりしていて、発見がありましたねー。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;昨年の振り返りで話題にしていた2つの資格については、無事に更新・取得できました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS SAA 更新&lt;/li&gt;
&lt;li&gt;HTML プロフェッショナル レベル1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;AWS SAP を受験する手もありましたが、ヒヨってしまったので AWS SAA 更新にとどまりましたね。&lt;/p&gt;
&lt;p&gt;HTML プロフェッショナル レベル1については、割とギリギリ目の合格でした...😇
来年余裕があればレベル2にチャレンジするのもいいかもしれませんね。&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;昨年から勉強記録を Notion に移行して、今も継続中。
もはや TIL 活動というより、自分用のメモを記録していっている感じですね。&lt;/p&gt;
&lt;p&gt;非公開のページでひたすらメモを残していっているので、外部の方に内容を示すことはできませんが、まぁこの方が続けられている気がします。&lt;/p&gt;
&lt;h3&gt;その他&lt;/h3&gt;
&lt;p&gt;devChallenges の Front-end 編を年始の方に終わらせました。
（最後の課題については、案内されている API が廃止されていて実質できない状態だったので、7個目まで）&lt;/p&gt;
&lt;p&gt;Full-stack 編もやろうとしていたものの、復職して会社に慣れることを優先していたので、ほとんど進められていません。
まぁ、しょうがない。&lt;/p&gt;
&lt;p&gt;Recursion の勉強は停止したままですね...。
自分が停止している間も色々更新があっているのは見かけていたので、また再開したいところではあります。&lt;/p&gt;
&lt;h2&gt;2023年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;いつもの1年で12記事は継続していきます。
今年はあまり技術の中身的な記事が書けてなかったので、来年は何か書きたいところ。
Zenn では会社の Publication に所属しているので、会社のブランディングに良い影響を与えられるような記事も書けると良さそう？&lt;/p&gt;
&lt;p&gt;ブログリプレースはどこかでやりたいですねー。
Astro + 質素なデザインでリプレースなら、多分やれるはず...。というかやれ。
Jekyll は悪くないですが、いい加減 Ruby とおさらばしたい気持ち。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;OOUI の課題を仕切り直すかなと思いつつ、多分やらないような気がします。&lt;/p&gt;
&lt;p&gt;web1week のような、小規模アプリを作るような機会があれば何か作るかも。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;h4&gt;フロントエンド&lt;/h4&gt;
&lt;p&gt;TypeScript × React × Next.js をメインに据えるのは継続。
今年ちゃんとキャッチアップできていなかった React 18 と Next.js 13 も勉強しておきたいところ。&lt;/p&gt;
&lt;p&gt;案件で Storybook やテストを書くようになったこともあり、個人活動でも自然と書けるくらいにはしておきたいですね。
作成したストーリーをテストに流用できるの、なかなか便利でした。&lt;/p&gt;
&lt;h4&gt;バックエンド&lt;/h4&gt;
&lt;p&gt;あくまで自分の技術としてはフロントエンドを軸にしつつ。
今年、Kotlin を勉強したので、せっかくならそれで何か作ってみたいですね。
手頃なものでは、devChallenges の Full-stack 編のバックエンド側で使ってみるかなぁという構想があったりします。&lt;/p&gt;
&lt;p&gt;FE、BE 両方実装が必要なため、来年だけで完走しきるのは難しそうですが。
いくつか進められたらいいなぁと思います。&lt;/p&gt;
&lt;h4&gt;資格試験&lt;/h4&gt;
&lt;p&gt;余裕があれば HTML プロフェッショナル レベル2にチャレンジしてみようかと。
レベル1と比べてだいぶ出題範囲が広いので、割と勉強しないとやばそうですね😇&lt;/p&gt;
&lt;p&gt;あとは、エンジニア0〜1年目の時に3回不合格だった応用情報をリベンジしてみるのもいいかなと思いつつ。
あれはまず起床試験がだるいので、受験するかは微妙です。
当時よりは知識増えてる分、いくらか飲み込みが早くはなりそうなんですけどね。早起きしんどいんですよ...。&lt;/p&gt;
&lt;h3&gt;キャリアアップ的な話&lt;/h3&gt;
&lt;p&gt;プログラミング的な技術力もさることながら。
仕事でキャリアアップをしていく中で、以下の能力も伸ばしていかないとなと思っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;リードエンジニア的な立ち回り&lt;/li&gt;
&lt;li&gt;チームのサポート力&lt;/li&gt;
&lt;li&gt;ドキュメント力&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;書籍で学んだり。
社内で目標となる人を見つけて。その人から話を聞いてみたり、真似をしてみたり。
試行錯誤しながら、能力を伸ばしていきたいですね。&lt;/p&gt;
&lt;h4&gt;リードエンジニア的な立ち回り&lt;/h4&gt;
&lt;p&gt;この辺りは、前社時代にほとんど経験できていなかったところで苦手意識すらありました。
でも、自分の経験年数的には当然求められることだよなと。&lt;/p&gt;
&lt;p&gt;そこから今の案件でエピックの担当になり、そのエピックでリード的な立ち回りをやるようになりました。
チームのリードメンバーのサポートを受けながらのチャレンジで、非常に学びが多かったですね。&lt;/p&gt;
&lt;p&gt;この辺りを自分1人でもちゃんとできるように、自分の中で確立させていきたいなと。
（何もかも1人でやればいいというわけではなくて、必要に応じて人を巻き込みながら自力で進められる程度）&lt;/p&gt;
&lt;h4&gt;チームのサポート力&lt;/h4&gt;
&lt;p&gt;リードエンジニア的な立ち回りの話をしましたが、自分の特性上、チームを前で引っ張っていくというのは向いていないようでした。
どうもチームのサポート的なポジションが向いているようだったので、後方支援型のリードとして動いていけたらいいのかなと思っています。&lt;/p&gt;
&lt;p&gt;ちなみに今の会社でのエンジニア職位としては、リードとテックリードがまた別で定義されており。
その中で、テックリードを目指すといいのかなぁとぼんやり考えていたりします。&lt;/p&gt;
&lt;h4&gt;ドキュメント力&lt;/h4&gt;
&lt;p&gt;会社でドキュメント力を評価されることが増えたので、そこをさらに伸ばそうかなと。
これも後方支援的な話になりそう？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今年は何より社会復帰できたことがよかったなぁと。
会社に慣れることを優先していた中でも、仕事を通して非常に学びのある1年になった気がします。&lt;/p&gt;
&lt;p&gt;ただ、リードといったチームとしての動き的な能力が伸びた一方で、実装力的な技術力がそこまで伸びなかったかなとも思います。
この辺りは、来年個人勉強や活動で強化していきたいところですね。&lt;/p&gt;
&lt;p&gt;ここまで読んでくださった方、ありがとうございました。
ではでは、皆様、よいお年をお迎えくだされー。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>simple-git-hooksでもブランチ名からIssue番号抽出をやりたくて、わちゃわちゃした話</title><link>https://changeofpace.site/posts/2022-11-21-simple-git-hooks-auto-issue-number</link><guid isPermaLink="true">https://changeofpace.site/posts/2022-11-21-simple-git-hooks-auto-issue-number</guid><description>simple-git-hooksでもブランチ名からIssue番号自動抽出の仕組みをやりたくて、七転八倒しながら設定した記録</description><pubDate>Mon, 21 Nov 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、よしです。
今回は記事リハビリということで、ちょっとした小ネタ記事を書いてみました。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;今回やりたかったこと&lt;/h2&gt;
&lt;p&gt;簡潔に言うと
「simple-git-hooks を使った Git フック設定で、ブランチ名から Issue 番号を抽出してコミットメッセージへ自動付与するようにしたい」
というやつです。&lt;/p&gt;
&lt;p&gt;背景はいいから実際の設定方法見せてくれや、という方は&lt;a href=&quot;#%E8%A8%AD%E5%AE%9A%E6%96%B9%E6%B3%95%E3%81%AE%E7%B5%90%E8%AB%96&quot;&gt;設定方法の結論&lt;/a&gt;へどうぞ。&lt;/p&gt;
&lt;h3&gt;もうちょっと背景を説明してみる&lt;/h3&gt;
&lt;h4&gt;Git フック&lt;/h4&gt;
&lt;p&gt;皆さんおなじみの Git に Git フックという機能があります。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;他のバージョンコントロールシステムと同じように、Git にも特定のアクションが発生した時にカスタムスクリプトを叩く方法があります。
このようなフックは、クライアントサイドとサーバーサイドの二つのグループに分けられます。
クライアントサイドフックはコミットやマージといったクライアントでの操作の際に、サーバーサイドフックはプッシュされたコミットの受け取りといったネットワーク操作の際に、それぞれ実行されます。
これらのフックは、さまざまな目的に用いることができます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;公式にも書いてある通り、Git の特定のアクションが発生した時にカスタムスクリプトを叩く仕組みのことです。
（詳細やフックの種類は当記事では説明しないので、公式ページをご参照ください）&lt;/p&gt;
&lt;p&gt;この Git フックを使った設定としてよくあるのが...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;コミット前に Linter、フォーマッターでコード整形する&lt;/li&gt;
&lt;li&gt;ブランチ名から Issue 番号を抽出して、コミットメッセージに自動で付与する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;など。&lt;/p&gt;
&lt;p&gt;前提として、コミットメッセージに Issue 番号を書いておくと、自動的にその Issue と紐づけをしてくれるのもおなじみですね。（PR なんかでもそうですが）
自分は以前から手動で Issue 番号を書いていたので、これを Git フックで自動化したいなぁと思ったのでした。&lt;/p&gt;
&lt;h4&gt;Git フックに関するパッケージ&lt;/h4&gt;
&lt;p&gt;Git フックを設定となると、多くの方はそれを扱いやすくしてくれるパッケージを利用しているのではないでしょうか。
割と有名なものだと Husky とか。
&amp;lt;OG url=&quot;https://typicode.github.io/husky&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;自分が Git フックを活用するようになった頃、ちょうど Husky はライセンス問題でごたごたしていた時だったので、自分は simple-git-hooks の方を採用したのでした。
Husky に対して、こちらは Zero dependency かつ軽量版なやつ。
&amp;lt;OG url=&quot;https://github.com/toplenboren/simple-git-hooks&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;じゃあ、simple-git-hooks で今回やりたい仕組みを設定しよう！となったわけですが、ググっても Husky の記事ばかりヒットしまして。
基本的な設定方法はおおよそ同じとは思いつつ、実際にやってみるといろいろハマったので、せっかくなら記事にするかとなった次第です。&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;simple-git-hooks を初期設定済み&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;動作確認環境.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows：Git Bash × GNU sed&lt;/li&gt;
&lt;li&gt;Mac：iTerm × BSD sed&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;設定方法の結論&lt;/h2&gt;
&lt;h3&gt;使用する Git フック&lt;/h3&gt;
&lt;p&gt;検索でヒットした多くの記事は commit-msg フックを使用して、コミットメッセージの先頭か末尾に番号を付与しているような感じでした。
ただ、自分の場合はコミットメッセージの&lt;strong&gt;一行目末尾&lt;/strong&gt;に Issue 番号をつけたくて。&lt;/p&gt;
&lt;p&gt;commit-msg フックだとちょっとめんどくさくね...？
一行目だけ抽出して末尾に番号付けて、残りの行を結合するとかやらないけなくね...？&lt;/p&gt;
&lt;p&gt;ということで、prepare-commit-msg フックを使用する方向に。
あらかじめコミットメッセージテンプレに置換用の文字を入れておき、コミットメッセージ入力前にブランチ名から抽出した Issue 番号に置換する、ということをやることにしました。&lt;/p&gt;
&lt;p&gt;このやり方は、こちらの記事を参考にさせていただきました。
&amp;lt;OG url=&quot;https://tech.mobilefactory.jp/entry/2021/12/06/000000&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;コミットメッセージテンプレ&lt;/h3&gt;
&lt;p&gt;使用するコミットメッセージテンプレの1行目に置換用のテキストを入れておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(#Issue)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;prepare-commit-msg フックのスクリプト&lt;/h3&gt;
&lt;p&gt;prepare-commit-msg はコミットメッセージを入力する前に実行されるフックです。
&lt;a href=&quot;#sed-%E3%82%B3%E3%83%9E%E3%83%B3%E3%83%89%E3%81%AE%E9%81%95%E3%81%84&quot;&gt;後述&lt;/a&gt;しますが、sed コマンドが GNU でも BSD でも動作するように組んでます。&lt;/p&gt;
&lt;p&gt;ブランチ名の前部分が&lt;code&gt;ブランチ種別/Issue番号&lt;/code&gt;という命名だと抽出する仕組み。
ブランチ種別部分は一旦以下の3つにしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;feature&lt;/li&gt;
&lt;li&gt;bugfix&lt;/li&gt;
&lt;li&gt;release&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ブランチ名の例.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;feature/1&lt;/li&gt;
&lt;li&gt;feature/1-test&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh

## sed コマンドが GNU か BSD か確認
GNU_SED=true
sed --version 1&amp;gt;/dev/null 2&amp;gt;/dev/null || GNU_SED=false
echo $GNU_SED

## コミットメッセージ入力前に、ブランチ名から Issue 番号を抽出して置換する
COMMIT_MSG_FILE=$1
MESSAGE=$(cat &quot;$COMMIT_MSG_FILE&quot;)

ISSUE_NUMBER=$(git rev-parse --abbrev-ref HEAD | grep -Eo &quot;^(feature|bugfix|release)/[0-9]+&quot; | grep -Eo &quot;[0-9]+&quot;)
if [ -n &quot;$ISSUE_NUMBER&quot; ]; then
  if [ &quot;$GNU_SED&quot; == &quot;true&quot; ]; then
    sed -i &quot;s/(#Issue)/(#$ISSUE_NUMBER)/&quot; $COMMIT_MSG_FILE
  else
    sed -i &quot;&quot; &quot;s/(#Issue)/(#$ISSUE_NUMBER)/&quot; $COMMIT_MSG_FILE
  fi
  exit 0
fi

## Issue 番号が抽出できなかった + そのまま続行する場合は、置換用文字列を除去する
read -p &quot;Issue 番号がブランチ名にないので置換できませんが、続行しますか？ (y/N): &quot; YM &amp;lt; /dev/tty
case &quot;$YM&quot; in
  [yY]*)
    if [ &quot;$GNU_SED&quot; == &quot;true&quot; ]; then
      sed -i &quot;s/(#Issue)//&quot; $COMMIT_MSG_FILE
    else
      sed -i &quot;&quot; &quot;s/(#Issue)//&quot; $COMMIT_MSG_FILE
    fi;;
  *) echo &quot;abort.&quot; ; exit 1 ;;
esac
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Mac の場合は、ファイル作成時に実行権限がつかない？ようだったので実行権限を付与しておく必要がありました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;chmod a+x .githooks/prepare-commit-msg
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;simple-git-hooks の設定&lt;/h3&gt;
&lt;p&gt;前述のカスタムのフックスクリプトを実行するように書けばいいわけですが、&lt;code&gt;\&quot;$@\&quot;&lt;/code&gt;で引数を渡すのを忘れずに（引数に関しては&lt;a href=&quot;#%E5%BC%95%E6%95%B0%E3%81%8C%E5%85%A5%E3%82%89%E3%81%AA%E3%81%84&quot;&gt;後述&lt;/a&gt;も参照）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 抜粋
&quot;simple-git-hooks&quot;: {
  &quot;prepare-commit-msg&quot;: &quot;./.githooks/prepare-commit-msg \&quot;$@\&quot;&quot;
},
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;これらの内容で、simple-git-hooks の設定を反映。
以降は、コミットしてコミットメッセージ入力前にフックスクリプトが動作して、置換用テキストがブランチ名から抽出した Issue 番号に指し変わっているはずです。&lt;/p&gt;
&lt;h2&gt;躓いたところと補足説明&lt;/h2&gt;
&lt;p&gt;たったこれだけの設定をするだけでも、意外といろんなところに躓きました。
（普段、シェル書き慣れてないのもあるかも😇）&lt;/p&gt;
&lt;p&gt;せっかくなので、どのあたりに躓いたのかと、その補足説明も書いておこうかと。&lt;/p&gt;
&lt;h3&gt;引数が入らない？&lt;/h3&gt;
&lt;p&gt;最初、いろんな記事を読んでみたところ、フックスクリプトの引数&lt;code&gt;$1&lt;/code&gt;にコミットメッセージファイルの情報が入ってくることがわかりました。
なので、&lt;code&gt;$1&lt;/code&gt;を使うようなシェルを書いていったわけですが、いざ実行するとなぜか値が入ってこない...。
引数の数が入る&lt;code&gt;$#&lt;/code&gt;を確認すると0。&lt;/p&gt;
&lt;p&gt;どういうこと??? simple-git-hooks だとなんか引数の渡り方が違うんか???と混乱。
原因としては、simple-git-hooks の設定定義時に引数を渡すような書き方をしていなかったせいでした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 誤
&quot;prepare-commit-msg&quot;: &quot;./.githooks/prepare-commit-msg&quot;

// 正
&quot;prepare-commit-msg&quot;: &quot;./.githooks/prepare-commit-msg \&quot;$@\&quot;&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;simple-git-hooks は設定したフックの種類に応じて、&lt;code&gt;.git/hooks&lt;/code&gt;配下にフックファイルを作成します。
（今回だと&lt;code&gt;.git/hooks/prepare-commit-msg&lt;/code&gt;）
そのフックファイルの中で、simple-git-hooks 設定で設定したコマンドを実行する、というような仕組みになっているのです。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/sh
./git-hook/commit-msg &quot;$@&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのため、元々のフックファイルへ渡されるようになっている引数をカスタムのフックの方でも使用したい場合は、そのまま引数を渡してあげれば OK。&lt;/p&gt;
&lt;p&gt;引数渡してないんだから、そりゃあ引数ないってなるやん。&lt;/p&gt;
&lt;h3&gt;read の処理で止まってくれない&lt;/h3&gt;
&lt;p&gt;ブランチ名から Issue 番号を抽出するにあたり、もし番号が見つからなかった場合は、そのまま続行してよいか応答処理を入れるようにしました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://dev.classmethod.jp/articles/waiting-for-your-input-with-read-command&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;こちらの記事を見て、シェルで応答処理を実現するには read を使えばいいらしいぞとわかり、書いてみて動作確認。
すると、本来はユーザの入力待ち状態になり止まってくれるはずなのですが、止まってくれない...。
どうもユーザ入力部分が勝手に入力されて、y でも Y でもないので続行しない方の処理に進んでしまっているようでした。&lt;/p&gt;
&lt;p&gt;この件を調べてみると、ループ処理の中で read を使っているとそういうことがあるという記事がいくつかヒット。
今回はループ処理の中ではないんだけどなと思いつつ。
コンソールからの入力しか受け付けないようにするには&lt;code&gt;&amp;lt; /dev/tty&lt;/code&gt;をつけるとよいとのことだったので、つけたところ止まってくれるようになりました。
&amp;lt;OG url=&quot;https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12125082002&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;sed コマンドの違い&lt;/h3&gt;
&lt;p&gt;Windows（Git Bash）でも Mac でも使用できる sed コマンドですが、どうも2種類あって、微妙に挙動が違うらしいとわかり。
種類としては以下の2つ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows（Git Bash）or Linux：GNU sed&lt;/li&gt;
&lt;li&gt;Mac：BSD sed&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回使用している、ファイル上書きの&lt;code&gt;-i&lt;/code&gt;オプションも挙動が違うものの1つでして。
BSD sed の方では、&lt;code&gt;-i&lt;/code&gt;オプションの後ろにバックアップファイルの拡張子をつける必要があるとのこと。
（バックアップファイルを作りたくない場合は、空文字にすると作られません）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# GNU sed
sed -i &quot;s/(#Issue)/(#$ISSUE_NUMBER)/&quot; $COMMIT_MSG_FILE

# BSD sed
sed -i &quot;&quot; &quot;s/(#Issue)/(#$ISSUE_NUMBER)/&quot; $COMMIT_MSG_FILE
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのため、できればどちらでも動作するようなスクリプトにしたいなぁと試行錯誤でわちゃわちゃして。
結果的に、&lt;code&gt;sed --version&lt;/code&gt;コマンドの実行結果でどちらの sed なのか判断するフラグ変数を作り、それで分岐させるようにしました。
BSD sed の場合は、&lt;code&gt;--version&lt;/code&gt;オプションが存在しないのでエラーになることを利用したものです。&lt;/p&gt;
&lt;p&gt;それと&lt;code&gt;sed --version&lt;/code&gt;の実行結果を出力はしたくなかったので、&lt;code&gt;1&amp;gt;/dev/null 2&amp;gt;/dev/null&lt;/code&gt;で出力しないようにしています。
&amp;lt;OG url=&quot;https://qiita.com/harasakih/items/868a850fcdc99a2c37b0&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以上、小ネタ記事をお送りしました。
自分は普段シェルをあまり書いてない人間なので、なかなかうまくいかなくて四苦八苦...😇&lt;/p&gt;
&lt;p&gt;とはいえ、前からやりたいと思っていた仕組みを作ることができたので、これからばっちり活用していきたいですね。&lt;/p&gt;
&lt;p&gt;採用こそしなかったですが、Git フックってほかの言語でも書けるらしく、へーそうなんだーという発見があったり。
Git フックの理解が少し深まったので、ほかにも便利そうなスクリプト組んでみるのもよさそうです。
また、何かあったら記事にするかもしれません。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://git-scm.com/book/ja/v2/Git-%E3%81%AE%E3%82%AB%E3%82%B9%E3%82%BF%E3%83%9E%E3%82%A4%E3%82%BA-Git-%E3%83%95%E3%83%83%E3%82%AF&quot;&gt;Git - Git のカスタマイズ - Git フック&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.mobilefactory.jp/entry/2021/12/06/000000&quot;&gt;コミットメッセージにブランチ名を自動挿入する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.classmethod.jp/articles/waiting-for-your-input-with-read-command/&quot;&gt;BASHシェルスクリプトで「キー入力待ち」プロンプトを実装する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://detail.chiebukuro.yahoo.co.jp/qa/question_detail/q12125082002&quot;&gt;知恵袋 - unixコマンドについてご教授下さい&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/harasakih/items/868a850fcdc99a2c37b0&quot;&gt;bashで標準出力、エラーを捨てるとか、ファイルディスクリプタ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>devChallenges - Front-end 編を完走？したので、さっくり振り返ってみた</title><link>https://changeofpace.site/posts/2022-03-04-devchallenges-front-end</link><guid isPermaLink="true">https://changeofpace.site/posts/2022-03-04-devchallenges-front-end</guid><description>devChallengesのFront-end編の課題内容と使用技術等の振り返り</description><pubDate>Fri, 04 Mar 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、先日復職して現在進行形で復活中のよしです。
devChallenges の課題をさらに1コース完走？したので、また振り返ってみました。&lt;/p&gt;
&lt;p&gt;※翻訳は Google 翻訳を使用しています。
※2023/09/25...devChallenges が全面リニューアルしたため、旧課題は legacy サブドメインの方に移りました。リンクを修正。
&lt;strong&gt;※2025/02/09...legacy サブドメインのサイトがクローズされたようでアクセスできなくなっていました。それに伴いサイトへのリンクを除去。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;devChallenges のおさらい&lt;/h2&gt;
&lt;p&gt;※&lt;code&gt;https://legacy.devchallenges.io&lt;/code&gt;というドメインでかつてはサイトがありましたが、現在はクローズされたようです。&lt;/p&gt;
&lt;p&gt;ざっくりいうと、Web Developer になるための学習コンテンツや課題を提供しているコミュニティです。
こちらで提供されているものに、Figma のデザインデータとユーザストーリーを元に課題作品を作るというものが3コースあり。そのうちの Fron-end 編をやったので振り返るよというのが今回の趣旨です。&lt;/p&gt;
&lt;p&gt;この辺りは Responsive Web 編の振り返りをした前回の記事でも書いたので、ちょっと端折ります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-12-24-devchallenges-responsive-web&quot;&gt;devChallenges - Responsive Web 編を完走したので、さっくり振り返ってみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Front-end 編の課題を振り返ってみる&lt;/h2&gt;
&lt;p&gt;Responsive Web 編が主に Web サイトのページの制作だったのに対して、Front-end 編ではモックデータや外部 API を利用した Web アプリを作るようなものが主になります。&lt;/p&gt;
&lt;p&gt;難易度的には先に Responsive Web 編をやった方がいいかなぁといった感じですが、自分は Front-end 編を先に開始してしまったので、前半の課題はなかなか苦戦しました...。
（Front-end 編折り返しくらいで Responsive Web 編を先にやってから、続きをやりました）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Use a Front-end Framework and choose any Frameworks or Libraries&lt;/p&gt;
&lt;p&gt;訳：
フロントエンドフレームワークを使用し、フレームワークまたはライブラリを選択します&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Front-end 編のルールの1つにこうあるので、自分は Next.js を練習がてら採用。
どの課題も TypeScript × Next.js × emotion で作成しています。&lt;/p&gt;
&lt;p&gt;では、8課題をさっくり振り返っていきます。
（あまり実装のネタバレにならないような書き方としました）&lt;/p&gt;
&lt;h3&gt;完走？&lt;/h3&gt;
&lt;p&gt;振り返ると言っておきながら、先に言っておくことがありまして。&lt;/p&gt;
&lt;p&gt;当記事のタイトルを&lt;code&gt;完走？&lt;/code&gt;としている件。
Front-end 編も8課題用意されてはいるのですが、実は最後の課題だけ進めることができませんでした。
というのも、この課題で使用案内されていた GitHub Jobs API がすでに廃止されて使えなくなっていたためです。
&amp;lt;OG url=&quot;https://github.blog/changelog/2021-04-19-deprecation-notice-github-jobs-site&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;デザインデータはあるので、適当にモックデータを作ってやる手もあったのですが、そこまでしてやる価値があるかな？というのが気になってしまいまして。
他に求人情報を提供する API は探せばあるでしょうが、同様にそこまでしてやらなくていいかな、というので今回は進めていません。&lt;/p&gt;
&lt;p&gt;そのため、振り返るのは最後の課題以外の7課題になります。&lt;/p&gt;
&lt;h3&gt;1 - Button component&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see different button types: default, outline and text
（訳：さまざまなボタンタイプが表示されます：デフォルト、アウトライン、テキスト）&lt;/li&gt;
&lt;li&gt;I can choose to disable box-shadow
（訳：ボックスシャドウを無効にすることを選択できます）&lt;/li&gt;
&lt;li&gt;I can choose to disable the button
（訳：ボタンを無効にすることを選択できます）&lt;/li&gt;
&lt;li&gt;I can choose to have an icon on the left or right (Use Google Icon and at least 5 variants)
（訳：左側または右側にアイコンを表示するように選択できます - Googleアイコンと少なくとも5つのバリエーションを使用）&lt;/li&gt;
&lt;li&gt;I can have different button sizes
（訳：ボタンのサイズを変えることができます）&lt;/li&gt;
&lt;li&gt;I can have different colors
（訳：色を変えてもいい）&lt;/li&gt;
&lt;li&gt;When I hover or focus, I can see visual indicators
（訳：ホバーまたはフォーカスすると、視覚的なインジケーターが表示されます）&lt;/li&gt;
&lt;li&gt;I can still access all button attributes
（訳：引き続きすべてのボタン属性にアクセスできます）&lt;/li&gt;
&lt;li&gt;(optional) Show button in a similar way like the design or use Storybook. Otherwise, showing the button in multiple states is enough
（訳：デザインと同じようにボタンを表示するか、ストーリーブックを使用します。それ以外の場合は、ボタンを複数の状態で表示するだけで十分です）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-button-component&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ボタンコンポーネントと、そのカタログページを作る課題。
ボタンコンポーネントをいかに汎用的に作るか？を考えていきます。&lt;/p&gt;
&lt;p&gt;自分が devChallenges の課題で一番最初にやった課題で、当時は HTML・CSS 基礎力もガタガタというひどい状態だったので、なかなか時間がかかりました。
まだ自分のやり方があまり定まってなかった部分もありましたし。
慣れてる方だったらポンポンと終わるんじゃないかなぁと。&lt;/p&gt;
&lt;p&gt;執筆時点で、オプションの Storybook のやつはやっていません。&lt;/p&gt;
&lt;h3&gt;2 - Input component&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see error state
（訳：エラー状態を確認できます）&lt;/li&gt;
&lt;li&gt;I can choose to disable input
（訳：入力を無効にすることを選択できます）&lt;/li&gt;
&lt;li&gt;I can choose to have helper text
（訳：ヘルパーテキストを選択できます）&lt;/li&gt;
&lt;li&gt;I can choose to have an icon on the left or right (Use Google Icon and at least 5 variants)
（訳：左側または右側にアイコンを表示するように選択できます - Googleアイコンと少なくとも5つのバリエーションを使用）&lt;/li&gt;
&lt;li&gt;I can have different input sizes
（訳：さまざまな入力サイズを使用できます）&lt;/li&gt;
&lt;li&gt;I can have different colors
（訳：色を変えてもいい）&lt;/li&gt;
&lt;li&gt;I can choose to have input take the width of the parent
（訳：入力に親の幅を使用させることを選択できます）&lt;/li&gt;
&lt;li&gt;I can have multiline input like a textarea
（訳：テキストエリアのように複数行入力できます）&lt;/li&gt;
&lt;li&gt;When I hover or focus, I can see visual indicators
（訳：ホバーまたはフォーカスすると、視覚的なインジケーターが表示されます）&lt;/li&gt;
&lt;li&gt;I can still access all input attributes
（訳：引き続きすべての入力属性にアクセスできます）&lt;/li&gt;
&lt;li&gt;(optional) Show input in a similar way like the design or use Storybook. Otherwise, showing the input in multiple states is enough
（訳：デザインと同様の方法で入力を表示するか、ストーリーブックを使用します。それ以外の場合は、複数の状態で入力を表示するだけで十分です）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-input-component&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;インプットコンポーネントと、そのカタログページを作る課題。
前回の課題の input 版ですね。
同様にいかに汎用的なインプットコンポーネントを作るか、アイコンも付与できるようなコンポーネントをどう作るか、などを考えていきます。&lt;/p&gt;
&lt;p&gt;input は button と比べて装飾の仕方が変わってくるので、まだそれに慣れてなかった当時の自分はそこそこ苦戦してた気がします。
アイコン入れるのどうやんのー！？みたいな。&lt;/p&gt;
&lt;p&gt;これも執筆時点で、オプションの Storybook のやつはやっていません。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;I can still access all input attributes
（訳：引き続きすべての入力属性にアクセスできます）&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;p&gt;ユーザストーリーのこちらに関して。
解釈の仕方は色々ありそうな気がするのですが、カスタムの Input コンポーネントと input タグとで競合する props を排除してたりするので、満たせてないかな？と思い下記のような補足説明を加えた状態にしています。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;excluded some props whose settings conflict with custom props.
Also, Since it was created as a component for text input, the type of input element is limited to text format.&lt;/p&gt;
&lt;p&gt;訳：
設定がカスタム props と競合する一部の props を除外しました。
また、テキスト入力のコンポーネントとして作成されたため、入力要素のタイプはテキスト形式に制限されます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;3 - Windbnb&lt;/h3&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/no-doubled-joshi --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a list of properties
（訳：プロパティのリストが表示されます）&lt;/li&gt;
&lt;li&gt;I can see the property card with a name, rating, apartment type, and super host
（訳：名前、評価、アパートの種類、スーパーホストが記載されたプロパティカードが表示されます）&lt;/li&gt;
&lt;li&gt;I can open the filter drawer
（訳：フィルタードロワーを開けます）&lt;/li&gt;
&lt;li&gt;I can filter properties by location and number of guests
（訳：場所とゲストの数でプロパティをフィルタリングできます）&lt;/li&gt;
&lt;li&gt;I can see the number of filtered items
（訳：フィルタリングされたアイテムの数を確認できます）&lt;/li&gt;
&lt;li&gt;I can see pages following given designs
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-windbnb&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;簡易的な検索機能を持つ、ミニマムな Airbnb クローンを作る課題。
データについてはモックデータが用意されているので、それを使用。
データの表示をどうやるか、ドロワーをどう作るか、などを考えていきます。&lt;/p&gt;
&lt;p&gt;自分は最初、無理に React Query を使うようなやり方をしていたので余計に複雑化させてしまっていました😇
推しているライブラリだからって、何でもかんでも使うの良くない。
（のちにローカル state を使うようなリファクタリングして除去しました）&lt;/p&gt;
&lt;p&gt;それとドロワーの実装はすごい悩みました。
これもまた当時は慣れてなくて。
なんかアニメーションがうまくいかない！？（クローズ時のアニメーションが動作しない）とか、ドロワー開いている時のその下にあるコンテンツの操作制御のあたりとか。&lt;/p&gt;
&lt;p&gt;Material UI のドロワーコンポーネントのスタイルを参考にしたりしつつ、操作制御に関して悩んだツイートをしていたら、フォロワーの方に inert 属性のことを教えていただきました。&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;
&amp;lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&amp;gt;Material UI の実装はわからないですかが、この辺が役に立つかもしれません&amp;lt;a href=&quot;https://t.co/mJu19xy3Em&quot;&amp;gt;https://t.co/mJu19xy3Em&amp;lt;/a&amp;gt;&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;ポリフィル&amp;lt;a href=&quot;https://t.co/NXV7BWLdaP&quot;&amp;gt;https://t.co/NXV7BWLdaP&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;— ジャングル大帝 (@LEO_i7) &amp;lt;a href=&quot;https://twitter.com/LEO_i7/status/1406195146287321089?ref_src=twsrc%5Etfw&quot;&amp;gt;June 19, 2021&amp;lt;/a&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;こんな便利な属性があったのかー！と大きな発見でしたね。
これ以降、ドロワーやメニューを実装する時は inert 属性を活用するようになりました。
Responsive Web 編でも活用してたやつですね。&lt;/p&gt;
&lt;h3&gt;4 - Todo app&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can add a new task
（訳：新しいタスクを追加できます）&lt;/li&gt;
&lt;li&gt;I can complete a task
（訳：タスクを完了できます）&lt;/li&gt;
&lt;li&gt;I can toggle between All, Active and Completed
（訳：All、Active、Completedを切り替えることができます）&lt;/li&gt;
&lt;li&gt;I can remove one or all tasks under the Completed tab
（訳：タブで1つまたはすべてのタスクを削除できます）&lt;/li&gt;
&lt;li&gt;(optional) Store the data in local storage that when I refresh the page I can still see my progress
（訳：データをローカルストレージに保存します。ページを更新しても、進行状況を確認できます。）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-todo-app&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Todo アプリを作る課題。
Todo のデータや状態をどう管理するかを考えていきます。&lt;/p&gt;
&lt;p&gt;自分はローカルストレージによる永続化にもチャレンジしました。
ローカルストレージを使ったことは何気にこれが初めてだったので、最初はちょっと戸惑いましたね。やや癖がある感じというか。
慣れてさえしまえば処理の使い回しとかできるんでしょうが。
他タブでのストレージ変更を検知するイベントの存在なんかも知ることができました。&lt;/p&gt;
&lt;h3&gt;5 - Random quote generator&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a random quote
（訳：私はランダムな引用を見ることができます）&lt;/li&gt;
&lt;li&gt;I generate a new random quote
（訳：新しいランダムな引用を生成します）&lt;/li&gt;
&lt;li&gt;When I select quote author, I can see a list of quotes from them
（訳：引用著者を選択すると、それらからの引用のリストが表示されます）&lt;/li&gt;
&lt;li&gt;I can see quote genre under the author
（訳：著者の下で引用のジャンルを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-random-quote-generator&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://pprathameshmore.github.io/QuoteGarden/&quot;&gt;QuoteGarden API&lt;/a&gt; を使用した、名言引用アプリを作る課題。
この課題から外部 API を使うような形式に。
外部 API から取得したデータを活用して、どうアプリを作っていくかを考えていきます。&lt;/p&gt;
&lt;p&gt;業務で API との連携は経験あったので、この課題はそんなに苦戦しなかった気がします。&lt;/p&gt;
&lt;h3&gt;6 - Country quiz&lt;/h3&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/no-doubled-joshi --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see at least 2 types of questions: a city is the capital of.. or a flag belong to country..
（訳：私は少なくとも2つのタイプの質問を見ることができます：都市は首都です..または旗は国に属します..）&lt;/li&gt;
&lt;li&gt;I can see select an answer
（訳：私は答えを選択するのを見ることができます）&lt;/li&gt;
&lt;li&gt;I can see if my answer is correct or incorrect
（訳：答えが正しいか間違っているかがわかります）&lt;/li&gt;
&lt;li&gt;When I answer correctly, I can move on to the next question
（訳：正解したら次の質問に移ります）&lt;/li&gt;
&lt;li&gt;When I answer incorrectly, I can see my results and try again
（訳：間違って答えると、結果を確認して再試行できます）&lt;/li&gt;
&lt;li&gt;I can try again
（訳：もう一度試すことができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-country-quiz&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;REST COUNTRIES API を使用した、国情報クイズアプリを作る課題。
外部 API から取得したデータをそのまま表示するのではなく、クイズ用に加工するのをどうするか、というのを考えていきます。&lt;/p&gt;
&lt;p&gt;課題上で案内されていた REST COUNTRIES API は&lt;code&gt;https://restcountries.eu/&lt;/code&gt;でしたが、こちらは現在サブスクリプション API になったそうで使用できませんでした。
そのため、この API のオープンソース版を作られた方がいて、今回はそちらを使わせていただきました。
&amp;lt;OG url=&quot;https://restcountries.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;最初は、取得したデータをクイズ用に加工するイメージや、どの状態管理が必要であるかといったイメージがなかなかスムーズにいかなくて苦戦しました。
その中でふと Responsive Web 編を先にやろうかななんて思いついてしまったので、その時点での記録を Issue に残しておいて、この課題をしばらく中断。
Responsive Web 編を終えて戻ってきた頃には、なんとなく実装のイメージが浮かんで課題を進められました。
詰まった時に時間を置くのは、やはり有効ですね(笑)&lt;/p&gt;
&lt;p&gt;ただ、ユーザストーリーを少し勘違いしていたところがあり。
本来は連続何問正解できるかな？というクイズ形式のはずが、10問中いくつ正解できるかな？という仕様で実装してしまっていました。
実はこの記事を書いて振り返りをしている時に気づいて、慌てて修正してたりします😅&lt;/p&gt;
&lt;p&gt;これまで何気に使ったことがなかったコンテクストを使った実装にもチャレンジできて、その使い方がおおよそわかってよかったです。&lt;/p&gt;
&lt;h3&gt;7 - Weather app&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see city weather as default, preferably my current location
（訳：私はデフォルトとして都市の天気を見ることができます、できれば私の現在の場所）&lt;/li&gt;
&lt;li&gt;I can search for city
（訳：都市を検索できます）&lt;/li&gt;
&lt;li&gt;I can see weather of today and the next 5 days
（訳：今日と次の5日間の天気を見ることができます）&lt;/li&gt;
&lt;li&gt;I can see the date and location of the weather
（訳：天気の日付と場所がわかります）&lt;/li&gt;
&lt;li&gt;I can see according to image for each type of weather
（訳：天気の種類ごとに画像で見ることができます）&lt;/li&gt;
&lt;li&gt;I can see the min and max degree each day
（訳：毎日最小度と最大度を見ることができます）&lt;/li&gt;
&lt;li&gt;I can see wind status and wind direction
（訳：風の状態と風向がわかります）&lt;/li&gt;
&lt;li&gt;I can see humidity percentage
（訳：湿度のパーセンテージがわかります）&lt;/li&gt;
&lt;li&gt;I can see a visibility indicator
（訳：視界インジケーターが見えます）&lt;/li&gt;
&lt;li&gt;I can see the air pressure number
（訳：気圧数がわかります）&lt;/li&gt;
&lt;li&gt;(optional) I can request my current location weather
（訳：現在地の天気をリクエストできます）&lt;/li&gt;
&lt;li&gt;(optional) I can convert temperature in Celcius to Fahrenheit and vice versa
（訳：摂氏の温度を華氏に、またはその逆に変換できます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-weather-app&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;MetaWeather API を使用した、天気予報アプリを作るチャレンジ。
位置情報をどうやって取得するか、都市検索機能をどう実装するかなどを考えていきます。
API をフロント側から直接呼ぶと CORS に引っかかるので、その辺りの対応も必要になってきます。&lt;/p&gt;
&lt;p&gt;※2023/11/25追記
MetaWeather API が使えなくなったため、現在は &lt;a href=&quot;https://openweathermap.org/api&quot;&gt;OpenWeather API&lt;/a&gt;で案内されています。&lt;/p&gt;
&lt;p&gt;Country quiz のようなデータ加工はあまり必要ないので、人によってはこちらの方が簡単かも？
少しデザイン性のある課題なので、完成した時に「おー！」ってなったりするかもです。自分はちょっとそうなりました(笑)
メーターを表現する要素のことを知れたりと、新しい発見もありましたね。&lt;/p&gt;
&lt;h2&gt;主な使用した技術やライブラリなどの振り返り&lt;/h2&gt;
&lt;p&gt;これを書くと若干実装のネタバレになる気もしますが、せっかくなので今回も一緒に振り返ってみました。
Responsive Web 編でも書いたやつは、サラッとだけ書いてます。
（当記事の投稿時点での使用技術です）&lt;/p&gt;
&lt;h3&gt;TypeScript × Next.js × emotion&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://www.typescriptlang.org&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://nextjs.org&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://emotion.sh/docs/introduction&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;はじめの方に書いた通り、Front-end 編では全部この構成でやっています。
Next.js の勉強というのと、 CSS in JS をどれかひとつはわかるようになりたいというので emotion を採用したという経緯がありました。
課題を通して理解を深められたので、実案件にも活かしていきたいところです。&lt;/p&gt;
&lt;h3&gt;静的解析 + フォーマッタ&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://eslint.org&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://prettier.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://stylelint.io&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;フロントエンドでお馴染みのやつです。
VSCode 拡張として自動整形を動作させたり、GitHub Actions 上でもチェック、コミット時に自動整形したりとしてました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/toplenboren/simple-git-hooks&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/okonet/lint-staged&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Material Icons&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://google.github.io/material-design-icons&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Front-end 編でも、アイコンはこちらを使うように案内があったので使用しています。
emotion を使っているので、emotion-icons 経由で使用。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/emotion-icons/emotion-icons&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;modern-css-reset&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/hankchizljaw/modern-css-reset&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;個人的にちょうどいいと思っているリセット CSS なので使ってます。&lt;/p&gt;
&lt;h3&gt;wicg-inert&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/WICG/inert&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Windbnb のところでも触れた、inert 属性のポリフィル。
ドロワーやメニュー、モーダル UI での要素の非活性制御をやりやすくするやつ。
（スクロール抑制には対応してないので、そこは別途対応が必要）&lt;/p&gt;
&lt;p&gt;inert 属性の説明に関しては、こちらの記事がやはりとても参考になるのでまた載せておきます。
&amp;lt;OG url=&quot;https://standard.shiftbrain.com/blog/unavailable-inert-regions-and-inert-attribute&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;focus-visible&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/WICG/focus-visible&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;focus-visible 疑似クラスのポリフィル。
focus-visible 疑似クラスは Safari の対応がイマイチなので、ポリフィルを使うようにしています。&lt;/p&gt;
&lt;p&gt;主に使用しているのは button 要素の outline 制御のあたり。&lt;/p&gt;
&lt;h3&gt;csx&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/typestyle/csx&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;CSS に関するユーティリティな関数を提供するやつ。
主に色の制御に使うことが多いです。&lt;/p&gt;
&lt;p&gt;RGB 値からでなくカラーコードを基準とした暗い色や明るい色、透明度を反映させた色を作りたいことがあったので、このライブラリを使った関数を作って活用。
Sass だったら元々関数が用意されているんですが、emotion だと使えないっぽかったので。&lt;/p&gt;
&lt;h3&gt;uuid&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/uuidjs/uuid&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;UUID を生成してくれるやつ。
Todo app で一意の ID をつけたかったので使用。
今思えば、別に UUID じゃなくてもよかったかな...。&lt;/p&gt;
&lt;h3&gt;ky・ky-universal&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/sindresorhus/ky&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/sindresorhus/ky-universal&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;fetch ベースの HTTP クライアントライブラリ。
後者の方は、フロントエンド、サーバーサイド両方で使えるやつ。&lt;/p&gt;
&lt;p&gt;外部 API を利用する課題で使用。
最近は axios からこちらに移行している流れを感じているので、昨年あたりから使うようになりました。
ただ、型安全な HTTP リクエストでお馴染みの  Aspida が ky をサポートしていないので、今後は使わなくなっていくかも？&lt;/p&gt;
&lt;h3&gt;React Query&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://react-query.tanstack.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;キャッシュ機構も含んだ状態管理ライブラリ。
一部の課題で外部 API から取得したデータを保持するのに使用。&lt;/p&gt;
&lt;p&gt;同様の機能を持つライブラリはいくつかありますが、個人的には React Query が使いやすい感じに思えたので使用しています。
コンテクストやローカル state との使い分け、もしくは組み合わせの辺りをもっと追求して、いい感じにしていきたい。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-successive-word, ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;完走？してどうだった？&lt;/h2&gt;
&lt;p&gt;devChallenges を始めたきっかけに関しては、Responsive Web 編の記事でも書いたのでざっくりいうと、自分の HTML・CSS 基礎力弱弱じゃね？と思う出来事があったからです。
ホント、トラウマになりそうな出来事がありましてね...。
それを克服するために始めたわけです。
React 周りを伸ばしていきたかったので、その流れで Next.js や emotion の勉強も兼ねて。&lt;/p&gt;
&lt;p&gt;やっぱり実際に何か作ってみるというのは、すごく勉強になりますね！
自信にも繋がりますし、本当にやってよかったなぁと心から思います。&lt;/p&gt;
&lt;p&gt;ちなみに devChallenges を始めた頃は復職活動をしていた時期でして、つい先月に復職したところです。
この復職先でのコーディング試験では、devChallenges を通して学んだことがすごく活きたと思っているので、そういう意味でもやってよかったなぁと。&lt;/p&gt;
&lt;p&gt;ただ、テストコードが全然書けていないので、テストの勉強がてらテストコードを書いてみようかなとも構想中です。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回は Front-end 編の振り返りをさっくりお送りしました。&lt;/p&gt;
&lt;p&gt;残りは Full-stack 編となりましたが、これはバックエンドも含むので1課題作るだけでも時間かかりそうですね。
仕事との兼ね合いもあるので、やるかどうかは微妙なところではありますが、勉強になることは間違いないので前向きに検討しようかなと。&lt;/p&gt;
&lt;p&gt;devChallenges、興味を持たれた方はぜひチャレンジしてみてはいかがでしょうか。
ではではー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;p&gt;※記事系のみ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://standard.shiftbrain.com/blog/unavailable-inert-regions-and-inert-attribute&quot;&gt;UIにおける見えるけど利用できない非活性な領域の実装とinert属性について&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>TypeScript × Aspida × Zodで型安全なHTTPリクエストについて考えてみた</title><link>https://changeofpace.site/posts/2022-02-13-ts-type-safe-request</link><guid isPermaLink="true">https://changeofpace.site/posts/2022-02-13-ts-type-safe-request</guid><description>AspidaとZodを使って型安全なHTTPリクエストをやってみようという検証をした記録</description><pubDate>Sat, 12 Feb 2022 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;TypeScrpt の型定義に日々助けられているなぁ、なんて思っている、よしです。
今回は型安全な HTTP リクエストについて検証してみたことを書いてみました。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;検証しようと思った背景&lt;/h2&gt;
&lt;p&gt;ざっくりいうと、フロントエンドの実装をしていて、API のレスポンスチェック用に毎回自前の型ガード実装するのだるいなと思ったことがあったからです。&lt;/p&gt;
&lt;p&gt;こういうやつ.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type User = {
  id: number;
  name: string;
};

export type Users = User[]

const isUser = (arg: unknown): arg is User =&amp;gt; {
  const u = arg as User;

  return (
    typeof u.id === &apos;number&apos; &amp;amp;&amp;amp;
    typeof u.name === &apos;string&apos;
  );
};

const isUsers = (args: unknown): args is Users =&amp;gt; {
  const us = args as Users;

  return us.every((u) =&amp;gt; isUser(u));
};

export { isUser, isUsers }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のレスポンスボディの中身が少なければ、まぁさっくり実装すればいいんですが...。
外部 API とかで中身がなんか多いやつだと、一個一個書くの地味につらみがありまして。&lt;/p&gt;
&lt;p&gt;そこから、型ガード対応しているバリデーションライブラリ導入してみるかな？
↓
せっかくならリクエスト時も型安全にしてみる？
という流れで導入検証してみようという発想になりました。&lt;/p&gt;
&lt;h2&gt;使用するライブラリ&lt;/h2&gt;
&lt;p&gt;当記事ではさらっと紹介だけします。
ライブラリの詳細は各リポジトリの README 等をご参照ください。&lt;/p&gt;
&lt;h3&gt;Aspida&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/aspida/aspida&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ブラウザと node.js のための TypeScript フレンドリーな HTTP クライアントラッパー&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;HTTP クライアントライブラリ自体と、その HTTP クライアントライブラリのラッパーパッケージを組み合わせるような使い方をします。&lt;/p&gt;
&lt;p&gt;作者の方の紹介記事はこちら
&amp;lt;OG url=&quot;https://zenn.dev/solufa/articles/getting-started-with-aspida&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;型安全な HTTP リクエスト、といえば Aspida の名を前からよく聞いて気になっていました。
記事などでも割と好印象な話を聞いていたイメージ。
よっしゃ、使ってみようとまず改めて少し調べてみると、ちょっと気になったところが。&lt;/p&gt;
&lt;h4&gt;レスポンスの型チェックについて&lt;/h4&gt;
&lt;p&gt;Aspida の特徴の1つとして以下のようなものがあります。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;パス・URL クエリ・ヘッダー・ボディ・レスポンス全てに型を指定できる&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;とても頼もしいライブラリであることは確かなのですが、この「レスポンスの型」について。
紹介記事なんかでも、よくこのことに触れられていて「レスポンスに型がつく！」「型がつくので補完がきく！」なんて書いてあったり。
でも「そもそもレスポンスがちゃんと想定した形式で返ってきているかのチェック」について書いてあるのは、ほとんど見かけなかったんですよね。&lt;/p&gt;
&lt;p&gt;自分は最近では、HTTP リクエストで取得したデータに対して、型ガードでチェックするような実装をすることが多く。
そのため、このチェックまで Aspida が面倒を見てくれるのか否かが気になってしまったのでした。
結論を言うと、Aspida はさすがにそこまでは面倒を見てくれません。
他のライブラリや自前の型ガードと組み合わせるなりする必要があります。&lt;/p&gt;
&lt;p&gt;ちなみに v2 でその辺も対応しようと現在検討されていることを、メンテナの方に教えていただきました。
ただ、どうも難航しているようです。&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;
&amp;lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&amp;gt;現状、レスポンスデータの検証がされていないのは仕様です。aspida v2で対応したいと考えており、開発中ですが、他ライブラリとの兼ね合いなどにより難航しています。
&amp;lt;/p&amp;gt;— はす (@83_hasu)
&amp;lt;a href=&quot;https://twitter.com/83_hasu/status/1489539954904481793?ref_src=twsrc%5Etfw&quot;&amp;gt;February 4, 2022&amp;lt;/a&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;なかなか難しい問題ではあるでしょうが、もし対応されたら、ますます強力なものになりますね。&lt;/p&gt;
&lt;h4&gt;@aspida/ky について&lt;/h4&gt;
&lt;p&gt;個人的に最近使用している HTTP クライアントライブラリが ky であるため、Aspida は ky に対応しているのかが気になりました。
紹介記事なんかでは ky に対応しているともあるものの、リポジトリの packages 配下に ky に関するパッケージが存在しない...。&lt;/p&gt;
&lt;p&gt;調べてみたところ、どうも v1.1.0 リリース時にサポートを終了されたそうです。
そんなにダウンロード数少なかったのか...。&lt;/p&gt;
&lt;p&gt;作者の方のツイート.&lt;/p&gt;
&lt;p&gt;&amp;lt;blockquote class=&quot;twitter-tweet&quot;&amp;gt;
&amp;lt;p lang=&quot;ja&quot; dir=&quot;ltr&quot;&amp;gt;aspida v1.1.0 をリリースしました🎉🎉&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;- 1DL/日しかない ky のサポート終了&amp;lt;br /&amp;gt;- swr と swrv の型を整理&amp;lt;br /&amp;gt;- パス名の@以降に number/string 以外を指定できる隠し機能を削除&amp;lt;br /&amp;gt;&amp;lt;br /&amp;gt;十分な利用実績が出来たのでv1に上げました&amp;lt;br /&amp;gt;隠し機能を使っていなければコード修正なく更新出来ます&amp;lt;a href=&quot;https://t.co/bLjiq5P99w&quot;&amp;gt;https://t.co/bLjiq5P99w&amp;lt;/a&amp;gt;
&amp;lt;/p&amp;gt;— Solufa (@m_mitsuhide) &amp;lt;a href=&quot;https://twitter.com/m_mitsuhide/status/1338257674043936769?ref_src=twsrc%5Etfw&quot;&amp;gt;December 13, 2020&amp;lt;/a&amp;gt;
&amp;lt;/blockquote&amp;gt;
&amp;lt;script async src=&quot;https://platform.twitter.com/widgets.js&quot; charset=&quot;utf-8&quot;&amp;gt;&amp;lt;/script&amp;gt;&lt;/p&gt;
&lt;p&gt;ただ、npm パッケージとしては一応残っているようです。
&amp;lt;OG url=&quot;https://www.npmjs.com/package/@aspida/ky&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ky は fetch API を拡張したものなので、色々と便利な機能が追加されていたりします。
その1つとして、レスポンスがステータスコード 400、500番台で返ってきたときに自動で HTTPError をスローしてくれる、というものがありまして。
これに関しては、@aspida/fetch（もしくは @aspida/node-fetch）のオプションで、そのような挙動へできることが分かったため、今回は @aspida/fetch を採用することにしました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;Zod&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/colinhacks/zod&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Zod is a TypeScript-first schema declaration and validation library. I&apos;m using the term &quot;schema&quot; to broadly refer to any data type, from a simple string to a complex nested object.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;型ガードに対応しているバリデーションライブラリはどれがいいかなと調べてみると、割と結構いろんなものがありましたね。
その中で、今回は Blitz.js でも採用されている Zod を採用することにしました。&lt;/p&gt;
&lt;p&gt;あらかじめ Zod でスキーマを定義しておいて、そのスキーマに沿ってバリデーションチェックを行うような使い方をします。&lt;/p&gt;
&lt;h4&gt;型ガードは廃止？&lt;/h4&gt;
&lt;p&gt;意気揚々と導入しておいてなんですが、いざ実装する時に、Zod に存在すると思っていた型ガード機能が廃止されていることを知りました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The .check method has been removed in Zod 3. For details see&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;どうも、以前は&lt;code&gt;check()&lt;/code&gt;というものがあったものの、v3 リリース時に削除されたそうです。&lt;/p&gt;
&lt;p&gt;公式のサンプル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import * as z from &apos;zod&apos;;

const stringSchema = z.string();
const blob: any = &apos;Albuquerque&apos;;
if (stringSchema.check(blob)) {
  // blob is now of type `string`
  // within this if statement
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;じゃあ、チェックはどうする？というところですが、Zod でバリデーションチェックするものとして&lt;code&gt;parse()&lt;/code&gt;というものがあります。
これはバリデーションチェック OK であれば通過したものを返し、NG であれば ZodError をスローする、というものです。
これでもやりたいことはできるので特に問題なし。&lt;code&gt;check()&lt;/code&gt;が削除されたのも恐らくそのためでしょう。
ちなみに NG 時に ZodError をスローしない&lt;code&gt;safeParse()&lt;/code&gt;というものもあります。&lt;/p&gt;
&lt;p&gt;公式のサンプル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { z } from &quot;zod&quot;;

// creating a schema for strings
const mySchema = z.string();

// parsing
mySchema.parse(&quot;tuna&quot;); // =&amp;gt; &quot;tuna&quot;
mySchema.parse(12); // =&amp;gt; throws ZodError

// &quot;safe&quot; parsing (doesn&apos;t throw error if validation fails)
mySchema.safeParse(&quot;tuna&quot;); // =&amp;gt; { success: true; data: &quot;tuna&quot; }
mySchema.safeParse(12); // =&amp;gt; { success: false; error: ZodError }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これまでの型ガード実装時は、型チェック結果の boolean により if 分岐をかけて、NG 時にエラーをスローするのを自前でやっていました。
それに対して、この&lt;code&gt;parse()&lt;/code&gt;を使えば if ブロックを1つ減らせるので、よりスッキリ書けますね。&lt;/p&gt;
&lt;h4&gt;既存の型定義からスキーマを作成することについて&lt;/h4&gt;
&lt;p&gt;Zod で定義したスキーマを元に型定義を生成する方法は、README に記載がありました。
&lt;code&gt;infer&lt;/code&gt;と&lt;code&gt;typeof&lt;/code&gt;の組み合わせでいけるそうです。&lt;/p&gt;
&lt;p&gt;公式のサンプル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const A = z.string();
type A = z.infer&amp;lt;typeof A&amp;gt;; // string

const u: A = 12; // TypeError
const u: A = &quot;asdf&quot;; // compiles
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;では、逆に既存の型定義からスキーマを生成することは出来るのか？というところが気になりました。
途中から Zod を導入する場合とか、このパターンが欲しいこと割とあると思ったんですよね。&lt;/p&gt;
&lt;p&gt;調べていたら、ちょうど試している方がいて、このやり方を使わせていただきました。
&amp;lt;OG url=&quot;https://zenn.dev/uttk/articles/bd264fa884e026#%E5%9E%8B%E5%BC%95%E6%95%B0%E3%81%AE%E6%B8%A1%E3%81%97%E6%96%B9&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import * as z from &quot;zod&quot;;

// zodに渡せる型に変換する型
type toZod&amp;lt;T extends Record&amp;lt;string, any&amp;gt;&amp;gt; = {
  [K in keyof T]-?: z.ZodType&amp;lt;T[K]&amp;gt;;
}

interface Hoge {
  hello: string;
  world: string;
}

// Hogeを型引数として渡す
const HogeSchema = z.object&amp;lt;toZod&amp;lt;IHoge&amp;gt;&amp;gt;({
  hello: z.string(),
  world: z.string()
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記の場合。
HogeSchema では、元の型定義にないプロパティを定義しようとしたり、違う型にしようとするとちゃんとエラーになってくれます。
プロパティの入力補完も効いてくれるので便利でした。&lt;/p&gt;
&lt;h2&gt;実装例&lt;/h2&gt;
&lt;p&gt;では、今回自分がやってみた実装を書いていきます。
なお、Aspida の README の内容をベースとしました。&lt;/p&gt;
&lt;p&gt;今回の使用バージョンは以下の通りです.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript：4.5.5&lt;/li&gt;
&lt;li&gt;Node.js：16.13.0&lt;/li&gt;
&lt;li&gt;Next.js：12.0.9&lt;/li&gt;
&lt;li&gt;@aspida/fetch：1.7.1&lt;/li&gt;
&lt;li&gt;Zod：3.11.6&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ディレクトリ階層（関連する部分のみ抜粋）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;（ルート）
┣ src
║  ┣ api/  #Adpida 用の API 型定義置き場
║  ║  ┣ users/
║  ║  ║  ┣ _userId@number/
║  ║  ║  ║    ┗ index.ts
║  ║  ║  ┗ index.ts
║  ║  ┗ $api.ts
║  ┣ compnents/  #各種コンポーネント置き場
║  ┣ domains/  #ドメイン（HTTP リクエストロジック置き場）
║  ┣ hooks/  #カスタムフック置き場（domains のラッパー）
║  ┣ lib/  #ライブラリの設定やユーティリティなどの置き場
║  ┣ mock/  #モックデータ置き場
║  ┣ models/  #モデル型定義置き場
║  ┗ pages/
║      ┣ api
║      ┣ _app.tsx
║      ┗ index.tsx
┣ .gitignore
┣ aspida.config.js
┣ package.json
┣ tsconfig.json
┗ yarn.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コードはこちらのリポジトリにあるので、よろしければどうぞ。
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/type-safe-request&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;API 情報&lt;/h3&gt;
&lt;p&gt;API は Next.js の API ルートでモック API を作成。&lt;/p&gt;
&lt;h4&gt;ユーザ情報一覧取得 API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;パス：&lt;code&gt;api/users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;メソッド：GET&lt;/li&gt;
&lt;li&gt;クエリパラメータ：
&lt;ul&gt;
&lt;li&gt;limit?: number&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;レスポンスボディ：
&lt;ul&gt;
&lt;li&gt;ユーザ情報の配列&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;ユーザ情報取得 API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;パス：&lt;code&gt;api/users/{userId}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;メソッド：GET&lt;/li&gt;
&lt;li&gt;パスパラメータ：
&lt;ul&gt;
&lt;li&gt;userId: number&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;レスポンスボディ：
&lt;ul&gt;
&lt;li&gt;ユーザ情報&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;ユーザ新規作成 API&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;パス：&lt;code&gt;api/users&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;メソッド：POST&lt;/li&gt;
&lt;li&gt;リクエストボディ：
&lt;ul&gt;
&lt;li&gt;name: string&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;レスポンスボディ：
&lt;ul&gt;
&lt;li&gt;ユーザ情報&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;前準備&lt;/h3&gt;
&lt;h4&gt;インストール&lt;/h4&gt;
&lt;p&gt;Aspida と Zod のインストール。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add @aspida/fetch zod
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;直接 Adpida と Zod には関係ありませんが、コマンド並列実行をするためにこちらもいれておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D npm-run-all
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Aspida の設定&lt;/h4&gt;
&lt;p&gt;Aspida 用の API 型定義ファイルを置く場所を作る必要があるのですが、自分は&lt;code&gt;src/api&lt;/code&gt;としました。
設定ファイルでこのパスを指定しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  input: &apos;src/api&apos;,
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Zod の設定&lt;/h4&gt;
&lt;p&gt;tsconfig の strict モードを有効化しておく必要があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  // ...
  &quot;compilerOptions&quot;: {
    // ...
    &quot;strict&quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;それと、型定義からスキーマ作成する型を使いやすいように定義して、エクスポートしておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { z } from &apos;zod&apos;;

export type ToZod&amp;lt;T extends Record&amp;lt;string, any&amp;gt;&amp;gt; = {
  [K in keyof T]-?: z.ZodType&amp;lt;T[K]&amp;gt;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;モデル型定義とスキーマ定義&lt;/h4&gt;
&lt;p&gt;Aspida の API 型定義ファイルに直接書いてもいいんですが、個人的な好みとしてモデルの型定義は別ファイルとしました。
より厳密な型チェックをしたかったので、&lt;code&gt;strict()&lt;/code&gt;をつけてます。
また、リクエスト時のパラメータに関するものも一緒に定義しておきました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { z } from &apos;zod&apos;;
import { ToZod } from &apos;@/lib/zod&apos;;

export type GetListRequestQuery = {
  limit?: number;
};

export const getListRequestQuerySchema = z
  .object&amp;lt;ToZod&amp;lt;GetListRequestQuery&amp;gt;&amp;gt;({
    limit: z.number().optional(),
  })
  .strict();

export type PostRequestBody = Pick&amp;lt;User, &apos;name&apos;&amp;gt;;

export const postRequestBodySchema = z
  .object&amp;lt;ToZod&amp;lt;PostRequestBody&amp;gt;&amp;gt;({
    name: z.string(),
  })
  .strict();

export type RequestPathParams = {
  userId: string;
};

// パスパラメータとしては全て文字列になるので、文字列で数字のみにする
export const requestPathParamsSchema = z.object&amp;lt;ToZod&amp;lt;RequestPathParams&amp;gt;&amp;gt;({
  userId: z.string().regex(/\d+/),
});

export type User = {
  id: number;
  name: string;
};

export const userSchema = z
  .object&amp;lt;ToZod&amp;lt;User&amp;gt;&amp;gt;({
    id: z.number(),
    name: z.string(),
  })
  .strict();

export type Users = User[];

export const usersSchema = z.array(userSchema);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーレスポンスの型も簡単に定義しておきました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { z } from &apos;zod&apos;;
import { ToZod } from &apos;@/lib/zod&apos;;

export type ErrorResponse = {
  message: string;
};

export const errorResponseSchema = z.object&amp;lt;ToZod&amp;lt;ErrorResponse&amp;gt;&amp;gt;({
  message: z.string(),
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;モックデータ&lt;/h4&gt;
&lt;p&gt;さっくり User モデル一覧のモックデータを作成しておきました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Users } from &apos;@/models/User&apos;;

const users: Users = [
  {
    id: 1,
    name: &apos;Adam&apos;,
  },
  {
    id: 2,
    name: &apos;Scott&apos;,
  },
  {
    id: 3,
    name: &apos;Matt&apos;,
  },
  {
    id: 4,
    name: &apos;John&apos;,
  },
  {
    id: 5,
    name: &apos;Frank&apos;,
  },
  {
    id: 6,
    name: &apos;Nicole&apos;,
  },
  {
    id: 7,
    name: &apos;Katie&apos;,
  },
  {
    id: 8,
    name: &apos;Nina&apos;,
  },
  {
    id: 9,
    name: &apos;Nancy&apos;,
  },
  {
    id: 10,
    name: &apos;Carol&apos;,
  },
];

export { users };
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Aspida での API 型定義&lt;/h4&gt;
&lt;p&gt;Aspida では API のエンドポイントに合わせたディレクトリ階層を作って、API の型を定義していきます。
前述のモデル型定義を活用しながら定義しました。&lt;/p&gt;
&lt;p&gt;エンドポイント：&lt;code&gt;api/users&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  GetListRequestQuery,
  PostRequestBody,
  User,
  Users,
} from &apos;@/models/User&apos;;

export type Methods = {
  get: {
    query?: GetListRequestQuery;

    resBody: Users;
  };

  post: {
    reqBody: PostRequestBody;

    resBody: User;
    /**
     * reqHeaders(?): ...
     * reqFormat: ...
     * status: ...
     * resHeaders(?): ...
     * polymorph: [...]
     */
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エンドポイント：&lt;code&gt;api/users/{userId}&lt;/code&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { User } from &apos;@/models/User&apos;;

export type Methods = {
  get: {
    resBody: User;
  };
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;パスパラメータが含まれるエンドポイントの場合は、このファイルのように&lt;code&gt;_パラメータ名@型&lt;/code&gt;というような名称にします。&lt;/p&gt;
&lt;h4&gt;Aspida 監視モード&lt;/h4&gt;
&lt;p&gt;前項の API 型定義がある状態で aspida コマンドを実行すると、そのディレクトリに&lt;code&gt;$api.ts&lt;/code&gt;というファイルが生成されます。
これが、型が反映されたリクエスト関数となり、これを使うことで型安全なリクエストができるようになるというわけです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* eslint-disable */
// prettier-ignore
import { AspidaClient, dataToURLString } from &apos;aspida&apos;
// prettier-ignore
import { Methods as Methods0 } from &apos;./users&apos;
// prettier-ignore
import { Methods as Methods1 } from &apos;./users/_userId@number&apos;

// prettier-ignore
const api = &amp;lt;T&amp;gt;({ baseURL, fetch }: AspidaClient&amp;lt;T&amp;gt;) =&amp;gt; {
  const prefix = (baseURL === undefined ? &apos;&apos; : baseURL).replace(/\/$/, &apos;&apos;)
  const PATH0 = &apos;/users&apos;
  const GET = &apos;GET&apos;
  const POST = &apos;POST&apos;

  return {
    users: {
      _userId: (val1: number) =&amp;gt; {
        const prefix1 = `${PATH0}/${val1}`

        return {
          get: (option?: { config?: T }) =&amp;gt;
            fetch&amp;lt;Methods1[&apos;get&apos;][&apos;resBody&apos;]&amp;gt;(prefix, prefix1, GET, option).json(),
          $get: (option?: { config?: T }) =&amp;gt;
            fetch&amp;lt;Methods1[&apos;get&apos;][&apos;resBody&apos;]&amp;gt;(prefix, prefix1, GET, option).json().then(r =&amp;gt; r.body),
          $path: () =&amp;gt; `${prefix}${prefix1}`
        }
      },
      get: (option?: { query?: Methods0[&apos;get&apos;][&apos;query&apos;], config?: T }) =&amp;gt;
        fetch&amp;lt;Methods0[&apos;get&apos;][&apos;resBody&apos;]&amp;gt;(prefix, PATH0, GET, option).json(),
      $get: (option?: { query?: Methods0[&apos;get&apos;][&apos;query&apos;], config?: T }) =&amp;gt;
        fetch&amp;lt;Methods0[&apos;get&apos;][&apos;resBody&apos;]&amp;gt;(prefix, PATH0, GET, option).json().then(r =&amp;gt; r.body),
      post: (option: { body: Methods0[&apos;post&apos;][&apos;reqBody&apos;], config?: T }) =&amp;gt;
        fetch&amp;lt;Methods0[&apos;post&apos;][&apos;resBody&apos;]&amp;gt;(prefix, PATH0, POST, option).json(),
      $post: (option: { body: Methods0[&apos;post&apos;][&apos;reqBody&apos;], config?: T }) =&amp;gt;
        fetch&amp;lt;Methods0[&apos;post&apos;][&apos;resBody&apos;]&amp;gt;(prefix, PATH0, POST, option).json().then(r =&amp;gt; r.body),
      $path: (option?: { method?: &apos;get&apos;; query: Methods0[&apos;get&apos;][&apos;query&apos;] }) =&amp;gt;
        `${prefix}${PATH0}${option &amp;amp;&amp;amp; option.query ? `?${dataToURLString(option.query)}` : &apos;&apos;}`
    }
  }
}

// prettier-ignore
export type ApiInstance = ReturnType&amp;lt;typeof api&amp;gt;
// prettier-ignore
export default api
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ただ、開発中に毎回 aspida コマンドを手動実行するのは手間なので、監視モードにしておくと楽です。
今回は Next.js 側のローカルサーバ起動と並列実行するようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
    &quot;dev&quot;: &quot;run-p dev:*&quot;,
    &quot;dev:next&quot;: &quot;next dev&quot;,
    &quot;dev:aspida&quot;: &quot;aspida --watch&quot;,
    &quot;build&quot;: &quot;aspida &amp;amp;&amp;amp; next build&quot;,
    ...
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;それと、&lt;code&gt;$api.ts&lt;/code&gt;は自動生成分なので、&lt;code&gt;.gitignore&lt;/code&gt;に追加しておくと良いです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;## aspida
$api.ts
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Aspida でのリクエスト時の設定とクライアント作成&lt;/h4&gt;
&lt;p&gt;こちらは既定のファイルではなく、自分で&lt;code&gt;src/lib&lt;/code&gt;配下に作成。
@aspida/ky の項でも書いてましたが、リクエストが400・500番台で返ってきたときに自動で HTTPError をスローしてくれるオプションを有効にしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import aspida, { FetchConfig } from &apos;@aspida/fetch&apos;;
import api from &apos;@/api/$api&apos;;

const fetchConfig: FetchConfig = {
  credentials: &apos;include&apos;,
  baseURL: &apos;/api&apos;,
  throwHttpErrors: true, // throw an error on 4xx/5xx, default is false
};

export const client = api(aspida(fetch, fetchConfig));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Aspida でのリクエストクライアントをエクスポートしておき、ドメインでこのクライアントを使うようにします。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;これで前準備は終わりです。&lt;/p&gt;
&lt;h3&gt;API ルート&lt;/h3&gt;
&lt;p&gt;モックの API を作成。
冒頭に書いた通り、&lt;code&gt;parse()&lt;/code&gt;でバリデーションチェックを行っています。
クエリパラメータやリクエストボディのチェックをして、内容がおかしければ ZodError をキャッチから400を返す感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { NextApiRequest, NextApiResponse } from &apos;next&apos;;
import {
  getListRequestQuerySchema,
  postRequestBodySchema,
  User,
} from &apos;@/models/User&apos;;
import { ErrorResponse } from &apos;@/models/ErrorResponse&apos;;
import { users } from &apos;@/mock/user&apos;;
import { ZodError } from &apos;zod&apos;;

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  switch (req.method) {
    case &apos;GET&apos;:
      try {
        const query = getListRequestQuerySchema.parse(req.query);
        if (query.limit &amp;amp;&amp;amp; query.limit &amp;lt; users.length) {
          res.status(200).json(users.slice(0, query.limit));
        } else {
          res.status(200).json(users);
        }
      } catch (e) {
        if (e instanceof ZodError) {
          const errorResponse: ErrorResponse = {
            message: &apos;不正なクエリパラメータです。&apos;,
          };
          console.error(errorResponse, e);
          res.status(400).json(errorResponse);
        }
      }
      break;
    case &apos;POST&apos;:
      try {
        const body = postRequestBodySchema.parse(req.body);
        const newUser: User = {
          id: users.length + 1,
          name: body.name,
        };
        res.status(201).json(newUser);
      } catch (e) {
        if (e instanceof ZodError) {
          const errorResponse: ErrorResponse = {
            message: &apos;不正なリクエストパラメータです。&apos;,
          };
          console.error(errorResponse, e);
          res.status(400).json(errorResponse);
        }
      }
      break;
    default:
      const errorResponse: ErrorResponse = {
        message: &apos;このエンドポイントで、そのメソッドは定義されていません。&apos;,
      };
      console.error(errorResponse);
      res.status(405).json(errorResponse);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// Next.js API route support: https://nextjs.org/docs/api-routes/introduction
import { NextApiRequest, NextApiResponse } from &apos;next&apos;;
import { requestPathParamsSchema } from &apos;@/models/User&apos;;
import { ErrorResponse } from &apos;@/models/ErrorResponse&apos;;
import { users } from &apos;@/mock/user&apos;;
import { ZodError } from &apos;zod&apos;;

export default function handler(req: NextApiRequest, res: NextApiResponse) {
  switch (req.method) {
    case &apos;GET&apos;:
      try {
        const query = requestPathParamsSchema.parse(req.query);
        const user = users.find((user) =&amp;gt; user.id === Number(query.userId));
        if (user) {
          res.status(200).json(user);
        } else {
          const errorResponse: ErrorResponse = {
            message: &apos;指定のIDを持つユーザは存在しません&apos;,
          };
          console.error(errorResponse);
          res.status(404).json(errorResponse);
        }
      } catch (e) {
        if (e instanceof ZodError) {
          const errorResponse: ErrorResponse = {
            message: &apos;不正なパスパラメータです。&apos;,
          };
          console.error(errorResponse, e);
          res.status(400).json(errorResponse);
        }
      }
      break;
    default:
      const errorResponse: ErrorResponse = {
        message: &apos;このエンドポイントで、そのメソッドは定義されていません。&apos;,
      };
      console.error(errorResponse);
      res.status(405).json(errorResponse);
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ドメイン&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;src/lib/aspida.ts&lt;/code&gt;でエクスポートしているクライアントを使っていく感じ。
このクライアントから各 API リクエストの関数（生成した&lt;code&gt;$api.ts&lt;/code&gt;のやつ）が使えるため、API の型定義が間違ってない限り、おかしなリクエストをする心配がありません。入力補完もばっちり。
これが型安全に HTTP リクエストができるという Aspida の大きな特徴ですね。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;parse()&lt;/code&gt;でレスポンスの型チェックを実施。
NG で ZodError がスローされた時は、そのまま再スローにしていて、後述のラッパーのカスタムフック側で処理するようにしました。
ちなみにメソッド部分を&lt;code&gt;$&lt;/code&gt;をつけたものにする（例：&lt;code&gt;get&lt;/code&gt; → &lt;code&gt;$get&lt;/code&gt;）と、直接レスポンスボディを受け取るようにもできたりします。&lt;/p&gt;
&lt;p&gt;※各ドメインは&lt;code&gt;index.ts&lt;/code&gt;で再エクスポートしてます。&lt;/p&gt;
&lt;p&gt;ユーザ情報一覧取得.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTTPError } from &apos;@aspida/fetch&apos;;
import { GetListRequestQuery, usersSchema } from &apos;@/models/User&apos;;
import { errorResponseSchema } from &apos;@/models/ErrorResponse&apos;;
import { client } from &apos;@/lib/aspida&apos;;

const getUsers = async (query?: GetListRequestQuery) =&amp;gt; {
  try {
    const res = await client.users.get({ query });
    return usersSchema.parse(res.body);
  } catch (e) {
    if (e instanceof HTTPError) {
      errorResponseSchema.parse(await e.response.json());
    }
    throw e;
  }
};

export default getUsers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザ情報取得.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTTPError } from &apos;@aspida/fetch&apos;;
import { userSchema } from &apos;@/models/User&apos;;
import { errorResponseSchema } from &apos;@/models/ErrorResponse&apos;;
import { client } from &apos;@/lib/aspida&apos;;

const getUser = async (userId: number) =&amp;gt; {
  try {
    const res = await client.users._userId(userId).get();
    return userSchema.parse(res.body);
  } catch (e) {
    if (e instanceof HTTPError) {
      errorResponseSchema.parse(await e.response.json());
    }
    throw e;
  }
};

export default getUser;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザ新規作成.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { HTTPError } from &apos;@aspida/fetch&apos;;
import { PostRequestBody, userSchema } from &apos;@/models/User&apos;;
import { errorResponseSchema } from &apos;@/models/ErrorResponse&apos;;
import { client } from &apos;@/lib/aspida&apos;;

const postUser = async (body: PostRequestBody) =&amp;gt; {
  try {
    const res = await client.users.post({ body });
    return userSchema.parse(res.body);
  } catch (e) {
    if (e instanceof HTTPError) {
      errorResponseSchema.parse(await e.response.json());
    }
    throw e;
  }
};

export default postUser;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;カスタムフック&lt;/h3&gt;
&lt;p&gt;ドメインのラッパー的なやつで、コンポーネント側からは直接ドメインを呼ばないように。
リクエスト中のフラグやエラーメッセージの管理ができるようにしてあります。
リクエストに関係する処理なども併せて定義。&lt;/p&gt;
&lt;p&gt;リクエストが正常系・異常系問わず、レスポンスの内容がおかしい場合は ZodError の所に行きつくので、そこは改善の余地がありそう...。&lt;/p&gt;
&lt;p&gt;※各カスタムフックは&lt;code&gt;index.ts&lt;/code&gt;で再エクスポートしてます。&lt;/p&gt;
&lt;p&gt;ユーザ情報一覧取得.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useState, useEffect, useCallback } from &apos;react&apos;;
import { HTTPError } from &apos;@aspida/fetch&apos;;
import { ZodError } from &apos;zod&apos;;
import { Users } from &apos;@/models/User&apos;;
import getUsers from &apos;@/domains/getUsers&apos;;

const useUsers = () =&amp;gt; {
  const [isLoading, setIsLoading] = useState&amp;lt;boolean&amp;gt;(false);
  const [errorMessage, setErrorMessage] = useState&amp;lt;string&amp;gt;();
  const [users, setUsers] = useState&amp;lt;Users&amp;gt;();

  useEffect(() =&amp;gt; {
    setIsLoading(true);
    getUsers()
      .then((data) =&amp;gt; {
        setErrorMessage(&apos;&apos;);
        setUsers(data);
      })
      .catch((err) =&amp;gt; {
        if (err instanceof HTTPError) {
          setErrorMessage(&apos;ユーザ一覧情報の取得に失敗しました&apos;);
        } else if (err instanceof ZodError) {
          setErrorMessage(&apos;想定しないデータの取得が行われました&apos;);
        }
      })
      .finally(() =&amp;gt; {
        setIsLoading(false);
      });
  }, []);

  return { isLoading, errorMessage, users };
};

export default useUsers;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザ情報取得.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { ChangeEvent, FormEvent, useState, useCallback } from &apos;react&apos;;
import { HTTPError } from &apos;@aspida/fetch&apos;;
import { ZodError } from &apos;zod&apos;;
import { User } from &apos;@/models/User&apos;;
import getUser from &apos;@/domains/getUser&apos;;

const useSearchUser = () =&amp;gt; {
  const [userId, setUserId] = useState&amp;lt;number&amp;gt;(1);
  const [isLoading, setIsLoading] = useState&amp;lt;boolean&amp;gt;(false);
  const [errorMessage, setErrorMessage] = useState&amp;lt;string&amp;gt;();
  const [user, setUser] = useState&amp;lt;User&amp;gt;();

  const handleChangeUserId = useCallback((e: ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
    setUserId(Number(e.target.value));
  }, []);

  const handleSearchUser = useCallback(
    (e: FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
      e.preventDefault();

      setIsLoading(true);
      getUser(userId)
        .then((data) =&amp;gt; {
          setErrorMessage(&apos;&apos;);
          setUser(data);
        })
        .catch((err) =&amp;gt; {
          if (err instanceof HTTPError) {
            err.response.status === 404
              ? setErrorMessage(&apos;指定のIDを持つユーザは存在しません&apos;)
              : setErrorMessage(&apos;ユーザ情報の取得に失敗しました&apos;);
          } else if (err instanceof ZodError) {
            setErrorMessage(&apos;想定しないデータの取得が行われました&apos;);
          }
        })
        .finally(() =&amp;gt; {
          setIsLoading(false);
        });
    },
    [userId]
  );

  return {
    userId,
    isLoading,
    errorMessage,
    user,
    handleChangeUserId,
    handleSearchUser,
  };
};

export default useSearchUser;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ユーザ新規作成.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { ChangeEvent, FormEvent, useState, useCallback } from &apos;react&apos;;
import { HTTPError } from &apos;@aspida/fetch&apos;;
import { ZodError } from &apos;zod&apos;;
import postUser from &apos;@/domains/postUser&apos;;

type FormData = {
  name: string;
};

const useCreateUser = () =&amp;gt; {
  const [values, setValues] = useState&amp;lt;FormData&amp;gt;({
    name: &apos;&apos;,
  });
  const [isLoading, setIsLoading] = useState&amp;lt;boolean&amp;gt;(false);
  const [errorMessage, setErrorMessage] = useState&amp;lt;string&amp;gt;();

  const clearValues = useCallback(() =&amp;gt; {
    setValues({ name: &apos;&apos; });
  }, []);

  const handleChangeInput = useCallback(
    (key: keyof FormData) =&amp;gt; (e: ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
      setValues({ ...values, [key]: e.target.value });
    },
    [values]
  );

  const handleCreateUser = useCallback(
    (e: FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
      e.preventDefault();

      setIsLoading(true);
      postUser(values)
        .then((data) =&amp;gt; {
          setErrorMessage(&apos;&apos;);
          alert(
            `id: ${data.id} name: ${data.name}\nユーザを新規作成しました（モックなので実際には作成されてません）`
          );
          clearValues();
        })
        .catch((err) =&amp;gt; {
          if (err instanceof HTTPError) {
            setErrorMessage(&apos;ユーザ新規作成に失敗しました&apos;);
          } else if (err instanceof ZodError) {
            setErrorMessage(
              &apos;ユーザ新規作成に成功しました（モック）が、想定しないデータが返却されました&apos;
            );
          }
        })
        .finally(() =&amp;gt; {
          setIsLoading(false);
        });
    },
    [values, clearValues]
  );

  return {
    values,
    isLoading,
    errorMessage,
    handleChangeInput,
    handleCreateUser,
  };
};

export default useCreateUser;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでロジックができました。
あとはこれをコンポーネント側から使っていきます。&lt;/p&gt;
&lt;h3&gt;コンポーネント側&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;pages&lt;/code&gt;配下は、ルーティングと Head の責務だけにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Fragment } from &apos;react&apos;;
import Head from &apos;next/head&apos;;
import HomePage from &apos;@/components/page/Home&apos;;

export default function Home() {
  return (
    &amp;lt;Fragment&amp;gt;
      &amp;lt;Head&amp;gt;
        &amp;lt;title&amp;gt;Type Safe Request&amp;lt;/title&amp;gt;
        &amp;lt;meta name=&quot;description&quot; content=&quot;型安全なHTTPリクエスト検証&quot; /&amp;gt;
        &amp;lt;link rel=&quot;icon&quot; href=&quot;/favicon.ico&quot; /&amp;gt;
      &amp;lt;/Head&amp;gt;
      &amp;lt;HomePage /&amp;gt;
    &amp;lt;/Fragment&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;さっくり作ったビューの実体はこちら。
（&lt;code&gt;index.ts&lt;/code&gt;で再エクスポートしてます。）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import styles from &apos;./Home.module.css&apos;;
import useUsers from &apos;@/hooks/useUsers&apos;;
import useSearchUser from &apos;@/hooks/useSearchUser&apos;;
import useCreateUser from &apos;@/hooks/useCreateUser&apos;;

const Home: VFC = () =&amp;gt; {
  const { isLoading, errorMessage, users } = useUsers();
  const {
    values,
    isLoading: createIsLoading,
    errorMessage: createErrMsg,
    handleChangeInput,
    handleCreateUser,
  } = useCreateUser();
  const {
    userId,
    isLoading: searchIsLoading,
    errorMessage: searchErrMsg,
    user,
    handleChangeUserId,
    handleSearchUser,
  } = useSearchUser();

  return (
    &amp;lt;div className={styles.container}&amp;gt;
      &amp;lt;div className={styles.layout}&amp;gt;
        &amp;lt;div&amp;gt;
          &amp;lt;h2&amp;gt;ユーザ一覧&amp;lt;/h2&amp;gt;
          {(isLoading || errorMessage) &amp;amp;&amp;amp; (
            &amp;lt;p className={errorMessage &amp;amp;&amp;amp; styles.errorText}&amp;gt;
              {isLoading ? &apos;読み込み中...&apos; : errorMessage}
            &amp;lt;/p&amp;gt;
          )}
          {!(isLoading || errorMessage) &amp;amp;&amp;amp;
            users?.map((user) =&amp;gt; {
              return (
                &amp;lt;div key={user.id}&amp;gt;
                  &amp;lt;p&amp;gt;{`id: ${user.id} name: ${user.name}`}&amp;lt;/p&amp;gt;
                &amp;lt;/div&amp;gt;
              );
            })}
        &amp;lt;/div&amp;gt;
        &amp;lt;div className={styles.rightBlock}&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;ユーザ検索&amp;lt;/h2&amp;gt;
            &amp;lt;form onSubmit={handleSearchUser}&amp;gt;
              &amp;lt;label&amp;gt;
                id:{&apos; &apos;}
                &amp;lt;input
                  type=&quot;search&quot;
                  required
                  pattern=&quot;^\d+$&quot;
                  title=&quot;数値で入力してください。&quot;
                  value={userId}
                  onChange={handleChangeUserId}
                /&amp;gt;
              &amp;lt;/label&amp;gt;
              &amp;lt;button type=&quot;submit&quot; disabled={searchIsLoading}&amp;gt;
                検索
              &amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
            {!(searchIsLoading || searchErrMsg) &amp;amp;&amp;amp; user &amp;amp;&amp;amp; (
              &amp;lt;p&amp;gt;{`id: ${user.id} name: ${user.name}`}&amp;lt;/p&amp;gt;
            )}
            {!searchIsLoading &amp;amp;&amp;amp; searchErrMsg &amp;amp;&amp;amp; (
              &amp;lt;p className={styles.errorText}&amp;gt;{searchErrMsg}&amp;lt;/p&amp;gt;
            )}
          &amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;h2&amp;gt;ユーザ新規作成&amp;lt;/h2&amp;gt;
            &amp;lt;form onSubmit={handleCreateUser}&amp;gt;
              &amp;lt;label&amp;gt;
                name:{&apos; &apos;}
                &amp;lt;input
                  type=&quot;text&quot;
                  required
                  value={values.name}
                  onChange={handleChangeInput(&apos;name&apos;)}
                /&amp;gt;
              &amp;lt;/label&amp;gt;
              &amp;lt;button type=&quot;submit&quot; disabled={createIsLoading}&amp;gt;
                新規作成
              &amp;lt;/button&amp;gt;
            &amp;lt;/form&amp;gt;
            {createErrMsg &amp;amp;&amp;amp; &amp;lt;p className={styles.errorText}&amp;gt;{createErrMsg}&amp;lt;/p&amp;gt;}
          &amp;lt;/div&amp;gt;
        &amp;lt;/div&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default Home;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;検証してみてどうだったか&lt;/h2&gt;
&lt;p&gt;型安全な HTTP リクエストにしたいとして、実際に自分のやり方に組み込むにはどうすればいいか？
というところが、おおよそ解決したのでまぁ満足です。
自前で型ガード実装するつらみから解放されたのと、より安全な HTTP リクエストができるようになったのとで、検証してみてよかったなぁと。&lt;/p&gt;
&lt;h2&gt;余談：tRPC&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://trpc.io&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Zod の README を見ていたところ、同じ作者の方が作ったこのライブラリに関する記述を見かけました。
まだあまり詳しくは見られていませんが、Aspida とはまた違ったアプローチの、型安全な HTTP リクエストをするためのライブラリのようです。
Zod との組み合わせをしているようなサンプルもあったので、気になる方はドキュメントをぜひ読んでみてはいかがでしょうか。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回は Aspida と Zod を使った、型安全な HTTP リクエストについてのお話でした。
当記事に書いたのは、あくまで自分のやり方ではありますが、導入を検討されている方の何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/no-doubled-joshi --&amp;gt; */}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/aspida/aspida&quot;&gt;GitHub - Aspida&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/colinhacks/zod&quot;&gt;GitHub - Zod&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.npmjs.com/package/@aspida/ky&quot;&gt;npm @aspida/ky&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://trpc.io/&quot;&gt;tRPC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/solufa/articles/getting-started-with-aspida&quot;&gt;月間2万DL突破！REST APIを型安全にする最強のTypeScript製HTTPクライアントaspidaを始めよう&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/uttk/scraps/a4da447adc5dcb&quot;&gt;Zodを試す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/uttk/articles/bd264fa884e026&quot;&gt;TypeScriptのゾッとする話 ~ Zodの紹介 ~&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.mobilefactory.jp/entry/2021/12/10/000000&quot;&gt;外からやってくる値から TypeScript の型を守るライブラリ・ツールまとめ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>2021年振り返り ～技術活動、ブログ編～</title><link>https://changeofpace.site/posts/2021-12-30-looking-back-2021-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-12-30-looking-back-2021-tech</guid><description>2021年のGitのコミット履歴から、記事執筆など技術活動の振り返り</description><pubDate>Thu, 30 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;今年も色々あったなぁなんで感慨深くなっている、よしです。
今年も、体調編に引き続き技術活動の振り返りもやっていきますー。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2021年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;いつものように、まずは Git の Contributions 履歴。
今年は1つの GitHub アカウントしか使ってなかったので、そちらだけ。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2021/looking-back-2021-tech/github-contribute.png&quot; alt=&quot;2021年のGitHub Contributionsの履歴画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;昨年は 920（個人） + 541（仕事）= 1461 でしたが、今年はそれを超えましたね。
まぁ、新しい会社がなかなか決まらなかった分、時間はあったので日中は何かしら活動してた日が多かったです。&lt;/p&gt;
&lt;p&gt;2月～5月くらいまでが割と活動活発でしたね。
5月に復職活動を開始するまでは、個人開発をガリガリ実装したり、ハンズオン教材を進めていたり。
その過程で学んだことを記事にしたりしてました。&lt;/p&gt;
&lt;p&gt;6月からは devChallenges の課題を開始。&lt;/p&gt;
&lt;p&gt;8月～9月は勉強をいったん中断し、過去に作った個人開発の TypeScript 化 + リファクタリングや React 関連記事の全体見直し更新をやってました。
（勉強が途切れるなと思いつつ、今思えばこの判断は良かったと思っています）&lt;/p&gt;
&lt;p&gt;10月中旬～12月から devChallenges を再開。&lt;/p&gt;
&lt;p&gt;あとは合間で記事を書いたり、ポートフォリオサイトの更新をしたりなどしてた感じです。&lt;/p&gt;
&lt;h3&gt;復職活動&lt;/h3&gt;
&lt;p&gt;今年で一番優先度が高かったのはこれですね。
身体の調子を見ながら5月に開始しました。&lt;/p&gt;
&lt;p&gt;応募状況に関しては、おおよそこんな感じでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;5月：
&lt;ul&gt;
&lt;li&gt;0社目：以前から憧れだった会社さんに問い合わせてみるも、自分では実力不足の気がしてこの時は一旦諦める&lt;/li&gt;
&lt;li&gt;1社目：スカウト → カジュアル面談&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;6月：
&lt;ul&gt;
&lt;li&gt;1社目：技術試験でボロボロになり見送り&lt;/li&gt;
&lt;li&gt;2社目：スカウト → カジュアル面談&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;7月：
&lt;ul&gt;
&lt;li&gt;2社目：技術試験でまたもボロボロになり見送り&lt;/li&gt;
&lt;li&gt;3社目：事前面談 → 書類選考落ち&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;8月：
&lt;ul&gt;
&lt;li&gt;4社目：カジュアル面談 → 書類選考&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;9月：
&lt;ul&gt;
&lt;li&gt;4社目：1次面接 → 2次面接&lt;/li&gt;
&lt;li&gt;5社目：メッセージ受信 → カジュアル面談&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;10月：
&lt;ul&gt;
&lt;li&gt;4社目：3次面接 → 4次面接落ち&lt;/li&gt;
&lt;li&gt;6社目：メッセージ受信&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;11月：
&lt;ul&gt;
&lt;li&gt;5社目：書類選考 → 1次面接落ち&lt;/li&gt;
&lt;li&gt;6社目：カジュアル面談&lt;/li&gt;
&lt;li&gt;(0社目もとい)7社目：カジュアル面談申し込み&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;12月：
&lt;ul&gt;
&lt;li&gt;7社目：カジュアル面談 → 技術試験 → 1次面接 → 2次面接 → （結果待ち）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;主に、
精神疾患について一定の理解がある
技術面だけでなく体調面でも気軽に相談できる環境がある
という会社さんを探していました。
あと、楽しそうな雰囲気だとよりいいなとか。
ただ、小規模の会社さんだと、自分がもしうまく立ち上がれなかった時にご迷惑をおかけしてしまう影響が大きいと判断しました。
そのため、いい会社さんだと思っても、ご遠慮させていただいたりもしてました。&lt;/p&gt;
&lt;p&gt;一度に複数社とのやり取りだとテンパりそうだったので、あまり同時に応募することはしなかったです。
早い方だと、同時に何社も受けてパパっと決めちゃうんだろうな、なんて思ったり。
（現に、Twitter でそういう方を見かけました）&lt;/p&gt;
&lt;p&gt;この他にもスカウトメッセージをいただいたりしましたが、いかにもテンプレっぽいとか。
自分のどこに興味を持ったのかあまり書いてないとか。
ホントにプロフィール読んだのかな？と思うような内容だったとか。
文面の95%くらい自社のアピールが長々と書いてあるとか。
そういった会社さんはすみませんが返信してないです。&lt;/p&gt;
&lt;p&gt;こういったメッセージでない会社さんでも、返信しきれてないものもあります（申し訳ないです...🙇‍♂️）
返信しつつも、お話はお受けできなかったものもありました。&lt;/p&gt;
&lt;p&gt;スカウトをいただきながら技術試験でボロボロの結果だというのは、なんだかスカウトしてくださった方にも申し訳なかったですね...。
特に1社目に関しては、リモートで繋いで画面共有をしながらその場でやるというものだったので、すごい気まずかったです。
ただ、そんなボロボロの自分にもちゃんとフィードバックをくださって、とてもありがたかったなと。
自分が抱えるスキルの問題がはっきりしたので、その後それを克服すべく勉強に打ち込むことができました。&lt;/p&gt;
&lt;p&gt;執筆時点では、7社目の2次面接（最終面接）を終えたところです。
一応、技術試験と1次面接では割といい評価をいただけているような印象でしたが、こればっかりはわかりませんからね。
でも、以前から憧れの会社さんなので、もし内定出たらめちゃくちゃ嬉しい。&lt;/p&gt;
&lt;p&gt;前社を退職してから1年ちょっと経ち、だいぶ貯金が寂しい感じになってきたので、決まるといいなぁ。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;h4&gt;アクセス履歴&lt;/h4&gt;
&lt;p&gt;今年のブログ履歴はこんな感じ。
例年通り、Google Analytics のデータ部分のみ抜粋しています。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;1,312&lt;/td&gt;
&lt;td&gt;1,785&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;1,369&lt;/td&gt;
&lt;td&gt;1,852&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;59&lt;/td&gt;
&lt;td&gt;1,391&lt;/td&gt;
&lt;td&gt;1,811&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;60&lt;/td&gt;
&lt;td&gt;1,081&lt;/td&gt;
&lt;td&gt;1,395&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;1,024&lt;/td&gt;
&lt;td&gt;1,309&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;1,013&lt;/td&gt;
&lt;td&gt;1,309&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;881&lt;/td&gt;
&lt;td&gt;1,228&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;62&lt;/td&gt;
&lt;td&gt;807&lt;/td&gt;
&lt;td&gt;1,185&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;751&lt;/td&gt;
&lt;td&gt;1,157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;64&lt;/td&gt;
&lt;td&gt;946&lt;/td&gt;
&lt;td&gt;1,395&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;807&lt;/td&gt;
&lt;td&gt;1,157&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;69&lt;/td&gt;
&lt;td&gt;628&lt;/td&gt;
&lt;td&gt;881&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：12,002&lt;/li&gt;
&lt;li&gt;ページビュー数計：16,512&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年との比較.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数：6,722 → 5,280増&lt;/li&gt;
&lt;li&gt;ページビュー数：9,670 → 6,842増&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今年はなんだか段々と減っていくような感じでした。
まぁ、技術記事に関しては Zenn の方にもクロス投稿しているので、そちらで見ている人の方が多いのかなと。
実際に記事タイトルで検索しても Zenn の方が先に出てくるので。
また、実際にページごとのアクセス数を見ると、上位はブログでしか投稿していない記事が多かったです。&lt;/p&gt;
&lt;h4&gt;記事執筆について&lt;/h4&gt;
&lt;p&gt;6月～9月中旬は勉強や過去記事の見直し更新を優先しがちだったので、数か月の間は新規記事投稿がなかった状態。
今年も月に最低1記事書こうという目標が達成できなかったわけではありますが、せめて12記事にはしたかったのでリカバリして、なんとか当記事で12記事達成にできました。&lt;/p&gt;
&lt;p&gt;8月～9月中旬に思い切って時間をとって、React 関連記事の見直し更新をやったのは良かったなと。
元々、まだ React 歴の浅い状態で書いた記事だったので、今見るとイマイチな部分とかが割と多くて気になってたんですよね。
改めて検証しなおしたり公式ドキュメントを読んだりと、情報が整理できてすっきりしました。&lt;/p&gt;
&lt;p&gt;今年も React の記事は書いていたものの、React 入門シリーズが全然書けてないので、何かそろそろ書きたいところです。&lt;/p&gt;
&lt;h4&gt;リニューアルについて&lt;/h4&gt;
&lt;p&gt;昨年末の構想として、今の Jekyll 製なのを Next.js で作り直したいなというものがありました。
これの進捗としては、あまり変わっていません。&lt;/p&gt;
&lt;p&gt;Next.js の勉強はできたのと、リニューアルする上での移行ライブラリの調査などはおおよそできたのですが...。
そもそもデザインどうするの？というところで悩んだ結果、優先度が低い状態でした。&lt;/p&gt;
&lt;p&gt;デザインの知識は正直あまりないので、デザインを学ぶとか、ギャラリーサイトなどで参考にできそうなデザインを見てみるとかをやらないとなぁといった感じです。&lt;/p&gt;
&lt;h3&gt;ブログ以外の技術記事活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn および Qiita に投稿した記事は、すべて当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;昨年、Zenn がリリースされてから ブログ + Zenn で活動してきたので、Qiita に新規投稿はしていません。
ただ、React 関連記事の見直し更新をする際、Qiita に投稿してあった分も更新してあります。&lt;/p&gt;
&lt;p&gt;ちなみに Zenn からいただいた振り返りはこんな感じでした。
（12/23までなので、devChallenges の記事は含まれてません）
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2021/looking-back-2021-tech/zenn-recap.png&quot; alt=&quot;Zenn 2021 Recap - 2021年の活動振り返りデータ - 211,651字の執筆、9記事、60,096ビュー、572Likes、3個のサポートバッジ&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;やっぱり MUI v5 記事が伸びてましたね。
ああいった新機能まとめみたいな記事は、やはり需要があるんだなぁと。
v4との比較なんかも頑張ったかいがありました。&lt;/p&gt;
&lt;p&gt;Crieit に関しては、今年は web1week に参加しなかったので新規投稿なし。
web1week の開催自体は知っていて、ネタ集めをした回なんかもありましたが、結局参加まではしませんでした。
問われる発想力...🙄&lt;/p&gt;
&lt;p&gt;※2024/09/14追記：Crieit はサービスクローズされました。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;h4&gt;OOUI ワークアウト - メモアプリ&lt;/h4&gt;
&lt;p&gt;昨年末くらいから OOUI のワークアウトを実際に作ってみる活動を細々と再開して。
メモアプリの実装を2月～5月くらいは割とガリガリやってました。&lt;/p&gt;
&lt;p&gt;JavaScript × React は経験ありますが、TypeScript × React は経験なかったので調べながら進めていった感じ。
はじめは型定義に戸惑いつつも、その強力な型補完や入力補完を体験して、今では型定義ない方が不安になります（笑）&lt;/p&gt;
&lt;p&gt;TypeScript × React の組み合わせを対象としている技術書典の書籍「りあクト！」には特にお世話になりました。
&amp;lt;OG url=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot; /&amp;gt;
開発環境整備のことから React における概念、よく使われるライブラリのことまで網羅されていて。
改めてちゃんと React を勉強できたなぁと。&lt;/p&gt;
&lt;p&gt;コンポーネント設計やカスタムフックのことなど、だいぶ時間をかけて試行錯誤して。
その結果、おおよそ自分なりのやり方のようなものが定まりました。&lt;/p&gt;
&lt;p&gt;コツコツ進めていったメモアプリの実装ですが、復職活動の中で自分のスキルの問題が見えてきたので、そちらの克服を優先するために中断しました。&lt;/p&gt;
&lt;h4&gt;過去に作った web1week 作品&lt;/h4&gt;
&lt;p&gt;復職活動をするなかで個人開発のものとして記載するため、少しでも見栄え良くしたいなというのと。
OOUI - メモアプリで TypeScript × React の自分なりの実装方法が定まったので、それを適用したかったというので、リファクタリングを行いました。&lt;/p&gt;
&lt;p&gt;元々、2作品ともに JavaScript × React で作ったものだったので、TypeScript 化対応。
それとロジックが混在していた状態だったので、ロジックをカスタムフックに切り出し対応を実施。
おかげでコードがすっきりしました。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;p&gt;今年特に勉強した技術.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;TypeScript&lt;/li&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Next.js&lt;/li&gt;
&lt;li&gt;React Query&lt;/li&gt;
&lt;li&gt;emotion&lt;/li&gt;
&lt;li&gt;HTML・CSS・JavaScript 基礎&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;TypeScript × React については前述の通り、OOUI - メモアプリを実装する中で勉強。&lt;/p&gt;
&lt;p&gt;Next.js は公式チュートリアルやハンズオン教材をやったり。
&amp;lt;OG url=&quot;https://tk-rabbit-house.booth.pm/items/2381995&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://zenn.dev/dala/books/nextjs-firebase-service&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;現状、フロントエンドの技術スタックとして、素の React を採用するよりは Next.js として採用する方がデファクトのイメージがありました。
そのため、Next.js も勉強しておきたいなということで、今年から勉強開始。
もちろん TypeScript × Next.js です。&lt;/p&gt;
&lt;p&gt;React Query も OOUI - メモアプリの中で使ってみてました。
元々、「りあクト！」の中で触れられていたことで知り、とても便利そうなので使ってみた感じです。
実際、すごい便利ですよね。コードがすっきり書けるの嬉しい。
いくつか React Query を使った記事を書いたりもしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-03-21-laravel-react-query-auth&quot;&gt;React × LaravelでReact Queryの練習がてら、ログイン機能を作ってみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-04-13-react-query-intersection-observer&quot;&gt;React QueryとIntersection Observer APIで無限スクロールを作ってみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;emotion は CSS in JS が流行っているので、どれか使えるようにしておきたいなというので、emotion に。
知名度的には styled-components の方が上かとは思いますが、個人的には emotion の方が好みで CSS Prop でスタイルを書く練習をしていました。&lt;/p&gt;
&lt;p&gt;復職活動をするなかで直面した自分のスキルの問題として...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;デザインデータの再現実装の経験がない&lt;/li&gt;
&lt;li&gt;HTML・CSS・JavaScript 基礎が微妙な部分あり&lt;/li&gt;
&lt;li&gt;コーディング速度が遅い&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;というものがありました。
フロントエンドを志望するならこの辺りができてないと絶望的だなと思ったため、これらを克服するために devChallenges という海外のコミュニティの課題を6月頃から開始。&lt;/p&gt;
&lt;p&gt;ざっくりいうと、あらかじめ用意された Figma デザインデータとユーザストーリーを元に課題作品を作るものです。
devChallenges の詳細に関しては、先日振り返り記事を書いたのでそちらから。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-12-24-devchallenges-responsive-web&quot;&gt;devChallenges - Responsive Web 編を完走したので、さっくり振り返ってみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;最初は Front-end 編を進めていましたが、4課題くらい終えた頃に Responsive Web 編を先にすることへ切り替えて、そちらは完走しました。
課題を通して、改めて基礎的な部分を考える機会になり、とても勉強になりましたね。
少し自信もついた気がしますし、本当にやってよかったなーと思います。&lt;/p&gt;
&lt;p&gt;コーディング速度の面は、Emmet を積極的に使うようになったり、スニペットを少し活用したりするようになりました。
課題の数をこなす中で少しずつ早くなったかな？とも。&lt;/p&gt;
&lt;p&gt;それと技術試験対策でアルゴリズム力も鍛えた方がいいかなというので、CS を学べるプラットフォームである Recursion の学習も少しやっていました。
&amp;lt;OG url=&quot;https://recursionist.io&quot; /&amp;gt;
今は中断したままですが、内容がすごくいいなと思ったので少しずつでも進めたいところです。&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;TIL（Today I Learned）という、その日学んだことを GitHub リポジトリに記録する活動のことです。&lt;/p&gt;
&lt;p&gt;昨年では TIL リポジトリに記録 → ドキュメントとしてまとめたやつをポートフォリオサイトへ投稿 みたいにやっていましたが...。
復職活動や勉強と比較すると優先度が低かったので、どうしても放置になりがちで。&lt;/p&gt;
&lt;p&gt;勉強した内容については Notion に記録していった方がよくないか？というので、Notion へ移行。
ドキュメントとしてまとめる件については、一旦一からやりなおそうということで、投稿していた分は削除しました。&lt;/p&gt;
&lt;p&gt;でも、ポートフォリオサイトではせっかく Docusaurus を使っているので、何かしらは書きたいところです。&lt;/p&gt;
&lt;h3&gt;その他&lt;/h3&gt;
&lt;p&gt;2月くらいに、各種技術アカウント ID を少し変えたい欲がわいてきたので変えました。
今は&lt;code&gt;h-yoshikawa44&lt;/code&gt;という ID を主に使用しています。
Zenn や Crieit など ID が変更できないやつは前のままです。&lt;/p&gt;
&lt;p&gt;ポートフォリオサイトのドメインもこれに合わせて変更。&lt;/p&gt;
&lt;h2&gt;2022年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;復職活動&lt;/h3&gt;
&lt;p&gt;執筆時点（2021/12/30）で選考中の会社さんにもし決まれば、すぐ社会復帰ですね。
合否の結果は年明けの楽しみにしつつ。
もしそうなったとして、入社してしばらくは環境の変化で疲れやすかったりすると思うので、体調に気を付けて少しずつ馴染んでいきたいところです。&lt;/p&gt;
&lt;p&gt;もしダメだった場合は、また改めて会社探さないとですね。
精神疾患について一定の理解があることや、技術面だけでなく体調面でも気軽に相談できる環境があるというのは、なるべく妥協したくないなぁと。
（会社決まっても続けられない環境なら意味ないので）
それ以外の部分は、多少の妥協が必要になってくるかもしれませんね。&lt;/p&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;月に1記事もしくは1年で12記事書ければいいかなと。
会社が決まったとして、仕事しながら記事書くのどうしようかなと悩ましいところではありますが。
Zenn のスクラップを活用して、少しずつ検証内容や文章を書いていくようにしようか、と構想してます。&lt;/p&gt;
&lt;p&gt;ブログのリニューアルについては、デザイン面の問題ですね。
デザインの基礎学習とか、少しずつギャラリーサイトを見て参考にできそうなものをストックしていくとかやろうかなと。
モノクロ基調のかっこいいデザインにしたいという構想はありつつ、デザイン力がないので悩ましいところです。&lt;/p&gt;
&lt;h3&gt;個人開発&lt;/h3&gt;
&lt;p&gt;OOUI - メモアプリが中断したきりなので、いずれは再開しないとですね。
ただ、devChallenges の方を優先度高くするつもりなので、あまり着手できないかも。&lt;/p&gt;
&lt;p&gt;web1week は来年も開催されるかはわかりませんが、また参加を検討したいですね。
ネタ探しするだけでも意外な収穫があったりするので、一旦は何かを作れないか考えてみるのも大事かなと。
もし開催されなかったとして、自分でお題のくじなんかを作って、ひいたお題で何が作れるか構想するのも発想力の訓練になるかも？&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;p&gt;TypeScript × React or Next.js はメインにしつつ。
それ関連で新しい技術が出てきたら、情報を追いつつ、自分でも試してみるというのを継続。
今年は、テストやデザイン、アクセシビリティ関連まではいけなかったので、その辺も勉強したいですね。
そのほか、業務で必要になる技術があれば、それを自分でも学んだり。&lt;/p&gt;
&lt;p&gt;AWS ソリューションアーキテクト アソシエイト資格の有効期限が8月で切れるので、更新の受験をする予定です。
自宅でも受験できるそうになったそうですが、自宅に自習室みたいな部屋がないので、受験会場へ行くことになりそう。
前回受験した時は、体調面でえらい目にあったので、ちょっと怖いですが...。
それと HTML プロフェッショナル試験も受けてみようかなーと。&lt;/p&gt;
&lt;p&gt;devChallenges の Front-end 編の続きをやらないとですね。
すでに投稿済みの作品についても、今見ると微妙なところがあったりするのでリファクタリングしたり。
それが終わったら Full-stack 編もやりたいところですが、バックエンドも絡んでくるので時間かかりそうだなと。
でも、設計の勉強にもなりそうですし、せっかくなら全コース完走したいですし。とりあえずはやる方向で考えています。&lt;/p&gt;
&lt;p&gt;Recursion の学習も少しずつでも進めたいところです。&lt;/p&gt;
&lt;h3&gt;TIL 活動&lt;/h3&gt;
&lt;p&gt;勉強したことのメモや記録については、引き続き Notion で行っていきます。
あくまで自分用みたいなところがあるので、さすがに Notion ページを公開まではしないかなと。&lt;/p&gt;
&lt;p&gt;内容まとめのドキュメント化 + 公開については、これもやりたいと思いつつ優先度が低くなりがちで、あまり着手できないかも。
どうやって進めるかが定まってないですし、なかなか悩ましいところで...。
よくやる処理なんかをいろんな言語で書けるような作りにする？という構想はありつつ。
個人勉強をする中でいい案が降ってこないかなぁと思っていたりします。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今年は復職活動をしつつも、なかなか会社が決まらなかった分の時間で色々やってきましたね。
残念ながら年内に社会復帰はできませんでしたが、精神疾患と向き合ったり、やりたかった勉強を集中してやれたりしたかなと。
自分が勉強してきたことは決して無駄じゃなかった、と思えるような出来事もあったので、これも必要な時間だったと前向きに捉えるつもりです。
来年こそは社会復帰して、仕事もしつつプライベートも両立できるようなスタイルを追求していきたいですね。&lt;/p&gt;
&lt;p&gt;ここまで読んでくださった方、ありがとうございました。
ではでは、皆様、よいお年をお迎えくださいー。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable  --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>devChallenges - Responsive Web 編を完走したので、さっくり振り返ってみた</title><link>https://changeofpace.site/posts/2021-12-24-devchallenges-responsive-web</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-12-24-devchallenges-responsive-web</guid><description>devChallengesのResponsive Web編の課題内容と使用技術等の振り返り</description><pubDate>Fri, 24 Dec 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、ここ1年コツコツ勉強してよかったなぁなんて思っている、よしです。
devChallenges というコミュニティの課題を1コース完走したので、さっくりと振り返ってみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;※翻訳は Google 翻訳を使用しています。
※2023/09/25...devChallenges が全面リニューアルしたため、旧課題は legacy サブドメインの方に移りました。リンクを修正。
&lt;strong&gt;※2025/02/09...legacy サブドメインのサイトがクローズされたようでアクセスできなくなっていました。それに伴いサイトへのリンクを除去。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;devChallenges とは？&lt;/h2&gt;
&lt;p&gt;※&lt;code&gt;https://legacy.devchallenges.io&lt;/code&gt;というドメインでかつてはサイトがありましたが、現在はクローズされたようです。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Web Development Resources and Community that help you to become a Web Developer by working with Real-life projects and practices.
↓
訳：
実際のプロジェクトやプラクティスを使用して Web Developer になるのに役立つ Web 開発リソースとコミュニティ。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;おおよそ翻訳通りで、Web Developer になるための学習コンテンツや課題を提供しているコミュニティです。
名称表記としては「devChallenges」だったり「DevChallenges」だったりするものの、個人的には前者を使うことが多く、当記事でもそう書いていきます。&lt;/p&gt;
&lt;p&gt;学習コンテンツ部分は準備中になっている項目が多かったりするのですが、課題の方は結構充実していまして。
課題の大カテゴリとして以下の3つがあり、それぞれで8課題ずつ用意されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Responsive Web Developer（難易度1～3）&lt;/li&gt;
&lt;li&gt;Front-end Developer（難易度2～3）&lt;/li&gt;
&lt;li&gt;Full-stack Developer（難易度3～8）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;あらかじめ用意された Figma のデザインデータとユーザストーリーがあり、それに沿って課題作品を作っていくような形です。
必ずしもデザインデータ通りというわけではなく、ユーザストーリーさえ満たしていれば多少のアレンジは OK。
実装方法の明確な回答は用意されていませんので、自分で試行錯誤しながら作っていきます。
作成した作品は自分でどこかしらのホスティングサービスに公開したのち投稿でき、他のユーザからレビューをもらうなんてことも出来たりします。
逆に他の方の投稿作品を見て参考にしたりも（まだ自分で作成していない課題の他の方の投稿作品を見ることは非推奨となってはいますが）&lt;/p&gt;
&lt;p&gt;自分は実務でやった React にハマってからフロントエンド志望になった人間で、それから React 関連の技術を主に勉強してきたのですが...。
デザインデータの再現実装を実務でやったことがないとか、HTML・CSS の基礎知識に微妙なところがあったり等課題を抱えておりまして。
それらを克服するために、この devChallenges の課題をはじめました。
また、実際に何かを作ってみるとして自分でネタが思いつきにくいところもありまして、そういう面でもこういう課題いいなと思ったというのもあります。&lt;/p&gt;
&lt;p&gt;最初は Front-end 編を進めていましたが、諸事情あって途中から Responsive Web 編の方を先に。
この度、その8課題を完走し終えたので、ちょっと振り返ってみようかなと思った次第です。&lt;/p&gt;
&lt;p&gt;※2022/03/04追記
Front-end 編振り返りも投稿しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2022-03-04-devchallenges-front-end&quot;&gt;devChallenges - Front-end 編を完走？したので、さっくり振り返ってみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Responsive Web 編の課題を振り返ってみる&lt;/h2&gt;
&lt;p&gt;Responsive Web 編では主に Web サイトのページをレスポンシブに作る課題となっています。&lt;/p&gt;
&lt;p&gt;最後の課題を除き、素の HTML・CSS・JavaScript を使うことが推奨とされていたので自分もそうしました。
実際には Sass や TypeScript 等を使うことが多いかなという印象ですが、まずは基礎的な書き方できないとなぁということで。
とはいえ、PostCSS や npm パッケージはいくつか使っています。&lt;/p&gt;
&lt;p&gt;では、8課題をさっくりと振り返っていきます。
（あまり実装のネタバレにならないような書き方としました）&lt;/p&gt;
&lt;h3&gt;1 - 404 Not Found&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-404-not-found&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;404ページを作成する課題。最初の課題なので易しめです。
HTML・CSS 基礎がわかっている人にとっては瞬殺ものやもしれませんね。&lt;/p&gt;
&lt;p&gt;自分の場合は、実装部分よりも、今後課題を進めていく上での環境構築やルール決め（クラスの命名規則など）で時間を使った記憶です。
クラスの命名規則に関しては、BEM のやり方を取り入れてみることにしました。&lt;/p&gt;
&lt;h3&gt;2 - My team page&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-my-team-page&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;チームメンバー一覧ページを作成する課題。
縦書きテキストや交互に余白の違うものをどう実装するか？を考えていきます。&lt;/p&gt;
&lt;p&gt;ただ単に要素を同じように並べるのと比較して、一工夫あることでちょっとおしゃれ感ありますね。&lt;/p&gt;
&lt;h3&gt;3 - Interior Consultant&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;li&gt;On mobile, I can see a collapsed navigation
（訳：モバイルでは、折りたたまれたナビゲーションを見ることができます）&lt;/li&gt;
&lt;li&gt;On mobile, when I select the hamburger menu, I can see a navigation
（訳：モバイルでハンバーガーメニューを選択すると、ナビゲーションが表示されます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-interior-consultant&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;インテリアサービスサイトのトップページ + メニューを作成する課題。
複数ページがあるサイトのようなデザインをしていますが、課題としてはトップページだけです。&lt;/p&gt;
&lt;p&gt;画面幅によって、ヘッダーメニューとメニューボタンを切り替える方法。
メニューの要素をどうやって実装するか、表示非表示の制御などを考えていきます。&lt;/p&gt;
&lt;p&gt;メニューの制御をするにあたっては、意外と考えるところ多いですよね。
改めて考えるいい機会になりました。&lt;/p&gt;
&lt;h3&gt;4 - Recipe page&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a recipe with ingredients and instructions
（訳：材料と説明書が入ったレシピを見ることができます）&lt;/li&gt;
&lt;li&gt;I can select a checkbox if I have the ingredients
（訳：材料があればチェックボックスを選択できます）&lt;/li&gt;
&lt;li&gt;I can see the number of servings, baking times
（訳：私はサービングの数、ベーキング時間を見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-recipe-page&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;レシピサイトのレシピページを作成する課題。
テキスト中の一部分のみスタイルをあてたり、カスタムのリスト番号をどうやって実装するかを考えていきます。&lt;/p&gt;
&lt;p&gt;テキストに使える HTML タグって意外といろんな種類ありますよねー。
それらをちゃんと正しく使い分けられているか？と言われると、なかなか沼りそうな気もしました。
カスタムのリスト番号は、おおよそのやり方がわかると自分でアレンジもできて、ちょっとおしゃれにできそうです。&lt;/p&gt;
&lt;h3&gt;5 - My Gallery&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-my-gallery&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ギャラリーページを作成する課題。
ギャラリー作品の配置をどうやって実装するか？を考えていきます。&lt;/p&gt;
&lt;p&gt;個人的には1つ前の Recipe page の方が難しかったです。&lt;/p&gt;
&lt;h3&gt;6 - Checkout Page&lt;/h3&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/max-comma --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;li&gt;I can input email, phone, full name, address, city, country, and postal code
（訳：メールアドレス、電話番号、氏名、住所、都市、国、郵便番号を入力できます）&lt;/li&gt;
&lt;li&gt;I can input the number of items
（訳：アイテム数を入力できます）&lt;/li&gt;
&lt;li&gt;I can select at least 3 countries from the dropdown
（訳：ドロップダウンから少なくとも3か国を選択できます）&lt;/li&gt;
&lt;li&gt;When I click submit button or press enter, I can see a warning if validation fails
（訳：送信ボタンをクリックするか Enter キーを押すと、検証が失敗した場合に警告が表示されます）&lt;/li&gt;
&lt;li&gt;When I click submit button or press enter, I can see a successful alert if validation succeeds
（訳：送信ボタンをクリックするか Enter キーを押すと、検証が成功した場合に成功したアラートが表示されます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-checkout-page&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;買い物フォームページを作成する課題。
input 要素の type や、バリデーションチェックをどうやって実装するか等を考えていきます。
ちなみにこのバリデーションチェックに関しては、JavaScript を使わなくてもいいよとなっています。&lt;/p&gt;
&lt;p&gt;input 要素の type 属性も割といろんな種類ありますよねー。
type によっては iOS Safari だと数字しか入力できないといった仕様もあるので、ちょっと注意が必要だなとか。
郵便番号や電話番号のバリデーションは、グローバルに対応する場合、どういう正規表現になるんだろうか...と考えたり。
（今回の実装では一旦日本仕様にしました）&lt;/p&gt;
&lt;p&gt;今回はおおよそデザイン通りにしましたが、入力例を下部に書いておくとか、必須かどうかを赤ラベルで表示しておくとか。
そういう配慮も本来は必要になってきますよね。
フォームもなかなか考えるところ多いなーと。&lt;/p&gt;
&lt;h3&gt;7 - Edie homepage&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see a page following the given design
（訳：与えられたデザインに続くページを見ることができます）&lt;/li&gt;
&lt;li&gt;I can see a page on mobile following the given design
（訳：与えられたデザインに従ってモバイルでページを見ることができます）&lt;/li&gt;
&lt;li&gt;I can go to certain locations by selecting links in navigation or footer
（訳：ナビゲーションまたはフッターでリンクを選択すると、特定の場所に移動できます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-edie-homepage&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;LP を作成する課題。
単純に作成する要素が多いので、なかなかやりごたえのある課題です。
ナビゲーションからの移動やレイアウトのずらし等の実装方法を考えていきます。&lt;/p&gt;
&lt;p&gt;今までの課題でやってきたことの集大成 + レイアウトのずらしテクニックを使う感じで、個人的には割と楽しめた課題でした。
LP を作れるようになると、フロントエンド基礎はおおよそ大丈夫そう？&lt;/p&gt;
&lt;h3&gt;8 - Portfolio&lt;/h3&gt;
&lt;p&gt;ユーザストーリー.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;I can see personal details
（訳：個人情報を見ることができます）&lt;/li&gt;
&lt;li&gt;I can see skills
（訳：スキルが見えます）&lt;/li&gt;
&lt;li&gt;I can see projects
（訳：プロジェクトを見ることができます）&lt;/li&gt;
&lt;li&gt;I can filter projects by tag
（訳：タグでプロジェクトをフィルタリングできます）&lt;/li&gt;
&lt;li&gt;I can see hobbies or certificates
（訳：趣味や証明書が見えます）&lt;/li&gt;
&lt;li&gt;(optional): I can see experiences
（訳：私は経験を見ることができます）&lt;/li&gt;
&lt;li&gt;(optional): I can see blogs
（訳：ブログが見えます）&lt;/li&gt;
&lt;li&gt;(optional): I can see projects on different pages
（訳：別のページでプロジェクトを見ることができます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;自分の投稿作品
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ch-portfolio&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ポートフォリオページを作成する課題。
この課題では React や Vue などを使ってもいいよ、とのことだったので、自分は Next.js × emotion で作りました。
これまでの課題と違って、デザインデータそのままを作るというよりは参考にする程度で、自分のデータをいれてカスタマイズする感じです。&lt;/p&gt;
&lt;p&gt;自分は別でポートフォリオサイトを持っているので、そこまで作りこみはしませんでした。
持っていない人はこの機会に作りこんでおいて、それを活用するのも1つの手ですね。&lt;/p&gt;
&lt;h2&gt;主な使用した技術やライブラリなどの振り返り&lt;/h2&gt;
&lt;p&gt;これを書くと若干実装のネタバレになる気もしますが、せっかくなので一緒に振り返ってみます。
（当記事の投稿時点での使用技術です）&lt;/p&gt;
&lt;h3&gt;静的解析 + フォーマッタ&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://stylelint.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://prettier.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://eslint.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;この辺はフロントエンドをやってるとおなじみですね。
VSCode 拡張として自動整形を動作させたり、GitHub Actions 上でもチェックするようにしてました。&lt;/p&gt;
&lt;p&gt;途中から Pre Commit 設定も導入したいなということで、以下の2つを導入してコミット時フォーマットかけるようにも設定。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/toplenboren/simple-git-hooks&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/okonet/lint-staged&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Vite&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.vitejs.dev&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;次世代フロントエンドツール。&lt;/p&gt;
&lt;p&gt;課題を進めるにあたり、最初は VSCode 拡張の Live Server を使ってローカルサーバを立ててやってました。
そこから JavaScript を使う課題へなった時に Vite のことを思い出し。
Web 制作でも活用できて爆速環境だよー、ということで使い始めました。
導入も簡単でしたし、ホットリロードやビルドも爆速で動作するので、すごい快適でしたね。&lt;/p&gt;
&lt;p&gt;ただ、Vite のホットリロードと VSCode 拡張として動作させていた Autoprefixer の自動整形がバッティングするという現象に遭遇して、記事を書いたりもしました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-10-24-vscode-autoprefixer-vite&quot;&gt;VSCode拡張によるファイル保存時自動整形の影響で、ViteのホットリロードがバグってCSSが空になった話&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以降、Autoprefixer は PostCSS として Vite 側で動作させるように。&lt;/p&gt;
&lt;h3&gt;Material Icons&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://google.github.io/material-design-icons&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;課題のデザインの中で使われている各種アイコンは Material Icons を使っていると案内があったので、それを使うようにしました。
1～7の課題では CDN で。8の課題では emotion-icons 経由で使用。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/emotion-icons/emotion-icons&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;modern-css-reset&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/hankchizljaw/modern-css-reset&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;リセット CSS はいろいろ種類があるわけですが、個人的にはこれがちょうどいいかなと最近使っています。&lt;/p&gt;
&lt;h3&gt;wicg-inert（+ メニューにおける制御）&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/WICG/inert&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;inert 属性のポリフィル。
そもそも inert 属性って何？って方は、以下の記事がとても参考になります。
&amp;lt;OG url=&quot;https://standard.shiftbrain.com/blog/unavailable-inert-regions-and-inert-attribute&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;メニューを開いた時、その下にある本コンテンツのことも考慮しないといけないんですよね。
Tab でフォーカスできないようにするとか、ポインターイベントを無効化するとか。
ざっくり言うと、そういう管理を楽にできるのが inert 属性です。
本実装はされていないため、このポリフィルを使用しています。&lt;/p&gt;
&lt;p&gt;便利な inert 属性ですが、スクロール抑制には対応していないので、そこは自分で overflow: hidden をつけるなりが必要です。&lt;/p&gt;
&lt;h4&gt;フェードイン/アウト時に display が絡む時の問題&lt;/h4&gt;
&lt;p&gt;それとメニューの表示/非表示時にフェードイン/アウトのようなアニメーションをつけたい時。
display が絡む制御だと、フェードインは問題ないのにフェードアウトはうまく動作しない問題で頭抱えてましたね。
フェードアウトアニメーションが終わる前に display: none へ切り替わってしまうための現象のようでした。&lt;/p&gt;
&lt;p&gt;これについては、ほぼ同じ悩みを抱えていた方がいて、display でなく visibility を使う方向で解決しました。
visibility だと transition 制御が効くようです。
&amp;lt;OG url=&quot;https://medium.com/eureka-engineering/html-css-%E3%81%9F%E3%81%8B%E3%81%8C%E3%83%95%E3%82%A7%E3%83%BC%E3%83%89%E3%82%A4%E3%83%B3-%E3%83%95%E3%82%A7%E3%83%BC%E3%83%89%E3%82%A2%E3%82%A6%E3%83%88%E3%81%99%E3%82%8B%E3%81%A0%E3%81%91%E3%81%AE%E6%8C%99%E5%8B%95%E3%81%AB%E5%85%A8%E5%8A%9B%E3%81%A7%E5%8F%96%E3%82%8A%E7%B5%84%E3%82%93%E3%81%A0%E7%B5%90%E6%9E%9C-%E6%9C%80%E5%BC%B7%E3%81%AEcss%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%A6%E3%81%97%E3%81%BE%E3%81%A3%E3%81%9F%E8%A9%B1-%E6%9C%80%E5%BC%B7-881152c4ff13&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;メニュー開閉時にスクロールバーの幅分ずれる問題&lt;/h4&gt;
&lt;p&gt;本コンテンツは縦スクロール出来るぐらい内容があり、スクロールバーが表示されている。
それに対して、メニューを開いている時は縦スクロール出来るほど内容がないため、スクロールバーが表示されないという場合。&lt;/p&gt;
&lt;p&gt;単純にフェードイン/アウトだけだと、スクロールバーの横幅分位置がずれてしまう現象が目立ってしまう問題がありました。
（GIF をとってないのでどういうことかわかりづらいやもですが、本コンテンツとメニューの左右余白を同じにしてメニュー開閉するとわかるかなと）&lt;/p&gt;
&lt;p&gt;スクロールバーの有無次第で余白を調整するみたいなことをやろうとしたものの、なんだかイマイチだったので、スライドイン/アウトも兼ねるようにして目立たなくするという回避策をとりました。&lt;/p&gt;
&lt;h4&gt;ページ初回読み込み時に transition が動作してしまう問題&lt;/h4&gt;
&lt;p&gt;スライドイン/アウトも兼ねるようにしたわけですが、今度はページ初回読み込み時に transition が動作してしまう問題に遭遇。
本来はメニューボタンの押下で、はじめてスライドイン/アウトが動作するはずですが、それがページ初回読み込み時にも動作してしまうというものです。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;gifs/2021/devchallenges-responsive-web/transition-bug.gif&quot; alt=&quot;ページ初回読み込み時にtransitionが動作する現象のGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;こんな感じで一瞬ちらついてしまいます。&lt;/p&gt;
&lt;p&gt;これに関しては、調べたところ Chrome のバグらしいです。
本来は transition の初期状態が最初の状態になりますよね。
そこが transition が反映されていない初期状態（メニューが画面内にある状態） → transition の初期状態（メニューが画面外にある状態）という感じで動作してしまっているとのことです。
あらかじめ transition を無効にするクラスを付与しておき、ページ読み込み後にそのクラスを外すようにして対応しました。
&amp;lt;OG url=&quot;https://css-tricks.com/transitions-only-after-page-load&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;focus-visible&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/WICG/focus-visible&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;focus-visible 疑似クラスのポリフィル。
focus-visible 疑似クラスは Safari の対応がイマイチなので、ポリフィルを使うようにしています。&lt;/p&gt;
&lt;p&gt;使うところとしては、ボタン要素の outline 制御です。
not と組み合わせて、キーボード操作以外の focus 時は outline を表示しないようにする感じ。&lt;/p&gt;
&lt;h3&gt;TypeScript × Next.js × emotion&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://www.typescriptlang.org&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://nextjs.org&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://emotion.sh/docs/introduction&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;8の課題の時のみ使用。
元々、Front-end 編をやってた時に使ってた構成です。
TypeScript と Next.js はデファクトくらいの印象なので、実際に使いながら勉強したいなということで採用しました。&lt;/p&gt;
&lt;p&gt;それと CSS in JS も流行っているので、どれか使えるようになりたいなというので、emotion を使うようになりました。
個人的には styled-components よりも emotion の方が好みで、CSS Prop を使ってスタイルを書いています。&lt;/p&gt;
&lt;h3&gt;csx&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/typestyle/csx&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;8の課題の時のみ使用。
CSS に関するユーティリティな関数を提供するライブラリ。
元々は TypeStyle というライブラリから使用されているもののようです。&lt;/p&gt;
&lt;p&gt;RGB 値からでなくカラーコードを基準とした暗い色や明るい色、透明度を反映させた色を作りたいことがあったので、このライブラリを使った関数を作って活用しています。
Sass だったら元々関数が用意されているんですが、emotion だと使えないっぽかったので。&lt;/p&gt;
&lt;h3&gt;rss-parser&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/rbren/rss-parser&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;8の課題の時のみ使用。
RSS 情報を取得し、JS オブジェクトとして扱えるようにしてくれるライブラリ。&lt;/p&gt;
&lt;p&gt;ブログの最新5記事を表示する、ということをやりたかったので活用しました。
割とあっさり使えて便利でしたね。&lt;/p&gt;
&lt;h2&gt;完走してどうだった？&lt;/h2&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;元々、自分の業務経験としては、バックエンド寄りの方が多い身でして。
フロントの業務経験としては、Web の管理システム系を React + UI コンポーネントライブラリで作ったことがある程度だったので、これまであまり素の HTML や CSS を書く機会がそんなになかったんですよね。
その結果、フロントエンド志望になったのはいいが HTML・CSS 基礎が微妙なところあるってどうなの？という感じで、お恥ずかしい限りでした...。&lt;/p&gt;
&lt;p&gt;そんな状態を克服するべく、この8課題を実際に自分で調べたり試行錯誤しながら作っていったことで、なんだか少し自信がついたといいますか。
自分なりの進め方とか、スタイルのパターンとか、そういうのが定まってきた感覚がありましたね。
本当に課題に取り組んでよかったなぁと。&lt;/p&gt;
&lt;p&gt;Front-end 編が中断したままなので、次はそちらの続きもぼちぼちやろうかなと思います。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;さっくりと振り返るつもりが、意外と長くなりましたね🙄
（長くなるのはもはやお家芸）&lt;/p&gt;
&lt;p&gt;devChallenges は、現役の方が腕試しや復習をするのにも、未経験の方が勉強するにもいいコンテンツだと思います。
興味がある方は、ぜひチャレンジしてみてはいかがでしょうか。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;p&gt;※記事系のみ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://standard.shiftbrain.com/blog/unavailable-inert-regions-and-inert-attribute&quot;&gt;UIにおける見えるけど利用できない非活性な領域の実装とinert属性について&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/eureka-engineering/html-css-%E3%81%9F%E3%81%8B%E3%81%8C%E3%83%95%E3%82%A7%E3%83%BC%E3%83%89%E3%82%A4%E3%83%B3-%E3%83%95%E3%82%A7%E3%83%BC%E3%83%89%E3%82%A2%E3%82%A6%E3%83%88%E3%81%99%E3%82%8B%E3%81%A0%E3%81%91%E3%81%AE%E6%8C%99%E5%8B%95%E3%81%AB%E5%85%A8%E5%8A%9B%E3%81%A7%E5%8F%96%E3%82%8A%E7%B5%84%E3%82%93%E3%81%A0%E7%B5%90%E6%9E%9C-%E6%9C%80%E5%BC%B7%E3%81%AEcss%E3%81%8C%E3%81%A7%E3%81%8D%E3%81%A6%E3%81%97%E3%81%BE%E3%81%A3%E3%81%9F%E8%A9%B1-%E6%9C%80%E5%BC%B7-881152c4ff13&quot;&gt;【HTML/CSS】たかがフェードイン/フェードアウトするだけの挙動に全力で取り組んだ結果、最強のCSSができてしまった話【最強】&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://css-tricks.com/transitions-only-after-page-load/&quot;&gt;Transitions Only After Page Load&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Windows側になるべく実行環境作りたくなかったやつが、Voltaでバージョン管理できるNode.js環境を作ってみた</title><link>https://changeofpace.site/posts/2021-11-09-volta</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-11-09-volta</guid><description>Node.js関連に特化したバージョン管理を担うVoltaを、Windows側に導入して使ってみたよという記録と紹介</description><pubDate>Tue, 09 Nov 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、開発環境整備って割と沼だよねと最近思っている、よしです。
Node.js 環境を改めて考える機会があり、Volta というバージョン管理ツールを使ってみました。
その記録と紹介です。&lt;/p&gt;
&lt;p&gt;※翻訳には Google 翻訳を使用しています。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Volta とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://volta.sh&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ざっくりいうと Node.js 関連に特化したバージョン管理ツールです。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;The Hassle-Free JavaScript Tool Manager
（訳：手間のかからない JavaScript ツールマネージャー）
公式では、このように書かれていました。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Node.js のバージョン管理というと、nodenv とか nvm、n、Nodist とか色々有名なものがありますよね。
それらに対して、Volta では Node.js だけでなく、パッケージマネージャである npm や yarn、npm パッケージも管理できます。
公式では Volta の特徴として以下の3つがあげられています。&lt;/p&gt;
&lt;h3&gt;Fast&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Install and run any JS tool quickly and seamlessly! Volta is built in Rust and ships as a snappy static binary.
（訳：JS ツールをすばやくシームレスにインストールして実行してください！ Volta は Rust に組み込まれており、きびきびとした静的バイナリとして出荷されます。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Volta は Rust で作られていることもあり、非常に動作が早いです。
実際に Volta 経由で新しく Node.js を落としてくるときも、待ち時間はほとんどなかったです。10秒くらい。&lt;/p&gt;
&lt;h3&gt;Reliable&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Ensure everyone in your project has the same tools—without interfering with their workflow.
（訳：プロジェクトの全員が同じツールを使用していることを確認します。ワークフローに干渉することはありません。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Google 翻訳だとなんかよくわからん感じになっていますが、プロジェクトメンバー全員が同じバージョンを使うようにできる、ということなのかなと。
プロジェクトごとにバージョン固定ができるのは、既存のバージョン管理ツールでもおなじみですね。&lt;/p&gt;
&lt;h3&gt;Universal&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;No matter the package manager, Node runtime, or OS, one command is all you need: volta install.
（訳：パッケージマネージャー、Node ランタイム、または OS に関係なく、必要なコマンドは1つだけです。volta install です。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Volta は OS に関係なく動作します。
これも Rust 製であることの恩恵の1つでしょうか。&lt;/p&gt;
&lt;p&gt;既存のバージョン管理ツールの多くは Unix 系のみ対応で、Windows だと WSL 側でしか使えませんでした。
Windows 向けバージョン管理ツールとして Nodist がありましたが、（2021/11/09現在で）最終リリースが2019年で古くなっていたりと問題を抱えており...。
Volta は Windows でも動作するので、Nodist から移行したという方も見受けられましたね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;自分は個人 PC として Windows を使っていまして。
これまでは Windows 側になるべく言語や実行環境を入れたくない派だったので、WSL 側や Docker で環境を作るようにして。
Node.js に関しては、WSL 側で Linuxbrew → anyenv → nodenv を使ってバージョン管理をして環境を作っており、特に困ったことはありませんでした。&lt;/p&gt;
&lt;p&gt;そのなか、開発環境について考える機会があり、その時期にこの Volta を知り。
実際に使ってみて良さそうであれば、今後は Windows 側で Node.js の管理しようかなと思ったわけです。&lt;/p&gt;
&lt;p&gt;それでは、基本的な使い方を紹介していきます。&lt;/p&gt;
&lt;h2&gt;動作確認バージョン&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Windows 10 Pro：20H2（19042.1288）&lt;/li&gt;
&lt;li&gt;Volta：1.0.5&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;h3&gt;Windows&lt;/h3&gt;
&lt;p&gt;インストーラが用意されているので、それをダウンロードしてインストール。
GitHub リポジトリの&lt;a href=&quot;https://github.com/volta-cli/volta/releases&quot;&gt;リリース&lt;/a&gt;から直接ダウンロードしてもいいですし、winget でも対応しているのでそっちでも。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;winget install --id Volta.Volta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストーラを使うと、以下のパスを自動で追加してくれます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザ環境変数の Path：&lt;code&gt;\Users\\{USER}\AppData\Local\Volta\bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;システム環境変数の Path：&lt;code&gt;\Program Files\Volta\&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;システム環境変数の追加もあるので、ターミナルを起動していた場合は、ターミナルごと再起動してコマンドが使えるか確認。&lt;/p&gt;
&lt;p&gt;補足として「&lt;strong&gt;Volta の機能はシンボリックリンクの作成に依存するため、次のいずれかを行う必要がある&lt;/strong&gt;」とあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;開発者モードを有効にする（推奨）&lt;/li&gt;
&lt;li&gt;昇格された特権で Volta を実行する（非推奨）&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;開発者モード無効でもいける？&lt;/h4&gt;
&lt;p&gt;開発者モードにするのはなんだか少し抵抗があった自分は、まずは無効のままでインストールしてみました。&lt;/p&gt;
&lt;p&gt;結論から言うと、Node.js とパッケージマネージャ（npm と yarn）のバージョン管理に関しては、特に問題なく動作した印象です。
仕組みとして、〇〇env 系でおなじみの shim が使われているそうなので、問題なかったのかも？&lt;/p&gt;
&lt;p&gt;しかし、npm パッケージに関してはシンボリックリンクが使われる仕組みのため、Volta 管理下でグローバルインストールするとエラーになりました。
Windows では、開発者モードでないと管理者権限のユーザでしかシンボリックリンクが作成できないことによるものでしょう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;volta install typescript
error: Could not create shared environment for package &apos;typescript&apos;

Please ensure you have correct permissions to the Volta directory.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なので、npm パッケージのグローバルインストールを併用しないのなら、無効のまま Volta を使用しても支障はなさそう？
ただ、公式では有効にすることが推奨されているので、無効のまま使用するのをおススメはしません。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;ちなみに、自分が Volta をインストールした時は、ちょっとトラブりまして。&lt;/p&gt;
&lt;p&gt;最初、インストール自体は成功しているのに volta コマンドが使えないという問題が生じてしまい...。
何度かアンインストールとインストールを繰り返してもダメで。
やっぱり開発者モードにしないとダメかーと思って有効にして、インストールしなおしてもダメで。&lt;/p&gt;
&lt;p&gt;「???」という感じでしたが、Volta に関するディレクトリを削除したうえでインストールしなおしたら無事コマンドが使えるようになりました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;\Users\\{USER}\AppData\Local\Volta&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;\Program Files\Volta\&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;もしかしたら最初にインストールした時、どこかファイルが壊れていて、それが残っていたのかも？&lt;/p&gt;
&lt;h3&gt;Unix&lt;/h3&gt;
&lt;p&gt;Mac や Linux の方は curl でシェルを落としてきて、実行すれば OK。
WSL でも同様です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;curl https://get.volta.sh | bash
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この方法では、使っているシェルに応じたファイル（&lt;code&gt;~/.zshrc&lt;/code&gt;とか）にパスを自動で追加してくれるとのこと。
（内部的に実行している&lt;code&gt;volta setup&lt;/code&gt;コマンドによるもの）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export VOLTA_HOME=&quot;$HOME/.volta&quot;
export PATH=&quot;$VOLTA_HOME/bin:$PATH&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみに brew 経由でもインストールできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;brew install volta
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらでは&lt;code&gt;volta setup&lt;/code&gt;の自動実行は行われないので、手動で実行。
（もしくは自分で直接パスを追記）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;volta setup
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;主な機能&lt;/h2&gt;
&lt;p&gt;コマンドの詳細は後述するので、さらっと書きます。&lt;/p&gt;
&lt;h3&gt;Node.js やパッケージマネージャ（npm、yarn）のバージョン管理&lt;/h3&gt;
&lt;p&gt;基本的には既存のバージョン管理ツールと同じく、指定バージョンのものを取得してきてインストール。
インストールしたもののなかで、使用するバージョンを指定するという流れ。&lt;/p&gt;
&lt;p&gt;以下は Node.js の例ですが、npm と yarn も同様です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# 最新 LTS バージョンの Node.js を取得 + インストール + デフォルト（グローバル）バージョンに指定
volta install node
# v15.14.3の Node.js を取得 + インストール
volta fetch node@15.14.3

# そのプロジェクトでの（ローカル）バージョン指定
volta pin node@15.14.3
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ローカルバージョンに指定したバージョンは、そのプロジェクトの package.json に記録されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  .
  .
  .
  &quot;volta&quot;: {
    &quot;node&quot;: &quot;15.14.3&quot;,
    &quot;yarn&quot;: &quot;1.22.17&quot;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;nodenv とかだと&lt;code&gt;.node-version&lt;/code&gt;に記録されるので、これは独自の方式ですね。
Netlify といったホスティングサービスでは、&lt;code&gt;.node-version&lt;/code&gt;でバージョン検知する仕組みがあったりするので、そういう恩恵は得られにくいような気もしました。&lt;/p&gt;
&lt;p&gt;Volta 側でインストールしたバージョンを確認するとこんな感じ。
default がデフォルト（グローバル）バージョン、current がそのプロジェクトでの（ローカル）バージョン。
（default と current で同バージョンの場合は、current が優先して表示される）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;volta list all
⚡️ User toolchain:

    Node runtimes:
        v15.14.0 (current @ C:\Users\{USER}\XXXX\XXXX\package.json)
        v16.13.0 (default)

    Package managers:
        Yarn:
            v1.22.17 (current @ C:\Users\{USER}\XXXX\XXXX\package.json)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールできる各種バージョンについては、確認コマンドが見当たらなかったので、自分で Node.js 公式等を見る必要がありそうです。
デフォルトでは、パブリックソースおよびレジストリ（&lt;code&gt;https://nodejs.org&lt;/code&gt;、&lt;code&gt;https://yarnpkg.com&lt;/code&gt;、&lt;code&gt;https://www.npmjs.com&lt;/code&gt;）から取得するとのこと。
nodenv でいう&lt;code&gt;nodenv install -l&lt;/code&gt;みたいな、一覧確認できるコマンドが今後追加されたらいいなぁと。&lt;/p&gt;
&lt;p&gt;ちなみにパッケージマネージャに関しては、新しいものとして&lt;code&gt;pnpm&lt;/code&gt;がありますが、一応は使えるかなという感じでした。
（試しにインストールしてみようとしたところ、パッケージマネージャというよりは、後述する npm パッケージとして認識されたので）&lt;/p&gt;
&lt;h3&gt;npm パッケージのバージョン管理&lt;/h3&gt;
&lt;p&gt;グローバルインストールしたものとローカルインストールしたものとで、同名の npm パッケージが存在していた場合、ローカルの方を優先して使うよ。
というやつです。&lt;/p&gt;
&lt;p&gt;例えば、普段ちょっとしたスクリプトを実行するのに TypeScript をグローバルインストールしていたとします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ※npm や yarn でグローバルインストールしても Volta 管理下になる
volta install typescript
# or
npm install -g typescript
# or
yarn global add typescript

tsc --version # 4.4.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;その一方で、とあるプロジェクトでは違うバージョンをローカルでインストールしていた場合。
そのプロジェクトに移動した際には、ローカルバージョンが自動的に使われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cd /path/to/project-using-typescript-3.9.4
tsc --version # 3.9.4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、グローバルインストールした npm パッケージは、インストール時のデフォルト（グローバル）バージョンである Node.js と関連付けられます。
この npm パッケージを、グローバルバージョンとして使用する際は、この関連付けられた Node.js が使われるようになっているそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;volta list all
⚡️ User toolchain:
    .
    .
    .
    Packages:
        typescript@4.4.4 (default)
            binary tools: tsc, tsserver
            platform:
                runtime: node@16.13.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;その他の機能&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.volta.sh/advanced/hooks&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;Node.js・npm・yarn をパブリックソースおよびレジストリ（&lt;code&gt;https://nodejs.org&lt;/code&gt;、&lt;code&gt;https://yarnpkg.com&lt;/code&gt;、&lt;code&gt;https://www.npmjs.com&lt;/code&gt;）以外からダウンロードしたい場合の、ダウンロードプロセスへのフックを提供。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.volta.sh/advanced/workspaces&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;1つのリポジトリに複数のプロジェクトがあり、それら全てで Volta 設定を共有したいワークスペース環境内で Volta を使用する方法を提供。
（extends を使って、親 package.json の volta プロパティを参照するようなやり方）&lt;/p&gt;
&lt;h2&gt;コマンド&lt;/h2&gt;
&lt;h3&gt;volta fetch&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta fetch command will allow you to fetch a tool into the local cache, without setting it as a default or making it available, for future offline use.
（訳：ツールをデフォルトとして設定したり、将来オフラインで使用できるようにすることなく、ツールをローカルキャッシュにフェッチできます。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Fetches a tool to the local machine

USAGE:
    volta fetch [FLAGS] &amp;lt;tool[@version]&amp;gt;...

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information

ARGS:
    &amp;lt;tool[@version]&amp;gt;...    Tools to fetch, like `node`, `yarn@latest` or `your-package@^14.4.3`.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初は、後述する&lt;code&gt;install&lt;/code&gt;とかぶってるように思えて、この訳もよくわからんって感じでした。
&lt;code&gt;install&lt;/code&gt;がツールの取得 + インストール + デフォルト（グローバル）設定であるのに対して、こちらはどうもツールの&lt;strong&gt;取得とインストールのみ&lt;/strong&gt;を行うもののようです。&lt;/p&gt;
&lt;p&gt;バージョン指定についてはこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# v15.14.0 の Node.js を取得 + インストール
volta fetch node@15.14.0

# v15 の最新バージョンの Node.js を取得 + インストール
volta fetch node@15

# 最新 LTS バージョンの Node.js を取得 + インストール
volta fetch node
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;volta install&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta install command will set your default version of a tool. It will also fetch that tool if it isn’t already cached locally
（訳：volta install コマンドは、ツールのデフォルトバージョンを設定します。また、まだローカルにキャッシュされていない場合は、そのツールをフェッチします）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Installs a tool in your toolchain

USAGE:
    volta install [FLAGS] &amp;lt;tool[@version]&amp;gt;...

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information

ARGS:
    &amp;lt;tool[@version]&amp;gt;...    Tools to install, like `node`, `yarn@latest` or `your-package@^14.4.3`.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これは訳のとおりです。
nodenv なんかだと、インストール（&lt;code&gt;nodenv install&lt;/code&gt;）とグローバルバージョン設定（&lt;code&gt;nodenv global バージョン&lt;/code&gt;）が別コマンドだったので、ちょっと不思議な感じですね。&lt;/p&gt;
&lt;p&gt;バージョン指定については fetch と同様です。&lt;/p&gt;
&lt;h3&gt;volta uninstall&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta uninstall command allows you to remove any global package that has been installed with volta install.
（訳：volta uninstall コマンドを使用すると、volta install でインストールされたグローバルパッケージを削除できます。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Uninstalls a tool from your toolchain

USAGE:
    volta uninstall [FLAGS] &amp;lt;tool&amp;gt;

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information

ARGS:
    &amp;lt;tool&amp;gt;    The tool to uninstall, e.g. `node`, `npm`, `yarn`, or &amp;lt;package&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このコマンドでアンインストールできるらしいのですが、現状対応しているのはグローバルインストールした npm パッケージのみのようでした。
自分が node と yarn で試してみた時は、エラーになってアンインストールできず。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;gt; volta uninstall yarn
error: Uninstalling yarn is not supported yet.

&amp;gt; volta uninstall yarn@1.22.17
warning:  No package &apos;yarn@1.22.17&apos; found to uninstall

&amp;gt; volta uninstall node
error: Uninstalling node is not supported yet.

&amp;gt; volta uninstall node@15.14.0
warning:  No package &apos;node@15.14.0&apos; found to uninstall
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実体としては以下のディレクトリにいるので、そこから削除してしまえば一応対応できるみたいです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Windows：&lt;code&gt;%LOCALAPPDATA%\Volta\tools\image&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;Unix：&lt;code&gt;~/.volta/tools/image&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ちなみにグローバルインストールした npm パッケージは、従来通り npm、yarn コマンドでもアンインストール可能。&lt;/p&gt;
&lt;h3&gt;volta pin&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta pin command will update a project’s package.json file to use the selected version of a tool.
（訳：volta pin コマンドは、選択したバージョンのツールを使用するようにプロジェクトの package.json ファイルを更新します。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Pins your project&apos;s runtime or package manager

USAGE:
    volta pin [FLAGS] &amp;lt;tool[@version]&amp;gt;...

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information

ARGS:
    &amp;lt;tool[@version]&amp;gt;...    Tools to pin, like `node@lts` or `yarn@^1.14`.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そのプロジェクトでのツール（Node.js、npm、yarn のみ）のバージョンを固定する時に使用。nodenv でいうローカルバージョン指定。
package.json がないところで使ってもエラーになります。&lt;/p&gt;
&lt;p&gt;固定したバージョンは packge.json へ記録されるようになっています。&lt;/p&gt;
&lt;h3&gt;volta list&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta list command allows you to inspect your installed Node runtimes, package managers, and packages with binaries.
（訳：volta list コマンドを使用すると、インストールされているノードランタイム、パッケージマネージャー、およびバイナリを含むパッケージを検査できます。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Displays the current toolchain

USAGE:
    volta list [FLAGS] [OPTIONS] [tool]

FLAGS:
    -c, --current
            Show the currently-active tool(s).

            Equivalent to `volta list` when not specifying a specific tool.
    -d, --default
            Show your default tool(s).

        --verbose
            Enables verbose diagnostics

        --quiet
            Prevents unnecessary output

    -h, --help
            Prints help information


OPTIONS:
        --format &amp;lt;format&amp;gt;
            Specify the output format.

            Defaults to `human` for TTYs, `plain` otherwise. [possible values: human, plain]

ARGS:
    &amp;lt;tool&amp;gt;
            The tool to lookup: `all`, `node`, `yarn`, or the name of a package or binary.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Volta 管理下の各種バージョンを確認するのに使用。&lt;/p&gt;
&lt;h3&gt;volta completions&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta completions command will generate command completion information for your shell.
（訳：volta completions コマンドは、シェルのコマンド補完情報を生成します。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Generates Volta completions

By default, completions will be generated for the value of your current shell,
shell, i.e. the value of `SHELL`. If you set the `&amp;lt;shell&amp;gt;` option, completions
will be generated for that shell instead.

If you specify a directory, the completions will be written to a file there;
otherwise, they will be written to `stdout`.


USAGE:
    volta completions [FLAGS] [OPTIONS] &amp;lt;shell&amp;gt;

FLAGS:
    -f, --force
            Write over an existing file, if any.

        --verbose
            Enables verbose diagnostics

        --quiet
            Prevents unnecessary output

    -h, --help
            Prints help information


OPTIONS:
    -o, --output &amp;lt;out_file&amp;gt;
            File to write generated completions to


ARGS:
    &amp;lt;shell&amp;gt;
            Shell to generate completions for [possible values: zsh, bash, fish, powershell,
            elvish]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コマンド入力補完のスクリプトを生成してくれます。
使用しているシェルを指定することで、そのシェル用のスクリプトを出力してくれるので、あとはそれを読み込むようにすれば OK。&lt;/p&gt;
&lt;p&gt;自分は bash で&lt;code&gt;~/bash_completion&lt;/code&gt;配下にスクリプトを配置して、&lt;code&gt;~/.bash_profile&lt;/code&gt;から読み込むようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;then
  for COMPLETION in &quot;${HOME}/bash_completion/&quot;*
  do
    [[ -r &quot;${COMPLETION}&quot; ]] &amp;amp;&amp;amp; source &quot;${COMPLETION}&quot;
  done
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;volta which&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta which command will unwrap Volta’s shims and locate the actual binary that will be launched by Volta.
（訳：volta which コマンドは、Volta の shim をアンラップし、Volta によって起動される実際のバイナリを見つけます。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Locates the actual binary that will be called by Volta

USAGE:
    volta which [FLAGS] &amp;lt;binary&amp;gt;

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information

ARGS:
    &amp;lt;binary&amp;gt;    The binary to find, e.g. `node` or `npm`
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これは訳文のとおりです。
実体のバイナリがあるパスを表示してくれます。&lt;/p&gt;
&lt;h3&gt;volta setup&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta setup command will enable Volta by modifying the PATH for the current user (in a platform-dependent way) to include the Volta shim directory
（訳：volta setup コマンドは、現在のユーザーの PATH を（プラットフォームに依存する方法で）変更して Volta shim ディレクトリを含めることにより、Volta を有効にします。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Enables Volta for the current user

USAGE:
    volta setup [FLAGS]

FLAGS:
        --verbose    Enables verbose diagnostics
        --quiet      Prevents unnecessary output
    -h, --help       Prints help information
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Volta のパスを通してくれるもの。
Volta を brew ではなく、インストーラで入れた場合は自動実行されるので、手動実行することはあまりなさそうです。&lt;/p&gt;
&lt;h3&gt;volta run&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;The volta run command will run the command that you give it, using versions of tools that are specified at the command line.
（訳：volta run コマンドは、コマンドラインで指定されたバージョンのツールを使用して、指定されたコマンドを実行します。）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Run a command with custom Node, npm, and/or Yarn versions

USAGE:
    volta run [FLAGS] [OPTIONS] &amp;lt;command&amp;gt; [--] [args]...

FLAGS:
        --bundled-npm    Forces npm to be the version bundled with Node
        --no-yarn        Disables Yarn
        --verbose        Enables verbose diagnostics
        --quiet          Prevents unnecessary output
    -h, --help           Prints help information

OPTIONS:
        --node &amp;lt;version&amp;gt;         Set the custom Node version
        --npm &amp;lt;version&amp;gt;          Set the custom npm version
        --yarn &amp;lt;version&amp;gt;         Set the custom Yarn version
        --env &amp;lt;NAME=value&amp;gt;...    Set an environment variable (can be used multiple times)

ARGS:
    &amp;lt;command&amp;gt;    The command to run
    &amp;lt;args&amp;gt;...    Arguments to pass to the command
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Volta を通して、各種コマンドを実行。
オプションでコマンド実行時の使用バージョン指定ができるので、事前にバージョン切り替えをしなくていい。
オプションとして各ツールのバージョンを指定しなかった場合は、そのプロジェクトの固定バージョンが。
プロジェクトの固定バージョンもなければ、デフォルト（グローバル）バージョンが使われます。&lt;/p&gt;
&lt;p&gt;バージョンごとの検証をする時などによさそうです。&lt;/p&gt;
&lt;h3&gt;volta help&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;Prints this message or the help of the given subcommand(s)

USAGE:
    volta help [subcommand]...

ARGS:
    &amp;lt;subcommand&amp;gt;...    The subcommand whose help message to display
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Volta コマンドのヘルプを表示。&lt;/p&gt;
&lt;h2&gt;余談：asdf でのバージョン管理とその速度&lt;/h2&gt;
&lt;p&gt;今回、開発環境について考える機会があったことで Volta を使ってみたわけですが、実は元々 asdf を使おうかと思っていました。
というのも、発端としては Twitter で anyenv から asdf に移行した方を見かけたことでした。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://asdf-vm.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;この asdf とは、言語や実行環境だけでなく CLI ツールにも対応しているバージョン管理ツールです。
基本的な機構を有する asdf 本体がまずあって、そこへ各種言語・ツールに応じたプラグインを追加していくという構造になっています。
プラグインの作り方は公開されており、有志の方がいろんなプラグインを作っているため、実に膨大な言語・ツールに対応しているなにやらすごいやつです。&lt;/p&gt;
&lt;p&gt;これは anyenv の完全上位互換なのでは...！と想い、意気揚々と asdf を導入してみた自分。
Node.js もちゃんと動作しているし、大丈夫そうかなと思っていた矢先、実行速度の問題に直面しました。&lt;/p&gt;
&lt;p&gt;同時期に導入したプロンプトの Starship。
この Starship はプロジェクトディレクトリに移動すると、そのプロジェクトのファイルなどをスキャンして情報を表示してくれる機能を持っています。
例えば Node.js だったら、package.json や .node-version、node_modules などの有無で Node.js のプロジェクトだと認識。
&lt;code&gt;node -v&lt;/code&gt;でバージョンを取得して表示してくれます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ただ、このバージョン取得にやたら時間がかかってしまい、Starship のデフォルトのスキャン時間がタイムアウトしてしまう事態に...。
じゃあ、一体 asdf 経由での Node.js 実行は、どのくらい時間かかっているのか？
一旦タイムアウト時間を伸ばしてみて&lt;code&gt;starship explain&lt;/code&gt;で確認すると、1200ms～1500ms くらいかかっていました。時間かかりすぎでは...。
anyenv だと 500ms～700ms くらい（シェル起動時のスクリプトとしては、asdf の方が早いらしいです）
Starship のスキャンタイムアウト時間をこれに合わせて設定すると、当然プロンプトの表示時間が遅いので微妙だなと考えるようになり。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;これについて調べていくと、どうも以前から速度面の問題はあったようで、こちらの記事でその Issue についての話があります。
&amp;lt;OG url=&quot;https://tech.buty4649.net/entry/2021/07/29/201613&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;直接バイナリを実行すると早いのに、asdf の shim 経由で実行すると遅くなるという現象。
direnv を使って対応する手もあるそうですが、プロジェクトごとに設定を書かないといけないのが気になってしまい...。
また、自分の実行速度ほど遅い方は見かけなかったので asdf だけの問題ではなく、単純に自分の WSL 環境がよろしくない可能性もあるなと。
そのため、WSL を初期化しなおしてみるとか、WSL2 にするとかを試す手もありました。&lt;/p&gt;
&lt;p&gt;しかし、そんな時に Volta を知り。
もう Windows 側でそこそこ早くて、ちゃんと動作するならそっちの方がよくないか？と思い、今回に至ったというわけです。
ちなみに Windows 側に Volta と Starship をいれて、&lt;code&gt;starship explain&lt;/code&gt;で Node.js のスキャン時間を計測すると 70ms～120ms くらいでした。
WSL 側の anyenv、asdf と比べると、だいぶ早くなりましたね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回は、紆余曲折あって行きついた Volta を紹介してみました。
他の方の Volta 記事との差別化や、未来の自分が見ても納得するように書こうとしたら、だいぶ長くなりましたね🙄
まぁでも、おかげで自分の中の疑問点がおおよそ解消されました。&lt;/p&gt;
&lt;p&gt;Volta 導入を検討している方の、何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://volta.sh/&quot;&gt;Volta&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/taichifukumoto/articles/how-to-use-volta&quot;&gt;Node.jsのバージョン管理にVoltaを推したい&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog1.mammb.com/entry/2021/11/01/090000&quot;&gt;Node のバージョン管理は Volta でいいかな&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;余談.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://asdf-vm.com/&quot;&gt;asdf&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.buty4649.net/entry/2021/07/29/201613&quot;&gt;starship+asdfでプロンプトの表示が遅くなるのを改善する&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>VSCode拡張によるファイル保存時自動整形の影響で、ViteのホットリロードがバグってCSSが空になった話</title><link>https://changeofpace.site/posts/2021-10-24-vscode-autoprefixer-vite</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-10-24-vscode-autoprefixer-vite</guid><description>VSCode拡張として動かしていたAutoprefixerの影響で、Viteのホットリロードがバグってしまった話とその対応について</description><pubDate>Sun, 24 Oct 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、よしです。
先日、Vite デビューをしたところ思わぬ現象に遭遇してしまいました...。
せっかくなので、その対応方法とともに記録を残します。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;要約&lt;/h2&gt;
&lt;p&gt;さらっと結論だけ知りたい方向けに、先に結論を書いておきます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;VSCode 拡張の Autoprefixer を導入している + ファイル保存時にこの拡張の自動整形を動作させている
↓
Vite のローカルサーバを立ち上げてコードを書いている
↓
Vite の（CSS ファイル保存時の）ホットリロードが時折バグって、生成物の CSS の中身が空になってしまうことがある
↓
Vite と Autoprefixer を併用する場合は、Autoprefixer を VSCode 拡張として動作させるのでなく、PostCSS として Vite 側で実行してもらうようにしようね！&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ちなみに当記事では Vite についての説明は特にしません。&lt;/p&gt;
&lt;h2&gt;遭遇したときの状況とその現象&lt;/h2&gt;
&lt;p&gt;海外のとある課題サイトの課題を進めていたところ。
微量ながら JavaScript も書くので、せっかくなら Vite を導入してみるかーと途中から導入。
&amp;lt;OG url=&quot;https://ja.vitejs.dev&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;導入には公式サイトともに、こちらの記事を参考にさせていただきました。
&amp;lt;OG url=&quot;https://ics.media/entry/210708/#contents-anchor-pitfalls&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Vite を使ったのはこれが初めて。
導入の楽さと爆速の環境を体験して、次世代のフロントエンドツールとして注目されているのもわかるなぁという気持ちでした。&lt;/p&gt;
&lt;p&gt;そのなか、スタイリングをするために CSS ファイルを編集してホットリロードを何度かさせていると...。
爆速のホットリロードで、全くスタイルがあたってない状態になってしまいました。
あまりに不意のことだったので、「急にどうした！？」とテンパる自分。&lt;/p&gt;
&lt;p&gt;そこからさらにホットリロードを何度かさせると、元に戻ることもあればそのままの状態が続いたり。
たまに起こるくらいならまだしも、4、5回に1回くらいの頻度で発生するので、こりゃあ原因調査してみようということに。&lt;/p&gt;
&lt;h2&gt;原因調査&lt;/h2&gt;
&lt;h3&gt;JavaScript ファイルが読み込まれてない？実行されていない？&lt;/h3&gt;
&lt;p&gt;Vite を使っている場合、CSS ファイルの読み込みに関しては、起点となる JavaScript ファイルからインポートということもできます。
自分の場合もそうしていました。
そのため、最初は JavaScript が読み込まれてないとか、実行されていないとかなのかな？と。&lt;/p&gt;
&lt;p&gt;試しに JavaScript ファイルへ setInterval を仕込むと普通に動作していたので、この説はなし。
また、この現象は CSS ファイル保存時のホットリロード時のみ発生していたので、CSS がなんかおかしくなっている説へ。&lt;/p&gt;
&lt;h3&gt;生成される CSS がおかしい？&lt;/h3&gt;
&lt;p&gt;正常時と現象発生時の CSS を比較して見てみることに。&lt;/p&gt;
&lt;h4&gt;起点 JavaScript ファイルから CSS ファイルをインポートした場合&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;import &apos;../css/style.css&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Vite の本来の挙動としては以下のようになっています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.vitejs.dev/guide/features.html#css&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;.css ファイルをインポートすると、HMR をサポートする style タグを介してそのコンテンツがページに挿入されます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-import-normal.png&quot; alt=&quot;起点JavaScriptファイルからCSSファイルをインポートした場合に挿入されるstyleタグ - 正常&quot; /&amp;gt;
本来はこのように head タグ配下に style タグが挿入され、そこに CSS の内容が展開されます。&lt;/p&gt;
&lt;p&gt;しかし、現象発生時は head タグ配下に style タグ自体は挿入されているものの、内容が空になっている状態。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-import-abnormal.png&quot; alt=&quot;起点JavaScriptファイルからCSSファイルをインポートした場合に挿入されるstyleタグ - 異常&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;link タグから CSS ファイルを読み込んだ場合&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;link href=&quot;css/style.css&quot; rel=&quot;stylesheet&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;正常時は、CSS ファイルにクエリパラメータがついたものを読み込むような感じに変化。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-link-normal.png&quot; alt=&quot;linkタグからCSSファイルを読み込んだ場合のlinkタグ - 正常&quot; /&amp;gt;
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-link-file-normal.png&quot; alt=&quot;linkタグからCSSファイルを読み込んだ場合のCSSファイル - 正常&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;現象発生時は、CSS ファイルの中身が空になってしまっている状態。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-link-abnormal.png&quot; alt=&quot;linkタグからCSSファイルを読み込んだ場合のlinkタグ - 異常&quot; /&amp;gt;
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/vscode-autoprefixer-vite/css-link-file-abnormal.png&quot; alt=&quot;linkタグからCSSファイルを読み込んだ場合のCSSファイル - 異常&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;これで、とりあえず CSS がおかしくなっているということは特定。
あとは、なぜ CSS がおかしくなってしまっているのか？という部分の調査へ。&lt;/p&gt;
&lt;h3&gt;Google 先生に頼る・Issue を見てみる&lt;/h3&gt;
&lt;p&gt;まずは、同じ現象に遭遇した方がいないかググってみよう！
↓
似たような現象が起きてる方すら見つからず...。&lt;/p&gt;
&lt;p&gt;じゃあ、Vite のリポジトリの Issue に上がってないか見てみよう！
↓
少し似ている現象の Issue は見つかるも、よく読み込んでいくとなんか違う...&lt;/p&gt;
&lt;p&gt;これはもしや、レアケースな現象をひいてしまったのか？
もし原因を特定できたら Vite リポジトリに contribute するチャンスか？
なんて考えていました。&lt;/p&gt;
&lt;p&gt;...が、ここで VSCode 拡張によるファイル保存時の自動整形のことを思い出し。
あ、これ、自動整形とバッティングしてるんじゃね？ということで、そちらを見ていくことに。&lt;/p&gt;
&lt;h3&gt;バッティングしている VSCode 拡張の特定&lt;/h3&gt;
&lt;p&gt;この時、VSCode 上でファイル保存時の自動整形を動作させている拡張のうち、CSS ファイルに関するものは以下の3つ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;StyleLint：CSS の静的解析 + 自動整形&lt;/li&gt;
&lt;li&gt;Prettier：コードフォーマッタ&lt;/li&gt;
&lt;li&gt;Autoprefixer：自動でベンダープレフィックス付き CSS プロパティを付与してくれるやつ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;設定を調整しながら、1つずつ見ていくと...&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;StyleLint：現象発生せず&lt;/li&gt;
&lt;li&gt;Prettier：現象発生せず&lt;/li&gt;
&lt;li&gt;Autoprefixer：&lt;strong&gt;現象発生！&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;はい、Autoprefixer のファイル保存時の自動整形とバッティングして、おかしな挙動になっていたようです。&lt;/p&gt;
&lt;h2&gt;Vite と Autoprefixer の共存&lt;/h2&gt;
&lt;p&gt;ただ、Autoprefixer は非常に便利なのでどうにか共存させたい。
またググってみた結果、Vite 側で実行させるようにすればいいじゃん～という結論に至りました。
（というか、大多数の方は普通にそうしている気がする...🙄）&lt;/p&gt;
&lt;p&gt;VSCode 拡張の自動整形設定は削除（デフォルトがオフなので）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- &quot;autoprefixer.formatOnSave&quot;: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ライブラリとしてインストール.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D postcss autoprefixer
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PostCSS として Autoprefixer を使用するよう設定.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  plugins: [require(&apos;autoprefixer&apos;)],
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.vitejs.dev/guide/features.html#postcss&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;もしプロジェクトに有効な PostCSS が含まれている場合 (postcss-load-config でサポートされている任意の形式、例: postcss.config.js)、インポートされたすべての CSS に自動的に適用されます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;と公式にあるように、これだけで自動的に適用されます。
Autoprefixer のオプションについてはお好みで。
Vite の設定ファイルである&lt;code&gt;vite.config.js&lt;/code&gt;にも、PostCSS の設定を書けるプロパティがあるので、そっちで書いても OK。
自分の場合は、別ファイルにしたかったので分けてます。&lt;/p&gt;
&lt;p&gt;ローカルサーバを立ち上げてみると、展開される CSS にちゃんとベンダープレフィックス付き CSS プロパティが付与されてる！
本番ビルド生成物でも無事確認できたので、これにて解決🎉
これで Vite の爆速環境を満喫できるぞーー！&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ちなみに、これでベンダープレフィックス付き CSS プロパティは自分で書かないようになるので、StyleLint の&lt;code&gt;property-no-vendor-prefix&lt;/code&gt;というルールを導入しておくとよいです。
これはベンダープレフィックスを禁止にするルールなので、もし書いてしまっていてもすぐに気付けます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;余談&lt;/h2&gt;
&lt;p&gt;この調査をしている時に、ちょうど StyleLint の VSCode 拡張のアップデートにも遭遇。
1系リリースに伴い StyleLint 14系からのサポート + 破壊的変更、とあり、この拡張が動作しなくなったという...。&lt;/p&gt;
&lt;p&gt;調査してる時に微妙に関係ある別の対応入るのやめてくれよぉぉぉ
と内心思っていましたが、公式の移行ガイドを見ながら設定を見直して事なきを得ました(笑)&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回は、大した文字数の記事じゃないこともあり、いつもと少し文体を変えて遊んでみました。
うざかったらすみません。&lt;/p&gt;
&lt;p&gt;これ書いていて、この現象に遭遇したの自分くらいなのでは？という気もしたりしなかったりですが、まぁ何かのお役に立てば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable --&amp;gt; */}&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.vitejs.dev/&quot;&gt;Vite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ics.media/entry/210708/#contents-anchor-pitfalls&quot;&gt;jQueryからTypeScript・Reactまで！Viteで始めるモダンで高速な開発環境構築&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://flex-box.net/vite-for-coder/&quot;&gt;Webエンジニア（コーダー）向けVite環境の紹介【初心者向け解説】&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>こんにちはMUI！ 新しくなったMaterial UI v5</title><link>https://changeofpace.site/posts/2021-09-26-material-ui-v5</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-09-26-material-ui-v5</guid><description>2021/09にリリースされ、新ブランドになるなど新しくなったMaterial UIをさらっと紹介してみる</description><pubDate>Sun, 26 Sep 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/max-comma --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;どうもよしです。久しぶりの投稿になります。
元々、Material Design をベースとした UI コンポーネントライブラリである、Material UI の v5 が遂にリリースされました！
v4 からどう変わったのか自分でも試すなど、さらっと見てみたので、ここにまとめてみました。&lt;/p&gt;
&lt;p&gt;※2023/07/09 MUI の更新に伴い、MUI v5.0系のドキュメント参照したほうが良い部分はリンクを差し替えました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Material UI とは？&lt;/h2&gt;
&lt;p&gt;元々、Google の Material Design をベースに開発された、UI コンポーネントライブラリです。&lt;/p&gt;
&lt;p&gt;Material UI の概要や v4 の機能に関しては、以前に記事を書いていますので、よろしければそちらをご参照ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2020-04-23-react-material-ui&quot;&gt;React向けUIコンポーネントライブラリ、Material UI(v4)の紹介&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;※今回の記事は、主に以下の公式ブログ記事をもとにしています。引用文の出典もそちらです。
そのため、すでにそちらをご覧になっている方には、あまり真新しい情報はないやもしれません。
&amp;lt;OG url=&quot;https://mui.com/blog/material-ui-is-now-mui&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://mui.com/blog/mui-core-v5&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※翻訳には Google 翻訳を使用しています。
※比較では、v4.12.3 と v5.0.0 とで比較しています。&lt;/p&gt;
&lt;h2&gt;v5 の開発背景&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;In our last survey, the number of developers that commented about improving the Material Design implementation was down by 60% compared to the year before. At the same time, 5X more developers were struggling to customize the components.&lt;/p&gt;
&lt;p&gt;It was based on this context that we started work on v5 in 2019. Our primary focus was to revamp the customization Developer Experience (DX). It had become clear that design (aesthetic, UX) and DX were key to unlocking the next stage of growth.&lt;/p&gt;
&lt;p&gt;The new brand supports the v5.0.0 release while also creating space for new initiatives that broaden the company horizon. It&apos;s a big deal!&lt;/p&gt;
&lt;p&gt;Our ultimate goal is to become the most effective and efficient tool to build UIs while making it accessible to the many.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;前回の調査では、Material Design の実装の改善についてコメントした開発者の数は、前年と比較して60％減少しました。同時に、5倍以上の開発者がコンポーネントのカスタマイズに苦労していました。&lt;/p&gt;
&lt;p&gt;2019年に v5 の作業を開始したのは、このコンテキストに基づいていました。私たちの主な焦点は、カスタマイズの開発者エクスペリエンス（DX）を刷新することでした。デザイン（美的、UX）と DX が成長の次の段階を解き放つ鍵であることが明らかになりました。&lt;/p&gt;
&lt;p&gt;新しいブランドはv5.0.0リリースをサポートすると同時に、会社の視野を広げる新しいイニシアチブのためのスペースを作成します。それは大したことです！&lt;/p&gt;
&lt;p&gt;私たちの究極の目標は、多くの人がアクセスできるようにしながら、UI を構築するための最も効果的かつ効率的なツールになることです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;Material UI は元々、React の開発者が Material Design を使用できるようにと、Material Design ガイドラインの React 実装として 2014年に開発開始されました。
2018年に v1 をリリース。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;その後も開発は続けられ、日々改善が行われていましたが、コンポーネントのカスタマイズ性に関して課題を抱えていたようです。
それを解決するというのが、v5 開発の大きな目的だったとのこと。
実際に、v5 にはコンポーネントのカスタマイズをよりやりやすくするような新機能が取り入れられています。&lt;/p&gt;
&lt;h2&gt;新ブランド&lt;/h2&gt;
&lt;p&gt;これまでの&lt;code&gt;Material UI&lt;/code&gt;から、&lt;code&gt;MUI&lt;/code&gt;というブランド名に変わりました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;We are breaking the strong association with Material Design as we have seen too many people confusing Material-UI with Google, or as a synonym of Material Design.&lt;/p&gt;
&lt;p&gt;We are now called MUI. It stands for Material to build UIs and is pronounced /ɛm juː aɪ/.&lt;/p&gt;
&lt;p&gt;It&apos;s shorter, it distinguishes us, and it&apos;s familiar – many people already used it for abbreviating Material-UI. More importantly, it allows for the release of products not directly coupled to Material Design, such as an unstyled/headless version of the components, a brand new second design system as an alternative to MD, and more ambitious initiatives.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Material-UI を Google と混同している人や、Material Design の同義語として多くの人が見ているため、Material Design との強い関連性を断ち切っています。&lt;/p&gt;
&lt;p&gt;現在は MUI と呼ばれています。 UI を構築するための Material の略で、/ɛmjuːaɪ/と発音されます。&lt;/p&gt;
&lt;p&gt;短く、私たちを区別し、よく知られています。多くの人がすでにMaterial-UI の省略形として使用しています。さらに重要なことは、コンポーネントのスタイルなし/ヘッドレスバージョン、MD の代替としてのまったく新しいセカンドデザインシステム、より野心的なイニシアチブなど、Material Design に直接結合されていない製品のリリースを可能にすることです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Material Design の枠だけにとらわれない、新たな試みを行っていくことが示されていますね。&lt;/p&gt;
&lt;p&gt;その中で、3つのプロダクトに分かれることにもなりました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MUI Core：すぐに使用できる基本コンポーネント群 + 永久に無料で使えるもの&lt;/li&gt;
&lt;li&gt;MUI X：より複雑なユースケース向けの高度なコンポーネント群 + 有料部分も含むもの&lt;/li&gt;
&lt;li&gt;Templates：コミュニティによって開発された、MUI を使用したテンプレート&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;ロゴ&lt;/h3&gt;
&lt;p&gt;微妙に変わりました。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2021/material-ui-v5/material-ui-new-logo.png&quot; alt=&quot;Material UI - ロゴの比較画像&quot; /&amp;gt;
（画像は公式ブログ記事より）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;It&apos;s basically the same, to keep it familiar – we&apos;re keeping the geometrical shape, to resonate with the building blocks idea of the components – however, we&apos;re reducing the emphasis on the 3D perspective, stepping away from the notion of elevation that Material Design coined.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;基本的には同じです。慣れるために、幾何学的な形状を維持し、コンポーネントのビルディングブロックのアイデアに共鳴します。ただし、3D パースペクティブの重要性を減らし、標高の概念から離れています。Material Design の造語。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;リポジトリ名&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/mui-org/material-ui&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;2021/09/26現在、material-ui のままでした。
ただ、README では、ちゃんと&lt;code&gt;MUI&lt;/code&gt;という表記になっていました。&lt;/p&gt;
&lt;h3&gt;ドメイン&lt;/h3&gt;
&lt;p&gt;これまではこちらでした。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://material-ui.com/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;現在はこちら。シンプルになりましたね。
v5 の公式サイトはデザインが一新されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mui.com/&quot;&gt;Material UI：https://mui.com/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com/&quot;&gt;Material UI v4：https://v4.mui.com/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ちなみに&lt;code&gt;https://material-ui.com/&lt;/code&gt;にアクセスすると、&lt;code&gt;https://mui.com/&lt;/code&gt;へリダイレクトするようになっていました。&lt;/p&gt;
&lt;h3&gt;パッケージ名&lt;/h3&gt;
&lt;p&gt;こちらも新ブランド名に応じた名称へ変わりました。&lt;/p&gt;
&lt;p&gt;MUI Core プロダクトに含まれるパッケージ一覧。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@material-ui/core -&amp;gt; @mui/material
@material-ui/system -&amp;gt; @mui/system
@material-ui/unstyled -&amp;gt; @mui/core
@material-ui/styles -&amp;gt; @mui/styles
@material-ui/icons -&amp;gt; @mui/icons-material
@material-ui/lab -&amp;gt; @mui/lab
@material-ui/types -&amp;gt; @mui/types
@material-ui/styled-engine -&amp;gt; @mui/styled-engine
@material-ui/styled-engine-sc -&amp;gt;@mui/styled-engine-sc
@material-ui/private-theming -&amp;gt; @mui/private-theming
@material-ui/codemod -&amp;gt; @mui/codemod
@material-ui/docs -&amp;gt; @mui/docs
@material-ui/envinfo -&amp;gt; @mui/envinfo
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストール方法も同様に変わっています。
以下は基本パッケージである&lt;code&gt;@material-ui/core&lt;/code&gt;（&lt;code&gt;@mui/material&lt;/code&gt;）をライブラリとしてインストールする例です。&lt;/p&gt;
&lt;p&gt;v4&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：16.8.0 以上&lt;/li&gt;
&lt;li&gt;react-dom：16.8.0 以上&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;npm install @material-ui/core
or
yarn add @material-ui/core
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;p&gt;v5&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：17.0.0 以上&lt;/li&gt;
&lt;li&gt;react-dom：17.0.0 以上&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# emotion を使う場合
npm install @mui/material @emotion/react @emotion/styled
or
yarn add @mui/material @emotion/react @emotion/styled

# styled-components を使う場合
npm install @mui/material @mui/styled-engine-sc styled-components
or
yarn add @mui/material @mui/styled-engine-sc styled-components
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;後述しますが、v5 ではデザインシステムの実体であるスタイリングソリューションが変更され、emotion か styled-components を使うようになりました。
そのため、このようなインストール方法となっています。&lt;/p&gt;
&lt;p&gt;パッケージ名が変更になっている分、v4 と v5 を共存させることもできそうです。（CodeSandbox では確認できました）
1プロジェクトのコードでパフォーマンスの比較をしたり、一時的に共存させて、段階的に v5 に移行していくということもできそうですね。&lt;/p&gt;
&lt;h3&gt;セカンドデザインシステム&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;Developing a new design system is one of the main initiatives, alongside this new brand and the unstyled components, to grow MUI beyond Material Design. Its codename is Joy and we have just started &lt;a href=&quot;https://github.com/mui-org/material-ui/discussions/27803&quot;&gt;the high-level discussion&lt;/a&gt; around it.&lt;/p&gt;
&lt;p&gt;It will be built on top of our foundation packages (the base/unstyled components and &lt;code&gt;@mui/system&lt;/code&gt;) to provide the API shaped similarly to our Material Design components. We plan on having built-in CSS variables generated from the theme, a great dark mode API, a custom look and feel, and more.&lt;/p&gt;
&lt;p&gt;We&apos;re aiming for developing the most frequently used components first, as we plan on releasing them continuously with each milestone. We intend to have Joy versions of all components already available on Material Design so that developers can then pick between them, choosing the design they like the most. The main difference will be the theme structure, where we want to achieve the right amount of flexibility while providing an amazing design by default.&lt;/p&gt;
&lt;p&gt;We&apos;ll keep you updated about the progress. And as always, you&apos;re invited to contribute to it as well. Stay tuned!&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;新しいデザインシステムの開発は、この新しいブランドやスタイルのないコンポーネントと並んで、Material Design を超えて MUI を成長させるための主要なイニシアチブの1つです。そのコードネームは Joy で、私たちはそれに関する&lt;a href=&quot;https://github.com/mui-org/material-ui/discussions/27803&quot;&gt;ハイレベルな議論&lt;/a&gt;を始めたばかりです。&lt;/p&gt;
&lt;p&gt;これは、ファンデーションパッケージ（ベース/スタイルなしコンポーネントおよび &lt;code&gt;@mui / system&lt;/code&gt;）の上に構築され、Material Design コンポーネントと同様の形状の API を提供します。テーマから生成された組み込みのCSS変数、優れたダークモード API、カスタムのルックアンドフィールなどを計画しています。&lt;/p&gt;
&lt;p&gt;マイルストーンごとに継続的にリリースする予定であるため、最も頻繁に使用されるコンポーネントを最初に開発することを目指しています。開発者が最も気に入ったデザインを選択できるように、Material Design ですでに利用可能なすべてのコンポーネントの Joy バージョンを用意する予定です。主な違いはテーマの構造です。テーマの構造では、デフォルトですばらしいデザインを提供しながら、適切な量の柔軟性を実現したいと考えています。&lt;/p&gt;
&lt;p&gt;進捗状況を随時お知らせします。そしていつものように、あなたもそれに貢献するように招待されています。乞うご期待！&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;前述の通り、新ブランドへの移行に関して Material Design の枠だけにとらわれない、新たな試みを行っていくことが示されていました。
そのなかでセカンドデザインシステムのことに触れられていましたが、Joy というコードネームのデザインシステムが開発されているようです。
全てのコンポーネントの Joy バージョンを開発予定ということで、より表現の幅が広がりそうですね！&lt;/p&gt;
&lt;h2&gt;デザインシステム&lt;/h2&gt;
&lt;h3&gt;スタイリングソリューションの再考&lt;/h3&gt;
&lt;p&gt;v5 でコンポーネントのカスタマイズ性の向上させるにあたって、まずはデザインシステムの実体であるスタイリングソリューションを再考。&lt;/p&gt;
&lt;p&gt;これまでも何度か移行を繰り返してきた中で、v4 ではスタイリングソリューションとして JSS が使われていました。
&amp;lt;OG url=&quot;https://github.com/cssinjs/jss&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;しかし、以下のような問題を抱えていたとのこと。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;The React community is settling on styled() as the most popular CSS-in-JS API. We have used popularity as a proxy for &quot;best&quot;.
You can find it in styled-components, emotion, goober, stitches, or linaria. While MUI is compatible with any styling solution (as long as the styles have more specificity, for example, Tailwind CSS), many developers still felt the need to learn something new: the makeStyles API.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Our React integration with JSS (@mui/styles) is too slow to unlock the next layer of customization DX we aim for. The static CSS generation using v4 was fast enough, even faster than emotion, however, the dynamic style generation was too slow to be used in production. We would have needed to reimplement it.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Many developers were advocating for MUI to migrate to styled-components, which would allow us to drop the custom React JSS wrapper we maintain. From our experience, maintaining a custom styling solution takes a considerable amount of time.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;React コミュニティは、最も人気のある CSS-in-JS APIとしてstyled（）を採用しています。私たちは「最高」の代用として人気を利用してきました。
あなたはそれを styled-components, emotion, goober, stitches, または linaria で見つけることができます。 MUI はどのスタイリングソリューションとも互換性がありますが（たとえば、Tailwind CSS など、スタイルの特異性が高い限り）、多くの開発者は、makeStyles API という新しいことを学ぶ必要性を感じていました。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;React と JSS（&lt;code&gt;@mui/styles&lt;/code&gt;）の統合は遅すぎて、私たちが目指すカスタマイズ DX の次のレイヤーのロックを解除できません。 v4 を使用した静的 CSS 生成は十分に高速で、emotion よりも高速でしたが、動的スタイル生成は遅すぎて本番環境で使用できませんでした。それを再実装する必要があったでしょう。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;多くの開発者は、MUI を styled-components に移行することを提唱していました。これにより、私たちが維持しているカスタム React JSS ラッパーを削除できるようになります。私たちの経験から、カスタムスタイリングソリューションの維持にはかなりの時間がかかります。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;これらを解決するために検討を重ねた結果がこちら。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;After exploring many different options, we settled on what we believe is a great tradeoff to solve the above issues:&lt;/p&gt;
&lt;p&gt;1.We have made styled() the lowest level primitive to add styles. This API is already known by many.&lt;/p&gt;
&lt;p&gt;2.We have defined a common interface with concrete implementations:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@mui/styled-engine: implemented with emotion (default).&lt;/li&gt;
&lt;li&gt;@mui/styled-engine-sc: implemented with styled-components&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;If you are using a different styling library, feel free to contribute a wrapper. For instance, there is one attempt with goober, a library obsessing on bundle size (3kB gzipped).
This allows developers to swap between different style engines. For example, styled-components users no longer need to bundle emotion and styled-component, nor do they need to configure the server-side rendering for each. How does the swap work? The same way it does from React to Preact.&lt;/p&gt;
&lt;p&gt;3.For the last couple of months, we have been sponsoring emotion with a $100/month grant. We are now increasing this amount to $1,000/month. It&apos;s in our best interest to help ensure the library keeps pushing the envelope, leading the state of the art in a competitive space.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;多くの異なるオプションを検討した後、上記の問題を解決するための大きなトレードオフであると私たちが信じていることを決定しました。&lt;/p&gt;
&lt;p&gt;1.styled()を、スタイルを追加するための最低レベルのプリミティブにしました。この API はすでに多くの人に知られています。&lt;/p&gt;
&lt;p&gt;2.具体的な実装との共通インターフェースを定義しました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@mui/styled-engine&lt;/code&gt;：emotion で実装されます（デフォルト）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@mui/styled-engine-sc&lt;/code&gt;：styled-components で実装&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;別のスタイリングライブラリを使用している場合は、ラッパーを自由に提供してください。たとえば、バンドルサイズ（3kB gzip圧縮）にこだわるライブラリである goober を使用した1つの試みがあります。
これにより、開発者は異なるスタイルのエンジン間で交換できます。たとえば、styled-components のユーザーは、emotion と styled-components をバンドルする必要がなくなり、それぞれのサーバー側レンダリングを構成する必要もなくなりました。スワップはどのように機能しますか？ React から Preact までと同じ方法です。&lt;/p&gt;
&lt;p&gt;3.過去数か月間、私たちは月額100ドルの助成金で emotion を後援してきました。現在、この金額を月額1,000ドルに増やしています。図書館が限界を押し上げ続け、競争の激しい分野で最先端をリードすることを確実にすることは、私たちの最大の利益です。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;インストール方法の所でも少し触れましたが、v5 では emotion もしくは styled-components がデザインシステムの実体として使われることになりました。
&amp;lt;OG url=&quot;https://github.com/emotion-js/emotion&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/styled-components/styled-components&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;共通インタフェースを定義したうえで、この2つの実装がされているため、どちらか好きな方を使えます。
デフォルトでは emotion が使われるようになっており、emotion を採用した決め手としては、パフォーマンス面が大きかったようです。&lt;/p&gt;
&lt;p&gt;こちらで Box コンポーネントを使ったパフォーマンス比較のコードが用意されていて、v4 と比べると5倍以上も高速化しているのが確認できます。早い👀
&amp;lt;OG url=&quot;https://codesandbox.io/s/zlh5w?file=/src/App.js&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;v4 でも &lt;code&gt;styled()&lt;/code&gt;を使ったスタイルのカスタマイズは可能でしたが、&lt;code&gt;makeStyles&lt;/code&gt;を使った書き方が一般的でした。&lt;/p&gt;
&lt;p&gt;v4：&lt;code&gt;makeStyles&lt;/code&gt;方式例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { makeStyles } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const useStyles = makeStyles({
  button: {
    backgroundColor: &apos;green&apos;
  }
});

type Props = {
  children: ReactNode;
}

const HooksButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  const classes = useStyles();

  return (
    &amp;lt;Button variant=&quot;contained&quot; className={classes.button}&amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
}

export default HooksButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v4：&lt;code&gt;styled&lt;/code&gt;方式例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { styled } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const CustomButton = styled(Button)({
  backgroundColor: &apos;red&apos;
});

type Props = {
  children: ReactNode;
}

const StyledButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return &amp;lt;CustomButton variant=&quot;contained&quot;&amp;gt;{children}&amp;lt;/CustomButton&amp;gt;;
};

export default StyledButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v5 では、&lt;code&gt;makeStyles&lt;/code&gt;は廃止され、&lt;code&gt;styled()&lt;/code&gt;でカスタマイズがメインになっていくようです。&lt;/p&gt;
&lt;p&gt;v5：&lt;code&gt;styled&lt;/code&gt;方式例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { styled } from &apos;@mui/material/styles&apos;;
import Button from &apos;@mui/material/Button&apos;;

const CustomButton = styled(Button)({
  backgroundColor: &apos;red&apos;
});

type Props = {
  children: ReactNode;
}

const StyledButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return &amp;lt;CustomButton variant=&quot;contained&quot;&amp;gt;{children}&amp;lt;/CustomButton&amp;gt;;
};

export default StyledButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、v5 でも&lt;code&gt;makeStyles&lt;/code&gt;方式を使いたい場合は、&lt;code&gt;@mui/styles&lt;/code&gt;に従来の JSS でのスタイルシステムが残されているので、そちらから使うことができるとのこと。
（基本的にはコアコンポーネントと一致させるために移行を推奨）&lt;/p&gt;
&lt;p&gt;プレーンな CSS、CSS Modules との併用は引き続き可能です。
emotion 版を使っているのであれば、emotion の css props を使うのも1つの手ですね。
（個人的な好みとして、emotion では css props を使う派でして...）&lt;/p&gt;
&lt;p&gt;ちなみに v4 時代から styled-components および emotion との親和性はあったようで、v4 の公式ドキュメントにも使い方は載っていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** @jsxImportSource @emotion/react */
import { VFC, ReactNode } from &apos;react&apos;;
import Button from &apos;@mui/material/Button&apos;;
import { css } from &apos;@emotion/react&apos;;

type Props = {
  children: ReactNode;
};

const EmotionButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return (
    &amp;lt;Button
      css={css`
        width: 100px;
      `}
      variant=&quot;contained&quot;
    &amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
};

export default EmotionButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;sx props&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/system/the-sx-prop&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;styled()&lt;/code&gt;を使うまでもない、少しのスタイルカスタマイズや1回きりのスタイル定義に使える新機能。
&lt;code&gt;sx&lt;/code&gt;という props にスタイルを定義することで、スタイルのカスタマイズができます。
その特性から「ユーティリティ」とも呼ばれているそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// my は marginTop と marginBottom のエイリアス
// margin: 8px 0px;
&amp;lt;Button sx={{ my: 1 }}&amp;gt;Test&amp;lt;/Button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この&lt;code&gt;sx props&lt;/code&gt;は、以下のコンポーネントで使用できます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MUI Core プロダクトに含まれる（&lt;code&gt;@mui/material&lt;/code&gt;, &lt;code&gt;@mui/icons-material&lt;/code&gt;, &lt;code&gt;@mui/lab&lt;/code&gt; など）コンポーネント&lt;/li&gt;
&lt;li&gt;（&lt;code&gt;@mui/material/styles&lt;/code&gt; の）&lt;code&gt;styled()&lt;/code&gt;で作成したカスタムコンポーネント&lt;/li&gt;
&lt;li&gt;babel プラグインを含む任意の要素（※2021/09/26時点で &lt;a href=&quot;https://github.com/mui-org/material-ui/issues/23220&quot;&gt;TODO&lt;/a&gt; になっていました）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;一見、&lt;code&gt;style&lt;/code&gt;によるインラインスタイルと似ていますね。
&lt;code&gt;sx props&lt;/code&gt;の場合は通常のスタイル定義が書けることに加え、特定の CSS プロパティでエイリアスが使えたり、テーマ情報とマッピングされた値が使えたり。
疑似要素やメディアクエリが使えたり、ネストができたり。
より柔軟に書くことができるようになっています。&lt;/p&gt;
&lt;p&gt;特定の CSS プロパティ（デザインシステムに実装されているシステムプロパティ）の一覧はこちら。
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/system/properties&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sx props&lt;/code&gt;の使用例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import Box from &apos;@mui/material/Box&apos;;

const BoxSx = () =&amp;gt; {
  return (
    &amp;lt;Box
      sx={{
        boxShadow: 1 // theme.shadows[1]
        color: &apos;primary.main&apos;, // theme.palette.primary.main
        m: 1, // margin: theme.spacing(1)
        p: {
          xs: 1, // [theme.breakpoints.up(&apos;xs&apos;)]: { padding: theme.spacing(1) }
          md: 2  // [theme.breakpoints.up(&apos;md&apos;)]: { padding: theme.spacing(2) }
        },
        zIndex: &apos;tooltip&apos;, // theme.zIndex.tooltip
      }}
    &amp;gt;
      test
    &amp;lt;/Box&amp;gt;
  );
}

export default BoxSx;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;疑似要素の例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Box
  sx={{
    &apos;&amp;amp;:hover&apos;: {
      boxShadow: 1
    }
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メディアクエリの例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Box
  sx={{
    &apos;@media (max-width: 600px)&apos;: {
      width: 300,
    }
  }}
/&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ネストの例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Box
  sx={{
    &apos;&amp;amp; div&apos;: {
      bgcolor: &apos;primary.main&apos;,
    }
  }}
&amp;gt;
  &amp;lt;div&amp;gt;test&amp;lt;/div&amp;gt;
&amp;lt;/Box&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、テーマ情報を受け取るコールバック式でも書くことができます。
こちらは、テーマ情報を元に加工した値を設定したい時などに使うイメージです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Box sx={{ height: (theme) =&amp;gt; theme.spacing(10) }} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;優先度&lt;/h4&gt;
&lt;p&gt;同一 CSS プロパティの指定が複数あった時の優先度は、&lt;strong&gt;低い&lt;/strong&gt;順から.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;そのコンポーネントが元々受け入れる props によるスタイル（Button コンポーネントの color props など）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sx props&lt;/code&gt;によるスタイル&lt;/li&gt;
&lt;li&gt;インラインスタイル&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;となっていました。&lt;/p&gt;
&lt;h4&gt;パフォーマンス&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/system/basics/#performance-tradeoff&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;公式に比較表がありました。
&amp;lt;ImageWrapper className=&quot;w-[90%]&quot; src=&quot;screenshots/2021/material-ui-v5/sx-props-performance.png&quot; alt=&quot;MUI - sx props のパフォーマンス比較画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;sx props&lt;/code&gt;を使用すると、やや速度が落ちるようです。
それでもほとんどの用途では十分高速であるとされていますが、あまりの多用は控えた方がいいやもしれませんね。&lt;/p&gt;
&lt;h4&gt;CSS コンポーネントユーティリティ&lt;/h4&gt;
&lt;p&gt;新機能の&lt;code&gt;sx props&lt;/code&gt;ですが、実は v4 の Box コンポーネントでも少し似たようなことは出来ていました。
こちらは&lt;code&gt;sx props&lt;/code&gt;を使わずに直接 props として渡す感じ。
違いといえば、疑似要素やメディアクエリ等は使えず、特定の CSS プロパティ（デザインシステムに実装されているシステムプロパティ）&lt;strong&gt;のみ&lt;/strong&gt;対応だったことでしょうか。&lt;/p&gt;
&lt;p&gt;v4 での Box コンポーネントの使用例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { Box, Button } from &apos;@material-ui/core&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;Box m={2}&amp;gt;
      &amp;lt;Button variant=&quot;contained&quot;&amp;gt;Test&amp;lt;/Button&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このシステムプロパティを受け入れるコンポーネントは、CSS コンポーネントユーティリティと言われるそうで。
v5 においては以下の4つのコンポーネントが該当します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Box&lt;/li&gt;
&lt;li&gt;Stack&lt;/li&gt;
&lt;li&gt;Typography&lt;/li&gt;
&lt;li&gt;Grid&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なぜこの4つだけなのかは、公式ドキュメントの API トレードオフの項に記述があります。
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/system/basics/#api-tradeoff&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これ以外のコンポーネントでシステムプロパティを使いたかったり、小さなスタイルのカスタマイズをしたい時に&lt;code&gt;sx props&lt;/code&gt;を使っていく感じになるようです。
ちなみに CSS コンポーネントユーティリティに該当する4つのコンポーネントでも&lt;code&gt;sx props&lt;/code&gt;は使用できます。
&lt;code&gt;sx props&lt;/code&gt;の方がカスタマイズ性に優れているので、直接システムプロパティを使っていくことはあまりないやもしれませんね。
（公式のサンプルコードでも&lt;code&gt;sx props&lt;/code&gt;を使っているものが多く見受けられました）&lt;/p&gt;
&lt;p&gt;直接システムプロパティを指定するのと、&lt;code&gt;sx props&lt;/code&gt;で指定するのとで、同一 CSS プロパティを指定した場合は後者の値が使われました。&lt;/p&gt;
&lt;h4&gt;開発背景&lt;/h4&gt;
&lt;p&gt;v5 におけるスタイルのカスタマイズは&lt;code&gt;styled()&lt;/code&gt;を使うのがメインになったわけですが、少しのスタイルカスタマイズでもこれを使うということは、以下のような問題がありました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;Switching context wastes time. The styled API forces you to constantly jump between the use of the styled components and where they are defined. Could we move the style descriptions right where we need them?&lt;/li&gt;
&lt;li&gt;Naming things is hard. Have you ever found yourself struggling to find a good name for a styled component? Could we remove the need to create and name yet another component?&lt;/li&gt;
&lt;li&gt;Enforcing consistency in UIs is hard. This is especially true when more than one person is building the application, as there has to be some coordination amongst members of the team regarding the choice of design tokens and how they are used, what parts of the theme structure should be used with what CSS properties, and so on.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;コンテキストを切り替えると時間が無駄になります。スタイル付き API を使用すると、styled-components の使用とそれらが定義されている場所の間を常にジャンプする必要があります。スタイルの説明を必要な場所に移動できますか？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;名前を付けるのは難しいです。styled-components の適切な名前を見つけるのに苦労したことがありますか？さらに別のコンポーネントを作成して名前を付ける必要をなくすことができますか？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;UI で一貫性を強制することは困難です。これは、複数の人がアプリケーションを構築している場合に特に当てはまります。デザイントークンの選択とその使用方法、テーマ構造のどの部分をどの CSS で使用するかについて、チームのメンバー間で調整を行う必要があるためです。プロパティなど。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;これらの問題を解決するための機能として、&lt;code&gt;sx props&lt;/code&gt;が実装されたとのことです。
（これも emotion への移行により可能となったものです）&lt;/p&gt;
&lt;p&gt;1と2に関しては、自分が個人的に styled-components 方式をあまり好きになれない理由に近いものがあり、すごいわかるーってなりました(笑)&lt;/p&gt;
&lt;h3&gt;動的 props&lt;/h3&gt;
&lt;p&gt;MUI のコンポーネントに独自の props を追加するなど拡張したい時、v4 ではラッパーコンポーネントを作るような方法で多くは対応されてきました。
しかし、以下のような問題を抱えていたとのこと。&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Each time you create a new component, it&apos;s another import option for your team. Now, you have to ensure that the right component is imported.&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Adding a new color=&quot;success&quot; prop to a Button component requires non-trivial CSS customizations. How do you ensure that all the styles (hover, focus, focus-visible) are consistent with the other built-in colors?&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;It adds a boilerplate.&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;新しいコンポーネントを作成するたびに、それはチームのもう1つのインポートオプションになります。ここで、適切なコンポーネントがインポートされていることを確認する必要があります。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;新しい color=&quot;success&quot; props を Button コンポーネントに追加するには、重要な CSS のカスタマイズが必要です。すべてのスタイル（hover、focus、focus-visible）が他の組み込みの色と一致していることをどのように確認しますか？&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ボイラープレートを追加します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;
&lt;p&gt;このため、v5 では、テーマから直接コンポーネントの組み込み動作を拡張する機能を追加。
公式ブログ記事においては、CodeSandbox のサンプルコードを添えて2パターン紹介されていました。&lt;/p&gt;
&lt;p&gt;既存のスタイルマッピングを利用して、Button コンポーネントの color props に色を追加する例
&amp;lt;OG url=&quot;https://codesandbox.io/s/stupefied-mclaren-ho4zs?file=/src/App.tsx&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;（このスタイルマッピング部分は v4 にもあり）
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/customization/palette/#adding-new-colors&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import createTheme from &quot;@mui/material/createTheme&quot;;
import ThemeProvider from &quot;@mui/material/ThemeProvider&quot;;
import Button from &quot;@mui/material/Button&quot;;

// 1. テーマを拡張
const theme = createTheme({
  palette: {
    neutral: {
      main: &quot;#d79b4a&quot;
    }
  }
});

// 2. パレットの新しい色について TypeScript に通知
declare module &quot;@mui/material/styles&quot; {
  interface Palette {
    neutral: Palette[&quot;primary&quot;];
  }
  interface PaletteOptions {
    neutral: PaletteOptions[&quot;primary&quot;];
  }
}

// 3. Button の color props を更新
declare module &quot;@mui/material/Button&quot; {
  interface ButtonPropsColorOverrides {
    neutral: true;
  }
}

export default function App() {
  // 4. 使用
  return (
    &amp;lt;ThemeProvider theme={theme}&amp;gt;
      &amp;lt;Button color=&quot;neutral&quot;&amp;gt;color=&quot;neutral&quot;&amp;lt;/Button&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テーマにカスタムバリアントを追加して、特定のコンポーネント props の組み合わせである CSS をオーバーライドする例
&amp;lt;OG url=&quot;https://codesandbox.io/s/sharp-sky-xwz3d?file=/src/App.tsx:0-746&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/customization/theme-components/#adding-new-component-variants&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import createTheme from &quot;@mui/material/createTheme&quot;;
import ThemeProvider from &quot;@mui/material/ThemeProvider&quot;;
import Button from &quot;@mui/material/Button&quot;;

// 1. テーマを拡張
const theme = createTheme({
  components: {
    MuiButton: {
      variants: [
        {
          props: { variant: &quot;dashed&quot;, color: &quot;error&quot; },
          style: {
            border: &quot;1px dashed red&quot;,
            color: &quot;red&quot;
          }
        }
      ]
    }
  }
});

// 2. Button の color props を更新
declare module &quot;@mui/material/Button&quot; {
  interface ButtonPropsVariantOverrides {
    dashed: true;
  }
}

export default function App() {
  // 3. 使用
  return (
    &amp;lt;ThemeProvider theme={theme}&amp;gt;
      &amp;lt;Button variant=&quot;dashed&quot; color=&quot;error&quot;&amp;gt;
        dashed
      &amp;lt;/Button&amp;gt;
    &amp;lt;/ThemeProvider&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;グローバルクラス&lt;/h3&gt;
&lt;p&gt;MUI の各種コンポーネントへ適用されることになるグローバル CSS のクラス名およびルール名は、公式ドキュメントの各種コンポーネントのページに一覧化されています。
それらのクラス自体のカスタマイズや、それらのクラスをベースとしたカスタマイズをしたい場合は、そこから名を特定。
特定したクラスを元に独自スタイルを定義する。ということができました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;以下は TextField コンポーネントで&lt;code&gt;variant=&quot;outlined&quot;&lt;/code&gt;を指定した時に適用される、&lt;code&gt;MuiOutlinedInput-notchedOutline&lt;/code&gt;クラス（&lt;code&gt;notchedOutline&lt;/code&gt;ルール）をカスタマイズして、枠線を赤色に変更している例です。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;v4：グローバル CSS でカスタマイズ.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* styles.css */
.MuiOutlinedInput-notchedOutline {
  border-color: red;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import &apos;./styles.css&apos;;
.
.
.
&amp;lt;TextField variant=&quot;outlined&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v4：コンポーネント個別でカスタマイズ
（&lt;code&gt;variant=&quot;outlined&quot;&lt;/code&gt;の時に&lt;code&gt;foo&lt;/code&gt;クラスを追加 + &lt;code&gt;&amp;amp; foo&lt;/code&gt;クラスでスタイル定義）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import TextField from &apos;@material-ui/core/TextField&apos;;
import { styled } from &apos;@material-ui/core/styles&apos;;

const CustomizedTextField = styled((props) =&amp;gt; (
  &amp;lt;TextField
    {...props}
    variant=&quot;outlined&quot;
    InputProps={{ classes: { notchedOutline: &apos;foo&apos; } }}
  /&amp;gt;
))({
  &apos;&amp;amp; .foo&apos;: {
    borderColor: &apos;red&apos;
  }
}) as typeof TextField;
.
.
.
&amp;lt;CustomizedTextField /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;v4 の後者では、グローバル CSS クラスと連動させた独自クラスを追加したうえで、そのクラスのスタイル定義をするようなやり方でした。&lt;/p&gt;
&lt;p&gt;v5 では、直接グローバル CSS のクラスにスタイルを追加するような感覚で扱える、新しいやり方が追加されました。
&amp;lt;OG url=&quot;https://codesandbox.io/s/zealous-dawn-0yr4g?file=/src/App.tsx&quot; /&amp;gt;
より型安全に扱うのであれば、2パターン目の方が良さそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import TextField from &apos;@mui/material/TextField&apos;;
import { outlinedInputClasses } from &apos;@mui/material/OutlinedInput&apos;;
import { styled } from &apos;@mui/material/styles&apos;;

// ※v5 では TextField の variant のデフォルト値が standard → outlined に変更された

// Option 1: global class
const CustomizedTextField1 = styled(TextField)({
  &apos;&amp;amp; .MuiOutlinedInput-notchedOutline&apos;: {
    borderColor: &apos;red&apos;,
  },
});

// Option 2: global class + const
const CustomizedTextField2 = styled(TextField)({
  [`&amp;amp; .${outlinedInputClasses.notchedOutline}`]: {
    borderColor: &apos;red&apos;,
  },
});
.
.
.
&amp;lt;CustomizedTextField1 /&amp;gt;
&amp;lt;CustomizedTextField2 /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際に適用される CSS を比較するとこんな感じ。&lt;/p&gt;
&lt;p&gt;v4 の後者のやり方で適用される CSS
&amp;lt;ImageWrapper src=&quot;screenshots/2021/material-ui-v5/global-class-v4.png&quot; alt=&quot;v4でのグローバルクラスカスタマイズで適用されるCSS（Dev Tools）画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;v5 のやり方で適用される CSS
&amp;lt;ImageWrapper src=&quot;screenshots/2021/material-ui-v5/global-class-v5.png&quot; alt=&quot;v5でのグローバルクラスカスタマイズで適用されるCSS（Dev Tools）画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;クラスを追加しているという点はどちらも同じですね。
v4 では、独自に追加したクラス（今回だと&lt;code&gt;foo&lt;/code&gt;）の他に、そのコンポーネントのルート DOM に&lt;code&gt;Components-root-XX&lt;/code&gt;クラスが別途追加されるような仕組みになっています。
v5 では、そのコンポーネントのルート DOM に、元々あるクラス（今回だと&lt;code&gt;MuiFormControl-root&lt;/code&gt;, &lt;code&gt;MuiTextField-root&lt;/code&gt;）をベースとしたクラス（今回だと&lt;code&gt;css-1mmysby-MuiFormControl-root-MuiTextField-root&lt;/code&gt;）が追加されていますね。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;スタイルなしコンポーネント&lt;/h3&gt;
&lt;p&gt;コンポーネントのロジックをフックとスタイルのないコンポーネントに分離したものを提供する、という新しい試みです。
現状、まだアルファ版で、以下のコンポーネントのみ対応しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Autocomplete&lt;/li&gt;
&lt;li&gt;Button&lt;/li&gt;
&lt;li&gt;Modal&lt;/li&gt;
&lt;li&gt;Pagination&lt;/li&gt;
&lt;li&gt;Slider&lt;/li&gt;
&lt;li&gt;Switch&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現在の開発状況に関しては、こちらの Issue で確認できます。
&amp;lt;OG url=&quot;https://github.com/mui-org/material-ui/issues/27170&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;スタイルなし Button コンポーネントの例
&amp;lt;OG url=&quot;https://codesandbox.io/s/7lc1r?file=/demo.tsx&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import clsx from &apos;clsx&apos;;
import Stack from &apos;@mui/material/Stack&apos;;
import { ButtonUnstyledProps, useButton } from &apos;@mui/core/ButtonUnstyled&apos;;
import { styled } from &apos;@mui/system&apos;;

const CustomButtonRoot = styled(&apos;button&apos;)(`
  background-color: #007fff;
  padding: 15px 20px;
  border-radius: 10px;
  color: #fff;
  font-weight: 600;
  font-family: Helvetica, Arial, sans-serif;
  font-size: 14px;
  transition: all 200ms ease;
  cursor: pointer;
  box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 0 rgba(0, 127, 255, 0);
  border: none;

  &amp;amp;:hover {
    background-color: #0059b2;
  }

  &amp;amp;.active {
    background-color: #004386;
  }

  &amp;amp;.focusVisible {
    box-shadow: 0 4px 20px 0 rgba(61, 71, 82, 0.1), 0 0 0 5px rgba(0, 127, 255, 0.5);
    outline: none;
  }

  &amp;amp;.disabled {
    opacity: 0.5;
    cursor: not-allowed;
    box-shadow: 0 0 0 0 rgba(0, 127, 255, 0);
  }
`);

const CustomButton = React.forwardRef(function CustomButton(
  props: ButtonUnstyledProps,
  ref: React.ForwardedRef&amp;lt;any&amp;gt;,
) {
  const { children } = props;
  const { active, disabled, focusVisible, getRootProps } = useButton({
    ...props,
    ref,
    component: CustomButtonRoot,
  });

  const classes = {
    active,
    disabled,
    focusVisible,
  };

  return (
    &amp;lt;CustomButtonRoot {...getRootProps()} className={clsx(classes)}&amp;gt;
      {children}
    &amp;lt;/CustomButtonRoot&amp;gt;
  );
});

export default function UseButton() {
  return (
    &amp;lt;Stack spacing={2} direction=&quot;row&quot;&amp;gt;
      &amp;lt;CustomButton onClick={() =&amp;gt; console.log(&apos;click!&apos;)}&amp;gt;Button&amp;lt;/CustomButton&amp;gt;
      &amp;lt;CustomButton disabled&amp;gt;Disabled&amp;lt;/CustomButton&amp;gt;
    &amp;lt;/Stack&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;開発背景&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;A key reason why developers pick MUI is to be able to build UIs faster. When they depend on us, they make a tradeoff. They estimate that applying new styles on top of the Material Design components will be faster than creating components from scratch or picking another library. They estimate that it will be performant enough, and they won&apos;t miss too much freedom.&lt;/p&gt;
&lt;p&gt;This tradeoff works really well when having a small, constrained engineering team or a large team building internal (/secondary) tools. But what about the medium/large size engineering team that works on ambitious projects? Shouldn&apos;t they have a better option for not including Material Design and maximizing freedom than building the components from scratch?&lt;/p&gt;
&lt;p&gt;We have started working on this exact problem, isolating the logic of the Material Design components into hooks and unstyled components. While the effort is still in alpha, you can already find the first building blocks in a new unstyled package.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;開発者が MUI を選択する主な理由は、UI をより高速に構築できるようにするためです。彼らが私たちに依存するとき、彼らはトレードオフをします。彼らは、Material Design コンポーネントの上に新しいスタイルを適用する方が、コンポーネントを最初から作成したり、別のライブラリを選択したりするよりも高速であると見積もっています。彼らはそれが十分に実行可能であると見積もっており、彼らはあまり多くの自由を逃すことはありません。&lt;/p&gt;
&lt;p&gt;このトレードオフは、小規模で制約のあるエンジニアリングチームや、内部（/セカンダリ）ツールを構築する大規模なチームがある場合に非常にうまく機能します。しかし、野心的なプロジェクトに取り組む中規模/大規模のエンジニアリングチームはどうでしょうか。コンポーネントを最初から作成するよりも、Material Design を含めず、自由度を最大化するためのより良いオプションがあるべきではないでしょうか。&lt;/p&gt;
&lt;p&gt;Material Design コンポーネントのロジックをフックとスタイルのないコンポーネントに分離して、この正確な問題に取り組み始めました。取り組みはまだ alpha版ですが、スタイルが設定されていない新しいパッケージで最初のビルディングブロックを見つけることができます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;スタイルなしコンポーネントに関しては、元々 2017/02 から議論されていたようで。
&amp;lt;OG url=&quot;https://github.com/mui-org/material-ui/issues/6218&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;よりカスタマイズ性を高めるための一環として、取り組みが開始されたようです。
デザインは自分たちで独自のこだわりがあるが、機能は MUI のものを活用したいという時に良さそうですね。&lt;/p&gt;
&lt;h2&gt;コンポーネント&lt;/h2&gt;
&lt;p&gt;大きな変更や基本的な部分のみ記載します。
（細かな変更点は公式の移行ガイドを参照ください）&lt;/p&gt;
&lt;p&gt;基本的な使い方に関して、シンプルな例であればインポート元が変わるくらいです。&lt;/p&gt;
&lt;p&gt;v4&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;Button variant=&quot;contained&quot; color=&quot;primary&quot;&amp;gt;
      Test
    &amp;lt;/Button&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;p&gt;v5&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import Button from &apos;@mui/material/Button&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;Button variant=&quot;contained&quot; color=&quot;primary&quot;&amp;gt;
      Test
    &amp;lt;/Button&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;機能追加されたコンポーネント&lt;/h3&gt;
&lt;h4&gt;Grid&lt;/h4&gt;
&lt;p&gt;グリッドレイアウトを表現するためのコンポーネントにいくつかの機能が追加されました。&lt;/p&gt;
&lt;p&gt;行と列の間隔をサポート.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Grid container rowSpacing={1} columnSpacing={2} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すべての props でレスポンシブ値をサポート.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Grid container spacing={{ xs: 2, md: 3 }} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;12とは異なる列数のサポート.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Grid container columns={16} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Material Icon&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/material-icons&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;5つの異なるテーマで、600の新しいアイコンをリリース。
パッケージとしては&lt;code&gt;@mui/icons-material&lt;/code&gt;。&lt;/p&gt;
&lt;p&gt;v4&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filled：1117&lt;/li&gt;
&lt;li&gt;Oulined：1116&lt;/li&gt;
&lt;li&gt;Rounded：1109&lt;/li&gt;
&lt;li&gt;Two tone：1107&lt;/li&gt;
&lt;li&gt;Sharp：1106&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;p&gt;v5&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Filled：1780&lt;/li&gt;
&lt;li&gt;Oulined：1780&lt;/li&gt;
&lt;li&gt;Rounded：1775&lt;/li&gt;
&lt;li&gt;Two tone：1771&lt;/li&gt;
&lt;li&gt;Sharp：1769&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;所属が変わったコンポーネント&lt;/h3&gt;
&lt;h4&gt;lab → material&lt;/h4&gt;
&lt;p&gt;v4 で lab だったものが、material に取り込まれました。&lt;/p&gt;
&lt;p&gt;Inputs
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/autocomplete&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/rating&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/toggle-button&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Feedback
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/skeleton&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Navigation
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/speed-dial&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/pagination&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;pickers → lab&lt;/h4&gt;
&lt;p&gt;※2023/11/25 リンクをリポジトリ URL に差し替え
&amp;lt;OG url=&quot;https://github.com/mui/material-ui-pickers&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;v4 の時点では、全くの別パッケージとして存在していた Date Pickers 的なコンポーネント群が lab に取り込まれました。
そのため、元々のリポジトリはすでにアーカイブとなっています。&lt;/p&gt;
&lt;h3&gt;新しく追加されたコンポーネント&lt;/h3&gt;
&lt;p&gt;()は所属パッケージ。
スクショ及び GIF の撮影元は、公式ドキュメントより。&lt;/p&gt;
&lt;h4&gt;Stack(material)&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/stack&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;種別は Layout。
1次元レイアウトを処理するためのコンポーネント。
Figma の auto layout に似ている。&lt;/p&gt;
&lt;h4&gt;Loading Buttons(lab)&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/buttons/#loading-buttons&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;種別は Input。
通常の Button コンポーネントを拡張したもので、読み込み中・二度押し防止などの表現もできるようにしたもの。&lt;/p&gt;
&lt;p&gt;LoadingButton の例（CircularProgress との組み合わせ）
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;gifs/2021/material-ui-v5/loading-button.gif&quot; alt=&quot;LoadingButtonコンポーネントのプレビューGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;Trap Focus(lab)&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/trap-focus/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;子孫コンポーネントの focus を管理するコンポーネント。
モーダルダイアログなどのオーバーレイを実装するときに活用できる。&lt;/p&gt;
&lt;h4&gt;Masonry(lab)&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5-0-6.mui.com/components/masonry&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;横幅は一定であるが高さが可変というコンテンツブロックリストである、組構造を表現できるコンポーネント。
グリッドではスペースが無駄になってしまうときに活用できる。&lt;/p&gt;
&lt;p&gt;Masonry、MasonryItem の例
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/material-ui-v5/masonry.png&quot; alt=&quot;Masonry、MasonryItemコンポーネントのプレビュー画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;MUI X&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://mui.com/x&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これまでに紹介してきたものは、MUI Core プロダクトを中心とした話でした。
今回の新ブランド移行に伴い、MUI X というプロダクトが新たに発表されました。&lt;/p&gt;
&lt;p&gt;MUI Core に対して MUI X は、より複雑なユースケース向けの高度なコンポーネント群を提供するもので、一部有料の機能も持っています。&lt;/p&gt;
&lt;p&gt;以前より、データグリッド機能を提供する&lt;code&gt;@material-ui/data-grid&lt;/code&gt;（フル機能版：&lt;code&gt;@material-ui/x-grid&lt;/code&gt;）というパッケージが v4-alpha 版で公開されていました。それが MUI X として正式にリリースされた形になるようです。
&amp;lt;OG url=&quot;https://github.com/mui-org/material-ui-x&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;パッケージ名&lt;/h3&gt;
&lt;p&gt;正式リリースに伴い、パッケージ名も変わりました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;@material-ui/data-grid → @mui/x-data-grid
@material-ui/x-grid → @mui/x-data-grid-pro
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;コンポーネント&lt;/h3&gt;
&lt;p&gt;公式サイトで確認できたものは、主に以下の8つ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;DataGrid&lt;/li&gt;
&lt;li&gt;Date Picker&lt;/li&gt;
&lt;li&gt;Tree View&lt;/li&gt;
&lt;li&gt;Sparkline（Coming soon!）&lt;/li&gt;
&lt;li&gt;Charts（Coming soon!）&lt;/li&gt;
&lt;li&gt;Upload（Coming soon!）&lt;/li&gt;
&lt;li&gt;Scheduler（Coming soon!）&lt;/li&gt;
&lt;li&gt;Gauge（Coming soon!）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;現状、Date Picker と Tree View は &lt;code&gt;@mui/lab&lt;/code&gt;の方に属してますが、その後は &lt;code&gt;@mui/material&lt;/code&gt;に取り込まれるのでなく、こちらに取り込まれるということなのかな？と思いました。
また、有料版の機能として、Date Range Picker を提供しているそうです。&lt;/p&gt;
&lt;p&gt;~開発ロードマップが公開されているので、そちらから開発状況が確認できます。~
※2025/09/14...リンク切れを起こしていたので削除。&lt;/p&gt;
&lt;h2&gt;その他の新仕様と v4 からの移行&lt;/h2&gt;
&lt;p&gt;公式ドキュメントにガイドがあるので、そちらを参照ください。
&amp;lt;OG url=&quot;https://mui.com/guides/migration-v4&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;やや長いページではありますが、コンポーネントごとの仕様変更など、細かい変更点もまとめられています。
移行の際は、公式ブログ記事とともに、ぜひ最後まで一読することをおすすめします。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;以前に Material UI v4 記事を書いていたこともあり、今回 v5 の記事も書いてみました。
公式ブログ記事の内容をそのまま書いてもしょうがないので、なるべく自分でも試しながら v4 と比較できるような内容にしたつもりです。&lt;/p&gt;
&lt;p&gt;代表的な部分のみ紹介のつもりが、ずいぶん長くなってしまいました...🙄
ここまで読んでいただいた方、ありがとうございます！&lt;/p&gt;
&lt;p&gt;MUI の使用を検討している方にとって、何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mui.com/&quot;&gt;MUI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com/&quot;&gt;Material UI v4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mui.com/blog/material-ui-is-now-mui/&quot;&gt;Material-UI is now MUI!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mui.com/blog/mui-core-v5/&quot;&gt;Introducing MUI Core v5.0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>React × LaravelでSocialiteを使ったGitHubソーシャルログインを実装してみた</title><link>https://changeofpace.site/posts/2021-05-19-react-query-socialite</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-05-19-react-query-socialite</guid><description>Socialiteを使ってソーシャルログイン画面を実装した例</description><pubDate>Wed, 19 May 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以前、React × Laravel で簡易なログイン機能を作った記事を書いたのですが、あれからソーシャルログインを実装する機会がありました。
正直微妙な部分もありつつ、せっかく実装したので記事化して供養します🙏&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;以前の記事に引き続き、React 側の状態管理には React Query を使用。&lt;/p&gt;
&lt;p&gt;ソーシャルログインを実装するにあたって、Laravel 側では OAuth ライブラリのラッパーである Socialite を使用しました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/laravel/socialite&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;デフォルトでは以下のサービスに対応しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Facebook&lt;/li&gt;
&lt;li&gt;Twitter&lt;/li&gt;
&lt;li&gt;Google&lt;/li&gt;
&lt;li&gt;LinkedIn&lt;/li&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;GitLab&lt;/li&gt;
&lt;li&gt;Bitbucket&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;他のサービスでのソーシャルログインを実装したいという場合は、コミュニティの方々が作られている Socialite を拡張したものを使うとよいです。
ものすごく幅広いサービスに対応しています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://socialiteproviders.com&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;今回作ってみたもの&lt;/h2&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;gifs/2021/react-query-socialite/github-social-login.gif&quot; alt=&quot;GitHubでのソーシャルログイン処理の流れのGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ソーシャルログイン機能。&lt;/p&gt;
&lt;p&gt;認証プロバイダーとなるサービスは、今回 GitHub を使用しました。
どのサービスにするとしても、基本的な実装方法は一緒かと思われますが、部分的に異なる可能性があることにご留意ください。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;また、API の認証方式については Cookie を使ったステートフルなものになります。
（※2021/11/25追記...Laravel の api ルートで使用するミドルウェアグループを変更、web のミドルウェアグループにある Cookie やセッションの機能を api ルートでも使用する、ということをしています）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;実装にあたって、主に以下の記事を参考にさせていただきました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/tetsu-upstr/items/d1cccfac362872ed140c&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/KeisukeKudo/items/18dd8a342a4bdd43913c&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/hareku/items/ea09602bf40bf0a42040&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;p&gt;React（Laravel Mix）× Laravel による SPA × API 構成です。&lt;/p&gt;
&lt;p&gt;今回使用した各種バージョンは以下のとおりです。&lt;/p&gt;
&lt;p&gt;基本部分.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js：14.2.0&lt;/li&gt;
&lt;li&gt;TypeScript：4.1.3&lt;/li&gt;
&lt;li&gt;React：16.14.0&lt;/li&gt;
&lt;li&gt;PHP：7.4.14&lt;/li&gt;
&lt;li&gt;Laravel：6.20.9&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ライブラリ - React 側.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Material UI
&lt;ul&gt;
&lt;li&gt;core：4.11.3&lt;/li&gt;
&lt;li&gt;icons：4.11.2&lt;/li&gt;
&lt;li&gt;lab：4.0.0-alpha.57&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;axios：0.21.1&lt;/li&gt;
&lt;li&gt;react-query：3.12.1&lt;/li&gt;
&lt;li&gt;camelcase-keys：6.2.2&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ライブラリ - Laravel 側.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;socialite：5.2.3&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;おおまかな処理の流れ&lt;/h2&gt;
&lt;h3&gt;主な登場人物&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ユーザ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;画面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ログイン画面&lt;/li&gt;
&lt;li&gt;ソーシャルログイン処理中画面&lt;/li&gt;
&lt;li&gt;アプリホーム画面&lt;/li&gt;
&lt;li&gt;（GitHub の）OAuth 認可画面&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;API&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;OAuth 認可画面 URL 取得 API&lt;/li&gt;
&lt;li&gt;ソーシャルログイン API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;モデル&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザ&lt;/li&gt;
&lt;li&gt;認証プロバイダー（ユーザを親に持つ）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;処理のフロー&lt;/h3&gt;
&lt;p&gt;ざっくり書くとこんな感じです。
※GitHub OAuth 認可画面へリダイレクト時に、GitHub 未ログインであれば先にログインが必要になります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;ログイン画面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザがログイン画面で、「Login with GitHub」ボタンを押下
→ OAuth 認可画面 URL 取得 API で URL 取得
&lt;ul&gt;
&lt;li&gt;失敗時：エラー表示&lt;/li&gt;
&lt;li&gt;成功時：取得した URL にリダイレクト&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;GitHub OAuth 認可画面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ユーザが認可承認もしくはキャンセル
→ ソーシャルログイン処理中画面をコールバック&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ソーシャルログイン処理中画面&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;認可失敗（キャンセル含む）時：エラー表示&lt;/li&gt;
&lt;li&gt;認可成功時：ソーシャルログイン API で以下のソーシャルログイン処理後に、アプリホーム画面へ
&lt;ul&gt;
&lt;li&gt;GitHub アカウントと紐づくユーザがあれば、そのユーザでログイン&lt;/li&gt;
&lt;li&gt;紐づくユーザはないが、同一メールアドレスユーザがあれば紐づけて、そのユーザでログイン&lt;/li&gt;
&lt;li&gt;ユーザがなければ、GitHub アカウントと紐づけたうえでユーザ新規登録して、そのユーザでログイン&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ディレクトリ構成&lt;/h2&gt;
&lt;p&gt;今回も Laravel 側は特に変わったことをしてないので、React 側だけ記載します。
（全部載せると多いので、当記事の趣旨にあまり関係ないものは省略しています）&lt;/p&gt;
&lt;p&gt;Laravel プロジェクトの resources/ts 配下.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;├ components
│   ├ atoms
│   │   ├ GeneralAlert.tsx
│   │   └ GitHubLoginButton.tsx
│   ├ molecules
│   │   └ SocialLoginAlert.tsx
│   ├ pages
│   │   ├ Login.tsx
│   │   └ SocialLoginProgress.tsx
├ constants
│   └ statusCode.ts
├ containers
│   ├ pages
│   │   ├ Login.tsx
│   │   └ SocialLoginProgress.tsx
├ hooks
│   ├ auth
│   │   ├ index.ts
│   │   ├ useOAuthUrl.ts
│   │   └ useSocialLogin.ts
├ models
│   └ OAuth.ts
└ app.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回も同様にリポジトリにタグをつけていますので、GitHub で見たいという方はこちらをどうぞ。
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ooui-memo/releases/tag/post%2Freact-query-socialite&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※2021/09/13追記 関数コンポーネントの型定義に FC ばっかり使ってますが、FC から children がなくなるまでは VFC を使った方がいいです...。&lt;/p&gt;
&lt;h2&gt;前準備&lt;/h2&gt;
&lt;p&gt;認証プロバイダーとなるサービスで、クライアント ID を発行する必要があります。&lt;/p&gt;
&lt;h3&gt;クライアント ID とクライアントシークレットの発行&lt;/h3&gt;
&lt;p&gt;GitHub の場合は以下のようになります。
Authorization callback URL は今回の場合、ソーシャルログイン処理中画面の URL ですね。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ログイン後、ユーザアイコンから「Settings」を選択&lt;/li&gt;
&lt;li&gt;「Developer settings」を選択&lt;/li&gt;
&lt;li&gt;「OAuth Apps」を選択し、「New OAuth App」でアプリを作成&lt;/li&gt;
&lt;li&gt;必要な情報を入力し「Register application」
&lt;ul&gt;
&lt;li&gt;Application name（アプリ名・必須）&lt;/li&gt;
&lt;li&gt;Homepage URL（アプリのホームページ URL・必須）&lt;/li&gt;
&lt;li&gt;Application description（アプリの説明・任意）&lt;/li&gt;
&lt;li&gt;Authorization callback URL（OAuth 処理後にコールバックする URL・必須）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これで OAuth 用のアプリが作成され、クライアント ID が発行されます。
クライアントシークレットも発行する必要があるので、アプリ設定画面から「Generate a new client secret」で発行。
ともに控えておきます。&lt;/p&gt;
&lt;h4&gt;アプリの設定がどのように使われるか&lt;/h4&gt;
&lt;p&gt;画面的な部分で自分が確認できたところとしては、こんな感じです。
Application logo については、一度 OAuth アプリ作成後に設定できます。&lt;/p&gt;
&lt;p&gt;OAuth 前のログイン画面
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/react-query-socialite/login-before-oauth.png&quot; alt=&quot;OAuth前のログイン画面画像 - Application logo、Application name&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;OAuth 認可画面
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/react-query-socialite/oauth.png&quot; alt=&quot;OAuth認可画面 - Application logo、Application name、Authorization callback URL（ドメイン部分まで）&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Authorized OAuth Apps 画面
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2021/react-query-socialite/oauth-app.png&quot; alt=&quot;Authorized OAuth Apps画面 - Application logo、Application name、Homepage URL&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;環境変数に追加&lt;/h3&gt;
&lt;p&gt;先ほど控えた値を設定。
&lt;code&gt;GITHUB_CALLBACK_URL&lt;/code&gt;については、Authorization callback URL で設定したものと同じで OK です。&lt;/p&gt;
&lt;p&gt;.env の例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;GITHUB_CLIENT_ID=XXXXXXXXXXXXXXXXXXX
GITHUB_CLIENT_SECRET=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
GITHUB_CALLBACK_URL=http://localhost:80/login/github/callback
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&apos;github&apos; =&amp;gt; [
    &apos;client_id&apos; =&amp;gt; env(&apos;GITHUB_CLIENT_ID&apos;),
    &apos;client_secret&apos; =&amp;gt; env(&apos;GITHUB_CLIENT_SECRET&apos;),
    &apos;redirect&apos; =&amp;gt; env(&apos;GITHUB_CALLBACK_URL&apos;),
],
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;API（Laravel）側&lt;/h2&gt;
&lt;h3&gt;Web ルートの確認&lt;/h3&gt;
&lt;p&gt;Laravel Mix を使った SPA × API 構成だと、Web ルートは全受けにして、SPA 側で画面切り分けというやり方になることが多いでしょうか。&lt;/p&gt;
&lt;p&gt;ここでの注意点として、&lt;code&gt;api&lt;/code&gt;プレフィックスのルートは除外しておかないと、API ルートで where での制約をかけたときに予期しない動作を起こす原因になります。&lt;/p&gt;
&lt;p&gt;というのも、where で制約をかけた場合に制約外のパスパラメータでアクセスすると、ルート不一致として通常は404が返ります。
...が、ここで全受けにしてると、このルートに来てしまい200が返るという現象が起きてしまいます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// apiプレフィックスは除外
Route::get(&apos;/{any?}&apos;, fn() =&amp;gt; view(&apos;index&apos;))-&amp;gt;where(&apos;any&apos;, &apos;(?!api).+&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;users テーブルの改修&lt;/h3&gt;
&lt;p&gt;ソーシャルログインにおいてはパスワードを使用しないため、null 許容にしておきます。
（メールアドレスについては、認証プロバイダーとなるサービスによっては取得できないものもあるようなので、その場合はメールアドレスも null 許容にしておきましょう。）&lt;/p&gt;
&lt;p&gt;マイグレーションファイル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

use App\Enums\AuthType;
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class ChangeSocialLoginUsersTable extends Migration
{
    public function __construct()
    {
        // dbalがenum型のカラム変更に対応していないので、エラー回避策としてstringにマッピングする
        DB::getDoctrineSchemaManager()-&amp;gt;getDatabasePlatform()-&amp;gt;registerDoctrineTypeMapping(&apos;enum&apos;, &apos;string&apos;);
    }

    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::table(&apos;users&apos;, function (Blueprint $table) {
            // ソーシャルログインではパスワードを使わないのでnull許容にする
            $table-&amp;gt;string(&apos;password&apos;)-&amp;gt;nullable()-&amp;gt;comment(&apos;パスワード&apos;)-&amp;gt;change();

            $table-&amp;gt;enum(&apos;auth_type&apos;, AuthType::getValues())-&amp;gt;after(&apos;name&apos;)-&amp;gt;comment(&apos;認証タイプ【&apos; . implode(&apos;, &apos;, AuthType::getValues()) . &apos;】&apos;);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::table(&apos;users&apos;, function (Blueprint $table) {
            $table-&amp;gt;string(&apos;password&apos;)-&amp;gt;nullable(false)-&amp;gt;comment(&apos;パスワード&apos;)-&amp;gt;change();

            $table-&amp;gt;dropColumn(&apos;auth_type&apos;);
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;認証タイプについて&lt;/h4&gt;
&lt;p&gt;認証タイプについては、以下の3パターンを enum で持たせています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ソーシャルログイン&lt;/li&gt;
&lt;li&gt;メール・パスワードログイン&lt;/li&gt;
&lt;li&gt;どちらも&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個人的に処理分けに使用するつもりなので追加してますが、別になくてもいいので詳細は省略します。&lt;/p&gt;
&lt;p&gt;ちなみに enum 型を扱う注意点として、テーブル情報変更を担う dbal が enum 型カラムの変更に対応していません（追加はできます）&lt;/p&gt;
&lt;p&gt;enum 型カラム自体の内容変更でなくても、enum 型カラムを持つテーブルの内容変更というだけでエラーになってしまいます。
今回の場合だとロールバックで down メソッド実行時とか。
そのため、エラー回避策として一旦 string にマッピングするという対応を入れています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://yshrfmru.hatenablog.com/entry/2019/01/06/191235&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;認証プロバイダーテーブル作成&lt;/h3&gt;
&lt;p&gt;ユーザと紐づく認証プロバイダーとなるサービスを管理するテーブルを作成します。
認証プロバイダーとなるサービスが1つのみの場合は、users テーブルにカラム増やして対応でもいいですが、今回は別でテーブルを作りました。&lt;/p&gt;
&lt;p&gt;もしユーザが削除された時は、一緒に削除したいので cascade を設定しています。
自分の場合、users テーブルの主キーは UUID で&lt;code&gt;user_id&lt;/code&gt;という名前にしているので以下のようになっていますが、適宜置き換えてださい。&lt;/p&gt;
&lt;p&gt;マイグレーションファイル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;

class CreateIdentityProvidersTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create(&apos;identity_providers&apos;, function (Blueprint $table) {
            $table-&amp;gt;uuid(&apos;user_id&apos;)-&amp;gt;comment(&apos;ユーザID&apos;);
            $table-&amp;gt;foreign(&apos;user_id&apos;)-&amp;gt;references(&apos;user_id&apos;)-&amp;gt;on(&apos;users&apos;)-&amp;gt;onDelete(&apos;cascade&apos;); // 外部キー制約
            $table-&amp;gt;string(&apos;provider_name&apos;)-&amp;gt;comment(&apos;プロバイダー名&apos;);
            $table-&amp;gt;string(&apos;provider_user_id&apos;)-&amp;gt;comment(&apos;プロバイダーユーザID&apos;);
            $table-&amp;gt;primary([&apos;provider_name&apos;, &apos;provider_user_id&apos;]); // 複合キー
            $table-&amp;gt;unique([&apos;user_id&apos;, &apos;provider_name&apos;]); // 複合ユニーク
            $table-&amp;gt;dateTime(&apos;created_at&apos;)-&amp;gt;nullable()-&amp;gt;comment(&apos;作成日時&apos;);
            $table-&amp;gt;dateTime(&apos;updated_at&apos;)-&amp;gt;nullable()-&amp;gt;comment(&apos;更新日時&apos;);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::dropIfExists(&apos;identity_providers&apos;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;モデルファイルの用意&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;identity_providers&lt;/code&gt;テーブルを作ったので、モデルファイルも作っておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App;

use Illuminate\Database\Eloquent\Model;

class IdentityProvider extends Model
{
    protected $primaryKey = [&apos;provider_name&apos;, &apos;provider_user_id&apos;];
    public $incrementing = false;

    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [&apos;provider_name&apos;, &apos;provider_user_id&apos;];

    /**
     * リレーション - User
     * @return \Illuminate\Database\Eloquent\Relations\BelongsTo
     */
    public function user()
    {
        return $this-&amp;gt;belongsTo(&apos;App\User&apos;, &apos;user_id&apos;, &apos;user_id&apos;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;User モデル側にもリレーションを定義しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * リレーション - IdentityProviders
     * @return \Illuminate\Database\Eloquent\Relations\HasMany
     */
    public function identityProviders()
    {
        return $this-&amp;gt;hasMany(&apos;App\IdentityProvider&apos;, &apos;user_id&apos;);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;OAuth 認可画面 URL 取得 API&lt;/h3&gt;
&lt;p&gt;ログイン画面でソーシャルログインボタンを押下した時に呼ばれる API。
OAuth 処理用のコントローラを用意して作成。&lt;/p&gt;
&lt;p&gt;Socialite に認証プロバイダー名を渡すことで、その認証プロバイダー用のドライバーを作成し、機能が使えるようになります。
&lt;code&gt;redirect&lt;/code&gt;メソッドで、OAuth 認可画面 URL を持った&lt;code&gt;RedirectResponse&lt;/code&gt;を生成してくれるので、そこからその URL だけ取得して返すようにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * （各認証プロバイダーの）OAuth認可画面URL取得API
     * @param string $provider 認証プロバイダーとなるサービス名
     * @return \Illuminate\Http\JsonResponse
     */
    public function getProviderOAuthURL(string $provider)
    {
        $redirectUrl = Socialite::driver($provider)-&amp;gt;redirect()-&amp;gt;getTargetUrl();
        return response()-&amp;gt;json([
            &apos;redirect_url&apos; =&amp;gt; $redirectUrl,
        ]);
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加。
where で制約をかけて、特定の認証プロバイダー名しか受け付けないようにします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::get(&apos;/login/{provider}&apos;, &apos;Auth\OAuthController@getProviderOAuthURL&apos;)
            -&amp;gt;where(&apos;provider&apos;, &apos;github&apos;)-&amp;gt;name(&apos;oauth.request&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※2021/11/27追記
ちなみにステートレス API として作る場合は、&lt;code&gt;stateless&lt;/code&gt;メソッドを使えばいいようです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$redirectUrl = Socialite::driver($provider)-&amp;gt;stateless()-&amp;gt;redirect()-&amp;gt;getTargetUrl();
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;OAuth 認可画面へのリダイレクトについて&lt;/h4&gt;
&lt;p&gt;SPA と API に分かれておらず、Laravel でビューも担当している場合は&lt;code&gt;redirect&lt;/code&gt;メソッドで302を返すだけで対応できます。&lt;/p&gt;
&lt;p&gt;API の場合は、302を返すと、API リクエストがそのリダイレクト先に対して行われるということになります。
今回の場合だと GitHub の OAuth 認可画面 URL ということで、&lt;strong&gt;別ドメイン&lt;/strong&gt;にリクエストする形です。
CORS ポリシーに引っ掛かってはじかれるだけなので、URL だけ取得して返し、SPA 側でリダイレクトする形にしています。&lt;/p&gt;
&lt;h4&gt;スコープについて&lt;/h4&gt;
&lt;p&gt;スコープは、その認証プロバイダーに対して要求するアクセス権限です。
Socialite における GitHub の場合のデフォルトスコープは&lt;code&gt;user:email&lt;/code&gt;となっており、特にスコープに関する記述がなければこのスコープが使われます。&lt;/p&gt;
&lt;p&gt;GitHub のスコープ一覧は以下のとおりです。
&amp;lt;OG url=&quot;https://docs.github.com/ja/developers/apps/scopes-for-oauth-apps&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;スコープを追加したい時は&lt;code&gt;scopes&lt;/code&gt;で、スコープを上書きしたい時は&lt;code&gt;setScopes&lt;/code&gt;で対応できます。
必要に応じた権限を設定しましょう。&lt;/p&gt;
&lt;h4&gt;Socialite が生成するリダイレクト URL について&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;redirect&lt;/code&gt;メソッドの中で生成されるリダイレクト URL は以下のような形式になっています。
ドライバーとなるクラスで保持している URL に、必要なクエリパラメータを足している感じです。
クライアント ID も使われてますね。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/login/oauth/authorize?client_id=XXXXXXXXXXXXXXXXXXXX&amp;amp;redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Fgithub%2Fcallback&amp;amp;scope=user%3Aemail&amp;amp;response_type=code&amp;amp;state=pp7ndAoGIvcTqxkTSxzBklISPWd1PQ7Vyrw4gUsa
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;state&lt;/code&gt;に関しては、推測不能なランダムな文字列となっていて、クロスサイトリクエストフォージェリ対策で使用されるものです。
後に検証するためセッションにもセットされます。
この&lt;code&gt;state&lt;/code&gt;は OAuth 認可処理後、GitHub からコールバックされるときに、一緒にクエリパラメータとして返却されます。
（※2021/11/25追記...ステートフルの場合のみ&lt;code&gt;state&lt;/code&gt;のセットおよび検証されるようです）&lt;/p&gt;
&lt;h3&gt;ソーシャルログイン API&lt;/h3&gt;
&lt;p&gt;GitHub の OAuth 認可処理後にコールバックされたソーシャルログイン処理中画面より呼ばれる API。&lt;/p&gt;
&lt;h4&gt;ソーシャルログイン処理を行うメソッド&lt;/h4&gt;
&lt;p&gt;ソーシャルログイン処理は少し複雑になるので、モデルファイルにメソッドを用意しておきます。
元々ある&lt;code&gt;find&lt;/code&gt;メソッドなどと同じ感じで使いたかったので、static にしました。&lt;/p&gt;
&lt;p&gt;処理の分岐についてはコメントに書いてある通りです。
複数テーブルの処理になるのでトランザクションブロックを使っています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;     /**
     * ソーシャルログイン処理
     * @param $providerUser プロバイダーユーザ情報
     * @param $provider プロバイダー名
     * @return App\User
     */
    public static function socialFindOrCreate($providerUser, $provider)
    {
        $account = IdentityProvider::whereProviderName($provider)
                    -&amp;gt;whereProviderUserId($providerUser-&amp;gt;getId())
                    -&amp;gt;first();

        // すでにアカウントがある場合は、そのユーザを返す
        if ($account) {
            return $account-&amp;gt;user;
        }

        $existingUser = User::whereEmail($providerUser-&amp;gt;getEmail())-&amp;gt;first();

        if ($existingUser) {
            // メールアドレスはユニークの関係上、同一メールアドレスユーザがいる場合は、そのユーザと紐づけて認証プロバイダー情報登録
            $user = DB::transaction(function () use ($existingUser, $providerUser, $provider) {
                $existingUser-&amp;gt;update([&apos;auth_type&apos; =&amp;gt; AuthType::BOTH]);
                $existingUser-&amp;gt;IdentityProviders()-&amp;gt;create([
                    &apos;provider_user_id&apos;   =&amp;gt; $providerUser-&amp;gt;getId(),
                    &apos;provider_name&apos; =&amp;gt; $provider,
                ]);

                return $existingUser;
            });
        } else {
            // アカウントがない場合は、ユーザ情報 + 認証プロバイダー情報を登録
            $user = DB::transaction(function () use ($providerUser, $provider) {
                // nameがない時もあるので、その時はnicknameを使う
                $providerUserName = $providerUser-&amp;gt;getName() ? $providerUser-&amp;gt;getName() : $providerUser-&amp;gt;getNickname();
                $user = User::create([
                    &apos;name&apos;  =&amp;gt; $providerUserName,
                    &apos;auth_type&apos; =&amp;gt; AuthType::SOCIAL,
                    &apos;email&apos; =&amp;gt; $providerUser-&amp;gt;getEmail(),
                ]);
                $user-&amp;gt;IdentityProviders()-&amp;gt;create([
                    &apos;provider_user_id&apos;   =&amp;gt; $providerUser-&amp;gt;getId(),
                    &apos;provider_name&apos; =&amp;gt; $provider,
                ]);

                return $user;
            });
        }

        return $user;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;API&lt;/h4&gt;
&lt;p&gt;OAuth 処理用のコントローラに API を追加。&lt;/p&gt;
&lt;p&gt;Socialite 経由で GitHub ユーザ情報取得は、内部的に GitHub API を利用しているので、エラー対応として try～catch ブロックを使用しています。
メッセージを取り出して500で再度例外を投げてるだけなので、あまり意味ない感じになってますが、本来はログ出力などした方がいいと思われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    /**
     * ソーシャルログインAPI（各認証プロバイダーからのコールバック後）
     * @param string $provider 認証プロバイダーとなるサービス名
     * @return App\User
     */
    public function handleProviderCallback(string $provider)
    {
        try {
            $providerUser = Socialite::driver($provider)-&amp;gt;user();
        } catch (\Exception $e) {
            // TODO ログ出力など
            abort(500, $e-&amp;gt;getMessage());
        }
        $authUser = User::socialFindOrCreate($providerUser, $provider);
        Auth::login($authUser, true);

        // ログインのみ or 既存ユーザに紐づけ + ログイン：200
        // 紐づけしたうえでユーザ新規登録 + ログイン：201
        return $authUser;
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加。
この API も同様に where で制約をかけておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::post(&apos;/login/{provider}/callback&apos;, &apos;Auth\OAuthController@handleProviderCallback&apos;)
            -&amp;gt;where(&apos;provider&apos;, &apos;github&apos;)-&amp;gt;name(&apos;oauth.callback&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;既存アカウントがあるかどうかの照合のやり方について&lt;/h4&gt;
&lt;p&gt;最初に Socialite 実装の記事を調べていたところ、メールアドレスで照合していたものが多い印象を受けました。
認証プロバイダーとなるサービスより取得したユーザ情報から、メールアドレスを取得し、そのアドレスのユーザアカウントがあるか確認する方法。&lt;/p&gt;
&lt;p&gt;ただこれだと一度ソーシャルログインでユーザアカウント作成後に、もし認証プロバイダーとなるサービス側でメールアドレスを変更されてしまうと、そのユーザアカウントで&lt;strong&gt;ログインできなくなります&lt;/strong&gt;。&lt;/p&gt;
&lt;p&gt;それってどうなんだろうか？と思っていたら、冒頭にも上げたこの記事のコメント欄で、このことに関するやり取りが行われていました。
&amp;lt;OG url=&quot;https://qiita.com/tetsu-upstr/items/d1cccfac362872ed140c&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;認証プロバイダーとなるサービスを示す識別子と、サービスから取得したユーザ識別子といった基本的に&lt;strong&gt;不変&lt;/strong&gt;なものを使うとよいとのことだったので、まずはこの2つを使って照合するようにしています。
そのうえで、なければ同一メールアドレスのユーザがいないか確認。
いれば紐づける、いなければユーザ情報も含め新規登録する感じです。&lt;/p&gt;
&lt;h4&gt;レスポンスについて&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;response&lt;/code&gt;メソッドを使って200で統一してもよかったのですが、ユーザ登録の部分が201にあたると思ったので、そのままにしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ログインのみ&lt;/li&gt;
&lt;li&gt;既存ユーザ紐づけ + ログイン&lt;/li&gt;
&lt;li&gt;ユーザ登録 + ログイン&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;の3パターンともに&lt;code&gt;App\User&lt;/code&gt;を返す形になります。
ですが、ユーザ登録 + ログインの場合は&lt;code&gt;wasRecentlyCreated&lt;/code&gt;が true になっているので、Laravel が201レスポンスにしてくれるわけです。&lt;/p&gt;
&lt;p&gt;既存ユーザ紐づけ + ログインパターンの、認証プロバイダー情報登録部分も201にあたるのでは？という疑問もあったりしますが、そこは特に対応せず200のままです。&lt;/p&gt;
&lt;h4&gt;Socialite が内部的にやっていることについて&lt;/h4&gt;
&lt;p&gt;コード的にはこれだけで、認証プロバイダーとなるサービスから情報が取得できますが、内部的には GitHub API が使われています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;$providerUser = Socialite::driver($provider)-&amp;gt;user();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、この内部処理の中では、&lt;code&gt;code&lt;/code&gt;と&lt;code&gt;state&lt;/code&gt;のパラメータが必要になります。
&lt;code&gt;code&lt;/code&gt;は10分の期限つきの一時コード。
&lt;code&gt;state&lt;/code&gt;は OAuth 認可画面 URL 取得 API のところで書いた通り、クロスサイトリクエストフォージェリ対策の文字列。&lt;/p&gt;
&lt;p&gt;これらは OAuth 認可処理後の GitHub からのコールバック時にクエリパラメータで渡されてくるので、それをそのままこの API に渡すようにすれば OK です。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;user&lt;/code&gt;メソッドの実装はここですね。
&amp;lt;OG url=&quot;https://github.com/laravel/socialite/blob/v5.2.3/src/Two/AbstractProvider.php#L226&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;state&lt;/code&gt;が（OAuth 認可画面 URL 取得 API の処理の中で）セッションに保持していたものと一致するかの確認が行われています。&lt;/p&gt;
&lt;p&gt;GitHub とのやり取りの部分は、まず&lt;code&gt;code&lt;/code&gt;を使ってアクセストークンを取得。
そのアクセストークンを使って、GitHub ユーザ情報を取得...といった感じです。&lt;/p&gt;
&lt;h2&gt;SPA（React）側&lt;/h2&gt;
&lt;h3&gt;アプリ初期化 + ルーティング&lt;/h3&gt;
&lt;p&gt;コードの記載は省略。&lt;/p&gt;
&lt;p&gt;React Query の初期化と最低限以下のルーティングが定義されていれば OK。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ログイン画面&lt;/li&gt;
&lt;li&gt;ソーシャルログイン処理中画面&lt;/li&gt;
&lt;li&gt;アプリホーム画面（ログイン後に遷移する画面）&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;定数&lt;/h3&gt;
&lt;p&gt;Laravel から返されるステータスコードを定数で定義。
（当記事の趣旨では使わないもの含む）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// リソースが見つからないエラー
export const NOT_FOUND = 404;

// CSRFトークン不一致などのエラー
export const UNKNOWN_STATUS = 419;

// バリデーションエラー
export const UNPROCESSABLE_ENTITY = 422;

// サーバエラー
export const INTERNAL_SERVER_ERROR = 500;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ソーシャルログイン（GitHub）ボタン&lt;/h3&gt;
&lt;p&gt;GitHub でソーシャルログイン用のボタンコンポーネント。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Button from &apos;@material-ui/core/Button&apos;;
import GitHubIcon from &apos;@material-ui/icons/GitHub&apos;;
import { Provider } from &apos;../../models/OAuth&apos;;

type Props = {
  handleSocialLoginRequest: (provider: Provider) =&amp;gt; void;
};

const GitHubLoginButton: FC&amp;lt;Props&amp;gt; = ({ handleSocialLoginRequest }) =&amp;gt; (
  &amp;lt;Button
    variant=&quot;contained&quot;
    startIcon={&amp;lt;GitHubIcon /&amp;gt;}
    fullWidth
    style={{ color: &apos;#fff&apos;, backgroundColor: &apos;#24292e&apos;, textTransform: &apos;none&apos; }}
    onClick={() =&amp;gt; {
      handleSocialLoginRequest(&apos;github&apos;);
    }}
  &amp;gt;
    Login with GitHub
  &amp;lt;/Button&amp;gt;
);

export default GitHubLoginButton;

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ソーシャルログインアラート表示&lt;/h3&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;ログイン画面において、ソーシャルログイン失敗（OAuth 認可画面 URL 取得失敗）時に表示するアラートのコンポーネント。&lt;/p&gt;
&lt;p&gt;通常のログインの方でも作っていたので、ソーシャルログイン版も一応作りました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import GeneralAlert from &apos;../atoms/GeneralAlert&apos;;
import {
  UNKNOWN_STATUS,
  INTERNAL_SERVER_ERROR,
} from &apos;../../constants/statusCode&apos;;

type Props = {
  statusCode: number;
};

const SocialLoginAlert: FC&amp;lt;Props&amp;gt; = ({ statusCode }) =&amp;gt; (
  &amp;lt;&amp;gt;
    {(statusCode === UNKNOWN_STATUS ||
      statusCode === INTERNAL_SERVER_ERROR) &amp;amp;&amp;amp; (
      &amp;lt;GeneralAlert
        type=&quot;error&quot;
        title=&quot;サーバエラー&quot;
        content={`予期しないエラーが発生し、ソーシャルログインに失敗しました。\n恐れ入りますが時間をおいて再度お試しください。`}
      /&amp;gt;
    )}
  &amp;lt;/&amp;gt;
);

export default SocialLoginAlert;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここで使っている GeneralAlert コンポーネントは、アラートコンポーネントのラッパーで、こんな感じです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Alert from &apos;@material-ui/lab/Alert&apos;;
import AlertTitle from &apos;@material-ui/lab/AlertTitle&apos;;

type Props = {
  type: &apos;error&apos; | &apos;info&apos; | &apos;success&apos; | &apos;warning&apos;;
  title: string;
  content: string;
  onClose?: VoidFunction;
};

const GeneralAlert: FC&amp;lt;Props&amp;gt; = ({ type, title, content, onClose }) =&amp;gt; (
  &amp;lt;Alert severity={type} onClose={onClose} style={{ whiteSpace: &apos;pre-wrap&apos; }}&amp;gt;
    &amp;lt;AlertTitle&amp;gt;{title}&amp;lt;/AlertTitle&amp;gt;
    {content}
  &amp;lt;/Alert&amp;gt;
);

export default GeneralAlert;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみにこういうやつです。
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2021/react-query-socialite/social-login-error.png&quot; alt=&quot;ログイン画面 - ソーシャルログイン失敗時アラート表示&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;ログイン画面&lt;/h3&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;通常のログインに関するコードもあるので少し長いですが、こんな感じです。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useOAuthUrl&lt;/code&gt;フックは&lt;code&gt;useMutation&lt;/code&gt;をラップしたカスタムフックです（後述）
OAuth 認可 URL 取得処理を行う関数を実行するトリガー関数を受け取り、ソーシャルログインボタン押下時の関数の中で実行しています。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ソーシャルログインにおいてもフレンドリーフォワーディングをやりたかったのですが、一旦はやっていません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC, useState, useCallback } from &apos;react&apos;;
import { useHistory, useLocation } from &apos;react-router-dom&apos;;
import Login from &apos;../../components/pages/Login&apos;;
import { useLogin, useOAuthUrl } from &apos;../../hooks/auth&apos;;
import { Provider } from &apos;../../models/OAuth&apos;;

const EnhancedLogin: FC = () =&amp;gt; {
  const history = useHistory();
  const location = useLocation();
  const { from } = (location.state as { from: string }) || {
    from: { pathname: &apos;/&apos; },
  };

  const { error, isLoading: loginIsLoading, mutate: login } = useLogin();
  const statusCode = error?.response?.status;
  const {
    error: socialLoginError,
    isLoading: socialLoginIsLoading,
    mutate: redirectOAuth,
  } = useOAuthUrl();
  const socialLoginStatusCode = socialLoginError?.response?.status;
  const isLoading = loginIsLoading || socialLoginIsLoading;

  const [email, setEmail] = useState(&apos;&apos;);
  const [password, serPassword] = useState(&apos;&apos;);

  const handleChangeEmail = useCallback(
    (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
      setEmail(ev.target.value);
    },
    []
  );

  const handleChangePassword = useCallback(
    (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
      serPassword(ev.target.value);
    },
    []
  );

  const handleLogin = useCallback(
    (ev: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
      ev.preventDefault();
      if (!email || !password) {
        return;
      }
      login(
        { email, password },
        {
          onSuccess: () =&amp;gt; {
            history.replace(from);
          },
        }
      );
    },
    [email, password, history, from, login]
  );

  const handleSocialLoginRequest = useCallback(
    (provider: Provider) =&amp;gt; {
      redirectOAuth(provider);
    },
    [redirectOAuth]
  );

  return (
    &amp;lt;Login
      email={email}
      password={password}
      handleChangeEmail={handleChangeEmail}
      handleChangePassword={handleChangePassword}
      statusCode={statusCode}
      socialLoginStatusCode={socialLoginStatusCode}
      isLoading={isLoading}
      handleLogin={handleLogin}
      handleSocialLoginRequest={handleSocialLoginRequest}
    /&amp;gt;
  );
};

export default EnhancedLogin;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;これも通常のログインに関するコードもあるので少し長いですが、こんな感じです。&lt;/p&gt;
&lt;p&gt;ログイン、ソーシャルログインいずれか実行中に再度ボタンを押されないようにするため、&lt;code&gt;isLoading&lt;/code&gt;を使って、実行中は&lt;code&gt;Backdrop&lt;/code&gt;を表示するようにしています。
（暗転してスピナークルクル表示の部分）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Backdrop from &apos;@material-ui/core/Backdrop&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import Button from &apos;@material-ui/core/Button&apos;;
import CircularProgress from &apos;@material-ui/core/CircularProgress&apos;;
import Container from &apos;@material-ui/core/Container&apos;;
import Card from &apos;@material-ui/core/Card&apos;;
import CardHeader from &apos;@material-ui/core/CardHeader&apos;;
import CardContent from &apos;@material-ui/core/CardContent&apos;;
import TextField from &apos;@material-ui/core/TextField&apos;;
import { useTheme, makeStyles } from &apos;@material-ui/core/styles&apos;;
import Header from &apos;../../containers/organisms/Header&apos;;
import LoginAlert from &apos;../molecules/LoginAlert&apos;;
import LegalLink from &apos;../molecules/LegalLink&apos;;
import SocialLoginAlert from &apos;../molecules/SocialLoginAlert&apos;;
import Footer from &apos;../organisms/Footer&apos;;
import GitHubLoginButton from &apos;../atoms/GitHubLoginButton&apos;;
import { Provider } from &apos;../../models/OAuth&apos;;

const useStyles = makeStyles(() =&amp;gt; ({
  decorationLine: {
    borderImage: &apos;linear-gradient(0.25turn, transparent, #888, transparent)&apos;,
    borderImageSlice: 1,
  },
}));

type Props = {
  email: string;
  password: string;
  handleChangeEmail: (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; void;
  handleChangePassword: (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; void;
  statusCode?: number;
  socialLoginStatusCode?: number;
  isLoading: boolean;
  handleLogin: (ev: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; void;
  handleSocialLoginRequest: (provider: Provider) =&amp;gt; void;
};

const Login: FC&amp;lt;Props&amp;gt; = ({
  email,
  password,
  handleChangeEmail,
  handleChangePassword,
  statusCode,
  socialLoginStatusCode,
  isLoading,
  handleLogin,
  handleSocialLoginRequest,
}) =&amp;gt; {
  const theme = useTheme();
  const classes = useStyles();
  return (
    &amp;lt;Box display=&quot;flex&quot; flexDirection=&quot;column&quot; minHeight=&quot;100vh&quot;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;main style={{ flex: 1 }}&amp;gt;
        &amp;lt;Container maxWidth=&quot;xs&quot;&amp;gt;
          &amp;lt;Card style={{ margin: `${theme.spacing(6)}px 0` }}&amp;gt;
            &amp;lt;CardHeader title=&quot;login&quot; style={{ textAlign: &apos;center&apos; }} /&amp;gt;
            &amp;lt;CardContent&amp;gt;
              &amp;lt;Box p={2} borderBottom={1} className={classes.decorationLine}&amp;gt;
                {socialLoginStatusCode &amp;amp;&amp;amp; (
                  &amp;lt;SocialLoginAlert statusCode={socialLoginStatusCode} /&amp;gt;
                )}
                &amp;lt;Box mt={2}&amp;gt;
                  &amp;lt;GitHubLoginButton
                    handleSocialLoginRequest={handleSocialLoginRequest}
                  /&amp;gt;
                &amp;lt;/Box&amp;gt;
              &amp;lt;/Box&amp;gt;
              &amp;lt;form onSubmit={handleLogin}&amp;gt;
                &amp;lt;Box
                  p={2}
                  display=&quot;flex&quot;
                  flexDirection=&quot;column&quot;
                  alignItems=&quot;center&quot;
                &amp;gt;
                  {statusCode &amp;amp;&amp;amp; &amp;lt;LoginAlert statusCode={statusCode} /&amp;gt;}
                  &amp;lt;TextField
                    label=&quot;メールアドレス&quot;
                    variant=&quot;outlined&quot;
                    fullWidth
                    value={email}
                    margin=&quot;normal&quot;
                    required
                    autoComplete=&quot;email&quot;
                    autoFocus
                    onChange={handleChangeEmail}
                  /&amp;gt;
                  &amp;lt;TextField
                    type=&quot;password&quot;
                    label=&quot;パスワード&quot;
                    variant=&quot;outlined&quot;
                    fullWidth
                    value={password}
                    margin=&quot;normal&quot;
                    required
                    autoComplete=&quot;current-password&quot;
                    onChange={handleChangePassword}
                  /&amp;gt;
                  &amp;lt;Box my={2}&amp;gt;
                    &amp;lt;LegalLink /&amp;gt;
                  &amp;lt;/Box&amp;gt;
                  &amp;lt;Box my={2}&amp;gt;
                    &amp;lt;Button type=&quot;submit&quot; color=&quot;primary&quot; variant=&quot;contained&quot;&amp;gt;
                      ログイン
                    &amp;lt;/Button&amp;gt;
                  &amp;lt;/Box&amp;gt;
                &amp;lt;/Box&amp;gt;
              &amp;lt;/form&amp;gt;
            &amp;lt;/CardContent&amp;gt;
          &amp;lt;/Card&amp;gt;
        &amp;lt;/Container&amp;gt;
      &amp;lt;/main&amp;gt;
      &amp;lt;Footer /&amp;gt;
      &amp;lt;Backdrop style={{ zIndex: theme.zIndex.drawer + 1 }} open={isLoading}&amp;gt;
        &amp;lt;CircularProgress color=&quot;inherit&quot; /&amp;gt;
      &amp;lt;/Backdrop&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
};

export default Login;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ソーシャルログイン処理中画面&lt;/h3&gt;
&lt;p&gt;認証プロバイダーとなるサービスの OAuth 認可画面での処理後にコールバックされる画面です。
OAuth 認可をキャンセルした場合などもコールバックは実行され、この画面にきます。&lt;/p&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useSocialLogin&lt;/code&gt;は&lt;code&gt;useMutation&lt;/code&gt;をラップしたカスタムフック（後述）
ソーシャルログイン処理を行う関数を実行するトリガー関数を受け取り、レンダリングとともに実行しています。
成功時はアプリホーム画面へリダイレクト。&lt;/p&gt;
&lt;p&gt;なお、GitHub からコールバックされた時のクエリパラメータがソーシャルログイン API で必要になるので、取り出してトリガー関数へ渡すようにしています。&lt;/p&gt;
&lt;p&gt;また、OAuth 認可キャンセルなどのエラー発生時は、クエリパラメータに&lt;code&gt;error&lt;/code&gt;がセットされて返ってくるので分岐をかけています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC, useState, useEffect, useMemo } from &apos;react&apos;;
import { useParams, useHistory, useLocation } from &apos;react-router-dom&apos;;
import queryString from &apos;query-string&apos;;
import SocialLoginProgress from &apos;../../components/pages/SocialLoginProgress&apos;;
import { useSocialLogin } from &apos;../../hooks/auth&apos;;
import { Provider, OAuthParams } from &apos;../../models/OAuth&apos;;

const EnhancedSocialLoginProgress: FC = () =&amp;gt; {
  const { provider } = useParams&amp;lt;{ provider: Provider }&amp;gt;();
  const history = useHistory();
  const location = useLocation();
  const socialResponse = useMemo(
    () =&amp;gt; queryString.parse(location.search) ?? {},
    [location.search]
  );
  const [oAuthError, setOAuthError] = useState&amp;lt;boolean&amp;gt;(false);
  const { error, mutate: socialLogin } = useSocialLogin();
  const statusCode = error?.response?.status;

  useEffect(() =&amp;gt; {
    if (Object.prototype.hasOwnProperty.call(socialResponse, &apos;error&apos;)) {
      setOAuthError(true);
      return;
    }
    socialLogin(
      { provider, authParams: socialResponse as OAuthParams },
      {
        onSuccess: () =&amp;gt; {
          history.replace(&apos;/&apos;);
        },
      }
    );
  }, [history, provider, socialResponse, socialLogin]);

  return (
    &amp;lt;SocialLoginProgress oAuthError={oAuthError} statusCode={statusCode} /&amp;gt;
  );
};

export default EnhancedSocialLoginProgress;

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;OAuth 認可処理でエラー発生時、自アプリ側のソーシャルログイン処理でエラー発生時とで、それぞれアラート表示をするようにしています。
エラーがなければ、処理中ということでスピナーを表示。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import { Link } from &apos;react-router-dom&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import Container from &apos;@material-ui/core/Container&apos;;
import Card from &apos;@material-ui/core/Card&apos;;
import CardHeader from &apos;@material-ui/core/CardHeader&apos;;
import CardContent from &apos;@material-ui/core/CardContent&apos;;
import CircularProgress from &apos;@material-ui/core/CircularProgress&apos;;
import Typography from &apos;@material-ui/core/Typography&apos;;
import { useTheme } from &apos;@material-ui/core/styles&apos;;
import Header from &apos;../../containers/organisms/Header&apos;;
import GeneralAlert from &apos;../atoms/GeneralAlert&apos;;

type Props = {
  oAuthError: boolean;
  statusCode?: number;
};

const Content: FC&amp;lt;Props&amp;gt; = ({ oAuthError, statusCode }) =&amp;gt; {
  if (oAuthError) {
    return (
      &amp;lt;&amp;gt;
        &amp;lt;GeneralAlert
          type=&quot;error&quot;
          title=&quot;認可エラー&quot;
          content={`ソーシャルサービス側の認可処理でエラーが発生しました。\n恐れ入りますが時間をおいて再度お試しください。`}
        /&amp;gt;
        &amp;lt;Box py={2} textAlign=&quot;center&quot;&amp;gt;
          &amp;lt;Typography variant=&quot;caption&quot;&amp;gt;
            &amp;lt;Link to=&quot;/login&quot;&amp;gt;ログイン画面&amp;lt;/Link&amp;gt;
            に戻る
          &amp;lt;/Typography&amp;gt;
        &amp;lt;/Box&amp;gt;
      &amp;lt;/&amp;gt;
    );
  }

  if (statusCode) {
    return (
      &amp;lt;&amp;gt;
        &amp;lt;GeneralAlert
          type=&quot;error&quot;
          title=&quot;サーバエラー&quot;
          content={`予期しないエラーが発生し、ソーシャルログインに失敗しました。\n恐れ入りますが時間をおいて再度お試しください。`}
        /&amp;gt;
        &amp;lt;Box py={2} textAlign=&quot;center&quot;&amp;gt;
          &amp;lt;Typography variant=&quot;caption&quot;&amp;gt;
            &amp;lt;Link to=&quot;/login&quot;&amp;gt;ログイン画面&amp;lt;/Link&amp;gt;
            に戻る
          &amp;lt;/Typography&amp;gt;
        &amp;lt;/Box&amp;gt;
      &amp;lt;/&amp;gt;
    );
  }

  return (
    &amp;lt;Box textAlign=&quot;center&quot;&amp;gt;
      &amp;lt;CircularProgress color=&quot;inherit&quot; /&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
};

const SocialLoginProgress: FC&amp;lt;Props&amp;gt; = ({ oAuthError, statusCode }) =&amp;gt; {
  const theme = useTheme();
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;main&amp;gt;
        &amp;lt;Container maxWidth=&quot;xs&quot;&amp;gt;
          &amp;lt;Card style={{ margin: `${theme.spacing(6)}px 0` }}&amp;gt;
            &amp;lt;CardHeader
              title={
                oAuthError || statusCode
                  ? &apos;ソーシャルログイン処理失敗&apos;
                  : &apos;ソーシャルログイン処理中...&apos;
              }
              style={{ textAlign: &apos;center&apos; }}
            /&amp;gt;
            &amp;lt;CardContent&amp;gt;
              &amp;lt;Content oAuthError={oAuthError} statusCode={statusCode} /&amp;gt;
            &amp;lt;/CardContent&amp;gt;
          &amp;lt;/Card&amp;gt;
        &amp;lt;/Container&amp;gt;
      &amp;lt;/main&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default SocialLoginProgress;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;モデル&lt;/h3&gt;
&lt;h4&gt;User&lt;/h4&gt;
&lt;p&gt;Laravel 側の User モデルに合わせて定義。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type User = {
  name: string;
  authType: &apos;SOCIAL&apos; | &apos;MAIL&apos; | &apos;BOTH&apos;;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;OAuth&lt;/h4&gt;
&lt;p&gt;ソーシャルログイン機能に関係する型定義をまとめて定義してあります。&lt;/p&gt;
&lt;p&gt;OAuthParams については、サービスごとにパラメータが違う可能性を考えて一応わけてます。
（Socialite のコードを追った感じだと同じっぽい？）&lt;/p&gt;
&lt;p&gt;OAuthRedirect については、OAuth 認可画面 URL 取得 API のレスポンスの型です。
当記事では記載していませんが、axios の interceptors でまとめて、キーのキャメルケース変換をかけているのでこうなっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type Provider = &apos;github&apos;;

export type GitHubOAuthParams = {
  code: string;
  state: string;
};

export type OAuthParams = GitHubOAuthParams;

export type OAuthRedirect = {
  redirectUrl: string;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ソーシャルログインに関するカスタムフック&lt;/h3&gt;
&lt;p&gt;※作成したカスタムフックは、それぞれ index.ts で名前付きで再エクスポートしています。&lt;/p&gt;
&lt;h4&gt;useOAuthURl&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useMutation&lt;/code&gt;をラップした、OAuth 認可画面 URL 取得処理を行うためのカスタムフック。
成功時は、返却された URL にリダイレクト。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { UseMutationResult, useMutation } from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;
import { Provider, OAuthRedirect } from &apos;../../models/OAuth&apos;;

const getOAuthUrl = async (provider: Provider): Promise&amp;lt;OAuthRedirect&amp;gt; =&amp;gt; {
  const { data } = await axios.get(`/api/login/${provider}`);
  return data;
};

const useOAuthUrl = (): UseMutationResult&amp;lt;
  OAuthRedirect,
  AxiosError,
  Provider,
  undefined
&amp;gt; =&amp;gt;
  useMutation(getOAuthUrl, {
    onSuccess: (data) =&amp;gt; {
      window.location.href = data.redirectUrl;
    },
  });

export default useOAuthUrl;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useSocialLogin&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useMutation&lt;/code&gt;をラップした、ソーシャルログイン処理を行うためのカスタムフック。
成功時は、返却されたログインユーザ情報を user キーにセット。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useQueryClient, UseMutationResult, useMutation } from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;
import { Provider, OAuthParams } from &apos;../../models/OAuth&apos;;
import { User } from &apos;../../models/User&apos;;

const socialLogin = async (
  provider: Provider,
  authParams: OAuthParams
): Promise&amp;lt;User&amp;gt; =&amp;gt; {
  const { data } = await axios.post(
    `/api/login/${provider}/callback`,
    authParams
  );
  return data;
};

const useSocialLogin = (): UseMutationResult&amp;lt;
  User,
  AxiosError,
  { provider: Provider; authParams: OAuthParams },
  undefined
&amp;gt; =&amp;gt; {
  const queryClient = useQueryClient();

  return useMutation(
    ({ provider, authParams }) =&amp;gt; socialLogin(provider, authParams),
    {
      onSuccess: (data) =&amp;gt; {
        queryClient.setQueryData(&apos;user&apos;, data);
      },
    }
  );
};

export default useSocialLogin;

&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;おまけ - 認可した OAuth アプリの解除について&lt;/h2&gt;
&lt;p&gt;自アプリ側でアカウント削除機能があったとして、自アプリ側のユーザに関するデータは削除できますが、自アプリとの OAuth 認可解除はどうするんだろうか？という疑問が浮かびました。&lt;/p&gt;
&lt;p&gt;調べた感じだとユーザ自身で解除してもらう感じになるのかなと。&lt;/p&gt;
&lt;p&gt;GitHub の場合だと、以下の URL で、このクライアント ID を持つ OAuth アプリの画面にリンクさせることができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;https://github.com/settings/connections/applications/:client_id
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;（GitHub の 「settings」からだと「Application」「Authorized OAuth Apps」からいけます。）&lt;/p&gt;
&lt;p&gt;今回の場合は特に触れてないですが、このリンクがあると親切になりそうですね。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;こんな感じでソーシャルログインを実装してみました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;Socialite の記事は、ビューも Laravel でやっているものがほとんどで、SPA + API 構成でやるための流れがなかなかつかめなくて苦戦しました🙄
この記事にまとめるのも時間かかってしまいました...。
とりあえず一旦は形にできてよかったです。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;当記事には記載していませんが、参考記事をベースに API 側のテストは一応書いてるので、気になる方はリポジトリからどうぞ。&lt;/p&gt;
&lt;p&gt;この実装が何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.github.com/ja/developers/apps/building-oauth-apps&quot;&gt;GitHub Docs - OAuth Appを構築する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/laravel/socialite&quot;&gt;GitHub - laravel/socialite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://readouble.com/laravel/6.x/ja/socialite.html&quot;&gt;Laravel 6.x - Socialite&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/query/v3&quot;&gt;React Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com&quot;&gt;Material UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/tetsu-upstr/items/d1cccfac362872ed140c&quot;&gt;Laravelログイン機能に複数のSNS認証を追加する（socialite）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/KeisukeKudo/items/18dd8a342a4bdd43913c&quot;&gt;Laravel+Socialiteで簡単ソーシャルログイン実装! (テスト付き)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/hareku/items/ea09602bf40bf0a42040&quot;&gt;Nuxt.jsとLaravelを使ってTwitterログイン機能を実装する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://yshrfmru.hatenablog.com/entry/2019/01/06/191235&quot;&gt;Doctrine\DBAL\DBALException : Unknown database type enum requested.&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>React QueryとIntersection Observer APIで無限スクロールを作ってみた</title><link>https://changeofpace.site/posts/2021-04-13-react-query-intersection-observer</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-04-13-react-query-intersection-observer</guid><description>React QueryとIntersection Observer APIを使って、無限スクロールを実装した例</description><pubDate>Tue, 13 Apr 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回、React Query の記事を書いたのですが、今回はその派生記事です。
React Query と Intersection Observer API を使った無限スクロールを実装したので記事にしてみました。&lt;/p&gt;
&lt;p&gt;※2023/07/09 各種リンクの URL を tanstack.com をベースに更新しました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;はじめに&lt;/h2&gt;
&lt;p&gt;React Query の概要については、前回の記事にも書いたので省略します。&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;/posts/2021-03-21-laravel-react-query-auth&quot;&gt;React × LaravelでReact Queryの練習がてら、ログイン機能を作ってみた&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;データ取得にあたって、通常は&lt;code&gt;useQuery&lt;/code&gt;を使いますが、無限スクロール実装においては&lt;code&gt;useInfiniteQuery&lt;/code&gt;を使います。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://tanstack.com/query/v3/docs/react/reference/useInfiniteQuery&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;そして、次ページデータ読み込みタイミング検知として、Intersection Observer（交差監視） API というものを使います。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;ターゲットとなる要素が、祖先要素もしくは文書の最上位のビューポートと交差する変更を非同期的に監視する方法を提供します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;これを使用することで、特定の要素が指定の割合分、画面へ表示された時に特定の処理をする、ということができます。&lt;/p&gt;
&lt;h3&gt;使用例&lt;/h3&gt;
&lt;p&gt;公式の例 では、Next.js の中で使われていました　※2023/07/09時点ではコードが変わっていました。
&amp;lt;OG url=&quot;https://codesandbox.io/p/sandbox/github/tanstack/query/tree/v3/examples/load-more-infinite-scroll&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;

export default function useIntersectionObserver({
  root,
  target,
  onIntersect,
  threshold = 1.0,
  rootMargin = &apos;0px&apos;,
  enabled = true,
}) {
  React.useEffect(() =&amp;gt; {
    if (!enabled) {
      return
    }

    const observer = new IntersectionObserver(
      entries =&amp;gt;
        entries.forEach(entry =&amp;gt; entry.isIntersecting &amp;amp;&amp;amp; onIntersect()),
      {
        root: root &amp;amp;&amp;amp; root.current,
        rootMargin,
        threshold,
      }
    )

    const el = target &amp;amp;&amp;amp; target.current

    if (!el) {
      return
    }

    observer.observe(el)

    return () =&amp;gt; {
      observer.unobserve(el)
    }
  }, [target.current, enabled])
}

&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;
import Link from &apos;next/link&apos;
import axios from &apos;axios&apos;

import { useInfiniteQuery, QueryClient, QueryClientProvider } from &apos;react-query&apos;
import { ReactQueryDevtools } from &apos;react-query/devtools&apos;

//

import useIntersectionObserver from &apos;../hooks/useIntersectionObserver&apos;

const queryClient = new QueryClient()

export default function App() {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;Example /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  )
}

function Example() {
  const {
    status,
    data,
    error,
    isFetching,
    isFetchingNextPage,
    isFetchingPreviousPage,
    fetchNextPage,
    fetchPreviousPage,
    hasNextPage,
    hasPreviousPage,
  } = useInfiniteQuery(
    &apos;projects&apos;,
    async ({ pageParam = 0 }) =&amp;gt; {
      const res = await axios.get(&apos;/api/projects?cursor=&apos; + pageParam)
      return res.data
    },
    {
      getPreviousPageParam: firstPage =&amp;gt; firstPage.previousId ?? false,
      getNextPageParam: lastPage =&amp;gt; lastPage.nextId ?? false,
    }
  )

  const loadMoreButtonRef = React.useRef()

  useIntersectionObserver({
    target: loadMoreButtonRef,
    onIntersect: fetchNextPage,
    enabled: hasNextPage,
  })

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;Infinite Loading&amp;lt;/h1&amp;gt;
      {status === &apos;loading&apos; ? (
        &amp;lt;p&amp;gt;Loading...&amp;lt;/p&amp;gt;
      ) : status === &apos;error&apos; ? (
        &amp;lt;span&amp;gt;Error: {error.message}&amp;lt;/span&amp;gt;
      ) : (
        &amp;lt;&amp;gt;
          &amp;lt;div&amp;gt;
            &amp;lt;button
              onClick={() =&amp;gt; fetchPreviousPage()}
              disabled={!hasPreviousPage || isFetchingPreviousPage}
            &amp;gt;
              {isFetchingNextPage
                ? &apos;Loading more...&apos;
                : hasNextPage
                ? &apos;Load Older&apos;
                : &apos;Nothing more to load&apos;}
            &amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
          {data.pages.map(page =&amp;gt; (
            &amp;lt;React.Fragment key={page.nextId}&amp;gt;
              {page.data.map(project =&amp;gt; (
                &amp;lt;p
                  style={{
                    border: &apos;1px solid gray&apos;,
                    borderRadius: &apos;5px&apos;,
                    padding: &apos;10rem 1rem&apos;,
                    background: `hsla(${project.id * 30}, 60%, 80%, 1)`,
                  }}
                  key={project.id}
                &amp;gt;
                  {project.name}
                &amp;lt;/p&amp;gt;
              ))}
            &amp;lt;/React.Fragment&amp;gt;
          ))}
          &amp;lt;div&amp;gt;
            &amp;lt;button
              ref={loadMoreButtonRef}
              onClick={() =&amp;gt; fetchNextPage()}
              disabled={!hasNextPage || isFetchingNextPage}
            &amp;gt;
              {isFetchingNextPage
                ? &apos;Loading more...&apos;
                : hasNextPage
                ? &apos;Load Newer&apos;
                : &apos;Nothing more to load&apos;}
            &amp;lt;/button&amp;gt;
          &amp;lt;/div&amp;gt;
          &amp;lt;div&amp;gt;
            {isFetching &amp;amp;&amp;amp; !isFetchingNextPage
              ? &apos;Background Updating...&apos;
              : null}
          &amp;lt;/div&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      &amp;lt;hr /&amp;gt;
      &amp;lt;Link href=&quot;/about&quot;&amp;gt;
        &amp;lt;a&amp;gt;Go to another page&amp;lt;/a&amp;gt;
      &amp;lt;/Link&amp;gt;
      &amp;lt;ReactQueryDevtools initialIsOpen /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useInfiniteQuery&lt;/h4&gt;
&lt;p&gt;基本的な使い方としては&lt;code&gt;useQuery&lt;/code&gt;と似ており、以下のようになっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1引数：クエリキー&lt;/li&gt;
&lt;li&gt;第2引数：データ取得の関数&lt;/li&gt;
&lt;li&gt;第3引数：オプション&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;渡せるオプションや、取得できる返り値の種類も&lt;code&gt;useQuery&lt;/code&gt;のものがベースとなっており、そこにページングに関するものが追加されている形です。&lt;/p&gt;
&lt;p&gt;オプションの&lt;code&gt;getPreviousPageParam&lt;/code&gt;と&lt;code&gt;getNextPageParam&lt;/code&gt;が特に重要なもの。
ここで返した値がそれぞれ前ページ、次ページのデータを取得する際の&lt;code&gt;pageParam&lt;/code&gt;として使われます。
値がない時は、それぞれ前ページ、次ページがないものとして認識されます。&lt;/p&gt;
&lt;p&gt;返り値の data の pages キーに1ページあたりのレスポンスデータが配列で格納されていくようになっています。
0：1ページ目
1：2ページ目
といった感じ。&lt;/p&gt;
&lt;p&gt;返り値の&lt;code&gt;fetchPreviousPage&lt;/code&gt;と&lt;code&gt;fetchNextPage&lt;/code&gt;がそれぞれ前ページ、次ページのデータを取得する関数です。
この例だと、ボタンを押した時と Intersection Observer API で交差を検知した時、実行するようになっていますね。&lt;/p&gt;
&lt;h4&gt;Intersect Observer API&lt;/h4&gt;
&lt;p&gt;Intersect Observer API をラップしたカスタムフックが作られていますね。&lt;/p&gt;
&lt;p&gt;コンストラクタに渡されたオプションについて書くと、こんな感じです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;root：交差監視の元となる要素（デフォルトではブラウザのビューポート）&lt;/li&gt;
&lt;li&gt;onIntersect：交差検知時に実行されるコールバック&lt;/li&gt;
&lt;li&gt;threshold：交差率&lt;/li&gt;
&lt;li&gt;rootMargin：root 要素のマージン&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この内容でオブザーバーを作成して、&lt;code&gt;observe&lt;/code&gt;関数で監視対象の要素を指定することで監視が動作し、監視対象が交差率を超えたときに通知。
コールバックが実行される、という仕組みになっています。&lt;/p&gt;
&lt;p&gt;上記の例で言うと、こんな感じ。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;root：指定なし（ブラウザのビューポート）&lt;/li&gt;
&lt;li&gt;onIntersect：&lt;code&gt;fetchNextPage&lt;/code&gt;（次ページ読み込み関数）&lt;/li&gt;
&lt;li&gt;threshold：1.0&lt;/li&gt;
&lt;li&gt;rootMargin：opx&lt;/li&gt;
&lt;li&gt;監視対象：loadMore ボタン&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なので、ブラウザのビューポートの中で loadMore ボタン要素が100%表示（全体が表示）されると検知され、次ページ読み込みを実行...ということになります。&lt;/p&gt;
&lt;p&gt;ちなみにターゲット要素が25%見えるたびに処理をしたいといった場合は、threshold に&lt;code&gt;[0, 0.25, 0.5, 0.75, 1]&lt;/code&gt;と配列でわたすことにより対応できたりします。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;unobserve&lt;/code&gt;関数を使うことで、そのターゲット要素の監視を解除できます。
上記の例では useEffect でクリーンアップ関数として使われますね。&lt;/p&gt;
&lt;h2&gt;今回作ってみたもの&lt;/h2&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;gifs/2021/react-query-intersection-observer/infinite-scroll.gif&quot; alt=&quot;無限スクロールするGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;左側の無限スクロールの部分です。
（わかりやすくするために1ページ当たりの件数を10件にしています）
前回と同じ勉強用の個人開発で実装したものになります。&lt;/p&gt;
&lt;p&gt;公式の実装例をベースとして、実装してみました。&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;p&gt;React（Laravel Mix）× Laravel による SPA × API 構成で実装しました。
といっても、各種フレームワークのページネーション機能仕様に合わせてさえもらえればいいので、API 側はなんでもいけると思われます。&lt;/p&gt;
&lt;p&gt;今回使用した各種バージョンは以下のとおりです。&lt;/p&gt;
&lt;p&gt;基本部分.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js：14.2.0&lt;/li&gt;
&lt;li&gt;TypeScript：4.1.3&lt;/li&gt;
&lt;li&gt;React：16.14.0&lt;/li&gt;
&lt;li&gt;PHP：7.4.14&lt;/li&gt;
&lt;li&gt;Laravel：6.20.9&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ライブラリ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Material UI
&lt;ul&gt;
&lt;li&gt;core：4.11.3&lt;/li&gt;
&lt;li&gt;lab：4.0.0-alpha.57&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;axios：0.21.1&lt;/li&gt;
&lt;li&gt;react-query：3.12.1&lt;/li&gt;
&lt;li&gt;camelcase-keys：6.2.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ディレクトリ構成&lt;/h2&gt;
&lt;p&gt;今回も Laravel 側は特に変わったことをしてないので、React 側だけ記載します。
（全部載せると多いので、当記事の趣旨にあまり関係ないものは省略しています）&lt;/p&gt;
&lt;p&gt;Laravel プロジェクトの resources/ts 配下.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;├ components
│   ├ molecules
│   │   ├ MemoListHeader.tsx
│   │   ├ MemoListItem.tsx
│   │   └ MemoListItemSkeleton.tsx
│   ├ organisms
│   │   └ MemoList.tsx
│   ├ pages
│   │   └ Memo.tsx
├ constants
│   └ statusCode.ts
├ containers
│   ├ organisms
│   │   └ MemoList.tsx
│   ├ pages
│   │   └ Memo.tsx
├ hooks
│   ├ memo
│   │   ├ index.ts
│   │   └ useGetmemoListQuery.ts
│   ├ util
│   │   ├ index.ts
│   │   └ useIntersectionObserver.ts
├ models
│   ├ Memo.ts
│   └ Memos.ts
└ app.tsx
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回もリポジトリにタグをつけていますので、GitHub で見たいという方はこちらをどうぞ。
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ooui-memo/releases/tag/post%2Freact-query-intersection-observer&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※2021/09/13追記 関数コンポーネントの型定義に FC ばっかり使ってますが、FC から children がなくなるまでは VFC を使った方がいいです...。&lt;/p&gt;
&lt;h2&gt;API（Laravel）側&lt;/h2&gt;
&lt;p&gt;※User モデルとリレーションを持つ、Memo モデルがあるものとして進めます。&lt;/p&gt;
&lt;h3&gt;メモ一覧取得 API&lt;/h3&gt;
&lt;p&gt;ログインユーザのメモ一覧取得 API を作成。&lt;/p&gt;
&lt;p&gt;更新日時の降順にしたかったので&lt;code&gt;orderBy&lt;/code&gt;で指定。
ページネーションを有効にするため、&lt;code&gt;paginate&lt;/code&gt;で取得したものを返すようにしています。&lt;/p&gt;
&lt;p&gt;この&lt;code&gt;paginate&lt;/code&gt;の引数に渡した値が1ページ当たりの件数になりますので、この辺はお好みで。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Create a new controller instance.
 *
 * @return void
 */
public function __construct()
{
    $this-&amp;gt;middleware(&apos;auth&apos;);
}

/**
 * （ログインユーザの）メモ一覧取得API
 *
 * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator
 */
public function index()
{
    $user = Auth::user();
    $memos = Memo::where(&apos;user_id&apos;, $user-&amp;gt;user_id)-&amp;gt;orderBy(Memo::UPDATED_AT, &apos;desc&apos;)-&amp;gt;paginate(30);

    return $memos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::get(&apos;/memos&apos;, &apos;MemoController@index&apos;)-&amp;gt;name(&apos;memo.index&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ページネーションを有効にした場合の挙動&lt;/h4&gt;
&lt;p&gt;Laravel においてページネーションを有効にすると、レスポンスが以下のように変化します。
data 以外にページネーションに関するものが追加されていますね。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
   &quot;total&quot;: 32,
   &quot;per_page&quot;: 30,
   &quot;current_page&quot;: 1,
   &quot;last_page&quot;: 2,
   &quot;first_page_url&quot;: &quot;http://localhost/api/memos?page=1&quot;,
   &quot;last_page_url&quot;: &quot;http://localhost/api/memos?page=2&quot;,
   &quot;next_page_url&quot;: &quot;http://localhost/api/memos?page=2&quot;,
   &quot;prev_page_url&quot;: null,
   &quot;from&quot;: 1,
   &quot;to&quot;: 30,
   &quot;path&quot;: &quot;http://localhost/api/memos&quot;,
   &quot;data&quot;:  [
     {
       &quot;memo_id&quot;: &quot;c53f4b50-2556-4285-b63c-0acca6da001f&quot;,
       &quot;title&quot;: &quot;Est sequi sapiente laudantium maxime aut.&quot;,
       &quot;content&quot;: &quot;Facere blanditiis et mollitia animi sapiente suscipit eos. Sunt earum dolorem soluta. Autem laboriosam dolor sed voluptas. Laudantium maiores enim numquam voluptas reprehenderit.&quot;,
       &quot;created_at&quot;: &quot;2021-03-29 16:33:04&quot;,
       &quot;updated_at&quot;: &quot;2021-03-29 16:33:04&quot;,
     }
    .
    .
    .
    {
       &quot;memo_id&quot;: &quot;5ada62cd-b590-4c7f-9198-da6a2996f49e&quot;,
       &quot;title&quot;: &quot;Numquam dolorem maiores quo natus quos tenetur.&quot;,
       &quot;content&quot;: &quot;Ut quibusdam amet optio amet. Rem ipsam quia sit. Impedit et at enim error et. Error consequatur dolore velit illo debitis inventore.&quot;,
       &quot;created_at&quot;: &quot;2021-03-29 16:33:04&quot;,
       &quot;updated_at&quot;: &quot;2021-03-29 16:33:04&quot;,
    }
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;last_page_url&lt;/code&gt;などにもあるように、クエリパラメータで&lt;code&gt;page&lt;/code&gt;を渡すことによって、そのページのデータを取得できるようになっています。&lt;/p&gt;
&lt;p&gt;この仕様を把握したうえで、フロント側からリクエストします。&lt;/p&gt;
&lt;h2&gt;SPA（React）側&lt;/h2&gt;
&lt;h3&gt;アプリ初期化 + ルーティング&lt;/h3&gt;
&lt;p&gt;React Query のセットアップを行いルーティングをしているという、前回の記事での内容と大体同じですが、一応載せておきます。
（解説を知りたい場合は前回の記事を参照ください）&lt;/p&gt;
&lt;p&gt;React Query のデフォルトリトライ回数の変更をしていますが、この辺はお好みで。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;/:memoId?&lt;/code&gt;のルートが今回無限スクロールを実装したメモ画面のルートになります。
ちなみに&lt;code&gt;?&lt;/code&gt;をつけているのはあえてやってますが、当記事の趣旨には関係ない部分なので、ここで解説はしません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Redirect,
} from &apos;react-router-dom&apos;;
import { QueryClient, QueryClientProvider, useQueryClient } from &apos;react-query&apos;;
import { ReactQueryDevtools } from &apos;react-query/devtools&apos;;
import CssBaseline from &apos;@material-ui/core/CssBaseline&apos;;

import Login from &apos;./containers/pages/Login&apos;;
import Memo from &apos;./containers/pages/Memo&apos;;
import Loding from &apos;./components/pages/Loding&apos;;
import { useGetUserQuery, useCurrentUser } from &apos;./hooks/user&apos;;

/**
 * First we will load all of this project&apos;s JavaScript dependencies which
 * includes React and other helpers. It&apos;s a great starting point while
 * building robust, powerful web applications using React + Laravel.
 */
require(&apos;./bootstrap&apos;);

/**
 * Next, we will create a fresh React component instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */
// require(&apos;./components/Example&apos;);

const client = new QueryClient({
  defaultOptions: {
    queries: {
      retry: 1,
    },
    mutations: {
      retry: 1,
    },
  },
});

type Props = {
  exact?: boolean;
  path: string;
  children: React.ReactNode;
};

const UnAuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={() =&amp;gt; (user ? &amp;lt;Redirect to={{ pathname: &apos;/&apos; }} /&amp;gt; : children)}
    /&amp;gt;
  );
};

const AuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={({ location }) =&amp;gt;
        user ? (
          children
        ) : (
          &amp;lt;Redirect to={{ pathname: &apos;/login&apos;, state: { from: location } }} /&amp;gt;
        )
      }
    /&amp;gt;
  );
};

const App: FC = () =&amp;gt; {
  const queryClient = useQueryClient();
  const { isLoading } = useGetUserQuery({
    retry: 0,
    initialData: undefined,
    onError: () =&amp;gt; {
      queryClient.setQueryData(&apos;user&apos;, null);
    },
  });

  if (isLoading) {
    return &amp;lt;Loding /&amp;gt;;
  }

  return (
    &amp;lt;Switch&amp;gt;
      &amp;lt;UnAuthRoute exact path=&quot;/login&quot;&amp;gt;
        &amp;lt;Login /&amp;gt;
      &amp;lt;/UnAuthRoute&amp;gt;
      &amp;lt;AuthRoute path=&quot;/:memoId?&quot;&amp;gt;
        &amp;lt;Memo /&amp;gt;
      &amp;lt;/AuthRoute&amp;gt;
    &amp;lt;/Switch&amp;gt;
  );
};

if (document.getElementById(&apos;app&apos;)) {
  ReactDOM.render(
    &amp;lt;Router&amp;gt;
      &amp;lt;QueryClientProvider client={client}&amp;gt;
        &amp;lt;CssBaseline /&amp;gt;
        &amp;lt;App /&amp;gt;
        {process.env.NODE_ENV === &apos;development&apos; &amp;amp;&amp;amp; (
          &amp;lt;ReactQueryDevtools initialIsOpen={false} /&amp;gt;
        )}
      &amp;lt;/QueryClientProvider&amp;gt;
    &amp;lt;/Router&amp;gt;,
    document.getElementById(&apos;app&apos;)
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;メモ一覧&lt;/h3&gt;
&lt;p&gt;構造として、メモ画面を構成する Memo コンポーネントがあり。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;メモ一覧を構成する MemoList コンポーネント&lt;/li&gt;
&lt;li&gt;メモ詳細を構成する MemoDetail コンポーネント&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;を使っています。&lt;/p&gt;
&lt;p&gt;Memo コンポーネントと MemoDetail コンポーネントでやっていることは、当記事の趣旨に直接関係ないので割愛します。&lt;/p&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;当記事の趣旨に関係ない部分もありますが、一旦、全容を載せると以下のようになっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC, useCallback, useEffect } from &apos;react&apos;;
import { useHistory } from &apos;react-router-dom&apos;;
import useMediaQuery from &apos;@material-ui/core/useMediaQuery&apos;;
import { useTheme } from &apos;@material-ui/core/styles&apos;;
import MemoList from &apos;../../components/organisms/MemoList&apos;;
import { useGetMemoListQuery, usePostMemoMutation } from &apos;../../hooks/memo&apos;;
import { useIntersectionObserver } from &apos;../../hooks/util&apos;;

type Props = {
  memoId?: string;
};

const EnhancedMemoList: FC&amp;lt;Props&amp;gt; = ({ memoId }) =&amp;gt; {
  const {
    isFetching,
    isLoading,
    error,
    data: paginateMemos,
    hasNextPage,
    isFetchingNextPage,
    fetchNextPage,
  } = useGetMemoListQuery();
  const history = useHistory();
  const statusCode = error?.response?.status;

  // データ取得中でない + 画面幅が広い + メモ未選択時は、メモ一覧の一番新しいメモへ遷移
  const theme = useTheme();
  const iswideDisplay = useMediaQuery(theme.breakpoints.up(&apos;sm&apos;));
  useEffect(() =&amp;gt; {
    const firstMemoId = paginateMemos?.pages[0]?.data[0].memoId;
    if (!isFetching &amp;amp;&amp;amp; !memoId &amp;amp;&amp;amp; iswideDisplay &amp;amp;&amp;amp; firstMemoId) {
      history.push(`/${firstMemoId}`);
    }
  }, [history, isFetching, paginateMemos, memoId, iswideDisplay]);

  // 無限スクロール処理
  const { loadMoreRef } = useIntersectionObserver({
    onIntersect: fetchNextPage,
    enabled: hasNextPage,
  });

  const { mutate } = usePostMemoMutation();
  const handleAddMemo = useCallback(() =&amp;gt; {
    mutate({ title: &apos;&apos;, content: &apos;&apos; });
  }, [mutate]);

  const handleSelectItem = useCallback(
    (selectMemoId: string) =&amp;gt; {
      history.push(`/${selectMemoId}`);
    },
    [history]
  );

  return (
    &amp;lt;MemoList
      paginateMemos={paginateMemos?.pages}
      isLoading={isLoading}
      statusCode={statusCode}
      loadMoreRef={loadMoreRef}
      hasNextPage={hasNextPage}
      isFetchingNextPage={isFetchingNextPage}
      handleAddMemo={handleAddMemo}
      handleSelectItem={handleSelectItem}
    /&amp;gt;
  );
};

export default EnhancedMemoList;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無限スクロールに関係する部分をさらっと説明します。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;・useInfiniteQuery を使ったデータ取得&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const {
    isFetching,
    isLoading,
    error,
    data: paginateMemos,
    hasNextPage,
    isFetchingNextPage,
    fetchNextPage,
  } = useGetMemoListQuery();
  const history = useHistory();
  const statusCode = error?.response?.status;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;useGetMemoListQuery という&lt;code&gt;useInfiniteQuery&lt;/code&gt;をラップしたカスタムフックを使って、メモ一覧を取得しています。
データだけでなく、読み込み中などの UI 表示に使用するものや次ページに関するものも併せて取得。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;isFetching：データ取得中であるか&lt;/li&gt;
&lt;li&gt;isLoading：（既存のキャッシュがない）初回データ取得中であるか&lt;/li&gt;
&lt;li&gt;error：エラー内容（エラー時のみ）&lt;/li&gt;
&lt;li&gt;hasNextPage：次ページがあるか&lt;/li&gt;
&lt;li&gt;isFetchingNextPage：次ページデータを取得中であるか&lt;/li&gt;
&lt;li&gt;fetchNextPage：次ページ取得関数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;・Intersection Observer API の設定&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  // 無限スクロール処理
  const { loadMoreRef } = useIntersectionObserver({
    onIntersect: fetchNextPage,
    enabled: hasNextPage,
  });
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Intersection Observer API をラップしたカスタムフックを使用して、無限スクロールの設定をしています。
loadMoreRef はコールバック ref になっており、無限スクロール検知用の要素へ渡すように。&lt;/p&gt;
&lt;p&gt;オプション設定により、次ページが存在する場合のみ監視し、通知を検知した時に次ページを読み込むようにしています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;・取得したデータを渡す&lt;/strong&gt;
冒頭に書いた通り。&lt;code&gt;useInfiniteQuery&lt;/code&gt;で取得したデータは、pages キーの中に配列で格納されるので、それを渡します。
その他、UI 側で使用するものを一緒に渡しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;return (
  &amp;lt;MemoList
    paginateMemos={paginateMemos?.pages}
    isLoading={isLoading}
    statusCode={statusCode}
    loadMoreRef={loadMoreRef}
    hasNextPage={hasNextPage}
    isFetchingNextPage={isFetchingNextPage}
    handleAddMemo={handleAddMemo}
    handleSelectItem={handleSelectItem}
  /&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;初回読み込み中はスケルトン表示、エラー時はアラート表示、読み込みが終わり次第、データを使ったメモ一覧を表示するようにしています。
エラー表示はとりあえず500エラーの時だけ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import List from &apos;@material-ui/core/List&apos;;
import GeneralAlert from &apos;../atoms/GeneralAlert&apos;;
import MemoListHeader from &apos;../molecules/MemoListHeader&apos;;
import MemoListItem from &apos;../molecules/MemoListItem&apos;;
import MemoListItemSkeleton from &apos;../molecules/MemoListItemSkeleton&apos;;
import { INTERNAL_SERVER_ERROR } from &apos;../../constants/statusCode&apos;;
import { Memos } from &apos;../../models/Memos&apos;;

type Props = {
  paginateMemos?: Memos[];
  isLoading: boolean;
  statusCode?: number;
  loadMoreRef: (node: Element) =&amp;gt; void;
  hasNextPage?: boolean;
  isFetchingNextPage: boolean;
  handleAddMemo: VoidFunction;
  handleSelectItem: (selectMemoId: string) =&amp;gt; void;
};

const MemoList: FC&amp;lt;Props&amp;gt; = ({
  paginateMemos,
  isLoading,
  statusCode,
  loadMoreRef,
  hasNextPage,
  isFetchingNextPage,
  handleAddMemo,
  handleSelectItem,
}) =&amp;gt; {
  if (isLoading) {
    return (
      &amp;lt;&amp;gt;
        &amp;lt;Box height={48} px={2} /&amp;gt;
        {[1, 2, 3, 4, 5].map((value) =&amp;gt; (
          &amp;lt;MemoListItemSkeleton key={value} /&amp;gt;
        ))}
      &amp;lt;/&amp;gt;
    );
  }

  if (statusCode) {
    return (
      &amp;lt;&amp;gt;
        &amp;lt;Box height={48} px={2} /&amp;gt;
        {statusCode === INTERNAL_SERVER_ERROR &amp;amp;&amp;amp; (
          &amp;lt;GeneralAlert
            type=&quot;error&quot;
            title=&quot;サーバエラー&quot;
            content=&quot;予期しないエラーが発生し、メモデータ取得に失敗しました。恐れ入りますが時間をおいて再度お試しください。&quot;
          /&amp;gt;
        )}
      &amp;lt;/&amp;gt;
    );
  }

  let loadMoreMessage;
  if (isFetchingNextPage) {
    loadMoreMessage = &apos;読み込み中...&apos;;
  } else {
    loadMoreMessage = hasNextPage ? &apos;続きを読み込む&apos; : &apos; &apos;;
  }

  return (
    &amp;lt;&amp;gt;
      &amp;lt;MemoListHeader handleAddMemo={handleAddMemo} /&amp;gt;
      {/* 140px = ヘッダー：64 + メモ一覧ヘッダー：48 + 下部余白：28 */}
      &amp;lt;List style={{ height: &apos;calc(100vh - 140px)&apos;, overflowY: &apos;scroll&apos; }}&amp;gt;
        {paginateMemos?.map((page) =&amp;gt; (
          &amp;lt;React.Fragment key={page.currentPage}&amp;gt;
            {page.data.map((memo) =&amp;gt; (
              &amp;lt;MemoListItem
                key={memo.memoId}
                memoId={memo.memoId}
                title={memo.title}
                content={memo.content}
                handleSelectItem={handleSelectItem}
              /&amp;gt;
            ))}
          &amp;lt;/React.Fragment&amp;gt;
        ))}
        &amp;lt;Box {...{ ref: loadMoreRef }} textAlign=&quot;center&quot;&amp;gt;
          {loadMoreMessage}
        &amp;lt;/Box&amp;gt;
      &amp;lt;/List&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default MemoList;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;・データ表示とスクロール&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  {/* 140px = ヘッダー：64 + メモ一覧ヘッダー：48 + 下部余白：28 */}
  &amp;lt;List style={{ height: &apos;calc(100vh - 140px)&apos;, overflowY: &apos;scroll&apos; }}&amp;gt;
    {paginateMemos?.map((page) =&amp;gt; (
      &amp;lt;React.Fragment key={page.currentPage}&amp;gt;
        {page.data.map((memo) =&amp;gt; (
          &amp;lt;MemoListItem
            key={memo.memoId}
            memoId={memo.memoId}
            title={memo.title}
            content={memo.content}
            handleSelectItem={handleSelectItem}
          /&amp;gt;
        ))}
      &amp;lt;/React.Fragment&amp;gt;
    ))}
    .
    .
    .
  &amp;lt;/List&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;渡されたデータ配列を map で回します。
1ページあたりのデータの data キーをさらに map を使って回して、一件ずつのデータで MemoListItem コンポーネントを構築しています。&lt;/p&gt;
&lt;p&gt;このメモ一覧をスクロールしたいので、height と overflowY を設定。
height の値の計算はコメントに書いている通りです。
動的に計算できればいいなと思ったのですが、複雑になりそうだったので固定で指定しています。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;・無限スクロール検知用要素&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;  let loadMoreMessage;
  if (isFetchingNextPage) {
    loadMoreMessage = &apos;読み込み中...&apos;;
  } else {
    loadMoreMessage = hasNextPage ? &apos;続きを読み込む&apos; : &apos; &apos;;
  }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;  &amp;lt;Box {...{ ref: loadMoreRef }} textAlign=&quot;center&quot;&amp;gt;
    {loadMoreMessage}
  &amp;lt;/Box&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;メモ一覧の末尾に Box コンポーネントで要素を置いており、これにコールバック ref を渡しています。
この要素がどれだけ表示されたかで検知するようにしているわけです。&lt;/p&gt;
&lt;p&gt;状態に応じて表示内容を変えたいので、分岐でメッセージを作っています。&lt;/p&gt;
&lt;h3&gt;モデル&lt;/h3&gt;
&lt;h4&gt;Memo&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;export type Memo = {
  memoId: string;
  title: string;
  content: string;
  createdAt: Date;
  updatedAt: Date;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Memos&lt;/h4&gt;
&lt;p&gt;Laravel のレスポンス形式に合わせてますが、データ取得時にプロパティキーをキャメルケースに変換するので、こちらではキャメルケースで定義しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Memo } from &apos;./Memo&apos;;

export type Memos = {
  total: number;
  perPage: number;
  currentPage: number;
  lastPage: number;
  firstPageUrl: string;
  lastPageUrl: string;
  nextPageUrl: string | null;
  prevPageUrl: string | null;
  path: string;
  from: number;
  to: number;
  data: Memo[];
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;カスタムフック&lt;/h3&gt;
&lt;p&gt;※作成したカスタムフックは、それぞれ index.ts で名前付きで再エクスポートしています。&lt;/p&gt;
&lt;h4&gt;useGetMemoListQuery&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useInfiniteQuery&lt;/code&gt;をラップした、メモ一覧取得のためのカスタムフック。
成功時は、取得したデータを memos キーにセット。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  UseInfiniteQueryResult,
  UseInfiniteQueryOptions,
  useInfiniteQuery,
} from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;
import camelcaseKeys from &apos;camelcase-keys&apos;;
import { Memos } from &apos;../../models/Memos&apos;;

const getMemoList = async ({ pageParam = 1 }): Promise&amp;lt;Memos&amp;gt; =&amp;gt; {
  const { data } = await axios.get(`/api/memos?page=${pageParam}`);
  return camelcaseKeys(data, { deep: true });
};

const useGetMemoListQuery = &amp;lt;TData = Memos&amp;gt;(
  options?: UseInfiniteQueryOptions&amp;lt;Memos, AxiosError, TData&amp;gt;
): UseInfiniteQueryResult&amp;lt;TData, AxiosError&amp;gt; =&amp;gt;
  useInfiniteQuery(&apos;memos&apos;, getMemoList, {
    ...options,
    getPreviousPageParam: (firstPage) =&amp;gt;
      firstPage.prevPageUrl ? firstPage.currentPage - 1 : false,
    getNextPageParam: (lastPage) =&amp;gt;
      lastPage.nextPageUrl ? lastPage.currentPage + 1 : false,
  });

export default useGetMemoListQuery;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API 定義のところで書いたように、Laravel でページネーションを有効にすると、&lt;code&gt;page&lt;/code&gt;クエリパラメータで取得ページを指定できるので、こちらもそれに合わせます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getPreviousPageParam&lt;/code&gt;オプションでは、前ページの URL が存在する時に、現在のページ数から -1 したページ数を返すように。&lt;/p&gt;
&lt;p&gt;同様に&lt;code&gt;getNextPageParam&lt;/code&gt;オプションでは、次ページの URL が存在する時に、現在のページ数から +1 したページ数を返すように。&lt;/p&gt;
&lt;p&gt;ちなみに取得したデータのプロパティキーのスネークケース・キャメルケース問題解消のために&lt;code&gt;camelcaseKeys&lt;/code&gt;で変換をかけています。本来は axios の interceptors でまとめてやった方がいいと思われます。&lt;/p&gt;
&lt;h4&gt;useIntersectionObserver&lt;/h4&gt;
&lt;p&gt;Intersect Observer API をラップしたカスタムフック。
公式の実装例をベースとしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { useState, useCallback, useEffect } from &apos;react&apos;;
import { FetchNextPageOptions, InfiniteQueryObserverResult } from &apos;react-query&apos;;
import { AxiosError } from &apos;axios&apos;;

type Argument = {
  root?: React.RefObject&amp;lt;HTMLElement&amp;gt; | null;
  onIntersect: (
    options?: FetchNextPageOptions | undefined
  ) =&amp;gt; Promise&amp;lt;InfiniteQueryObserverResult&amp;lt;unknown, AxiosError&amp;gt;&amp;gt;;
  threshold?: number | number[];
  rootMargin?: string;
  enabled?: boolean;
};

type Response = {
  loadMoreRef: (node: Element) =&amp;gt; void;
};

const useIntersectionObserver = ({
  root = null,
  onIntersect,
  threshold = 1.0,
  rootMargin = &apos;0px&apos;,
  enabled = true,
}: Argument): Response =&amp;gt; {
  const [target, setTarget] = useState&amp;lt;Element | null&amp;gt;(null);

  // コールバックref（呼び出し側はこれを無限スクロール検知用要素のrefに渡せばいい）
  const loadMoreRef = useCallback((node: Element) =&amp;gt; {
    if (node !== null) {
      setTarget(node);
    }
  }, []);

  const newIntersectionObserver = useCallback(
    () =&amp;gt;
      new IntersectionObserver(
        (entries) =&amp;gt;
          entries.forEach((entry) =&amp;gt; entry.isIntersecting &amp;amp;&amp;amp; onIntersect()),
        {
          root: root &amp;amp;&amp;amp; root.current,
          rootMargin,
          threshold,
        }
      ),
    [root, onIntersect, threshold, rootMargin]
  );

  useEffect(() =&amp;gt; {
    if (!enabled) {
      return;
    }
    const el = target;

    if (!el) {
      return;
    }
    const observer = newIntersectionObserver();

    observer.observe(el);

    // eslint-disable-next-line consistent-return
    return () =&amp;gt; {
      observer.unobserve(el);
    };
  }, [enabled, target, newIntersectionObserver]);

  return { loadMoreRef };
};

export default useIntersectionObserver;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;公式の例ではターゲット要素の指定に useRef の ref オブジェクトが使用されていましたが、コールバック ref にしています。
というのも ref オブジェクトの仕様上、少し都合が悪かったためです。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/hooks-reference.html#useref&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;useRef は中身が変更になってもそのことを通知しないということを覚えておいてください。.current プロパティを書き換えても再レンダーは発生しません。DOM ノードを ref に割り当てたり割り当てを解除したりする際に何らかのコードを走らせたいという場合は、コールバック ref を代わりに使用してください。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;自分の場合、初回データ取得中はスケルトンのみを表示していて、取得後に実際のデータのメモ一覧 + loadMore 要素を表示するようにしています。&lt;/p&gt;
&lt;p&gt;つまり、ターゲット要素の ref が
初回データ取得中：なし（null）
↓
データ取得後：loadMore 要素
と変化するわけですが、ref オブジェクトだとそれを通知してくれません。&lt;/p&gt;
&lt;p&gt;その結果、オブザーバーの監視対象がうまく設定されず動作しなくなってしまいました。
そのため、コールバック ref を使うことで対応しています。&lt;/p&gt;
&lt;p&gt;それと useEffect で値を返す return と返さない return が混在しているせいか、ESLint の consistent-return ルールにひっかかったので、無効にしています。
（うまい対応方法があれば知りたい...）&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;実は Intersection Observer API の存在を、公式の実装例を見て初めて知ったわけですが、なかなか便利だなぁと思いました。
今回は&lt;code&gt;useInfiniteQuery&lt;/code&gt;と組み合わせて使うような作りで作ってはいますが、少し変えれば他の状態管理ライブラリ等でも使えそうです。&lt;/p&gt;
&lt;p&gt;React Query の機能をまた1つ知れて、良い勉強になりました。
引き続き、使っていきますー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/query/v3&quot;&gt;React Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com&quot;&gt;Material UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.mozilla.org/ja/docs/Web/API/Intersection_Observer_API&quot;&gt;MDN - Intersection Observer API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.reactjs.org/docs/hooks-reference.html#useref&quot;&gt;React - フック API リファレンス - useRef&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>React × LaravelでReact Queryの練習がてら、ログイン機能を作ってみた</title><link>https://changeofpace.site/posts/2021-03-21-laravel-react-query-auth</link><guid isPermaLink="true">https://changeofpace.site/posts/2021-03-21-laravel-react-query-auth</guid><description>React Queryを使ったログイン画面を実装した例</description><pubDate>Sun, 21 Mar 2021 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;React の状態管理ライブラリと言うと Redux や Recoil がメジャーなイメージでしょうか。
少し前から React Query なるものが便利らしいぞと見聞きしたので使ってみたのを、せっかくなので記事にしてみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;※2023/07/09 各種リンクの URL を tanstack.com をベースに更新しました。&lt;/p&gt;
&lt;h2&gt;React Query とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://tanstack.com/query/v3&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ものすごくざっくりいうと、React アプリで使用するデータの取得、更新。そのデータ管理まで一気にやってくれるライブラリといった感じです。&lt;/p&gt;
&lt;p&gt;データの取得や更新の方法はこちらで指定する必要がありますが、実体のデータを Promise で返す関数なら何でも使えます。
なので、fetch でも ky でも axios でも OK。&lt;/p&gt;
&lt;p&gt;キャッシュ機能が統合されており、取得したデータは指定したクエリキーと紐づけて管理されます。
このキャッシュされたデータは、どのコンポーネントからもアクセス可能です。
キャッシュの有効時間が過ぎると、バックグラウンドでデータの再取得が自動で行われるようになっています。
（その分、API リクエスト等が増えるということでもあるので、導入するアプリに応じてオプションで調整した方がいいです）&lt;/p&gt;
&lt;p&gt;それと個人的に特にいいなと思った部分として、データ取得、更新の状態を返してくれます。
データ取得中なのか、成功したのか、エラーになったのかといったあたり。
この状態を使って、読み込み中やエラーの時の UI に切り替えるということも、楽にできるようになっています。&lt;/p&gt;
&lt;h3&gt;使用例&lt;/h3&gt;
&lt;p&gt;React Query を使えるようにするには、まず最初に&lt;code&gt;QueryClient&lt;/code&gt;を作成。
アプリを&lt;code&gt;QueryClientProvider&lt;/code&gt;で囲み、そこに作成したクライアントを渡すようにします。
こうすることで、この配下のコンポーネントで React Query の機能が使えるようになります。&lt;/p&gt;
&lt;p&gt;React Query が提供するフックはいろいろあるのですが、基本となるものとしては以下の2つです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;データ取得：useQuery&lt;/li&gt;
&lt;li&gt;データ更新：useMutation&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;useQuery&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://tanstack.com/query/v3/docs/react/reference/useQuery&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;公式の例
&amp;lt;OG url=&quot;https://codesandbox.io/s/github/tanstack/query/tree/v3/examples/simple&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/* eslint-disable jsx-a11y/anchor-is-valid */
import React from &quot;react&quot;;
import ReactDOM from &quot;react-dom&quot;;
import { QueryClient, QueryClientProvider, useQuery } from &quot;react-query&quot;;
import { ReactQueryDevtools } from &quot;react-query/devtools&quot;;

const queryClient = new QueryClient();

export default function App() {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;Example /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  );
}

function Example() {
  const { isLoading, error, data, isFetching } = useQuery(&quot;repoData&quot;, () =&amp;gt;
    fetch(
      &quot;https://api.github.com/repos/tannerlinsley/react-query&quot;
    ).then((res) =&amp;gt; res.json())
  );

  if (isLoading) return &quot;Loading...&quot;;

  if (error) return &quot;An error has occurred: &quot; + error.message;

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;{data.name}&amp;lt;/h1&amp;gt;
      &amp;lt;p&amp;gt;{data.description}&amp;lt;/p&amp;gt;
      &amp;lt;strong&amp;gt;👀 {data.subscribers_count}&amp;lt;/strong&amp;gt;{&quot; &quot;}
      &amp;lt;strong&amp;gt;✨ {data.stargazers_count}&amp;lt;/strong&amp;gt;{&quot; &quot;}
      &amp;lt;strong&amp;gt;🍴 {data.forks_count}&amp;lt;/strong&amp;gt;
      &amp;lt;div&amp;gt;{isFetching ? &quot;Updating...&quot; : &quot;&quot;}&amp;lt;/div&amp;gt;
      &amp;lt;ReactQueryDevtools initialIsOpen /&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const rootElement = document.getElementById(&quot;root&quot;);
ReactDOM.render(&amp;lt;App /&amp;gt;, rootElement);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;useQuery&lt;/code&gt;に対して、以下を渡しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1引数：クエリキー&lt;/li&gt;
&lt;li&gt;第2引数：データ取得の関数&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;クエリキーには配列の指定もできます。データ取得の関数により取得されたデータは、このクエリキーと紐づけて管理されます。
また、第3引数にオプションのオブジェクトを渡して、成功時や失敗時の処理を書いたりとカスタマイズすることも出来たりします。&lt;/p&gt;
&lt;h3&gt;useMutation&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://tanstack.com/query/v3/docs/react/reference/useMutation&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;公式の例（&lt;code&gt;useQuery&lt;/code&gt;と&lt;code&gt;useMutation&lt;/code&gt;）　※2023/07/09時点では TypeScript になっていました
&amp;lt;OG url=&quot;https://codesandbox.io/p/sandbox/github/tanstack/query/tree/v3/examples/optimistic-updates-typescript&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;
import axios from &apos;axios&apos;
import {
  useQuery,
  useQueryClient,
  useMutation,
  QueryClient,
  QueryClientProvider,
} from &apos;react-query&apos;
import { ReactQueryDevtools } from &apos;react-query/devtools&apos;

const queryClient = new QueryClient()

export default function App() {
  return (
    &amp;lt;QueryClientProvider client={queryClient}&amp;gt;
      &amp;lt;Example /&amp;gt;
    &amp;lt;/QueryClientProvider&amp;gt;
  )
}

function Example() {
  const queryClient = useQueryClient()
  const [text, setText] = React.useState(&apos;&apos;)
  const { status, data, error, isFetching } = useQuery(&apos;todos&apos;, async () =&amp;gt; {
    const res = await axios.get(&apos;/api/data&apos;)
    return res.data
  })

const addTodoMutation = useMutation(
    text =&amp;gt; axios.post(&apos;/api/data&apos;, { text }),
    {
      // Optimistically update the cache value on mutate, but store
      // the old value and return it so that it&apos;s accessible in case of
      // an error
      onMutate: async text =&amp;gt; {
        setText(&apos;&apos;)
        await queryClient.cancelQueries(&apos;todos&apos;)
        const previousValue = queryClient.getQueryData(&apos;todos&apos;)
        queryClient.setQueryData(&apos;todos&apos;, old =&amp;gt; ({
          ...old,
          items: [...old.items, text],
        }))
        return previousValue
      },
      // On failure, roll back to the previous value
      onError: (err, variables, previousValue) =&amp;gt;
        queryClient.setQueryData(&apos;todos&apos;, previousValue),
      // After success or failure, refetch the todos query
      onSuccess: () =&amp;gt; {
        queryClient.invalidateQueries(&apos;todos&apos;)
      },
    }
  )

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;
        In this example, new items can be created using a mutation. The new item
        will be optimistically added to the list in hopes that the server
        accepts the item. If it does, the list is refetched with the true items
        from the list. Every now and then, the mutation may fail though. When
        that happens, the previous list of items is restored and the list is
        again refetched from the server.
      &amp;lt;/p&amp;gt;
      &amp;lt;form
        onSubmit={e =&amp;gt; {
          e.preventDefault()
          addTodoMutation.mutate(text)
        }}
      &amp;gt;
        &amp;lt;input
          type=&quot;text&quot;
          onChange={event =&amp;gt; setText(event.target.value)}
          value={text}
        /&amp;gt;
        &amp;lt;button&amp;gt;{addTodoMutation.isLoading ? &apos;Creating...&apos; : &apos;Create&apos;}&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
      &amp;lt;br /&amp;gt;
      {status === &apos;loading&apos; ? (
        &apos;Loading...&apos;
      ) : status === &apos;error&apos; ? (
        error.message
      ) : (
        &amp;lt;&amp;gt;
          &amp;lt;div&amp;gt;Updated At: {new Date(data.ts).toLocaleTimeString()}&amp;lt;/div&amp;gt;
          &amp;lt;ul&amp;gt;
            {data.items.map(datum =&amp;gt; (
              &amp;lt;li key={datum}&amp;gt;{datum}&amp;lt;/li&amp;gt;
            ))}
          &amp;lt;/ul&amp;gt;
          &amp;lt;div&amp;gt;{isFetching ? &apos;Updating in background...&apos; : &apos; &apos;}&amp;lt;/div&amp;gt;
        &amp;lt;/&amp;gt;
      )}
      &amp;lt;ReactQueryDevtools initialIsOpen /&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;useMutation&lt;/code&gt;に対して、以下を渡しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1引数：データ更新の関数&lt;/li&gt;
&lt;li&gt;第2引数：オプションのオブジェクト&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この第1引数に指定した関数は、&lt;code&gt;useMutation&lt;/code&gt;が呼ばれただけでは実行されません。
&lt;code&gt;useMutation&lt;/code&gt;の返り値の中に、この関数を実行するトリガー関数（mutate）が含まれており、それを使うことで初めて実行されるようになっています。
上記の例だと&lt;code&gt;addTodoMutation.mutate(text)&lt;/code&gt;の部分です。
トリガー関数に渡した引数が、そのままデータ更新の関数に渡されます。&lt;/p&gt;
&lt;p&gt;上記の例で使われているオプションをさらっと説明すると、こんな感じです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;onMutate：データ更新の関数が実行される前に、先に実行される処理（前処理を書く）&lt;/li&gt;
&lt;li&gt;onError：エラー発生時に実行される処理&lt;/li&gt;
&lt;li&gt;onSuccess：成功時に実行される処理&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;成功時、エラー時ともに実行される onSettled というものもあります。&lt;/p&gt;
&lt;p&gt;また、onSuccess、onError、onSettled に関しては、&lt;code&gt;useMutation&lt;/code&gt;だけでなく、トリガー関数のオプション引数としても渡すことができます。
どちらにも同名のオプションを渡している場合は、&lt;code&gt;useMutation&lt;/code&gt;に渡した方が先に実行されます。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;ちなみに&lt;code&gt;ReactQueryDevtools&lt;/code&gt;は開発支援の DevTools です。
キャッシュの状態などがわかるので、いれておくとよいです。&lt;/p&gt;
&lt;p&gt;詳細については公式ドキュメントをご確認ください。&lt;/p&gt;
&lt;h2&gt;今回作ってみたもの&lt;/h2&gt;
&lt;p&gt;勉強用の個人開発でログイン画面を作りました。
React（Laravel Mix）× Laravel による SPA × API 構成です。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;gifs/2021/laravel-react-query-auth/email-login.gif&quot; alt=&quot;ログイン画面でログインする流れのGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;メールアドレスとパスワードでログインのスタンダードなタイプ。
後々、ソーシャルログインとゲストログインにしたいということもあり、今回メール認証は特にやっていません。&lt;/p&gt;
&lt;p&gt;また、API の認証方式については Cookie を使ったステートフルなものになります。&lt;/p&gt;
&lt;p&gt;実装にあたっていろんな文献を参考にさせていただいたのですが、以下の2つは特にお世話になりました。&lt;/p&gt;
&lt;p&gt;※連載記事
&amp;lt;OG url=&quot;https://www.hypertextcandy.com/vue-laravel-tutorial-introduction&quot; /&amp;gt;
※書籍
&amp;lt;OG url=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;それと各種ライブラリの公式ドキュメントも。&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;p&gt;今回使用した各種バージョンは以下のとおりです。&lt;/p&gt;
&lt;p&gt;基本部分.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js：14.2.0&lt;/li&gt;
&lt;li&gt;TypeScript：4.1.3&lt;/li&gt;
&lt;li&gt;React：16.14.0&lt;/li&gt;
&lt;li&gt;PHP：7.4.14&lt;/li&gt;
&lt;li&gt;Laravel：6.20.9&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ライブラリ.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Material UI
&lt;ul&gt;
&lt;li&gt;core：4.11.3&lt;/li&gt;
&lt;li&gt;lab：4.0.0-alpha.57&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;axios：0.21.1&lt;/li&gt;
&lt;li&gt;react-router-dom：5.2.0&lt;/li&gt;
&lt;li&gt;react-query：3.12.1&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下のセットアップはすでにすんでいるものとして進めます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;マイグレーション実行&lt;/li&gt;
&lt;li&gt;Laravel UI で React の導入&lt;/li&gt;
&lt;li&gt;TypeScript のセットアップ&lt;/li&gt;
&lt;li&gt;ライブラリインストール&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;ディレクトリ構成&lt;/h2&gt;
&lt;p&gt;Laravel 側は特に変わったことをしてないので、React 側だけ記載します。&lt;/p&gt;
&lt;p&gt;Laravel プロジェクトの resources/ts 配下.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;├ components
│   ├ molecules
│   │   └ LoginAlert.tsx
│   ├ organisms
│   │   └ Header.tsx
│   ├ pages
│   │   ├ Loding.tsx
│   │   ├ Login.tsx
│   │   └ Memo.tsx
├ constants
│   └ statusCode.ts
├ containers
│   ├ organisms
│   │   └ Header.tsx
│   ├ pages
│   │   ├ Login.tsx
│   │   └ Memo.tsx
├ hooks
│   ├ auth
│   │   ├ index.ts
│   │   ├ useLogin.ts
│   │   └ useLogout.ts
│   ├ user
│   │   ├ index.ts
│   │   ├ useCurrentUser.ts
│   │   └ useGetUserQuery.ts
├ models
│   └ User.ts
├ app.tsx
└ bootstrap.js
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以降、今回の実装のコードを記載していますが、だいぶ長くなりました...🙄
GitHub で見たいという方は、こちらのリポジトリにタグをつけてあります。
&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/ooui-memo/releases/tag/post%2Flaravel-react-query-auth&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※2021/09/13追記 関数コンポーネントの型定義に FC ばっかり使ってますが、FC から children がなくなるまでは VFC を使った方がいいです...。&lt;/p&gt;
&lt;h2&gt;API（Laravel）側&lt;/h2&gt;
&lt;p&gt;※今回、ユーザ新規登録の部分は記載していません。
Laravel に元々備わっているものを使って API を作るなり、tinker でユーザを作っておくなりでご対応ください。&lt;/p&gt;
&lt;h3&gt;ルーティング&lt;/h3&gt;
&lt;p&gt;画面の振り分けは React 側で行うので、Laravel 側では全てのリクエストを受けるようにします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::get(&apos;/{any?}&apos;, fn() =&amp;gt; view(&apos;index&apos;))-&amp;gt;where(&apos;any&apos;, &apos;.+&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※2021/05/19 追記
全受けにすると、API ルートで where 制約をかけた際に予期しない動作を引き起こすので api プレフィックスは除外しておいた方がいいです。
（制約外のパスパラメータでアクセスすると404のはずなのに、このルートに来て200になってしまうので）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::get(&apos;/{any?}&apos;, fn() =&amp;gt; view(&apos;index&apos;))-&amp;gt;where(&apos;any&apos;, &apos;(?!api).+&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;API ルートで使用するミドルウェアグループの変更&lt;/h3&gt;
&lt;p&gt;今回は Cookie を使った認証にしていきます。
下記のとおり、それに必要なミドルウェアは web のミドルウェアグループに含まれています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;protected $middlewareGroups = [
    &apos;web&apos; =&amp;gt; [
        \App\Http\Middleware\EncryptCookies::class,
        \Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
        \Illuminate\Session\Middleware\StartSession::class,
        // \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\VerifyCsrfToken::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],

    &apos;api&apos; =&amp;gt; [
        &apos;throttle:60,1&apos;,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
    ],
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;それを API でも使うように思い切って変えました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;    protected function mapApiRoutes()
    {
        // Webミドルウェアグループの機能を使いたいのでwebへ
        Route::prefix(&apos;api&apos;)
             -&amp;gt;middleware(&apos;web&apos;)
             -&amp;gt;namespace($this-&amp;gt;namespace)
             -&amp;gt;group(base_path(&apos;routes/api.php&apos;));
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なんか抵抗があるという場合は、ステートフルな API 用のミドルウェアグループを新たに作って、それを割り当てるのも手です。&lt;/p&gt;
&lt;h4&gt;CSRF 対策について&lt;/h4&gt;
&lt;p&gt;この web のミドルウェアグループには CSRF 対策の機能を持つ、&lt;code&gt;\App\Http\Middleware\VerifyCsrfToken::class&lt;/code&gt;も含まれています。
なので、リクエストの際には CSRF トークンを送らないとはじかれます。
&amp;lt;OG url=&quot;https://readouble.com/laravel/6.x/ja/csrf.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今回の場合、SPA 側からのリクエストには axios を使用するので、リクエストヘッダに&lt;code&gt;X-XSRF-TOKEN&lt;/code&gt;をつけて送る必要があります。
ただ、このあたりの設定に関して特にやることはありません。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;\App\Http\Middleware\VerifyCsrfToken::class&lt;/code&gt;で以下の設定があり、レスポンスヘッダの&lt;code&gt;Set-Cookie&lt;/code&gt;に&lt;code&gt;XSRF-TOKEN&lt;/code&gt;を設定してくれています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Indicates whether the XSRF-TOKEN cookie should be set on the response.
 *
 * @var bool
 */
protected $addHttpCookie = true;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;そして、axios は Cookie に&lt;code&gt;XSRF-TOKEN&lt;/code&gt;があると、自動で&lt;code&gt;X-XSRF-TOKEN&lt;/code&gt;にセットして送ってくれるようになっているためです。&lt;/p&gt;
&lt;h3&gt;ログイン API&lt;/h3&gt;
&lt;p&gt;Laravel が元々備えている機能を拡張して使用。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;LoginController&lt;/code&gt;で使用されている&lt;code&gt;AuthenticatesUsers&lt;/code&gt;トレイトに認証に関するメソッドが定義されています。
そこにログイン API のレスポンスカスタマイズ用のメソッドとして&lt;code&gt;authenticated&lt;/code&gt;があります。
これを使用して、ログイン時はログインしたユーザ情報を返すように。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * ログインAPI レスポンスカスタマイズ用メソッド
 *
 * @param Illuminate\Http\Request $request
 * @param \App\User $user
 * @return \App\User
 */
protected function authenticated(Request $request, $user)
{
    return $user;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::post(&apos;/login&apos;, &apos;Auth\LoginController@login&apos;)-&amp;gt;name(&apos;login&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ログアウト API&lt;/h3&gt;
&lt;p&gt;ログイン API と同様に作成。&lt;/p&gt;
&lt;p&gt;こちらは&lt;code&gt;loggedOut&lt;/code&gt;メソッドがレスポンスカスタマイズ用のメソッドになります。&lt;/p&gt;
&lt;p&gt;※2021/04/13 追記
レスポンスは&lt;code&gt;response(null, 204)&lt;/code&gt;で204を返した方がいいやもしれません。
※2021/04/27 追記
セッション再生成処理は、大元のログアウト処理の中ですでに行われているので不要です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * ログアウトAPI レスポンスカスタマイズ用メソッド
 *
 * @param Illuminate\Http\Request $request
 * @return \Illuminate\Http\JsonResponse
 */
protected function loggedOut(Request $request)
{
    // セッションを再生成する
    $request-&amp;gt;session()-&amp;gt;regenerate();

    return response()-&amp;gt;json();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::post(&apos;/logout&apos;, &apos;Auth\LoginController@logout&apos;)-&amp;gt;name(&apos;logout&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ログインユーザ取得 API&lt;/h3&gt;
&lt;p&gt;新しくコントローラーを作って定義します。
この API は認証をかけたかったので、auth ミドルウェアを使用。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class UserController extends Controller
{
    /**
     * Create a new controller instance.
     *
     * @return void
     */
    public function __construct()
    {
        $this-&amp;gt;middleware(&apos;auth&apos;);
    }

    /**
     * 現在ログインしているユーザ情報取得
     *
     * @return \App\User|null
     */
    public function show()
    {
        return Auth::user();
    }
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;API のルートに追加。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Route::get(&apos;/users/me&apos;, &apos;UserController@show&apos;)-&amp;gt;name(&apos;user&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;User モデルのレスポンスのプロパティを変更。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * The attributes that should be visible for arrays.
 *
 * @var array
 */
protected $visible = [
    &apos;name&apos;,
];
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;元々は&lt;code&gt;$hidden&lt;/code&gt;で書いてあるのですが、今回の場合は React 側で一旦 name しか使わないので、&lt;code&gt;$visible&lt;/code&gt;で name のみ返すようにしています。&lt;/p&gt;
&lt;h3&gt;ログイン済みの時に、非ログイン時にしかアクセスできない機能にアクセスした時のリダイレクト設定&lt;/h3&gt;
&lt;p&gt;元々は&lt;code&gt;RouteServiceProvider::HOME&lt;/code&gt;へリダイレクトするようになっています。
ただ、それだと HTML が返ってきてしまい SPA 的には不都合なので、ログインユーザ取得 API に変えておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/**
 * Handle an incoming request.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \Closure  $next
 * @param  string|null  $guard
 * @return mixed
 */
public function handle($request, Closure $next, $guard = null)
{
    if (Auth::guard($guard)-&amp;gt;check()) {
        return redirect()-&amp;gt;route(&apos;user&apos;);
    }

    return $next($request);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;SPA（React）側&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;bootstrap.js&lt;/code&gt;は特に変更していないので割愛。&lt;/p&gt;
&lt;h3&gt;アプリ初期化 + ルーティング&lt;/h3&gt;
&lt;p&gt;1ファイルの中でやってるので長いですが、こんな感じです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import {
  BrowserRouter as Router,
  Switch,
  Route,
  Redirect,
} from &apos;react-router-dom&apos;;
import { QueryClient, QueryClientProvider, useQueryClient } from &apos;react-query&apos;;
import { ReactQueryDevtools } from &apos;react-query/devtools&apos;;
import CssBaseline from &apos;@material-ui/core/CssBaseline&apos;;

import Login from &apos;./containers/pages/Login&apos;;
import Memo from &apos;./containers/pages/Memo&apos;;
import Loding from &apos;./components/pages/Loding&apos;;
import { useGetUserQuery, useCurrentUser } from &apos;./hooks/user&apos;;

/**
 * First we will load all of this project&apos;s JavaScript dependencies which
 * includes React and other helpers. It&apos;s a great starting point while
 * building robust, powerful web applications using React + Laravel.
 */
require(&apos;./bootstrap&apos;);

/**
 * Next, we will create a fresh React component instance and attach it to
 * the page. Then, you may begin adding components to this application
 * or customize the JavaScript scaffolding to fit your unique needs.
 */
// require(&apos;./components/Example&apos;);

const client = new QueryClient();

type Props = {
  exact?: boolean;
  path: string;
  children: React.ReactNode;
};

const UnAuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={() =&amp;gt; (user ? &amp;lt;Redirect to={{ pathname: &apos;/&apos; }} /&amp;gt; : children)}
    /&amp;gt;
  );
};

const AuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={({ location }) =&amp;gt;
        user ? (
          children
        ) : (
          &amp;lt;Redirect to={{ pathname: &apos;/login&apos;, state: { from: location } }} /&amp;gt;
        )
      }
    /&amp;gt;
  );
};

const App: FC = () =&amp;gt; {
  const queryClient = useQueryClient();
  const { isLoading } = useGetUserQuery({
    retry: 0,
    initialData: undefined,
    onError: () =&amp;gt; {
      queryClient.setQueryData(&apos;user&apos;, null);
    },
  });

  if (isLoading) {
    return &amp;lt;Loding /&amp;gt;;
  }

  return (
    &amp;lt;Switch&amp;gt;
      &amp;lt;UnAuthRoute exact path=&quot;/login&quot;&amp;gt;
        &amp;lt;Login /&amp;gt;
      &amp;lt;/UnAuthRoute&amp;gt;
      &amp;lt;AuthRoute exact path=&quot;/&quot;&amp;gt;
        &amp;lt;Memo /&amp;gt;
      &amp;lt;/AuthRoute&amp;gt;
    &amp;lt;/Switch&amp;gt;
  );
};

if (document.getElementById(&apos;app&apos;)) {
  ReactDOM.render(
    &amp;lt;Router&amp;gt;
      &amp;lt;QueryClientProvider client={client}&amp;gt;
        &amp;lt;CssBaseline /&amp;gt;
        &amp;lt;App /&amp;gt;
        {process.env.NODE_ENV === &apos;development&apos; &amp;amp;&amp;amp; (
          &amp;lt;ReactQueryDevtools initialIsOpen={false} /&amp;gt;
        )}
      &amp;lt;/QueryClientProvider&amp;gt;
    &amp;lt;/Router&amp;gt;,
    document.getElementById(&apos;app&apos;)
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;React Query のセットアップ&lt;/h4&gt;
&lt;p&gt;冒頭に書いた通り、まず&lt;code&gt;QueryClient&lt;/code&gt;を作成。
アプリを&lt;code&gt;QueryClientProvider&lt;/code&gt;で囲み、そこに作成したクライアントを渡します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const client = new QueryClient();
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;if (document.getElementById(&apos;app&apos;)) {
  ReactDOM.render(
    &amp;lt;Router&amp;gt;
      &amp;lt;QueryClientProvider client={client}&amp;gt;
        &amp;lt;CssBaseline /&amp;gt;
        &amp;lt;App /&amp;gt;
        {process.env.NODE_ENV === &apos;development&apos; &amp;amp;&amp;amp; (
          &amp;lt;ReactQueryDevtools initialIsOpen={false} /&amp;gt;
        )}
      &amp;lt;/QueryClientProvider&amp;gt;
    &amp;lt;/Router&amp;gt;,
    document.getElementById(&apos;app&apos;)
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ログインユーザ情報の保持&lt;/h4&gt;
&lt;p&gt;まず最初にログインユーザを取得する処理を入れることで、永続化っぽいことをしています。
この&lt;code&gt;useGetUserQuery&lt;/code&gt;フックは&lt;code&gt;useQuery&lt;/code&gt;をラップしたカスタムフックで、ログインユーザ情報が取得できた時は user キーの中へセットするようにしてあります（後述）&lt;/p&gt;
&lt;p&gt;逆にログインユーザが取得できなかった場合は&lt;code&gt;null&lt;/code&gt;をセット。
なお、最初の1回だけでいいので、リトライ回数は0に。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;isLoading&lt;/code&gt;を取得して、取得中の時は簡単なローディング画面を表示するようにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const App: FC = () =&amp;gt; {
  const queryClient = useQueryClient();
  const { isLoading } = useGetUserQuery({
    retry: 0,
    initialData: undefined,
    onError: () =&amp;gt; {
      queryClient.setQueryData(&apos;user&apos;, null);
    },
  });

  if (isLoading) {
    return &amp;lt;Loding /&amp;gt;;
  }

  return (
    &amp;lt;Switch&amp;gt;
      &amp;lt;UnAuthRoute exact path=&quot;/login&quot;&amp;gt;
        &amp;lt;Login /&amp;gt;
      &amp;lt;/UnAuthRoute&amp;gt;
      &amp;lt;AuthRoute exact path=&quot;/&quot;&amp;gt;
        &amp;lt;Memo /&amp;gt;
      &amp;lt;/AuthRoute&amp;gt;
    &amp;lt;/Switch&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;認証ルートと非認証ルート&lt;/h4&gt;
&lt;p&gt;React Router が持っている&lt;code&gt;Route&lt;/code&gt;コンポーネントをラップしたコンポーネントをそれぞれ作成。
この実装は React Router 公式の例を参考にしました。
&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/example/auth-workflow&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;useCurrentUser&lt;/code&gt;フックは、キャッシュからログインユーザ情報を取得するカスタムフックです（後述）
ログインユーザ情報の有無でリダイレクトするようにしています。&lt;/p&gt;
&lt;p&gt;認証ルートの方でリダイレクト時に location を state にセットしているのは、フレンドリーフォワーディングのためです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Props = {
  exact?: boolean;
  path: string;
  children: React.ReactNode;
};

const UnAuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={() =&amp;gt; (user ? &amp;lt;Redirect to={{ pathname: &apos;/&apos; }} /&amp;gt; : children)}
    /&amp;gt;
  );
};

const AuthRoute: FC&amp;lt;Props&amp;gt; = ({ exact = false, path, children }) =&amp;gt; {
  const user = useCurrentUser();
  return (
    &amp;lt;Route
      exact={exact}
      path={path}
      render={({ location }) =&amp;gt;
        user ? (
          children
        ) : (
          &amp;lt;Redirect to={{ pathname: &apos;/login&apos;, state: { from: location } }} /&amp;gt;
        )
      }
    /&amp;gt;
  );
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ローディング画面&lt;/h3&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;画面中央にスピナーを出すだけのシンプルな画面です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import CircularProgress from &apos;@material-ui/core/CircularProgress&apos;;
import Container from &apos;@material-ui/core/Container&apos;;

const Loding: FC = () =&amp;gt; (
  &amp;lt;Container maxWidth=&quot;xs&quot;&amp;gt;
    &amp;lt;Box
      width={1}
      height=&quot;100vh&quot;
      display=&quot;flex&quot;
      alignItems=&quot;center&quot;
      justifyContent=&quot;center&quot;
    &amp;gt;
      &amp;lt;CircularProgress color=&quot;inherit&quot; /&amp;gt;
    &amp;lt;/Box&amp;gt;
  &amp;lt;/Container&amp;gt;
);

export default Loding;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ヘッダー&lt;/h3&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useLogout&lt;/code&gt;フックは&lt;code&gt;useMutation&lt;/code&gt;をラップしたカスタムフックです（後述）
ログアウト処理を行う関数を実行するトリガー関数を受け取り、ログアウトボタン押下時の関数の中で実行しています。
ログアウト処理が成功した場合はログイン画面へリダイレクトするようにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC, useCallback } from &apos;react&apos;;
import { useHistory } from &apos;react-router-dom&apos;;
import Header from &apos;../../components/organisms/Header&apos;;
import { useLogout } from &apos;../../hooks/auth&apos;;
import { useCurrentUser } from &apos;../../hooks/user&apos;;

const EnhancedHeader: FC = () =&amp;gt; {
  const user = useCurrentUser();

  const history = useHistory();
  const { mutate } = useLogout();

  const handleLogout = useCallback(() =&amp;gt; {
    mutate(undefined, {
      onSuccess: () =&amp;gt; {
        history.push(&apos;/login&apos;);
      },
    });
  }, [history, mutate]);

  return &amp;lt;Header userName={user?.name} handleLogout={handleLogout} /&amp;gt;;
};

export default EnhancedHeader;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;ヘッダーにユーザ名表示とログアウトボタンを置くというと、メニューにすることが多いと思われますが、今回は横に並べて配置にしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import AppBar from &apos;@material-ui/core/AppBar&apos;;
import Button from &apos;@material-ui/core/Button&apos;;
import Toolbar from &apos;@material-ui/core/Toolbar&apos;;
import Typography from &apos;@material-ui/core/Typography&apos;;
import useTheme from &apos;@material-ui/core/styles/useTheme&apos;;

type Props = {
  userName?: string;
  handleLogout: VoidFunction;
};

const Header: FC&amp;lt;Props&amp;gt; = ({ userName, handleLogout }) =&amp;gt; {
  const theme = useTheme();
  return (
    &amp;lt;&amp;gt;
      &amp;lt;AppBar
        position=&quot;sticky&quot;
        style={{
          color: theme.palette.text.primary,
          backgroundColor: &apos;white&apos;,
        }}
      &amp;gt;
        &amp;lt;Toolbar&amp;gt;
          &amp;lt;Typography
            component=&quot;h1&quot;
            variant=&quot;h6&quot;
            style={{ flexGrow: 1 }}
            align=&quot;center&quot;
          &amp;gt;
            OOUI-MEMO
          &amp;lt;/Typography&amp;gt;
          {userName &amp;amp;&amp;amp; (
            &amp;lt;&amp;gt;
              &amp;lt;Typography&amp;gt;{userName}&amp;lt;/Typography&amp;gt;
              &amp;lt;Button type=&quot;button&quot; onClick={handleLogout}&amp;gt;
                ログアウト
              &amp;lt;/Button&amp;gt;
            &amp;lt;/&amp;gt;
          )}
        &amp;lt;/Toolbar&amp;gt;
      &amp;lt;/AppBar&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Header;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ログインアラート表示&lt;/h3&gt;
&lt;h4&gt;定数&lt;/h4&gt;
&lt;p&gt;Laravel から返されるステータスコードを定数で定義しています。
とりあえずこの2つだけ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// バリデーションエラー
export const UNPROCESSABLE_ENTITY = 422;

// サーバエラー
export const INTERNAL_SERVER_ERROR = 500;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;ログイン画面において、ログイン失敗時に表示するアラートのコンポーネント。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Alert from &apos;@material-ui/lab/Alert&apos;;
import AlertTitle from &apos;@material-ui/lab/AlertTitle&apos;;
import {
  UNPROCESSABLE_ENTITY,
  INTERNAL_SERVER_ERROR,
} from &apos;../../constants/statusCode&apos;;

type Props = {
  statusCode: number;
};

const LoginAlert: FC&amp;lt;Props&amp;gt; = ({ statusCode }) =&amp;gt; (
  &amp;lt;&amp;gt;
    {statusCode === UNPROCESSABLE_ENTITY &amp;amp;&amp;amp; (
      &amp;lt;Alert severity=&quot;error&quot;&amp;gt;
        &amp;lt;AlertTitle&amp;gt;認証失敗&amp;lt;/AlertTitle&amp;gt;
        入力した情報に誤りがないかご確認ください。
      &amp;lt;/Alert&amp;gt;
    )}
    {statusCode === INTERNAL_SERVER_ERROR &amp;amp;&amp;amp; (
      &amp;lt;Alert severity=&quot;error&quot;&amp;gt;
        &amp;lt;AlertTitle&amp;gt;サーバエラー&amp;lt;/AlertTitle&amp;gt;
        予期しないエラーが発生しました。恐れ入りますが時間をおいて再度お試しください。
      &amp;lt;/Alert&amp;gt;
    )}
  &amp;lt;/&amp;gt;
);

export default LoginAlert;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみにこういうやつです。
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2021/laravel-react-query-auth/login-alert.png&quot; alt=&quot;ログイン画面でのアラート表示画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;ログイン画面&lt;/h3&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useLogin&lt;/code&gt;フックは&lt;code&gt;useMutation&lt;/code&gt;をラップしたカスタムフックです（後述）
ログイン処理を行う関数を実行するトリガー関数を受け取り、ログインボタン押下時の関数の中で実行しています。&lt;/p&gt;
&lt;p&gt;フレンドリーフォワーディングにするため、location.state にセットされたものがあれば、ログイン時にその URL へリダイレクトするようにしています。&lt;/p&gt;
&lt;p&gt;※2021/05/31 追記
from の型を string にしてしまっていますが、正しくは&lt;code&gt;history&lt;/code&gt;の&lt;code&gt;Location&lt;/code&gt;ですね...。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC, useState, useCallback } from &apos;react&apos;;
import { useHistory, useLocation } from &apos;react-router-dom&apos;;
import Login from &apos;../../components/pages/Login&apos;;
import { useLogin } from &apos;../../hooks/auth&apos;;

const EnhancedLogin: FC = () =&amp;gt; {
  const history = useHistory();
  const location = useLocation();
  const { from } = (location.state as { from: string }) || {
    from: { pathname: &apos;/&apos; },
  };

  const { error, isLoading, mutate } = useLogin();
  const statusCode = error?.response?.status;

  const [email, setEmail] = useState(&apos;&apos;);
  const [password, serPassword] = useState(&apos;&apos;);

  const handleChangeEmail = useCallback(
    (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
      setEmail(ev.target.value);
    },
    []
  );

  const handleChangePassword = useCallback(
    (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; {
      serPassword(ev.target.value);
    },
    []
  );

  const handleLogin = useCallback(
    (ev: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; {
      ev.preventDefault();
      if (!email || !password) {
        return;
      }
      mutate(
        { email, password },
        {
          onSuccess: () =&amp;gt; {
            history.replace(from);
          },
        }
      );
    },
    [email, password, history, from, mutate]
  );

  return (
    &amp;lt;Login
      email={email}
      password={password}
      handleChangeEmail={handleChangeEmail}
      handleChangePassword={handleChangePassword}
      statusCode={statusCode}
      isLoading={isLoading}
      handleLogin={handleLogin}
    /&amp;gt;
  );
};

export default EnhancedLogin;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;ログイン実行中に再度ボタンを押されないようにするため、&lt;code&gt;isLoading&lt;/code&gt;を使って、実行中は&lt;code&gt;Backdrop&lt;/code&gt;を表示するようにしています。
（暗転してスピナークルクル表示の部分）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Backdrop from &apos;@material-ui/core/Backdrop&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import Button from &apos;@material-ui/core/Button&apos;;
import CircularProgress from &apos;@material-ui/core/CircularProgress&apos;;
import Container from &apos;@material-ui/core/Container&apos;;
import Card from &apos;@material-ui/core/Card&apos;;
import CardHeader from &apos;@material-ui/core/CardHeader&apos;;
import CardContent from &apos;@material-ui/core/CardContent&apos;;
import TextField from &apos;@material-ui/core/TextField&apos;;
import useTheme from &apos;@material-ui/core/styles/useTheme&apos;;
import Header from &apos;../../containers/organisms/Header&apos;;
import LoginAlert from &apos;../molecules/LoginAlert&apos;;

type Props = {
  email: string;
  password: string;
  handleChangeEmail: (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; void;
  handleChangePassword: (ev: React.ChangeEvent&amp;lt;HTMLInputElement&amp;gt;) =&amp;gt; void;
  statusCode?: number;
  isLoading: boolean;
  handleLogin: (ev: React.FormEvent&amp;lt;HTMLFormElement&amp;gt;) =&amp;gt; void;
};

const Login: FC&amp;lt;Props&amp;gt; = ({
  email,
  password,
  handleChangeEmail,
  handleChangePassword,
  statusCode,
  isLoading,
  handleLogin,
}) =&amp;gt; {
  const theme = useTheme();
  return (
    &amp;lt;&amp;gt;
      &amp;lt;Header /&amp;gt;
      &amp;lt;Container maxWidth=&quot;xs&quot;&amp;gt;
        &amp;lt;Card style={{ margin: `${theme.spacing(6)}px 0` }}&amp;gt;
          &amp;lt;CardHeader title=&quot;login&quot; style={{ textAlign: &apos;center&apos; }} /&amp;gt;
          &amp;lt;CardContent&amp;gt;
            &amp;lt;form onSubmit={handleLogin}&amp;gt;
              &amp;lt;Box
                p={2}
                display=&quot;flex&quot;
                flexDirection=&quot;column&quot;
                alignItems=&quot;center&quot;
              &amp;gt;
                {statusCode &amp;amp;&amp;amp; &amp;lt;LoginAlert statusCode={statusCode} /&amp;gt;}
                &amp;lt;TextField
                  label=&quot;メールアドレス&quot;
                  variant=&quot;outlined&quot;
                  fullWidth
                  value={email}
                  margin=&quot;normal&quot;
                  required
                  autoComplete=&quot;email&quot;
                  autoFocus
                  onChange={handleChangeEmail}
                /&amp;gt;
                &amp;lt;TextField
                  type=&quot;password&quot;
                  label=&quot;パスワード&quot;
                  variant=&quot;outlined&quot;
                  fullWidth
                  value={password}
                  margin=&quot;normal&quot;
                  required
                  autoComplete=&quot;current-password&quot;
                  onChange={handleChangePassword}
                /&amp;gt;
                &amp;lt;Box my={2}&amp;gt;
                  &amp;lt;Button type=&quot;submit&quot; color=&quot;primary&quot; variant=&quot;contained&quot;&amp;gt;
                    ログイン
                  &amp;lt;/Button&amp;gt;
                &amp;lt;/Box&amp;gt;
              &amp;lt;/Box&amp;gt;
            &amp;lt;/form&amp;gt;
          &amp;lt;/CardContent&amp;gt;
        &amp;lt;/Card&amp;gt;
      &amp;lt;/Container&amp;gt;
      &amp;lt;Backdrop style={{ zIndex: theme.zIndex.drawer + 1 }} open={isLoading}&amp;gt;
        &amp;lt;CircularProgress color=&quot;inherit&quot; /&amp;gt;
      &amp;lt;/Backdrop&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Login;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;メモ（アプリホーム）画面&lt;/h3&gt;
&lt;h4&gt;Container Component&lt;/h4&gt;
&lt;p&gt;まだ未実装なので特に処理はないです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Memo from &apos;../../components/pages/Memo&apos;;

const EnhancedMemo: FC = () =&amp;gt; &amp;lt;Memo /&amp;gt;;

export default EnhancedMemo;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Presentational Component&lt;/h4&gt;
&lt;p&gt;アプリのホーム画面になるわけですが、まだ未実装なので、とりあえず Memo とだけ表示するようにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React, { FC } from &apos;react&apos;;
import Box from &apos;@material-ui/core/Box&apos;;
import Container from &apos;@material-ui/core/Container&apos;;
import Header from &apos;../../containers/organisms/Header&apos;;

const Memo: FC = () =&amp;gt; (
  &amp;lt;&amp;gt;
    &amp;lt;Header /&amp;gt;
    &amp;lt;Container&amp;gt;
      &amp;lt;Box m={4}&amp;gt;Memo&amp;lt;/Box&amp;gt;
    &amp;lt;/Container&amp;gt;
  &amp;lt;/&amp;gt;
);

export default Memo;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;User モデル定義&lt;/h3&gt;
&lt;p&gt;name だけのシンプルな型定義です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export type User = {
  name: string;
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;認証に関するカスタムフック&lt;/h3&gt;
&lt;h4&gt;useLogin&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useMutation&lt;/code&gt;をラップした、ログイン処理を行うためのカスタムフック。
成功時は、返却されたログインユーザ情報を user キーにセット。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useQueryClient, UseMutationResult, useMutation } from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;
import { User } from &apos;../../models/User&apos;;

type FormData = {
  email: string;
  password: string;
};

const login = async (formData: FormData): Promise&amp;lt;User&amp;gt; =&amp;gt; {
  const { data } = await axios.post(&apos;/api/login&apos;, formData);
  return data;
};

const useLogin = (): UseMutationResult&amp;lt;
  User,
  AxiosError,
  FormData,
  undefined
&amp;gt; =&amp;gt; {
  const queryClient = useQueryClient();

  return useMutation(login, {
    onSuccess: (data) =&amp;gt; {
      queryClient.setQueryData(&apos;user&apos;, data);
    },
  });
};

export default useLogin;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useLogout&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useMutation&lt;/code&gt;をラップした、ログアウト処理を行うためのカスタムフック。
成功時は、user キーのキャッシュをリセット。&lt;/p&gt;
&lt;p&gt;※2021/04/13 追記
ログアウト API のレスポンスで何も返さない場合は、logout 関数でも何も返さず void 型にした方がいいかもです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useQueryClient, UseMutationResult, useMutation } from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;

const logout = async (): Promise&amp;lt;[]&amp;gt; =&amp;gt; {
  const { data } = await axios.post(&apos;/api/logout&apos;);
  return data;
};

const useLogout = (): UseMutationResult&amp;lt;[], AxiosError, void, undefined&amp;gt; =&amp;gt; {
  const queryClient = useQueryClient();

  return useMutation(logout, {
    onSuccess: () =&amp;gt; {
      queryClient.resetQueries(&apos;user&apos;);
    },
  });
};

export default useLogout;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;名前付きエクスポート&lt;/h4&gt;
&lt;p&gt;使いやすいように、再度エクスポートしてます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export { default as useLogin } from &apos;./useLogin&apos;;
export { default as useLogout } from &apos;./useLogout&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ユーザに関するカスタムフック&lt;/h3&gt;
&lt;h4&gt;useGetUserQuery&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useQuery&lt;/code&gt;をラップした、ログインユーザ情報取得処理を行うカスタムフック。&lt;/p&gt;
&lt;p&gt;※2021/04/13 追記
useGetUserQuery の返り値の型は UseQueryResult ですね...（中身的には一緒だったりするんですが）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { QueryObserverResult, useQuery, UseQueryOptions } from &apos;react-query&apos;;
import axios, { AxiosError } from &apos;axios&apos;;
import { User } from &apos;../../models/User&apos;;

const getLoginUser = async (): Promise&amp;lt;User&amp;gt; =&amp;gt; {
  const { data } = await axios.get(&apos;/api/users/me&apos;);
  return data;
};

const useGetUserQuery = &amp;lt;TData = User&amp;gt;(
  options?: UseQueryOptions&amp;lt;User, AxiosError, TData&amp;gt;
): QueryObserverResult&amp;lt;TData, AxiosError&amp;gt; =&amp;gt;
  useQuery(&apos;user&apos;, getLoginUser, options);

export default useGetUserQuery;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;useCurrentUser&lt;/h4&gt;
&lt;p&gt;キャッシュからログインユーザ情報を取得するカスタムフック。
いろんなところで使用するのでカスタムフック化してます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { useQueryClient } from &apos;react-query&apos;;
import { User } from &apos;../../models/User&apos;;

// ログイン：User  非ログイン時：null  デフォルト：undefined
const useCurrentUser = (): User | null | undefined =&amp;gt; {
  const queryClient = useQueryClient();
  return queryClient.getQueryData(&apos;user&apos;);
};

export default useCurrentUser;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;名前付きエクスポート&lt;/h4&gt;
&lt;p&gt;使いやすいように、再度エクスポートしてます。
改行してるのは、なんとなく API リクエストとそれ以外とでわかりやすくしたかったので。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;export { default as useGetUserQuery } from &apos;./useGetUserQuery&apos;;

export { default as useCurrentUser } from &apos;./useCurrentUser&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;ものすごく長くなってしまいましたが、今回はこんな感じで実装してみました。&lt;/p&gt;
&lt;p&gt;特に React 側に関しては、なるべくきれいに書けるようになりたいという思いもあり、&lt;a href=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot;&gt;りあクト！&lt;/a&gt;のコードを参考にしながら、コード分割をしていきました。
試行錯誤しながらやっていったので、時間もかかってコミット数も無駄に多いです🙄&lt;/p&gt;
&lt;p&gt;TypeScript に関しては、まだ使い始めたばかりなので慣れてないのですが、型定義がきれいにハマった時の入力補完が便利でやばいですね(笑)
引き続き向き合っていきます。&lt;/p&gt;
&lt;p&gt;課題として.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React.Suspense を使って宣言的に書く&lt;/li&gt;
&lt;li&gt;CSRF トークンの期限が切れたときの対応をいれる&lt;/li&gt;
&lt;li&gt;React QUery のオプションの調整&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;とか、まだできそうなことはあるので、そのへんはおいおいやっていこうかとー。&lt;/p&gt;
&lt;p&gt;この実装が何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://readouble.com/laravel/6.x/ja&quot;&gt;Laravel 6.x&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tanstack.com/query/v3&quot;&gt;React Query&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v5.reactrouter.com&quot;&gt;React Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com&quot;&gt;Material UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hypertextcandy.com/vue-laravel-tutorial-introduction&quot;&gt;Vue + Vue Router + Vuex + Laravelで写真共有アプリを作ろう ※連載記事&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot;&gt;GitHub - oukayuka/Riakuto-StartingReact-ja3.1 ※書籍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://fintan.jp/?p=5583&quot;&gt;React Queryを用いた開発事例の紹介&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2020年振り返り ～技術活動、ブログ編～</title><link>https://changeofpace.site/posts/2020-12-31-looking-back-2020-tech</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-12-31-looking-back-2020-tech</guid><description>2020年のGitのコミット履歴から、記事執筆など技術活動の振り返り</description><pubDate>Mon, 21 Dec 2020 16:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;いよいよ今年が終わりますね。どうも、よしです。
先日は精神疾患、体調に関する振り返りを行いましたが、今回は技術活動周りに関する振り返りをやっていきますー。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;2020年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;昨年と同様、まずは Git の Contributions を見ていきます。&lt;/p&gt;
&lt;h4&gt;個人 - GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/looking-back-2020-tech/github-contributions-personal.png&quot; alt=&quot;GitHubの個人アカウントのcontributions履歴&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;こうして見ると割とまばらですね(笑)
昨年から始めた TIL 活動は正直あまりできていなかったかなと。
それでも900を越えるくらいになってます。
昨年は230程度でしたので、まぁそれと比べれば活動できたとも言えるかもしれません。&lt;/p&gt;
&lt;p&gt;土日活動が少し多いなか、5月と9月が比較的多めなのは、web1week というハッカソンイベントに参加していたからです。
それぞれの作品自体は小規模なものでしたが、期間中はゴリゴリと開発やってました。
こういったイベントに参加したのは初めてでしたが、楽しかったですし、参加してよかったです。&lt;/p&gt;
&lt;p&gt;レポート記事.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2020-05-28-web1week-like&quot;&gt;ハッカソンイベントで、React Konva製のジグソーパズルっぽいパズルを作ってみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2020-09-20-web1week-2&quot;&gt;ハッカソンイベントで、「2」に近い式をあてるゲームを作ってみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;あ、でも、5月に関しては仕事が休業中で時間があったので勉強していたというのもありますね。&lt;/p&gt;
&lt;p&gt;8/8に226とかあるのは、当ブログのリポジトリを GitLab から GitHub へ一気に移行したからです。
ちなみにその移行時に使用したスクリプトツールに対して、プルリクを送って、無事にマージされるという経験をしました。
漏れている型定義を追加しただけなので、その内容自体は大したことないのですが、ちょっとした OSS 活動みたいでやってよかったなと。&lt;/p&gt;
&lt;h4&gt;仕事 - GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/looking-back-2020-tech/github-contributions-work.png&quot; alt=&quot;GitHubの仕事アカウントのcontributions履歴&quot; /&amp;gt;
※10月の退職にあたって、アカウントを削除する前に撮っておいたものです。&lt;/p&gt;
&lt;p&gt;3月までの案件では、割とゴリゴリと開発をやっていたので活動多めです。
4月からやっていた案件は保守案件であったため、控えめの活動になっています。&lt;/p&gt;
&lt;p&gt;また、5月は休業となったため活動がありません。&lt;/p&gt;
&lt;h4&gt;個人 - GitLab&lt;/h4&gt;
&lt;p&gt;元々、当ブログのリポジトリは GitLab で管理していたため、GitHub に移行した8月までは活動がありました。
ただ、移行に伴い GitLab でのリポジトリは削除し、 Contributions から消えたのでスクショは撮っていません。&lt;/p&gt;
&lt;p&gt;今後は GitHub をメインで使用していくので、使わなくなっていくと思います。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;p&gt;2020年のブログ履歴はこんな感じでした。
昨年と同様に Google Analytics のデータ部分のみ抜粋しています。
（12月に関しては、執筆時点でのものです）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&lt;/td&gt;
&lt;td&gt;36&lt;/td&gt;
&lt;td&gt;287&lt;/td&gt;
&lt;td&gt;502&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;38&lt;/td&gt;
&lt;td&gt;317&lt;/td&gt;
&lt;td&gt;442&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;568&lt;/td&gt;
&lt;td&gt;867&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;44&lt;/td&gt;
&lt;td&gt;1,095&lt;/td&gt;
&lt;td&gt;1,583&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;430&lt;/td&gt;
&lt;td&gt;640&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;397&lt;/td&gt;
&lt;td&gt;549&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;49&lt;/td&gt;
&lt;td&gt;586&lt;/td&gt;
&lt;td&gt;787&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;51&lt;/td&gt;
&lt;td&gt;518&lt;/td&gt;
&lt;td&gt;766&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;501&lt;/td&gt;
&lt;td&gt;709&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;53&lt;/td&gt;
&lt;td&gt;591&lt;/td&gt;
&lt;td&gt;859&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;55&lt;/td&gt;
&lt;td&gt;434&lt;/td&gt;
&lt;td&gt;623&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;57&lt;/td&gt;
&lt;td&gt;998&lt;/td&gt;
&lt;td&gt;1,343&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul&gt;
&lt;li&gt;アクセスユーザ数計：6,722&lt;/li&gt;
&lt;li&gt;ページビュー数計：9,670&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;昨年は一年集計しても、ユーザ数 711、ページビュー数 1,318 でしたので、悠々とそれを越えました。&lt;/p&gt;
&lt;p&gt;4月になんか一気に伸びてましたが、5月の Google コアアルゴリズムアップデートをもろに被弾してしまったようで、一気にアクセスが落ちました。
とはいえ、自分は別にブロガーというわけではないので、そこまで気にせず。&lt;/p&gt;
&lt;p&gt;それから少しずつアクセスが戻っていってる感じでしょうか。
12月の Google コアアルゴリズムアップデートに関しては、いい影響をうけられたみたいです。&lt;/p&gt;
&lt;p&gt;月に最低1記事は書くという目標が達成できなかったですが、書けなかった月はちょうど不調の時期でもあったので、まぁしょうがないかなと。
書いてる月は2記事以上書けていたので、1年で12記事という意味では達成できてますし。&lt;/p&gt;
&lt;h3&gt;ブログ以外の技術記事活動&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://zenn.dev/h_yoshikawa0724&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/h-yoshikawa44&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※Zenn および Qiita に投稿した記事は、すべて当ブログでも投稿しています。&lt;/p&gt;
&lt;p&gt;これまで、ブログ + Qiita で活動してきましたが、今年の9月に Zenn がリリースされたことにより、以降は Zenn + ブログで活動していくことにしました。
Qiita からの移行にあたって、React 関連の記事をいくつか Zenn にも転載しています。
Zenn は GitHub と連携して、コンテンツ管理できるのでいいですね（自分はプライベートリポジトリで管理してます）&lt;/p&gt;
&lt;p&gt;web1week に参加した時は、イベント自体が Crieit 発のものであるため、その参加レポート記事を Crieit に投稿しました。
こういった参加レポート記事は、いつもよりも自分の言葉そのままで書ける感じがして、書いていてちょっと楽しかったですね。&lt;/p&gt;
&lt;p&gt;※2024/09/14追記：Crieit はサービスクローズされました。&lt;/p&gt;
&lt;h2&gt;2021年はどうしていきたいか&lt;/h2&gt;
&lt;p&gt;※前回の体調編の振り返り記事と同じようなことを言っている部分もありますが、ご容赦ください。&lt;/p&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;p&gt;今年も TIL 活動を継続しようとして、 年始に Docusaurus でサイト化したのはよかったものの、あまり活動できていなかったなと。
最低限、毎月ポートフォリオページを更新することくらいは、さすがにやってはいたのですが。
なので来年はざっくりとでも、勉強・TIL 活動のペースを作るかなぁとか考えてます。&lt;/p&gt;
&lt;p&gt;まぁ、まずは社会復帰するところからですし、他にちゃんと趣味の時間も作りたいと思っているので、自分の調子を見ながら試行錯誤していく感じになるかと。&lt;/p&gt;
&lt;p&gt;「フロントエンドとバックエンドで1つずつ自分の技術の軸になるものがほしい」なんて、昨年の振り返りの時は書いていました。
...が、今の自分のスキルを考えると結局中途半端でなんだか嫌になってくるので、まずはフロントエンドかバックエンド、どちらかを専門とした方がいいかなと思うようになり。&lt;/p&gt;
&lt;p&gt;経験3年もあって、〇〇エンジニアと名乗れないレベルってどうなのよ？という。
React 周りを勉強して、もっと伸ばしたい欲があるので、フロントエンドをメインにしていこうかなぁとぼんやり思っています。
TypeScript とか Next.js とか習得したかったのに、全然できてないので、そこらへんとか。
そのうえで、バックエンドとか他の分野のことも少しわかるよみたいな。&lt;/p&gt;
&lt;p&gt;それと個人開発してたら、一応は一通り体験するわけですし、個人開発も何かしらやりたいですね。
web1week みたいなハッカソンイベントにまた参加したり、OOUI ワークアウト実践（設計するなら実際に作ってみよう企画）をやったりしようかなとか。&lt;/p&gt;
&lt;h3&gt;仕事&lt;/h3&gt;
&lt;p&gt;まずは新しい会社を探すところからですね。
会社を退職してから、最低でも年内は療養にあてるということで過ごしてきましたが、もう少し充電・勉強期間が欲しいかなという思いもあり。
とはいえ、無収入が続くのはつらいので、さすがに春先くらいまでには決めたいところです。&lt;/p&gt;
&lt;p&gt;できれば精神疾患に理解のある会社さんに巡り合いたいですね。
新しい会社を探すにあたって、精神疾患のことは不利に働く可能性が高いと思っていますが、自分はオープンにして活動するつもりです。
オープンにしておいた方が、後から無用のトラブルになるのを避けられると思うからです。&lt;/p&gt;
&lt;p&gt;精神疾患に理解がある = 手厚く配慮してほしい
というわけではないです。
もちろんサポートが得られるのであれば、それがいいのは確かにそうなんですが、それって別に精神疾患の人に限ったことじゃないよね？って。
普通にチームメンバー間でサポートしあったりすると思いますし、そういう環境であれば仕事しやすいんじゃないかなぁと。&lt;/p&gt;
&lt;h3&gt;ブログ・技術記事&lt;/h3&gt;
&lt;p&gt;来年も、1か月に最低1記事のペースでゆるりとやるつもりです。
今年、会社の中での目標として記事を月に2記事書くなんて決めたこともあったのですが、「書かないといけない」みたいな意識になってしまった結果捗りませんでした。
なので、ゆるりとやった方が自分には合っているようです。&lt;/p&gt;
&lt;p&gt;Zenn にはまだ新しい記事を投稿できていないですし、来年は Zenn + ブログで記事投稿していきます。&lt;/p&gt;
&lt;p&gt;また、構想として、ブログを Next.js 製に移行しようかなと考えていたりもします。
React 周りを勉強するにあたって、TypeScript や Next.js なども習得することになるので、なんとなく使用技術スタックを狭めたいという思いがあり。&lt;/p&gt;
&lt;p&gt;執筆時点の Jekyll 製でも特に困ったことはないので、まぁ気が向いたらやるかもしれません。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;緩やかな目標の中で過ごしてきた2020年。
達成できなかった目標もありましたが、総合してみれば、まぁそれなりに頑張った方かなと思うことにします。
前年より活動できていれば、まぁいいかっていう。&lt;/p&gt;
&lt;p&gt;昨年の振り返りの時期には、まさか会社を退職することまでになるとは考えてもいなかったですが、人生何が起こるかわからないですからね。
起きてしまったことはしょうがないので、また地道に人生の軌道修正をしていこうと思います。
この機会に今後の立ち回り方とか、改めて考えてみるのも楽しそうですね。&lt;/p&gt;
&lt;p&gt;2021年の振り返りの頃には、いろんな意味で自立できているといいなぁ。&lt;/p&gt;
&lt;p&gt;ではでは、皆様、よいお年をお迎えくださいー。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>GitリポジトリをGitLabからGitHubへ移行する</title><link>https://changeofpace.site/posts/2020-08-13-migrate-gitlab-to-github</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-08-13-migrate-gitlab-to-github</guid><description>GitLabで管理していたリポジトリのコード（コミット履歴）、ブランチ、タグ、ラベル、マイルストーン、Issue、マジリクをGitHubへ移行するための手順を記録</description><pubDate>Thu, 13 Aug 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;当ブログのソースコードは GitLab で管理していたのですが、普段 GitHub の方を使用することがほとんどなので、先日 GitHub へ移行しました。
ソースコードのほかに、Issue やマジリクなどの移行できたので、その手順を残しておきます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;移行にあたって&lt;/h2&gt;
&lt;h3&gt;ミラーリングで対応できるもの&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;ソースコード（+ コミット履歴）&lt;/li&gt;
&lt;li&gt;ブランチ&lt;/li&gt;
&lt;li&gt;タグ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらはリポジトリのミラーリングを行うことで比較的すぐに移行できます。&lt;/p&gt;
&lt;p&gt;これに加えて Wiki についても、Wiki データのミラーリングを行うことで対応できるようです。
※今回は Wiki については試してません。&lt;/p&gt;
&lt;h3&gt;移行スクリプトで対応できるもの&lt;/h3&gt;
&lt;p&gt;以下のデータも移行したかったのですが、リポジトリのミラーリングでは対応していませんでした。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ラベル&lt;/li&gt;
&lt;li&gt;マイルストーン&lt;/li&gt;
&lt;li&gt;Issue&lt;/li&gt;
&lt;li&gt;マジリク&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;しかし、有志の方がこれらのデータを移行するスクリプトを公開されていたので、今回はこちらを使わせていただきました。
&amp;lt;OG url=&quot;https://github.com/piceaTech/node-gitlab-2-github&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;移行スクリプトを使うための前提&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;Node.js 導入済み&lt;/li&gt;
&lt;li&gt;npm か yarn コマンドが使えること&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ちなみに移行元、移行先ともにプライベートリポジトリでも使用できました。&lt;/p&gt;
&lt;h3&gt;移行の仕様&lt;/h3&gt;
&lt;p&gt;移行スクリプトの &lt;a href=&quot;https://github.com/piceaTech/node-gitlab-2-github/tree/cf9b3761b4b398883f89e3cb68005fa81c9157e2&quot;&gt;2020/08/08 時点&lt;/a&gt;での仕様です。&lt;/p&gt;
&lt;h4&gt;ラベル&lt;/h4&gt;
&lt;p&gt;GitHub のデフォルトのラベルに、移行したラベルが追加されます。
色も同じっぽかったです。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;gitlab merge request&lt;/code&gt;というラベルも追加されます。
このラベルは、マジリクを Issue 変換したものに付与されます。&lt;/p&gt;
&lt;h4&gt;マイルストーン&lt;/h4&gt;
&lt;p&gt;タイトルや対象範囲、open か close なのかなど、ほぼそのまま移行されます。&lt;/p&gt;
&lt;h4&gt;Issue&lt;/h4&gt;
&lt;p&gt;移行元
※すみません、移行元のスクショは撮り忘れました...。&lt;/p&gt;
&lt;p&gt;移行先
&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/convert-issue.png&quot; alt=&quot;移行先のGitHub Issue詳細画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;タイトルやコメント、open なのか close なのかなど、ほぼそのまま移行されます。
ただ、その作成日時は移行実施した日時になるため、コメントに&lt;code&gt;In GitLab by @h-yoshikawa0724 on Jun 28, 2020, 18:14&lt;/code&gt;のような、移行元での情報が記載されるようになっています。&lt;/p&gt;
&lt;p&gt;画像アップロード内容まで移行されるかは未確認ですが、S3にアップロードするような設定もあるようです。&lt;/p&gt;
&lt;p&gt;また、何らかの原因で Issue 移行（移行先リポジトリに Issue 作成）に失敗することがありますが、その救済設定（&lt;code&gt;useReplacementIssuesForCreationFails&lt;/code&gt;）がデフォルトで有効になっています。
（&lt;a href=&quot;#%E7%A7%BB%E8%A1%8C%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A&quot;&gt;移行スクリプトの設定&lt;/a&gt;も参照）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;自分の場合は、最初の頃、少し手伝ってもらっていた先輩が作ったIssue が移行時にエラーとなっていました。
移行元と移行先ともにプライベートリポジトリであったこと、今は移行元リポジトリからも抜けていて、移行先リポジトリにいなかったことが、恐らくエラーの原因じゃないかと思われます。&lt;/p&gt;
&lt;p&gt;この設定が有効になっていると、以下のような代替 Issue が作成されます。
移行元の Issue の説明が失われてしまいますが、その他の情報は引き継がれるようです。
&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/convert-replacement-issue.png&quot; alt=&quot;移行先のGitHub （代替）Issue詳細画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;マジリク&lt;/h4&gt;
&lt;p&gt;そのマジリクの状態に応じて、挙動が変わります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Open → プルリク（Open）に移行される？（今回、この状態のマジリクがなかったので未確認）&lt;/li&gt;
&lt;li&gt;Closed → プルリク（Closed）に移行される&lt;/li&gt;
&lt;li&gt;Merged + ブランチ削除済み → Issue（Closed）に変換される（&lt;code&gt;gitlab merge request&lt;/code&gt;のラベルがつく）&lt;/li&gt;
&lt;li&gt;Merged + ブランチあり → エラーで変換されない&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;こちらについても作成日時は移行実施した日時になるため、コメントに&lt;code&gt;In GitLab by @h-yoshikawa0724 on Jun 28, 2020, 18:14&lt;/code&gt;のような、移行元での情報が記載されます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable  --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;注意点としては、マージ済みの際の挙動でしょうか。
3については以下のような感じです。&lt;/p&gt;
&lt;p&gt;移行元
&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/convert-merge-request-to-issue-before.png&quot; alt=&quot;移行元のGitLab マージ済みマジリク一覧画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;移行先
&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/convert-merge-request-to-issue-after.png&quot; alt=&quot;移行先のGitHub 変換されたクローズ済みIssue一覧画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/convert-merge-request-to-issue-info.png&quot; alt=&quot;移行先のGitHub 変換されたクローズ済みIssue詳細画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;4については、マージ元のブランチはあるが、マージ先との差分がないためにエラーとなってしまうようです。
何も移行されない問題がありますが、移行スクリプトの設定（&lt;code&gt;useIssuesForAllMergeRequests&lt;/code&gt;）により、強制的に3と同じ挙動にすることで対応可能です。
（&lt;a href=&quot;#%E7%A7%BB%E8%A1%8C%E3%82%B9%E3%82%AF%E3%83%AA%E3%83%97%E3%83%88%E3%81%AE%E8%A8%AD%E5%AE%9A&quot;&gt;移行スクリプトの設定&lt;/a&gt;も参照）&lt;/p&gt;
&lt;h4&gt;使用上限&lt;/h4&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/piceaTech/node-gitlab-2-github#import-limit&quot;&gt;README - import-limit&lt;/a&gt; に注意書きがあります。&lt;/p&gt;
&lt;p&gt;この移行スクリプトでは、内部的に GitHub API を使用しています。
この GitHub API では、1時間あたり5000 API リクエストという制限があるため、移行内容が多いリポジトリだと&lt;strong&gt;上限に引っ掛かる可能性がある&lt;/strong&gt;ことに注意です。&lt;/p&gt;
&lt;h2&gt;リポジトリ移行手順&lt;/h2&gt;
&lt;p&gt;前置きが長くなってしまいましたが、ここから実際の手順になります。&lt;/p&gt;
&lt;h3&gt;リポジトリの用意とミラーリング&lt;/h3&gt;
&lt;p&gt;&lt;strong&gt;※補足&lt;/strong&gt;&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;移行元 - GitLabリポジトリ：h-yoshikawa0724/changeofpace&lt;/li&gt;
&lt;li&gt;移行先 - GitHubリポジトリ：h-yoshikawa0724/change-of-pace&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;として書いていっているので、適宜ご自分のリポジトリ情報に置き換えてください。&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;GitHub で移行先リポジトリを用意（作成するだけで OK）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;移行元のベアリポジトリをクローン&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone --mirror git@gitlab.com:h-yoshikawa0724/changeofpace.git
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;移行元のベアリポジトリを、移行先に反映&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cd changeofpace.git

git push --no-verify --mirror git@github.com:h-yoshikawa0724/change-of-pace.git
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この時点で、コード（コミット履歴）、ブランチ、タグは移行完了です。&lt;/p&gt;
&lt;p&gt;なお、ベアリポジトリというのは作業ディレクトリを持たず、操作履歴などの情報のみを持つリポジトリのことを指すそうです。
末尾に&lt;code&gt;.git&lt;/code&gt;とつくのも特徴で、実際に配下ディレクトリを見ると以下のようになっていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ls changeofpace.git/

config  description  HEAD  hooks/  info/  objects/  packed-refs  refs/
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/devzooiiooz/items/56a02342d9d65d79f6c3&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;アクセストークンの用意&lt;/h3&gt;
&lt;h4&gt;GitLab&lt;/h4&gt;
&lt;p&gt;プロフィールアイコンのSettings → Access Tokens. から。
&lt;code&gt;api&lt;/code&gt;と&lt;code&gt;read_repository&lt;/code&gt;の scope を持ったトークンを生成し、その値を控えておきます。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/access-token-gitlab.png&quot; alt=&quot;GitLabでのアクセストークン設定画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;GitHub&lt;/h4&gt;
&lt;p&gt;プロフィールアイコンのSettings → Developer settings → Personal access tokens. から。
「Generate new token」を押すと、アカウントのパスワード入力を求められるので入力。
&lt;code&gt;repo&lt;/code&gt;の scope を持ったトークンを生成し、その値を控えておきます。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/migrate-gitlab-to-github/access-token-github.png&quot; alt=&quot;GitHubでのアクセストークン設定画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;移行スクリプトの用意&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;移行スクリプトのダウンロード&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/piceaTech/node-gitlab-2-github.git
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;ライブラリのインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cd node-gitlab-2-github

npm -i
# or
yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;移行スクリプトの設定&lt;/h3&gt;
&lt;h4&gt;設定ファイルの用意&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;src/settings.ts&lt;/code&gt;に以下を追記（&lt;code&gt;useReplacementIssuesForCreationFails: boolean;&lt;/code&gt;の下にでも）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;useIssuesForAllMergeRequests: boolean;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※最近追加された設定の型定義が漏れていたようです。これがないとスクリプトが動きません。
ただ、自分がこの修正のプルリクを出したので、マージされるなり対応されたら、この手順は不要になります。
→ &lt;strong&gt;2020/09/01追記&lt;/strong&gt; &lt;strong&gt;無事&lt;a href=&quot;https://github.com/piceaTech/node-gitlab-2-github/pull/74&quot;&gt;マージ&lt;/a&gt;されました！&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;sample_settings.ts&lt;/code&gt;を&lt;code&gt;settings.ts&lt;/code&gt;にリネームして、設定を編集
自分の場合の例.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;import Settings from &apos;./src/settings&apos;;

export default {
  gitlab: {
    // url: &apos;https://gitlab.mycompany.com&apos; // 公式でなく独自にホスティングしている場合のみ、その URL
    token: &apos;XXXXXXXXXXXXX&apos;, // 先ほど控えたトークン
    projectId: &apos;XXXXXXX&apos;, // 後述参考
  },
  github: {
    // baseUrl: &apos;https://gitlab.mycompany.com:123/etc&apos;, // 公式でなく独自にホスティングしている場合のみ、その URL
    owner: &apos;h-yoshikawa0724&apos;, // 移行先リポジトリのオーナーアカウント ID
    token: &apos;XXXXXXXXXXXXX&apos;, // 先ほど控えたトークン
    repo: &apos;change-of-pace&apos;, // 移行先リポジトリ名
  },
  usermap: {
    &apos;h-yoshikawa0724&apos;: &apos;h-yoshikawa0724&apos;, // GitLab アカウントID: GitHub アカウント ID のマッピング（移行時にこれに応じて変換される）
  },
  projectmap: {
    &apos;h-yoshikawa0724/changeofpace&apos;: &apos;h-yoshikawa0724/change-of-pace&apos;, // GitLab リポジトリ名: GitHub リポジトリ名
  },
  conversion: {
    useLowerCaseLabels: true,
  },
  debug: false, // デバッグ実行するかどうか
  usePlaceholderIssuesForMissingIssues: true, // 削除されたIssue があった時などに、移行元と移行先とでその分 Issue 番号がずれるを防ぐために空の Issue を作成するか
  useReplacementIssuesForCreationFails: true, // 何らかの原因で Issue 移行に失敗した際に、代替 Issue を作成するか（移行元の説明は失われるが、それ以外は引き継がれる）
  useIssuesForAllMergeRequests: true, // 強制的に全ての移行元マジリクを移行先 Issue に変換するか
  skipMatchingComments: [], // コメント移行をスキップするワード設定（このワードを含むコメントは移行されない、ワードの大文字小文字は区別しない）
  mergeRequests: {
    logFile: &apos;./merge-requests.json&apos;,
    log: false, // 移行元マジリクを logFile に出力するかどうか（出力する場合は移行先へ移行されない）
  },
} as Settings;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定の詳細や他の設定が知りたい場合は、&lt;a href=&quot;https://github.com/piceaTech/node-gitlab-2-github#where-to-find-info-for-the-settingsts&quot;&gt;README - Where to find info for the settings.ts&lt;/a&gt; に説明が記載されています。&lt;/p&gt;
&lt;h4&gt;プロジェクト ID の確認方法&lt;/h4&gt;
&lt;p&gt;GitLab のリポジトリトップ画面に表示されているので、そこで確認できます。&lt;/p&gt;
&lt;p&gt;もしくは、一度&lt;code&gt;projectId&lt;/code&gt;を&lt;code&gt;null&lt;/code&gt;にした状態で、このスクリプトを実行すると確認できます。
他の設定が問題なければ、リポジトリの一覧が表示されて、そこへプロジェクト ID が表示されるようになっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn start

yarn run v1.22.4
node node_modules/ts-node/dist/bin.js ./src/index.ts
XXXXXXX          change-of-pace         --



Select which project ID should be transported to github. Edit the settings.js accordingly. (gitlab.projectID)



Done in 14.89s.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;移行スクリプトの実行&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;npm run start
# or
yarn start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで移行が開始されます。
当然、移行内容の量が多いほど時間がかかるので気長に待ちましょう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Transfer complete!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;と表示されたら移行完了です。
ちゃんと移行されているか確認してみましょう。&lt;/p&gt;
&lt;p&gt;ちなみに自分の場合は、設定の関係上2回実行することになったのですが、&lt;strong&gt;基本的には&lt;/strong&gt;前回の移行で作成された分と重複して移行（作成）されるということはないようです。&lt;/p&gt;
&lt;p&gt;ログ出力を見た感じ、
ラベルやマイルストーンは「既に存在しています」
Issue やプルリクは「更新しました」
みたいな英語が表示されていたような気がします。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ただ、&lt;code&gt;useReplacementIssuesForCreationFails&lt;/code&gt;設定により作られた代替 Issue（Issue タイトル末尾に&lt;code&gt;[REPLACEMENT ISSUE]&lt;/code&gt;がついているもの）に関しては、既に存在すると認識されないようで、重複して作られてしまいました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;その他の設定移行&lt;/h2&gt;
&lt;p&gt;必要に応じて、その他の設定も移行しましょう。
自分の場合は、移行元リポジトリと Netlify を連携させていたので、移行先リポジトリで連携しなおしました。&lt;/p&gt;
&lt;h3&gt;Netlify&lt;/h3&gt;
&lt;p&gt;Settings → Build &amp;amp; Deploy → Continuous Deployment → Build settings → 「Edit settings」
から、連携するリポジトリ更新。&lt;/p&gt;
&lt;h2&gt;後処理&lt;/h2&gt;
&lt;p&gt;これもまた必要に応じて行いましょう。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;旧 GitLab リポジトリの削除&lt;/li&gt;
&lt;li&gt;ミラーリング時にローカルへ作った、ベアリポジトリの削除&lt;/li&gt;
&lt;li&gt;今回移行したプロジェクトのローカルリポジトリのリモートURLを、移行先リポジトリの方に設定&lt;/li&gt;
&lt;li&gt;アクセストークンを削除&lt;/li&gt;
&lt;/ul&gt;
&lt;hr /&gt;
&lt;p&gt;今回、特に大きな問題もなくリポジトリの移行ができて安心しました。&lt;/p&gt;
&lt;p&gt;この移行スクリプトを作成された方に感謝ですね！
使い方をちゃんと理解したうえで使用すれば、とても便利なスクリプトなので、移行を考えられている方はぜひ使ってみてはいかがでしょうか。&lt;/p&gt;
&lt;p&gt;ではでは。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/piceaTech/node-gitlab-2-github&quot;&gt;GitHub - node-gitlab-2-github&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/nishioka-sctv/items/e66cdab447aaf6ecb77c&quot;&gt;GitLabからGitHubへの移行&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mseeeen.msen.jp/migrate-gitlab-issues-to-github/&quot;&gt;GitLab の Issue を GitHub に移行する node.js スクリプトを試す&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/devzooiiooz/items/56a02342d9d65d79f6c3&quot;&gt;gitのベアリポジトリとノンベアリポジトリ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>React(SPA)アプリをFirebase Hostingにデプロイ</title><link>https://changeofpace.site/posts/2020-06-06-react-firebase-deploy</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-06-06-react-firebase-deploy</guid><description>Firebaseに登録してプロジェクトを作成、アプリを追加して、React(SPA)アプリを連携させてFirebase Hostingにデプロイするまでの手順を記録</description><pubDate>Sat, 06 Jun 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先日、初めて Firebase を使ってデプロイする機会がありました。
基本的には案内に沿って進めるだけでしたが、せっかくなので手順をメモしておきます。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/441da9ac75eaeea00537&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-10-06-react-firebase-deploy&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;デプロイする React アプリがすでにある&lt;/li&gt;
&lt;li&gt;npm or yarn が使える環境である（自分は yarn を使用しているので、以降 yarn で書いていきます）&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Firebase 側&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://firebase.google.com&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;新規登録・プロジェクト作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Firebase へアクセス&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;「使ってみる」「プロジェクトを作成」を選択
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase.png&quot; alt=&quot;Firebase トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;プロジェクトの作成&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;プロジェクト名を入力 + 利用規約にチェック して「続行」
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-project-name.png&quot; alt=&quot;Firebaseプロジェクト プロジェクト名入力画面&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;Google Analytics を有効にして「続行」（※今回は有効にしていますが任意）
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-ga.png&quot; alt=&quot;Firebaseプロジェクト Google Analytics設定画面&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;連携する Google Analytics アカウントを選択（もしくは新規作成）して「プロジェクトを作成」
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-ga-account.png&quot; alt=&quot;Firebaseプロジェクト Google Analytics連携設定画面&quot; /&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;少し時間がかかりますが、これでプロジェクトが作成されます。&lt;/p&gt;
&lt;h3&gt;アプリを追加&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;プロジェクトトップから、追加するアプリのプラットフォームの中から「ウェブ」を選択
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-project-top.png&quot; alt=&quot;Firebaseプロジェクト トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ウェブアプリに Firebase を追加&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;アプリのニックネームを入力 + Firebase Hosting 設定を有効化 して「アプリを登録」
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-app-add.png&quot; alt=&quot;Firebaseアプリ追加 基本設定画面&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;Firebase SDK を予約済み URL から使用する場合は、スクリプトを控えておき「次へ」（※予約済み URL については&lt;a href=&quot;#firebase-sdk-%E3%81%AE%E5%B0%8E%E5%85%A5&quot;&gt;こちら&lt;/a&gt;も参照）
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-app-sdk.png&quot; alt=&quot;Firebaseアプリ追加 SDKスクリプト案内画面&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;Firebase CLI のインストール方法を確認して「次へ」
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-app-cli-install.png&quot; alt=&quot;Firebaseアプリ追加 CLIインストール案内画面&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;Firebase Hosting へのデプロイ方法を確認して「コンソールに進む」
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-app-hosting-deploy.png&quot; alt=&quot;Firebaseアプリ追加 デプロイ案内画面&quot; /&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これで Firebase プロジェクトにウェブアプリが追加されました。&lt;/p&gt;
&lt;h2&gt;React アプリ側&lt;/h2&gt;
&lt;p&gt;基本的には、Firebase プロジェクトにアプリを追加した時に表示された手順で進めていきます。&lt;/p&gt;
&lt;h3&gt;Firebase SDK の導入&lt;/h3&gt;
&lt;p&gt;Firebase SDK を導入する方法としては、以下の3つがあり。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;予約済み URL にあるものを読み込み使用する&lt;/li&gt;
&lt;li&gt;CDN で配信されているものを読み込み使用する&lt;/li&gt;
&lt;li&gt;ライブラリとしてインストールし使用する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Firebase SDK Snippet に関しては Firebase プロジェクトの「設定」「プロジェクトを設定」のマイアプリから確認できます。&lt;/p&gt;
&lt;p&gt;補足として、予約済み URL というのは Firebase Hosting での URL のことを指します。
その URL 配下に SDK およびアプリの識別情報のファイルが配置されるようになっており、それを読み込んで SDK を使用する形になります。
そのため、この方法は Firebase Hosting を使ってホスティングする場合のみ、使用可能です。
主に同じコードを複数の Firebase Hosting へデプロイする際に便利な方法のようです。&lt;/p&gt;
&lt;p&gt;自分も最初は予約済み URL での方法にしていましたが、&lt;code&gt;yarn start&lt;/code&gt;での通常のローカルサーバからは SDK を読み込むことができないために、Warning が出てしまいます。
これが気になりやめました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2020/10/18追記&lt;/strong&gt;
&lt;code&gt;firebase serve&lt;/code&gt;によるエミュレートであれば、予約済み URL 経由の SDK 使用が可能のようです。
ただ、こちらの場合は、あくまでビルド生成物ディレクトリの中身をローカルでホスティングしているものです。
そのため、デプロイ前のテストに使うような感じで、ホットリロードで動作を見ながら開発という用途には向いていません。&lt;/p&gt;
&lt;p&gt;今回はライブラリとしてインストールして使用する方法で進めます。&lt;/p&gt;
&lt;h4&gt;ライブラリのインストール&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;yarn add firebase
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;&lt;code&gt;.env&lt;/code&gt;を作成&lt;/h4&gt;
&lt;p&gt;設定値は Firebase SDK Snippet の「構成」から確認できます。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-sdk.png&quot; alt=&quot;Firebase SDK Snippet 構成情報画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;.env&lt;/code&gt;の例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Firebase
REACT_APP_APP_KEY=※apikey
REACT_APP_AUTH_DOMAIN=※authDomain
REACT_APP_DATABASE_URL=※databaseURL
REACT_APP_PROJECT_ID=※projectId
REACT_APP_STORAGEBUCKET=※storageBucket
REACT_APP_MESSAGING_SENDER_ID=※messagingSenderId
REACT_APP_APP_ID=※appId
REACT_APP_MEASUREMENT_ID=※measurementId
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Firebase SDK の初期化を行うファイルを作成&lt;/h4&gt;
&lt;p&gt;各種 Firebase の機能を使うには最初に初期化処理を行う必要があります。
そのため、初期化処理をするファイルをあらかじめ用意しておき、機能を使う際、 import するようにします。&lt;/p&gt;
&lt;p&gt;例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import firebase from &apos;firebase/app&apos;;
import &apos;firebase/analytics&apos;;

const config = {
  apiKey: process.env.REACT_APP_APP_KEY,
  authDomain: process.env.REACT_APP_AUTH_DOMAIN,
  databaseURL: process.env.REACT_APP_DATABASE_URL,
  projectId: process.env.REACT_APP_PROJECT_ID,
  storageBucket: process.env.REACT_APP_STORAGEBUCKET,
  messagingSenderId: process.env.REACT_APP_MESSAGING_SENDER_ID,
  appId: process.env.REACT_APP_APP_ID,
  measurementId: process.env.REACT_APP_MEASUREMENT_ID,
};

firebase.initializeApp(config);

export default firebase;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Google Analyticsにデータ送信する設定を追記&lt;/h4&gt;
&lt;p&gt;自分の場合は&lt;code&gt;index.js&lt;/code&gt;に追記しました。
最初は、SDK 組み込みだけで自動的にデータ送信してくれるものと思っていましたが、&lt;code&gt;firebase.analytics()&lt;/code&gt;をやらないといけないようです。&lt;/p&gt;
&lt;p&gt;以下の内容を&lt;code&gt;ReactDOM.render&lt;/code&gt;の上に追記。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import firebase from &apos;./libs/Firebase&apos;;

// 本番環境のみ計測
if (process.env.NODE_ENV === &apos;production&apos;) {
  firebase.analytics();
}
.
.
.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Firebase プロジェクトのアプリとの連携&lt;/h3&gt;
&lt;h4&gt;Firebase CLI の導入&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;yarn global add firebase-tools
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで firebase コマンドが使えるようになります。&lt;/p&gt;
&lt;h4&gt;firebase へログイン&lt;/h4&gt;
&lt;p&gt;もし Docker の Node.js 環境を使用している場合は、OAuth 認証で&lt;code&gt;9005&lt;/code&gt;ポートを使用するので事前に開けておきましょう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;firebase login
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Firebase optionally collects CLI usage and error reporting information to help improve our products. Data is collected in accordance with Google&apos;s privacy policy (https://policies.google.com/privacy) and is not used to identify you.

? Allow Firebase to collect CLI usage and error reporting information? (Y/n)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;Firebase はオプションで、CLI の使用状況とエラー報告情報を収集して、製品の改善に役立てます。データは Google のプライバシーポリシー（ https://policies.google.com/privacy ）に従って収集され、ユーザーの特定には使用されません。&lt;/p&gt;
&lt;p&gt;Firebase が CLI の使用状況とエラー報告情報を収集することを許可しますか？ （はい/いいえ）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;任意で回答。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;To change your data collection preference at any time, run `firebase logout` and log in again.
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;データ収集の設定をいつでも変更するには、 &lt;code&gt;firebase logout&lt;/code&gt;を実行して、再度ログインします。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code&gt;Visit this URL on this device to log in:
https://accounts.google.com/o/oauth2/auth?XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX

Waiting for authentication...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;認証のための URL が表示されるので、ブラウザでアクセス（Docker を使用しない場合は、恐らく自動でブラウザが開きます）&lt;/p&gt;
&lt;p&gt;内容を確認して「許可」
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-oauth.png&quot; alt=&quot;Firebase Googleアカウント認証確認画面&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✔  Success! Logged in as XXX@gmail.com
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;無事に認証できると CLI 側はこの表示が出ます。
また、ブラウザ側では以下のような表示が出ます。
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-firebase-deploy/firebase-oauth-complete.png&quot; alt=&quot;Firebase認証完了画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;連携・初期化処理&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;firebase init
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;? Which Firebase CLI features do you want to set up for this folder? Press Space to select features, then Enter to confirm your choices. (Press &amp;lt;space&amp;gt; to select, &amp;lt;a&amp;gt; to toggle all, &amp;lt;i&amp;gt; to invert selection)
❯◯ Database: Deploy Firebase Realtime Database Rules
 ◯ Firestore: Deploy rules and create indexes for Firestore
 ◯ Functions: Configure and deploy Cloud Functions
 ◯ Hosting: Configure and deploy Firebase Hosting sites
 ◯ Storage: Deploy Cloud Storage security rules
 ◯ Emulators: Set up local emulators for Firebase features
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;このフォルダに設定する Firebase CLI 機能はどれですか？スペースを押して機能を選択し、Enter を押して選択を確認します。 （スペース を押して選択、a を押してすべてを切り替え、i を押して選択を反転します）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今回は&lt;code&gt;Hosting&lt;/code&gt;のみ選択。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;First, let&apos;s associate this project directory with a Firebase project.
You can create multiple project aliases by running firebase use --add,
but for now we&apos;ll just set up a default project.

? Please select an option: (Use arrow keys)
❯ Use an existing project
  Create a new project
  Add Firebase to an existing Google Cloud Platform project
  Don&apos;t set up a default project
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-mixed-period --&amp;gt; */}&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;まず、このプロジェクトディレクトリを Firebase プロジェクトに関連付けます。
複数のプロジェクトエイリアスを作成するには、firebase use --add,
ただし、ここではデフォルトのプロジェクトを設定します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;今回はすでに Firebase プロジェクトを作っているので&lt;code&gt;Use an existing project&lt;/code&gt;を選択。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Select a default Firebase project for this directory: (Use arrow keys)
❯ project-name (project-name)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;このディレクトリのデフォルトの Firebase プロジェクトを選択：（矢印キーを使用）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;連携させるプロジェクトを選択。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Your public directory is the folder (relative to your project directory) that
will contain Hosting assets to be uploaded with firebase deploy. If you
have a build process for your assets, use your build&apos;s output directory.

? What do you want to use as your public directory? (public)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;パブリックディレクトリは、プロジェクトディレクトリを基準にしたフォルダです。
firebase deploy でアップロードされるホスティングアセットが含まれます。もし、あなたが
アセットのビルドプロセスがある場合は、ビルドの出力ディレクトリを使用します。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;code&gt;yarn build&lt;/code&gt;での生成物は&lt;code&gt;/build&lt;/code&gt;に作成されるので、&lt;code&gt;build&lt;/code&gt;と入力。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Configure as a single-page app (rewrite all urls to /index.html)? (y/N)
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;SPA アプリとして構成しますか（すべての URL を /index.html に書き換えます）？ （はい/いいえ）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;今回は SPA なので&lt;code&gt;y&lt;/code&gt;を入力。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;✔  Wrote build/index.html

i  Writing configuration info to firebase.json...
i  Writing project information to .firebaserc...

✔  Firebase initialization complete!
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで設定ファイルが作成されて、連携は完了です。&lt;/p&gt;
&lt;h3&gt;Firebase Hosting へデプロイ&lt;/h3&gt;
&lt;p&gt;ビルドして、その生成物をデプロイします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn build

# 全ての Firebase サービスリソースをデプロイ
firebase deploy

# Hosting のリソースのみデプロイ + コメント
firebase deploy --only hosting -m &quot;※任意のコメント&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※2021/09/05追記
デプロイ時にコメントをつけておくと、Firebase Hosting のデプロイ履歴にも表示されるのでわかりやすくなります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=== Deploying to &apos;project-name&apos;...

i  deploying hosting
i  hosting[project-name]: beginning deploy...
i  hosting[project-name]: found 19 files in build
✔  hosting[project-name]: file upload complete
i  hosting[project-name]: finalizing version...
✔  hosting[project-name]: version finalized
i  hosting[project-name]: releasing new version...
✔  hosting[project-name]: release complete

✔  Deploy complete!

Project Console: https://console.firebase.google.com/project/project-name/overview
Hosting URL: https://project-name.web.app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示されるホスティング URL にアクセスして、アプリの内容がちゃんと表示されれば、無事デプロイ完了です。&lt;/p&gt;
&lt;h2&gt;おまけ・GitHub Actions での自動デプロイ&lt;/h2&gt;
&lt;p&gt;※2021/09/05追記.&lt;/p&gt;
&lt;h3&gt;CI 用トークンの取得&lt;/h3&gt;
&lt;p&gt;Firebase CLI を使うためにブラウザでログインが必要になりましたが、CI 環境ではそういうわけにもいかないのでトークンを使用する方法が提供されています。&lt;/p&gt;
&lt;p&gt;まずはローカルで以下コマンドを実行してログインし、CI 環境用のトークンを取得。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;firebase login:ci
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;後はこのトークンを Firebase CLI 使用時にオプションで渡すようにすれば OK です。
GitHub Secrets に&lt;code&gt;FIREBASE_TOKEN&lt;/code&gt;と登録しておいて、そこから使うようにします。&lt;/p&gt;
&lt;h3&gt;ビルド&lt;/h3&gt;
&lt;p&gt;※Node.js のセットアップやライブラリのインストールのステップ部分については、当記事では省略します。&lt;/p&gt;
&lt;p&gt;今回は、Firebase アプリの情報を環境変数で管理しているので、CI 環境でも環境変数を設定。
GitHub Secrets にあらかじめ登録しておき、その値で設定する例です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# ビルド
- name: Build
  env:
    REACT_APP_APP_KEY: ${{ secrets.REACT_APP_APP_KEY }}
    REACT_APP_AUTH_DOMAIN: ${{ secrets.REACT_APP_AUTH_DOMAIN }}
    REACT_APP_DATABASE_URL: ${{ secrets.REACT_APP_DATABASE_URL }}
    REACT_APP_PROJECT_ID: ${{ secrets.REACT_APP_PROJECT_ID }}
    REACT_APP_STORAGEBUCKET: ${{ secrets.REACT_APP_STORAGEBUCKET }}
    REACT_APP_MESSAGING_SENDER_ID: ${{ secrets.REACT_APP_MESSAGING_SENDER_ID }}
    REACT_APP_APP_ID: ${{ secrets.REACT_APP_APP_ID }}
    REACT_APP_MEASUREMENT_ID: ${{ secrets.REACT_APP_MEASUREMENT_ID }}
  run: yarn build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;デプロイ&lt;/h3&gt;
&lt;p&gt;Firebase CLI を使えるアクションがあるので、これを使うと楽にデプロイできます。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/w9jds/firebase-action&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Deploy to Firebase Hosting
  uses: w9jds/firebase-action@v2.0.0
  with:
    args: deploy --only hosting -m \&quot;${{ github.event.head_commit.message }}\&quot;
  env:
    FIREBASE_TOKEN: ${{ secrets.FIREBASE_TOKEN }}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2021/09/05 時点で最終リリースが 2020/11/09 というのが少し気になるところですが、比較的最近のコミット履歴はありましたので、このアクションを紹介してみました。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Firebase を初めて使いましたが、思っていた以上に簡単にデプロイ出来てよかったです。
React との相性もいいみたいですし、他の機能も使ってみたいなーと。
可能性が広がりますね！&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://firebase.google.com&quot;&gt;Firebase&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/58483428/google-analytics-on-my-react-app-with-firebase-sdk&quot;&gt;stack overflow - Google analytics on my React app with firebase SDK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/hibohiboo/items/091286736c461fe68fd5&quot;&gt;docker でfirebaseを試したメモ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/w9jds/firebase-action&quot;&gt;GitHub - firebase-action&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>React×HOC環境を支援する、Recompose入門</title><link>https://changeofpace.site/posts/2020-05-06-react-recompose</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-05-06-react-recompose</guid><description>高階コンポーネント (Higher-Order Component)を扱うユーティリティ的なライブラリである、Recomposeの主な機能を紹介</description><pubDate>Wed, 06 May 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今回は React Hooks が普及する前の話。
主に関数コンポーネントに機能を付与することに使われる HOC を取り扱う Recompose についてです。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/e6988691c6a0a9bd6833&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-10-03-react-recompose&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2022/01/24追記 あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました（旧：React入門 ～Recompose編～）&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Recompose とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/acdlite/recompose&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;前提知識として、React には高階コンポーネント (Higher-Order Component、通称 HOC)という概念があります。
具体的には、あるコンポーネントを受け取って、それに機能を付与した新規のコンポーネントを返すような関数のことを指します。
これまでの記事でも HOC と書いていたものは、これのことでした。&lt;/p&gt;
&lt;p&gt;HOC を利用することで、HOC 側にロジック、コンポーネント側はビューといったように責務を分離させたり、ロジック部分を複数のコンポーネントで再利用したりといったことができます。
また、state やライフサイクルを持てない関数コンポーネントに、これらの機能を付与も可能です。&lt;/p&gt;
&lt;p&gt;Recompose はこの HOC を扱うユーティリティ的なライブラリです。
以前は多くの方々に利用されていましたが、React 16.8 で追加された React Hooks により、React 本体だけでも同様のことができるようになりました。&lt;/p&gt;
&lt;p&gt;そのためライブラリの更新はすでに止まっており、今後は使われなくなっていくのではないかと考えられますが、業務で使用する機会があったので今回記事として書くことにしました。&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yarn add recompose
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回使用するバージョンは&lt;code&gt;0.30.0&lt;/code&gt;です。&lt;/p&gt;
&lt;h2&gt;使い方&lt;/h2&gt;
&lt;p&gt;以下、記載しているコードは公式サンプルのコードを元にしています。&lt;/p&gt;
&lt;h3&gt;基本的な使い方&lt;/h3&gt;
&lt;p&gt;HOC を使う場合、主に以下のような書き方をします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const enhance = HOC(Component);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでの定数名、HOC 名、コンポーネントはあくまで仮のものです。
その中身としては、以下のようなものになります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;enhance：機能が追加され、新たに作成されたコンポーネント&lt;/li&gt;
&lt;li&gt;HOC：引数のコンポーネントに何らかの処置を施す関数&lt;/li&gt;
&lt;li&gt;Componet：元となるコンポーネント&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;元となるコンポーネントをHOCでラップするイメージですね。&lt;/p&gt;
&lt;p&gt;これを踏まえたうえで、Recompose が提供する HOC を使用した例がこちら。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { withState } from &apos;recompose&apos;;

const enhance = withState(&apos;counter&apos;, &apos;setCounter&apos;, 0);

const Component = enhance(({ counter, setCounter }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;カウンター: {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(n =&amp;gt; n + 1)}&amp;gt;Increment&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(n =&amp;gt; n - 1)}&amp;gt;Decrement&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;gifs/2020/react-recompose/state-counter.gif&quot; alt=&quot;withStateを使用したカウンターのGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;上記では state を扱えるようにする&lt;code&gt;withState&lt;/code&gt;で、ビュー側であるコンポーネントをラップするようになっています。&lt;code&gt;withState&lt;/code&gt;で定義した state と state を更新する関数は props に渡されるので、そこから使用できます。&lt;/p&gt;
&lt;h3&gt;複数の HOC を併用するやり方&lt;/h3&gt;
&lt;p&gt;Recompose ではいろんな種類の HOC が提供されているので、それらを併用して使用したい場合もあります。&lt;/p&gt;
&lt;p&gt;その場合、普通にやろうとすると以下のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const enhance = HOC1(HOC2(Component));
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際のコードにするとこんな感じです。
&lt;code&gt;withState&lt;/code&gt;と&lt;code&gt;withHandlers&lt;/code&gt;の組み合わせ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { withState, withHandlers } from &apos;recompose&apos;;

const stateEnhance = withState(&apos;counter&apos;, &apos;setCounter&apos;, 0);

const handleEnhance = withHandlers({
  incrementCounter: props =&amp;gt; () =&amp;gt; {
    props.setCounter(v =&amp;gt; v + 1)
  },
  decrementCounter: props =&amp;gt; () =&amp;gt; {
    props.setCounter(v =&amp;gt; v - 1)
  },
});

const Component = stateEnhance(handleEnhance((
  { counter, incrementCounter, decrementCounter }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;カウンター: {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={incrementCounter}&amp;gt;Increment&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={decrementCounter}&amp;gt;Decrement&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}));

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例では2つの HOC なのでまだいい方ですが、こここからさらに数が増えるとラップする数が増えて可読性が落ちてえらいことに...。
また、先に書いた HOC から実行されるので、上記のように&lt;code&gt;withHandlers&lt;/code&gt;のなかで&lt;code&gt;withState&lt;/code&gt;にて定義したものを使用している場合は、&lt;code&gt;withState&lt;/code&gt;の方を先に書く必要があります。&lt;/p&gt;
&lt;p&gt;この可読性の問題を解消するためには&lt;code&gt;compose&lt;/code&gt;という関数を使うとよいです。
&lt;code&gt;compose&lt;/code&gt;を使うと、以下のように書くことができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const enhance = compose(HOC1, HOC2)(Component);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実際のコードだとこんな感じです。
複数の HOC をまとめて書けるので、すっきりしますね。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { compose, withState, withHandlers } from &apos;recompose&apos;;

const enhance = compose(
  withState(&apos;counter&apos;, &apos;setCounter&apos;, 0),
  withHandlers({
    incrementCounter: props =&amp;gt; () =&amp;gt; {
      props.setCounter(v =&amp;gt; v + 1)
    },
    decrementCounter: props =&amp;gt; () =&amp;gt; {
      props.setCounter(v =&amp;gt; v - 1)
    }
  })
)

const ComposeComponent = enhance(
  ({
    counter,
    incrementCounter,
    decrementCounter
  }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;カウンター：{counter}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incrementCounter}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={decrementCounter}&amp;gt;Decrement&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
})

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Redux との併用&lt;/h3&gt;
&lt;p&gt;Redux と併用したい場合は、react-redux の&lt;code&gt;connect&lt;/code&gt;を使うとよいです。
この&lt;code&gt;connect&lt;/code&gt;も HOC が使われているそうで、同様に&lt;code&gt;compose&lt;/code&gt;で他の HOC とまとめて書くことができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { connect } from &apos;react-redux&apos;;
import { compose } from &apos;recompose&apos;;
import { bindActionCreators } from &apos;redux&apos;;
import { incrementOn, decrementOn } from &apos;../../Actions/Counter&apos;;

const enhance = compose(
  connect(
    state =&amp;gt; ({
      counter: state.counter
    }),
    dispatch =&amp;gt; ({
      actions: bindActionCreators({ incrementOn, decrementOn }, dispatch)
    })
  )
)

const ComposeComponent = enhance(
  ({ counter, actions }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;カウンター：{counter}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={actions.incrementOn}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={actions.decrementOn}&amp;gt;Decrement&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
})

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HOC の種類&lt;/h3&gt;
&lt;p&gt;数が多いので一部のみ紹介。&lt;/p&gt;
&lt;h4&gt;無駄な再レンダリングを抑制する：pure&lt;/h4&gt;
&lt;p&gt;props が変更されない限り、コンポーネントが更新されないようにします。
変更されたことを検知するロジックとしては&lt;code&gt;shallowEqual&lt;/code&gt;が使われているようです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { pure } from &apos;recompose&apos;;

const enhance = pure;

const Component = enhance(
  () =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;pure test&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;props を置き換える：mapProps&lt;/h4&gt;
&lt;p&gt;現在の props を関数が返すものに置き換えます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;mapProps&lt;/code&gt;実行後は、&lt;code&gt;num1&lt;/code&gt;と&lt;code&gt;num2&lt;/code&gt;はなくなり、&lt;code&gt;sum&lt;/code&gt;という props のみに置き換えられます。&lt;/p&gt;
&lt;p&gt;使用例（propsに num1={10}、num={20} 指定）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { mapProps } from &apos;recompose&apos;;

const enhance = mapProps(props =&amp;gt; {
  return {
    sum: props.num1 + props.num2
  }
})
const Component = enhance(({ num1, num2, sum }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{num1 ? num1 : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{num2 ? num2 : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{sum}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/map-props.png&quot; alt=&quot;mapPropsを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;props を追加する：withProps&lt;/h4&gt;
&lt;p&gt;現在の props に関数が返すものを追加します。&lt;/p&gt;
&lt;p&gt;使用例（propsに num1={10}、num2={20} 指定）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { withProps } from &apos;recompose&apos;;

const enhance = withProps(props =&amp;gt; {
  return {
    sum: props.num1 + props.num2
  }
})
const Component = enhance(({ num1, num2, sum }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{num1 ? num1 : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{num2 ? num2 : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{sum}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/with-props.png&quot; alt=&quot;withPropsを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;指定した props が変更された時のみ、props を追加する：withPropsOnChange&lt;/h4&gt;
&lt;p&gt;基本的には&lt;code&gt;withProps&lt;/code&gt;と同じであるものの、こちらは指定した props が変更された場合のみ props の追加が行われます。&lt;/p&gt;
&lt;p&gt;使用例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { withPropsOnChange } from &apos;recompose&apos;;

const enhance = withPropsOnChange([&apos;num&apos;], props =&amp;gt; {
  return {
    sum: props.num * 2
  }
})
const Component = enhance(({ num, sum }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{num ? num : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{sum ? sum : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;props に&lt;code&gt;num&lt;/code&gt;を指定しなかった場合
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/with-props-on-change-no-props.png&quot; alt=&quot;withPropsOnChangeを使って、propsを指定しなかった場合のサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;props に&lt;code&gt;num&lt;/code&gt;を指定した場合
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/with-props-on-change.png&quot; alt=&quot;withPropsOnChangeを使って、propsを指定した場合のサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;props のデフォルト値を指定する：defaultProps&lt;/h4&gt;
&lt;p&gt;React 本体で使用できる&lt;code&gt;defaultProps&lt;/code&gt;プロパティとほぼ同じことができるものの、厳密には違う模様。
なお、コンポーネント呼び出し時に対象の props が指定されていた時は、そちらが優先して使われます。&lt;/p&gt;
&lt;p&gt;使用例（props に指定なし）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { defaultProps } from &apos;recompose&apos;;

const enhance = defaultProps({
  text: &apos;default&apos;
})

const Component = enhance(({ text }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{text}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/default-props.png&quot; alt=&quot;defaultPropsを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;props の名前を変更する：renameProp&lt;/h4&gt;
&lt;p&gt;第1引数の名称の props を第2引数の名称にリネーム。
この HOC 1つにつき、1つしか書けません。&lt;/p&gt;
&lt;p&gt;使用例（props に&lt;code&gt;text=&quot;テスト&quot;&lt;/code&gt;を指定）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { renameProp } from &apos;recompose&apos;;

const enhance = renameProp(&apos;text&apos;, &apos;renameText&apos;);

const Component = enhance(({ text, renameText }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{text ? text : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{renameText ? renameText : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/rename-prop.png&quot; alt=&quot;renamePropを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;一度に複数の props の名前を変更する：renameProps&lt;/h4&gt;
&lt;p&gt;renameProps の複数版。&lt;/p&gt;
&lt;p&gt;使用例（propsに&lt;code&gt;text=&quot;テスト&quot; num={10}&lt;/code&gt;を指定）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { renameProps } from &apos;recompose&apos;;

const enhance = renameProps({
  &apos;text&apos;: &apos;renameText&apos;,
  &apos;num&apos;: &apos;renameNum&apos;
});

const Component = enhance(({ text, renameText, num, renameNum }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{text ? text : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{renameText ? renameText : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{num ? num : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{renameNum ? renameNum : &apos;propsなし&apos;}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/rename-props.png&quot; alt=&quot;renamePropsを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;平坦化した props を追加する：flattenProp&lt;/h4&gt;
&lt;p&gt;あくまで平坦化した props を追加なので、平坦化の元になった props もそのまま残ります。&lt;/p&gt;
&lt;p&gt;使用例（propsに&lt;code&gt;obj={{&apos;a&apos;: &apos;A&apos;, &apos;b&apos;: &apos;B&apos;, &apos;c&apos;: &apos;C&apos;}}&lt;/code&gt;を指定）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &quot;react&quot;;
import { flattenProp } from &apos;recompose&apos;;

const enhance = flattenProp(&apos;obj&apos;);

const Component = enhance(({ obj, a, b, c }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;{obj.a}・{obj.b}・{obj.c}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{a}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{b}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;{c}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/flatten-prop.png&quot; alt=&quot;flattenPropを使ったサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;state を追加する：withState&lt;/h4&gt;
&lt;p&gt;第1引数に state 名、第2引数に state を更新する関数、第3引数にデフォルト値を指定します。
state を更新する関数を使用する際の引数は、ただ設定値だけを渡すほかに、現在の値を引数とした処理の記述も可能です。
デフォルト値の指定に関しても、単純な値のほかにコールバック関数も指定できます。&lt;/p&gt;
&lt;p&gt;使用例（基本的な使い方の例と同じです）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { withState } from &apos;recompose&apos;;

const enhance = withState(&apos;counter&apos;, &apos;setCounter&apos;, 0);

const Component = enhance(({ counter, setCounter }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;p&amp;gt;カウンター: {counter}&amp;lt;/p&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(n =&amp;gt; n + 1)}&amp;gt;Increment&amp;lt;/button&amp;gt;
      &amp;lt;button onClick={() =&amp;gt; setCounter(n =&amp;gt; n - 1)}&amp;gt;Decrement&amp;lt;/button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
});

export default Component;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;gifs/2020/react-recompose/state-counter.gif&quot; alt=&quot;withStateを使用したカウンターのGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;関数ハンドラーを追加する：withHandlers&lt;/h4&gt;
&lt;p&gt;定義した関数ハンドラーには props が渡されるので、その値を使った処理を記述できます。&lt;/p&gt;
&lt;p&gt;使用例（複数の HOC を併用するやり方の例と同じです）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { compose, withState, withHandlers } from &apos;recompose&apos;;

const enhance = compose(
  withState(&apos;counter&apos;, &apos;setCounter&apos;, 0),
  withHandlers({
    incrementCounter: props =&amp;gt; () =&amp;gt; {
      props.setCounter(v =&amp;gt; v + 1)
    },
    decrementCounter: props =&amp;gt; () =&amp;gt; {
      props.setCounter(v =&amp;gt; v - 1)
    }
  })
)

const ComposeComponent = enhance(
  ({
    counter,
    incrementCounter,
    decrementCounter
  }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;カウンター：{counter}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incrementCounter}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={decrementCounter}&amp;gt;Decrement&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
})

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※プレビューは withState の例と同じなので省略。&lt;/p&gt;
&lt;h4&gt;state と関数ハンドラーを追加する：withStateHandlers&lt;/h4&gt;
&lt;p&gt;state と、その state に関する関数ハンドラーをまとめて定義したい時は、こちらを使用。&lt;/p&gt;
&lt;p&gt;使用例（props に指定なし）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { withStateHandlers } from &apos;recompose&apos;;

const enhance = withStateHandlers(
  ({ initialCounter = 0 }) =&amp;gt; ({
    counter: initialCounter,
  }),
  {
    incrementOn: props =&amp;gt; () =&amp;gt; ({
      counter: props.counter + 1,
    }),
    decrementOn: props =&amp;gt; () =&amp;gt; ({
      counter: props.counter - 1,
    }),
    resetCounter: (_, { initialCounter = 0 }) =&amp;gt; () =&amp;gt; ({
      counter: initialCounter,
    }),
  }
)

const ComposeComponent = enhance(
  ({ counter, incrementOn, decrementOn, resetCounter }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;カウンター：{counter}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incrementOn}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={decrementOn}&amp;gt;Decrement&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={resetCounter}&amp;gt;Reset&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    )
})

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;gifs/2020/react-recompose/state-handlers-counter.gif&quot; alt=&quot;withStateHandlersを使用したカウンターのGIF&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;ローカル Reducer を追加する：withReducer&lt;/h4&gt;
&lt;p&gt;Action を発行して、そのタイプに応じた状態更新をする Redux ライクな処理を書くことができます。
Redux を使うまでではないが、より複雑な状態管理をしたいという時に向いています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { compose, withReducer, withHandlers } from &apos;recompose&apos;;

const counterReducer = (count, action) =&amp;gt; {
  switch (action.type) {
    case &apos;INCREMENT&apos;:
      return count + 1
    case &apos;DECREMENT&apos;:
      return count - 1
    default:
      return count
  }
}

const enhance = compose(
  withReducer(&apos;counter&apos;, &apos;dispatch&apos;, counterReducer, 0),
  withHandlers({
    incrementOn: props =&amp;gt; () =&amp;gt; props.dispatch({type: &apos;INCREMENT&apos;}),
    decrementOn: props =&amp;gt; () =&amp;gt; props.dispatch({type: &apos;DECREMENT&apos;}),
  })
);

const ComposeComponent = enhance(
  ({ counter, incrementOn, decrementOn }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;カウンター：{counter}&amp;lt;/p&amp;gt;
        &amp;lt;button onClick={incrementOn}&amp;gt;Increment&amp;lt;/button&amp;gt;
        &amp;lt;button onClick={decrementOn}&amp;gt;Decrement&amp;lt;/button&amp;gt;
      &amp;lt;/div&amp;gt;
    );
});

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※プレビューは withState の例と同じなので省略。&lt;/p&gt;
&lt;h4&gt;ライフサイクルを追加する：lifecycle&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;componentDidMount&lt;/code&gt;をはじめとした、ライフサイクルを追加できます。&lt;/p&gt;
&lt;p&gt;使用例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import { compose, withState, lifecycle } from &apos;recompose&apos;;

const enhance = compose(
  withState(&apos;text&apos;, &apos;setText&apos;, &apos;&apos;),
  lifecycle({
    componentDidMount() {
      this.props.setText(&apos;initial&apos;);
    }
  })
)

const ComposeComponent = enhance(({ text }) =&amp;gt; {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;p&amp;gt;{text}&amp;lt;/p&amp;gt;
      &amp;lt;/div&amp;gt;
    )
})

export default ComposeComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/react-recompose/lifecycle.png&quot; alt=&quot;lifecycleを使用したサンプル画像&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;Recompose は業務で使用した経験があったので、記事に起こすのそんなに難しくないと思いきや、思いのほか機能が多かったです（苦笑）
自分が使った機能はほんの一部にすぎなかったようです。&lt;/p&gt;
&lt;p&gt;またもや長くなって力尽きたので、一部機能のみ紹介にしました。
気が向いたら他の機能も書くかも？&lt;/p&gt;
&lt;p&gt;今後使われなくなっていくと思われるライブラリではありますが、保守案件とかで触れる機会がある可能性はあるので、さらっとした知識は一応持っておきたいですね。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/acdlite/recompose&quot;&gt;GitHub - recompose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://recompose.docsforhumans.com/&quot;&gt;Recompose&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.reactjs.org/docs/higher-order-components.html&quot;&gt;React - 高階 (Higher-Order) コンポーネント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/usagi-f/items/a04e8129654b43ee7d37&quot;&gt;小さく始めるrecompose 〜Reactリファクタリング手引き〜&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/locol23/items/ad9c7553176c239f0b15&quot;&gt;recomposeではじめるReact Higher-order Components(HoCs)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>React向けUIコンポーネントライブラリ、Material UI(v4)の紹介</title><link>https://changeofpace.site/posts/2020-04-23-react-material-ui</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-04-23-react-material-ui</guid><description>ReactのUIコンポーネントライブラリの1つであるMaterial UIについて、おおよそどんなことができるものなのか紹介</description><pubDate>Thu, 23 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今回は React 向けに UI コンポーネント群を提供する Material UI のお話です。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/efa33101b0a02cba7759&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-09-24-react-material-ui&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2021/08/26追記 TypeScript ベース + バージョン 4.12.3 で全体的に見直しました。
※2021/09/26追記 Material UI v5記事投稿に伴い、一部追記しました。
※2022/01/24追記 あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました（旧：React入門 ～Material UI(v4)編～）&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Material UI とは？&lt;/h2&gt;
&lt;p&gt;最新
&amp;lt;OG url=&quot;https://mui.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;4系
&amp;lt;OG url=&quot;https://v4.mui.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Google の Material デザインをベースに開発された、UI コンポーネントライブラリです。&lt;/p&gt;
&lt;p&gt;お手軽に Material デザインを取り入れられることに加えて、コンポーネントの種類が豊富に用意されているため、それらを組み合わせるだけでも見栄えの良いものを作ることができます。
一からコンポーネントを作るのはつらいとか、デザインを考えるのが難しいとか、それらに工数をあまりかけたくないなどの場合にもおすすめです。&lt;/p&gt;
&lt;p&gt;React 向けの UI コンポーネントライブラリはいろんなものがあります。
（2021/08/26時点で）最近では Chakra UI や daisy UI、Headless UI。などといった、Tailwind CSS をベースとしていたり、影響をうけたライブラリが特に伸びている印象ですね。
そんな中でも、Material UI は変わらず伸び続けています。
（Ant Design もすごい伸び続けてる👀）&lt;/p&gt;
&lt;p&gt;執筆時点でのリポジトリスター数（※2021/08/26更新）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;UI コンポーネントライブラリ&lt;/th&gt;
&lt;th&gt;GitHub スター数&lt;/th&gt;
&lt;th&gt;最終リリース&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/mui-org/material-ui&quot;&gt;Material UI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;70.5k&lt;/td&gt;
&lt;td&gt;2021/07/30&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/ant-design/ant-design&quot;&gt;Ant Design&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;73.9k&lt;/td&gt;
&lt;td&gt;2021/08/16&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/chakra-ui/chakra-ui&quot;&gt;Chakra UI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;20.1k&lt;/td&gt;
&lt;td&gt;2021/08/09&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/saadeghi/daisyui&quot;&gt;daisyUI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4.5k&lt;/td&gt;
&lt;td&gt;2021/08/22&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/tailwindlabs/headlessui&quot;&gt;Headless UI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10.5k&lt;/td&gt;
&lt;td&gt;2021/07/29&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/react-toolbox/react-toolbox&quot;&gt;React Toolbox&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;8.7k&lt;/td&gt;
&lt;td&gt;2018/06/02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/rebassjs/rebass&quot;&gt;Rebass&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;7.4k&lt;/td&gt;
&lt;td&gt;2019/08/28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/nikgraf/belle&quot;&gt;Belle&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;2.5k&lt;/td&gt;
&lt;td&gt;2017/10/10&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/muicss/mui&quot;&gt;MUI&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;4.4k&lt;/td&gt;
&lt;td&gt;2020/06/02&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/grommet/grommet&quot;&gt;Grommet&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;7.4k&lt;/td&gt;
&lt;td&gt;2021/07/28&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/react-bootstrap/react-bootstrap&quot;&gt;React-Bootstrap&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;19.8k&lt;/td&gt;
&lt;td&gt;2021/06/04&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href=&quot;https://github.com/reactstrap/reactstrap/&quot;&gt;reactstrap&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;10.1k&lt;/td&gt;
&lt;td&gt;2021/01/20&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;注意書き&lt;/h2&gt;
&lt;p&gt;あらかじめ言っておくのですが、今回はそこまで実践的なことは書いていません。
というのも、Material UI の機能を紹介するにしても、できることが多すぎてどこを抜粋するか悩んでしまったためです（苦笑）&lt;/p&gt;
&lt;p&gt;公式ドキュメントを見ていただくとよくわかるのですが、あらゆる使用例、サンプルコード（JS、TS ともに）およびプレビュー表示が豊富に載っています。さらっと見ていくだけでも、こんなコンポーネントも用意されているのかと発見があって楽しいくらいです。&lt;/p&gt;
&lt;p&gt;なので、提供されているコンポーネントに関しては、大まかな概要を紹介する程度にとどめます。
もし興味がわいた方はぜひ公式ドキュメントを見てみてください。
各コードには CodeSandbox へのリンクもあるので、お気軽に試せますよ。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&lt;s&gt;2021/09/16時点で、5系が beta リリースされています&lt;/s&gt; 2021/09/16に5系がリリースされましたが、&lt;strong&gt;当記事では4系を扱っています。&lt;/strong&gt;
4系と5系とでは、ブランド名が変わっていたり、一部コンポーネントの所属が変わっていたり（lab のものが core に取り込まれるなど）と大きな変更点もありますのでバージョンには注意です。
↓
※2021/09/26追記 v5 の記事を書きました。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2021-09-26-material-ui-v5&quot;&gt;こんにちはMUI！ 新しくなったMaterial UI v5&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;p&gt;一言で Material UI と言っても、パッケージ（ライブラリ）的にはいくつかに分かれています。
その中で基本となるのが&lt;code&gt;@material-ui/core&lt;/code&gt;で、このライブラリだけでもほとんどのコンポーネントが使用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add @material-ui/core
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;その他のライブラリとしては以下のようなものがあります。必要に応じて適宜インストールしてください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;@material-ui/icons&lt;/code&gt;：SVG アイコンコンポーネント集（アイコン一覧：&lt;a href=&quot;https://v4.mui.com/components/material-icons/&quot;&gt;Material UI - Material Icons&lt;/a&gt;）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@material-ui/lab&lt;/code&gt;：トグルボタンなど、core に取り込む前のコンポーネント集&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@material-ui/pickers&lt;/code&gt;：Date Picker 的なコンポーネント集&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@material-ui/x-grid&lt;/code&gt;：データグリッド機能を提供するコンポーネント（フル機能のエンタープライズ版）&lt;/li&gt;
&lt;li&gt;&lt;code&gt;@material-ui/data-grid&lt;/code&gt;：データグリッド機能を提供するコンポーネント（無料のコミュニティ版）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回の使用バージョンは以下のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：17.0.2&lt;/li&gt;
&lt;li&gt;@material-ui/core：4.12.3&lt;/li&gt;
&lt;li&gt;@material-ui/icons：4.12.2&lt;/li&gt;
&lt;li&gt;@material-ui/lab：4.0.0-alpha.60&lt;/li&gt;
&lt;li&gt;@material-ui/pickers：3.3.10&lt;/li&gt;
&lt;li&gt;@material-ui/data-grid：4.0.0-alpha.37&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;以下、記載しているコードは公式ドキュメントのコードを元にしています。
画像や GIF に関しても、公式ドキュメントのプレビューが撮影元です。&lt;/p&gt;
&lt;h2&gt;基本的な使い方&lt;/h2&gt;
&lt;p&gt;使いたいコンポーネントをインポートして使用。
独自定義のコンポーネントを使用する時とほぼ同じですが、コンポーネント特有の props を設定して見た目のカスタマイズができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;Button variant=&quot;contained&quot; color=&quot;primary&quot;&amp;gt;
      Test
    &amp;lt;/Button&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;スタイルのカスタマイズ&lt;/h2&gt;
&lt;p&gt;デザインシステムの実体であるスタイリングソリューションとしては JSS が採用されています。
&amp;lt;OG url=&quot;https://github.com/cssinjs/jss&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;カスタマイズ方法は、さすがに全部は紹介しきれないので、代表的なものだけ紹介。&lt;/p&gt;
&lt;p&gt;各コンポーネントは props でカスタマイズできる範囲外で、自分で好きなスタイルをあてることも可能です。
JSX においてインラインスタイルを書く時と同様に、CSS プロパティ名はキャメルケースで記述することに注意です。&lt;/p&gt;
&lt;p&gt;スタイル定義の優先度は、低い方から.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;各コンポーネント特有の props によるスタイル&lt;/li&gt;
&lt;li&gt;Hooks 式・styled-components 式・HOC 式による独自定義のスタイル&lt;/li&gt;
&lt;li&gt;インラインスタイルによる独自定義のスタイル&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;となります。
もし同一の CSS プロパティを複数指定した場合は、この優先度に沿ってスタイルがあたります。&lt;/p&gt;
&lt;h3&gt;Hooks 式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@material-ui/core/styles&lt;/code&gt;の&lt;code&gt;makeStyles&lt;/code&gt;を使います。&lt;/p&gt;
&lt;p&gt;「クラス名とスタイル定義をマッピングしたものを返す関数」を生成するので、あとはその関数をコンポーネント側で使用して取り出し。
従来の HTML・CSS でクラスをあてるように、各要素に割り当てていく方式です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { makeStyles } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const useStyles = makeStyles({
  button: {
    backgroundColor: &apos;green&apos;
  }
});

type Props = {
  children: ReactNode;
}

const HooksButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  const classes = useStyles();

  return (
    &amp;lt;Button variant=&quot;contained&quot; className={classes.button}&amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
}

export default HooksButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;引数を渡して、その値でスタイル定義をしたい場合はこんな感じ。
クラス単位でも CSS プロパティ単位でも受け取ることができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, CSSProperties, ReactNode } from &apos;react&apos;;
import { makeStyles } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

type MyButtonStyle = {
  bgColor: CSSProperties[&apos;backgroundColor&apos;];
  textColor: CSSProperties[&apos;color&apos;];
}

const useStyles = makeStyles({
  // クラス単位
  button: (style: MyButtonStyle) =&amp;gt; ({
    backgroundColor: style.bgColor,
  }),
  textColor: {
    // CSS プロパティ単位
    color: (style: MyButtonStyle) =&amp;gt; style.textColor
  }
});

type Props = {
  children: ReactNode;
}

const HooksExButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  const styles = { bgColor: &apos;green&apos;, textColor: &apos;black&apos;}
  const classes = useStyles(styles);

  return (
    &amp;lt;Button
      variant=&quot;contained&quot;
      className={`${classes.button} ${classes.textColor}`}
    &amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
};

export default HooksExButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例では受け取った引数の値をそのまま使っていますが、引数と三項演算子の組み合わせで、あてるスタイルを分岐させたりということも可能です。
さらにコンポーネントの呼び出し側から props で渡された値を引数として渡すようにすれば、コンポーネントの呼び出し側からスタイル指定する。ということもできますね。&lt;/p&gt;
&lt;p&gt;また、&lt;code&gt;makeStyles&lt;/code&gt;では後述するテーマの情報を受け取れるので、その値を使ってスタイル定義もできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { makeStyles } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const useStyles = makeStyles((theme) =&amp;gt; ({
  button: {
    backgroundColor: theme.palette.primary.dark
  }
}));

type Props = {
  children: ReactNode;
}

const HooksButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  const classes = useStyles();

  return (
    &amp;lt;Button variant=&quot;contained&quot; className={classes.button}&amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
}

export default HooksButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Styled Component 式&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@material-ui/core/styles&lt;/code&gt;の&lt;code&gt;styled&lt;/code&gt;を使います。&lt;/p&gt;
&lt;p&gt;あらかじめスタイルをあてたコンポーネントを作成し、それを使用していくような方式です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { styled } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const CustomButton = styled(Button)({
  backgroundColor: &apos;red&apos;
});

type Props = {
  children: ReactNode;
}

const StyledButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return &amp;lt;CustomButton variant=&quot;contained&quot;&amp;gt;{children}&amp;lt;/CustomButton&amp;gt;;
};

export default StyledButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;props の値を使ってスタイル定義をしたい場合はこんな感じ。
color props は&lt;code&gt;Button&lt;/code&gt;コンポーネントが元々受け付けている props のため、Omit で除外したうえで、独自定義側の color props 型定義と組み合わせています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, CSSProperties, ReactNode } from &apos;react&apos;;
import { styled } from &apos;@material-ui/core/styles&apos;;
import Button, { ButtonProps } from &apos;@material-ui/core/Button&apos;;

type MyButtonStyle = {
  color: CSSProperties[&apos;backgroundColor&apos;];
};

type MyButtonProps = MyButtonStyle &amp;amp; Omit&amp;lt;ButtonProps, keyof MyButtonStyle&amp;gt;;

const CustomButton = styled(({ color, ...other }: MyButtonProps) =&amp;gt; (
  &amp;lt;Button {...other} /&amp;gt;
))({
  // CSS プロパティ単位
  // 型を MyButtonStyle にしているが、
  // 実際は渡された props は全て受け取れる（今回だと variant, color, children）
  backgroundColor: (props: MyButtonStyle) =&amp;gt; props.color
});

type Props = {
  children: ReactNode;
}

const StyledExButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return (
    &amp;lt;CustomButton variant=&quot;contained&quot; color=&quot;red&quot;&amp;gt;
      {children}
    &amp;lt;/CustomButton&amp;gt;
  );
};

export default StyledExButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらも同様に、コンポーネント（この例だと StyledExButton）の呼び出し側から props で渡された値を使うようにすれば、コンポーネントの呼び出し側からスタイル指定ができますね。&lt;/p&gt;
&lt;h3&gt;HOC 式（※旧式）&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@material-ui/core/styles&lt;/code&gt;の&lt;code&gt;withStyles&lt;/code&gt;と&lt;code&gt;createStyles&lt;/code&gt;を使います。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;createStyles&lt;/code&gt;で、クラス名とスタイル定義をマッピングしたものを作成。
&lt;code&gt;withStyles&lt;/code&gt;でラップすることにより、定義したスタイルとコンポーネントを結合。
スタイルの情報は props の classes に格納されるので、それを従来の HTML・CSS でクラスをあてるように、各要素に割り当てていく方式です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import { withStyles, WithStyles, createStyles } from &apos;@material-ui/core/styles&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const styles = createStyles({
  button: {
    backgroundColor: &apos;orange&apos;
  }
});

type Props = WithStyles&amp;lt;typeof styles&amp;gt; &amp;amp; {
  children: ReactNode;
};

const HOCButton: VFC&amp;lt;Props&amp;gt; = ({ classes, children }) =&amp;gt; {
  return (
    &amp;lt;Button variant=&quot;contained&quot; className={classes.button}&amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
};

export default withStyles(styles)(HOCButton);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;props の値を使ってスタイル定義をしたい場合はこんな感じ。
こちらも、クラス単位でも CSS プロパティ単位でも受け取ることができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, CSSProperties, ReactNode } from &apos;react&apos;;
import { withStyles, WithStyles, createStyles } from &apos;@material-ui/core/styles&apos;;
import Button, { ButtonProps } from &apos;@material-ui/core/Button&apos;;

type MyButtonStyle = {
  bgColor: CSSProperties[&apos;backgroundColor&apos;];
  textColor: CSSProperties[&apos;color&apos;];
};

const styles = createStyles({
  // クラス単位
  // 型を MyButtonStyle にしているが、
  // 実際は呼び出し側から渡された props は全て受け取れる（今回だと bgColor, textColor, children）
  button: (props: MyButtonStyle) =&amp;gt; ({
    backgroundColor: props.bgColor
  }),
  textColor: {
    // CSS プロパティ単位
    // 型を MyButtonStyle にしているが、
    // 実際は呼び出し側から渡された props は全て受け取れる（今回だと bgColor, textColor, children）
    color: (props: MyButtonStyle) =&amp;gt; props.textColor
  }
});

type Props = WithStyles&amp;lt;typeof styles&amp;gt; &amp;amp;
  MyButtonStyle &amp;amp;
  Omit&amp;lt;ButtonProps, &apos;color&apos;&amp;gt; &amp;amp; {
    children: ReactNode;
  };

const HOCButton: VFC&amp;lt;Props&amp;gt; = ({ classes, children }) =&amp;gt; {
  return (
    &amp;lt;Button
      variant=&quot;contained&quot;
      className={`${classes.button} ${classes.textColor}`}
    &amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
};

export default withStyles(styles)(HOCButton);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// コンポーネント呼び出し側
&amp;lt;HOCButton bgColor=&quot;orange&quot; textColor=&quot;white&quot;&amp;gt;
  HOC
&amp;lt;/HOCButton&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;インラインスタイル&lt;/h3&gt;
&lt;p&gt;これは別に Material UI 特有の方式ではないですが、style でインラインスタイルを書くことも出来ます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ReactNode } from &apos;react&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

type Props = {
  children: ReactNode;
}

const InlineStyleButton: VFC&amp;lt;Props&amp;gt; = ({ children }) =&amp;gt; {
  return (
    &amp;lt;Button
      variant=&quot;contained&quot;
      style={{ backgroundColor: &apos;pink&apos; }}
    &amp;gt;
      {children}
    &amp;lt;/Button&amp;gt;
  );
};

export default InlineStyleButton;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;レイアウトコンポーネント&lt;/h3&gt;
&lt;p&gt;Material UI には以下のようなレイアウト用のコンポーネントがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Box：幅広いスタイルを props であてることができるコンポーネント&lt;/li&gt;
&lt;li&gt;Container：コンテンツを水平方向に中央揃えするコンポーネント&lt;/li&gt;
&lt;li&gt;Grid：Grid レイアウトが構成できるコンポーネント&lt;/li&gt;
&lt;li&gt;Hidden：画面サイズによって表示非表示を切り替えられるコンポーネント&lt;/li&gt;
&lt;li&gt;Image List：画像のコレクションを構成できるコンポーネント&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;大まかなレイアウトは、これらのコンポーネントを使って組んでいくのもありです。
この中で、&lt;code&gt;Box&lt;/code&gt;コンポーネントだけ少し紹介します。&lt;/p&gt;
&lt;h4&gt;Box コンポーネント&lt;/h4&gt;
&lt;p&gt;HTML を書いていて、スタイルをあてるために要素を div や span で囲んでスタイルを書くということがよくあります。
それと同じような感覚で扱える、&lt;code&gt;Box&lt;/code&gt;コンポーネントというものが存在しており、これがなかなか便利です。&lt;/p&gt;
&lt;p&gt;下記のコードの例では&lt;code&gt;m&lt;/code&gt;と指定していますが、これは&lt;code&gt;margin&lt;/code&gt;のエイリアスです。
スペースのデフォルトテーマ値は8であるため、8 * 2 = 16px が実際の設定値となります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { Box, Button } from &apos;@material-ui/core&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;Box m={2}&amp;gt;
      &amp;lt;Button variant=&quot;contained&quot;&amp;gt;Test&amp;lt;/Button&amp;gt;
    &amp;lt;/Box&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この 8px というのは、マテリアルデザインにおいて使われる値で、この値を基準として要素を配置していくようになっています。
そのため、&lt;code&gt;Box&lt;/code&gt;コンポーネントを使うと、自然とその配置ができるというわけです。&lt;/p&gt;
&lt;p&gt;また、特定の CSS プロパティはテーマの値と関連付けられており、その値を使うことも出来ます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Box
  color=&quot;primary.main&quot;
  bgcolor=&quot;background.paper&quot;
  fontFamily=&quot;h6.fontFamily&quot;
  fontSize={{ xs: &apos;h6.fontSize&apos;, sm: &apos;h4.fontSize&apos;, md: &apos;h3.fontSize&apos; }}
  p={{ xs: 2, sm: 3, md: 4 }} // 画面サイズによって設定値を変化
&amp;gt;
  Test
&amp;lt;/Box&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あてられるスタイルとその props の書き方の種類一覧は、こちらをご参照ください。
比較的よく使われる CSS プロパティが用意されている印象です。
&amp;lt;OG url=&quot;https://v4.mui.com/system/api&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Material UI - System 配下のページで、種類ごとのドキュメントがあるので詳細はそちらを。
&amp;lt;OG url=&quot;https://v4.mui.com/system/basics&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;独自のデザイン性の高いものでなければ、この&lt;code&gt;Box&lt;/code&gt;コンポーネントを活用することで、ほとんど CSS を書かずにすむなと個人的には感じました。&lt;/p&gt;
&lt;h3&gt;デフォルトテーマ&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;@material-ui/core&lt;/code&gt;には、全体のスタイル定義であるテーマ情報も含まれており、デフォルトで適用されています。
Material UI のコンポーネントは props でスタイルのカスタマイズができますが、仕組みとしては、その props の値によってクラスが割り当てられるようになっています。
そのクラスのスタイル定義の設定値として、このテーマ情報から使っていたりするわけです。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@material-ui/core/styles&lt;/code&gt;の&lt;code&gt;useTheme&lt;/code&gt;で、そのテーマ情報にアクセスし使用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { Button } from &apos;@material-ui/core&apos;;
import { useTheme } from &apos;@material-ui/core/styles&apos;;

const App: VFC = () =&amp;gt; {
  const theme = useTheme();

  return (
    &amp;lt;Button
      variant=&quot;contained&quot;
      style={{ backgroundColor: theme.palette.primary.light }}
    &amp;gt;
      Test
    &amp;lt;/Button&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;デフォルトテーマ情報に含まれている代表的なものとしては、以下のものがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;palette：パレットカラー&lt;/li&gt;
&lt;li&gt;typography：フォントなど、文字に関する設定値（h1 などのタグ別のフォント情報も）&lt;/li&gt;
&lt;li&gt;spacing：空白を計算する関数（デフォルトのベースは 8px で、8 * 引数 を返す）&lt;/li&gt;
&lt;li&gt;breakpoints：ブレイクポイントの値と、メディアクエリ指定を補助する関数&lt;/li&gt;
&lt;li&gt;z-index：z-index の設定値&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;このデフォルトテーマが持っている情報に関しては、こちらをご参照ください。
&amp;lt;OG url=&quot;https://v4.mui.com/customization/default-theme&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;デフォルトテーマベースの独自テーマ&lt;/h3&gt;
&lt;p&gt;デフォルトテーマをベースとした独自テーマを作りたい場合は、&lt;code&gt;createTheme&lt;/code&gt;を使った書き方ができます。&lt;/p&gt;
&lt;p&gt;ルート階層にて&lt;code&gt;ThemeProvider&lt;/code&gt;コンポーネントを使ってラップすることで、下層のコンポーネントにテーマ情報を渡します。&lt;/p&gt;
&lt;p&gt;以下はデフォルトテーマのパレットカラーを上書きしている例になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import ReactDOM from &quot;react-dom&quot;;
import { createTheme, ThemeProvider } from &apos;@material-ui/core/styles&apos;;
import purple from &apos;@material-ui/core/colors/purple&apos;;
.
.
.
const theme = createTheme({
  palette: {
    primary: purple,
  }
});
.
.
.
ReactDOM.render(
  &amp;lt;ThemeProvider theme={theme}&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/ThemeProvider&amp;gt;,
  document.getElementById(&apos;root&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テーマ情報は各コンポーネント側で使用できます。
primary の元々の色は青ですが、変更しているので紫になります。
一方、secondary の色は変更していないので、デフォルトテーマの色が使われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import Button from &apos;@material-ui/core/Button&apos;;

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;Button variant=&quot;contained&quot; color=&quot;primary&quot;&amp;gt;primary&amp;lt;/Button&amp;gt;
      &amp;lt;Button variant=&quot;contained&quot; color=&quot;secondary&quot;&amp;gt;secondary&amp;lt;/Button&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;デフォルトテーマに存在していない独自のプロパティを追加する場合は、型定義を拡張したうえで独自テーマを定義する感じです。
また、&lt;code&gt;ThemeProvider&lt;/code&gt;はネストして使うこともできるようになっています。&lt;/p&gt;
&lt;p&gt;ちなみに、&lt;code&gt;createTheme&lt;/code&gt;と似ている&lt;code&gt;createMuiTheme&lt;/code&gt;というものもあります。
どうも元々&lt;code&gt;createMuiTheme&lt;/code&gt;だったものが、&lt;code&gt;createTheme&lt;/code&gt;に名称が変わったとのこと。
（4系から5系への移行ガイドにその旨記述がありました）
現在の4系では移行期間として両方存在しているみたいです。&lt;/p&gt;
&lt;h3&gt;2014 マテリアルデザイン カラーパレット&lt;/h3&gt;
&lt;p&gt;テーマ情報のなかにも primary や secondary といったカラー情報がありますが、その他に 2014 マテリアルデザイン カラーパレットというものが存在します。
元々、マテリアルデザインによって2014年に作成されたもので、調和して機能するように設計された色で構成されています。&lt;/p&gt;
&lt;p&gt;使用するには&lt;code&gt;@material-ui/core/colors&lt;/code&gt;からインポートして、HUE（赤、ピンクなどの色）と SHADE（500、600などの濃さ）を指定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import purple from &apos;@material-ui/core/colors/purple&apos;;
import red from &apos;@material-ui/core/colors/red&apos;;

const primary = red[500]; // #f44336
const accent = purple[&apos;A200&apos;]; // #e040fb
const accent = purple.A200; // #e040fb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;色の種類については、こちらに一覧があります。
&amp;lt;OG url=&quot;https://v4.mui.com/customization/color/#2014-material-design-color-palettes&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;カラー作成に使えるツール&lt;/h3&gt;
&lt;p&gt;色を考えるときは、コミュニティの方が作られたツールを使うのも手です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://react-theming.github.io/create-mui-theme/&quot;&gt;create-mui-theme&lt;/a&gt;：マテリアルデザインカラーツールを介して Material UI テーマを作成するためのオンラインツール&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://in-your-saas.github.io/material-ui-theme-editor/&quot;&gt;material-ui-theme-editor&lt;/a&gt;：色を選択してライブプレビューを行うだけで、Material UI テーマを生成するツール&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://material.io/inline-tools/color/&quot;&gt;Material palette generator&lt;/a&gt;：マテリアルパレットジェネレータを使用して、入力した任意の色のパレットを生成できるツール&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;その他スタイルの書き方&lt;/h3&gt;
&lt;p&gt;このクラスの中のこの要素や、この要素のこのアクションといったような、ネストのスタイル指定はこのようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { makeStyles } from &apos;@material-ui/core/styles&apos;;
import Typography from &apos;@material-ui/core/Typography&apos;;

const useStyles = makeStyles({
  msg: {
    color: &apos;blue&apos;,
    &apos;&amp;amp; span&apos;: {
      color: &apos;red&apos;,
      &apos;&amp;amp;:hover&apos;: {
        color: &apos;black&apos;
      }
    }
  }
})

const App: VFC = () =&amp;gt; {
  const classes = useStyles();

  return (
    &amp;lt;Typography className={classes.msg}&amp;gt;
      これは&amp;lt;span&amp;gt;テスト&amp;lt;/span&amp;gt;です
    &amp;lt;/Typography&amp;gt;
  )
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでは紹介していませんが、プレーンな CSS や CSS Modules との併用も可能です。
Material UI で適用されているグローバル CSS をカスタマイズしたり。
別ライブラリとしては、styled-comopnents や emotion との併用もできます（v5 ではこの2つがデザインシステムの実体として採用されています）&lt;/p&gt;
&lt;h2&gt;コンポーネントの種類&lt;/h2&gt;
&lt;p&gt;大まかな種類の概要だけさらっと書きます。
なので、ここに書いているもの以外のコンポーネントもあります。
（それでも長くなりました...）&lt;/p&gt;
&lt;p&gt;()はどのライブラリに属しているかです。&lt;/p&gt;
&lt;h3&gt;Layout&lt;/h3&gt;
&lt;p&gt;他のコンポーネントをラップして使われる、レイアウトに関するコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Box(core)&lt;/h4&gt;
&lt;p&gt;他のコンポーネントをラップし、様々なスタイルをあてるためのコンポーネント。
デフォルトでは div としてラップするが、他のものに指定もできる。
&lt;a href=&quot;#box-%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88&quot;&gt;Box コンポーネント&lt;/a&gt; の項も参照。&lt;/p&gt;
&lt;h4&gt;Container(core)&lt;/h4&gt;
&lt;p&gt;コンテンツを水平方向に中央揃えするコンポーネント。&lt;/p&gt;
&lt;h4&gt;Grid(core)&lt;/h4&gt;
&lt;p&gt;グリッドレイアウトを表現するためのコンポーネント。&lt;/p&gt;
&lt;h4&gt;Hidden(core)&lt;/h4&gt;
&lt;p&gt;コンポーネントを非表示にするコンポーネント。
ブレイクポイントを設定して、画面幅によって非表示にするなどができる。&lt;/p&gt;
&lt;h4&gt;ImageList(core)&lt;/h4&gt;
&lt;p&gt;画像のコレクションを構成できるコンポーネント群。
少し前のバージョンでは GridList だった。&lt;/p&gt;
&lt;h3&gt;Inputs&lt;/h3&gt;
&lt;p&gt;フォームなどで使用する、入力に関するコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Button(core)&lt;/h4&gt;
&lt;p&gt;ボタンコンポーネント群。
Button はシンプルなものから、枠線あり、塗りつぶしのパターン。アイコンと組み合わせたものなど、いろんなパターンに対応。
IconButton はアイコン自体をボタンにしたもの。&lt;/p&gt;
&lt;p&gt;Button の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/button.png&quot; alt=&quot;Buttonコンポーネントのプレビュー画像&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;IconButton の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/icon-button.png&quot; alt=&quot;IconButtonコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;ButtonGroup(core)&lt;/h4&gt;
&lt;p&gt;ボタンをグループ化したコンポーネント。&lt;/p&gt;
&lt;p&gt;ButtonGroup の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/button-group.png&quot; alt=&quot;ButtonGroupコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Checkbox(core)&lt;/h4&gt;
&lt;p&gt;チェックボックスコンポーネント。
FormControlLabel と組み合わせることで、ラベル付きのものも作れる。&lt;/p&gt;
&lt;p&gt;Checkbox の例（FormControlLabel と組み合わせ）
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/react-material-ui/check-box.png&quot; alt=&quot;Checkboxコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Fab(core)&lt;/h4&gt;
&lt;p&gt;Floating Action Button の略。
少し浮いているようなボタンのコンポーネント。&lt;/p&gt;
&lt;p&gt;Fab の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/fab.png&quot; alt=&quot;Fabコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Date / Time(pickers)&lt;/h4&gt;
&lt;p&gt;Date Picker 的なコンポーネント群。
詳細は Material-UI Pickers から（2023/11/25 URLが変わっていたので修正）&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/mui/material-ui-pickers&quot; /&amp;gt;
なお、この Material-UI Pickers は Matarial UI 5系において lab の方に取り込まれるようです。&lt;/p&gt;
&lt;p&gt;KeyboardDatePickerの例（variant に inline 指定）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/date-picker-inline.gif&quot;  alt=&quot;KeyboardDatePicker(inline)コンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;KeyboardDatePicker の例（variant に dialog 指定）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/date-picker-dialog.gif&quot; alt=&quot;KeyboardDatePicker(dialog)コンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;p&gt;KeyboardTimePicker の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/time-picker.gif&quot; alt=&quot;KeyboardTimePickerコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Radio(core)&lt;/h4&gt;
&lt;p&gt;ラジオボタンコンポーネント群。
FormControlLabel と組み合わせることで、ラベル付きのものも作れる。&lt;/p&gt;
&lt;p&gt;RadioGroup、Radio の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/radio.png&quot; alt=&quot;RadioGroup、Radioコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Select(core)&lt;/h4&gt;
&lt;p&gt;セレクトボックスコンポーネント。選択肢をグルーピングもできる。&lt;/p&gt;
&lt;p&gt;Select の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;gifs/2020/react-material-ui/select.gif&quot; alt=&quot;SelectコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Slider(core)&lt;/h4&gt;
&lt;p&gt;スライダーコンポーネント。&lt;/p&gt;
&lt;p&gt;Slider の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/slider.png&quot; alt=&quot;Sliderコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Switch(core)&lt;/h4&gt;
&lt;p&gt;スイッチコンポーネント。
FormControlLabel と組み合わせることで、ラベル付きのものも作れる。&lt;/p&gt;
&lt;p&gt;Switch の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/switch.png&quot; alt=&quot;Switchコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Text Field(core)&lt;/h4&gt;
&lt;p&gt;テキスト入力フォームのコンポーネント群。
マルチラインにしたり、セレクトボックスにしたりもできる。
また、フォームにアイコンをつけたり、単位の文字をつけるといったことも可能。
InputBase といった下位レベルのコンポーネントが複数存在しており、より細かくカスタマイズしたい時は、そちらを使うとよい。&lt;/p&gt;
&lt;p&gt;TextField の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/text-field.png&quot; alt=&quot;TextFieldコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Navigation&lt;/h3&gt;
&lt;p&gt;ナビゲーションに関するコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Bottom Navigation(core)&lt;/h4&gt;
&lt;p&gt;スマホアプリでよくあるような、ボタンナビゲーションコンポーネント群。&lt;/p&gt;
&lt;p&gt;BottomNavigation、BottomNavigationAction の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/button-navigation.png&quot;  alt=&quot;BottomNavigation、BottomNavigationActionコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Breadcrumbs(core)&lt;/h4&gt;
&lt;p&gt;パンくずリスト的なコンポーネント。Link と組み合わせたりする。&lt;/p&gt;
&lt;p&gt;Breadcrumbs の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/breadcrumbs.png&quot; alt=&quot;Breadcrumbsコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Drawer(core)&lt;/h4&gt;
&lt;p&gt;ドロワーコンポーネント。Button と組み合わせて、クリックされたときにドロワーを開くといったように使う。
画面の上下左右どこからドロワーが開くか設定できる。&lt;/p&gt;
&lt;p&gt;Drawer の例（上から開いた例）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/drawer.gif&quot; alt=&quot;DrawerコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Link(core)&lt;/h4&gt;
&lt;p&gt;リンクコンポーネント。React Router を併用する場合は、あまり使う機会ないかも？&lt;/p&gt;
&lt;p&gt;Link の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/link.png&quot; alt=&quot;Linkコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Menu(core)&lt;/h4&gt;
&lt;p&gt;メニューコンポーネント群。
Button と組み合わせて、クリックされたときにメニューを開くといったように使う。&lt;/p&gt;
&lt;p&gt;Menu、MenuItem の例（メニューを開いた後）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/menu.gif&quot; alt=&quot;Menu、MenuItemコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Stepper(core)&lt;/h4&gt;
&lt;p&gt;ステッパーコンポーネント群。ステップ手順を踏んでいくような表現ができる。&lt;/p&gt;
&lt;p&gt;Stepper、Step、StepLabel の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/stepper.png&quot; alt=&quot;Stepper、Step、StepLabelコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Tabs(core)&lt;/h4&gt;
&lt;p&gt;タブコンポーネント群。
アイコンを使って、ボタンナビゲーションのようにもできる。&lt;/p&gt;
&lt;p&gt;Tabs の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/react-material-ui/tabs.png&quot; alt=&quot;Tabsコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Surfaces&lt;/h3&gt;
&lt;p&gt;表面的な UI に関するコンポーネント集。&lt;/p&gt;
&lt;h4&gt;App Bar(core)&lt;/h4&gt;
&lt;p&gt;ヘッダーに使えるようなバーのコンポーネント群。&lt;/p&gt;
&lt;p&gt;AppBar、ToolBar の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/app-bar.png&quot; alt=&quot;AppBar、ToolBarコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Paper(core)&lt;/h4&gt;
&lt;p&gt;積み重なった紙のような表現ができるコンポーネント。&lt;/p&gt;
&lt;p&gt;Paper の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/paper.png&quot; alt=&quot;Paperコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Card(core)&lt;/h4&gt;
&lt;p&gt;カードコンポーネント群。文字だけでなく画像もいれられる。&lt;/p&gt;
&lt;p&gt;Card、CardActions、CardContent、CardHeader、CardMedia の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/card.png&quot; alt=&quot;Card、CardActions、CardContent、CardHeader、CardMediaコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Accordion(core)&lt;/h4&gt;
&lt;p&gt;ハンバーガメニューのようなコンポーネント群。
少し前のバージョンでは ExpansionPanel だった。&lt;/p&gt;
&lt;p&gt;Accordion、AccordionDetails、AccordionSummary の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;gifs/2020/react-material-ui/accordion.gif&quot; alt=&quot;Accordion、AccordionDetails、AccordionSummaryコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Feedback&lt;/h3&gt;
&lt;p&gt;ユーザに状態を伝えるようなコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Progress(core)&lt;/h4&gt;
&lt;p&gt;プログレスコンポーネント群。読み込み中の表現ができる。&lt;/p&gt;
&lt;p&gt;CircularProgress の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/progress.gif&quot; alt=&quot;CircularProgressコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Dialog(core)&lt;/h4&gt;
&lt;p&gt;ダイアログコンポーネント群。
Button と組み合わせて、クリックされたときにダイアログを開くといったように使う。&lt;/p&gt;
&lt;p&gt;SimpleDialog の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/dialog.gif&quot; alt=&quot;SimpleDialogコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Snackbar(core)&lt;/h4&gt;
&lt;p&gt;トースト的な表現ができるコンポーネント群。
Button と組み合わせて、クリックされたときにメッセージを出したり。
Alert と組み合わせて、API リクエストエラー時のアラート表示にしたり。&lt;/p&gt;
&lt;p&gt;Snackbar の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;gifs/2020/react-material-ui/snack-bar.gif&quot; alt=&quot;SnackbarコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Backdrop(core)&lt;/h4&gt;
&lt;p&gt;アプリ上の状態変化を表現できる、背景のコンポーネント。&lt;/p&gt;
&lt;p&gt;Backdrop の例（CircularProgress との組み合わせ）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/backdrop.gif&quot; alt=&quot;BackdropコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Data Display&lt;/h3&gt;
&lt;p&gt;データを表示することに関するコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Avatar(core)&lt;/h4&gt;
&lt;p&gt;アバターコンポーネント群。
画像や名称を指定してユーザのアイコンのような表現ができる。&lt;/p&gt;
&lt;p&gt;Avatar の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/avatar.png&quot; alt=&quot;Avatarコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Badge(core)&lt;/h4&gt;
&lt;p&gt;バッジコンポーネント。アイコンなどと組み合わせて通知を表現できる。&lt;/p&gt;
&lt;p&gt;Badge の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/badge.png&quot; alt=&quot;Badgeコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Chip(core)&lt;/h4&gt;
&lt;p&gt;チップコンポーネント。入力や属性、アクションを表現できる。&lt;/p&gt;
&lt;p&gt;Chip の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/react-material-ui/chip.png&quot; alt=&quot;Chipコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Divider(core)&lt;/h4&gt;
&lt;p&gt;線を表現できるコンポーネント。デフォルトでは hr として変換される。&lt;/p&gt;
&lt;p&gt;Divider の例（線の部分）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/divider.png&quot; alt=&quot;Dividerコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Material Icons(icons)&lt;/h4&gt;
&lt;p&gt;アイコンのコンポーネント集。
アイコンの種類は &lt;a href=&quot;https://v4.mui.com/components/material-icons/&quot;&gt;Material UI - Material Icons&lt;/a&gt; を参照。&lt;/p&gt;
&lt;h4&gt;Listr(core)&lt;/h4&gt;
&lt;p&gt;リスト表示を表現できるコンポーネント群。&lt;/p&gt;
&lt;p&gt;List、ListItem、ListItemIcon、ListItemText の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/list.png&quot; alt=&quot;List、ListItem、ListItemIcon、ListItemTextコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Table(core)&lt;/h4&gt;
&lt;p&gt;テーブル表示を表現できるコンポーネント群。
テーブル内のパーツごとにコンポーネントが分かれており、ソート機能も導入できる。&lt;/p&gt;
&lt;p&gt;Table、TableBody、TableCell、TableContainer、TableHead、TableRow の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/react-material-ui/table.png&quot; alt=&quot;Table、TableBody、TableCell、TableContainer、TableHead、TableRowコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Tooltip(core)&lt;/h4&gt;
&lt;p&gt;ツールチップコンポーネント。
ラップしているコンポーネントをホバーした時に、簡易説明を表示するような表現ができる。&lt;/p&gt;
&lt;p&gt;Tooltip の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/tooltip.gif&quot; alt=&quot;TooltipコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Typography(core)&lt;/h4&gt;
&lt;p&gt;文字表示を表現できるコンポーネント。
文字位置や文字色、どのタグ（h1 など）とするか、どのタグのスタイルをあてるかなどを設定できる。&lt;/p&gt;
&lt;p&gt;Typography の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/typography.png&quot; alt=&quot;Typographyコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Utils&lt;/h3&gt;
&lt;p&gt;ユーティリティ的なコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Click Away Listener(core)&lt;/h4&gt;
&lt;p&gt;要素の外でクリックイベントが発生したかどうかを検出するコンポーネント。&lt;/p&gt;
&lt;h4&gt;CSS Baseline(core)&lt;/h4&gt;
&lt;p&gt;normalize.css のような、CSS をリセットするコンポーネント群。
ブラウザごとの表示を統一したい時に使用。&lt;/p&gt;
&lt;h4&gt;Modal(core)&lt;/h4&gt;
&lt;p&gt;モーダルコンポーネント。
Dialog、Drawer、Menu、Popover から活用されている下位レベルのコンポーネント。
モーダルダイアログを作成する場合は、Modal を直接使用するのではなく、Dialog コンポーネントを使用することを推奨としている。&lt;/p&gt;
&lt;p&gt;Modal の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/modal.gif&quot; alt=&quot;ModalコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;No SSR(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントを SSR（サーバサイドレンダリング）の対象から外すコンポーネント。
SSR の時間を短縮、SSR に対応していないコンポーネントをエスケープなどに使う。&lt;/p&gt;
&lt;h4&gt;Popover(core)&lt;/h4&gt;
&lt;p&gt;ポップオーバーコンポーネント。
コンテンツを別のコンテンツの上に表示する表現ができる。&lt;/p&gt;
&lt;p&gt;Popover の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/popover.gif&quot; alt=&quot;PopoverコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Popper(core)&lt;/h4&gt;
&lt;p&gt;Popper.js をベースとしたポッパーコンポーネント。
これもコンテンツを別のコンテンツの上に表示する表現ができる。
上に表示されたコンテンツはスクロールについてこないなど、Popover と微妙な違いがある。&lt;/p&gt;
&lt;p&gt;Popper の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/popper.gif&quot; alt=&quot;PopperコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Portal(core)&lt;/h4&gt;
&lt;p&gt;ポータルコンポーネント。
ラップしたコンポーネントを現在の DOM 階層外の新しいサブツリーへレンダリングすることに使う。&lt;/p&gt;
&lt;h4&gt;Textarea Autosize(core)&lt;/h4&gt;
&lt;p&gt;テキストエリアコンポーネント。
デフォルトでは自由にサイズを変更できるが、サイズや行数を固定もできる。&lt;/p&gt;
&lt;p&gt;TextareaAutosize の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/textarea.gif&quot; alt=&quot;TextareaAutosizeコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Collapse(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントで、上から下へ表示される表現ができるコンポーネント。&lt;/p&gt;
&lt;p&gt;Collapse の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/collapse.gif&quot; alt=&quot;CollapseコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Fade(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントで、フェードイン、フェードアウトを表現できるコンポーネント。&lt;/p&gt;
&lt;p&gt;Fade の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/fade.gif&quot; alt=&quot;FadeコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Grow(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントを順番にフェードインするような表現ができるコンポーネント。&lt;/p&gt;
&lt;p&gt;Grow の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/grow.gif&quot; alt=&quot;GrowコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Slide(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントをスライド表示する表現ができるコンポーネント。スライドする方向は指定できる。&lt;/p&gt;
&lt;p&gt;Slide の例（derection に up 指定）
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/slide.gif&quot; alt=&quot;SlideコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Zoom(core)&lt;/h4&gt;
&lt;p&gt;ラップしたコンポーネントが、要素の中心から外側に広がるように表示する（もしくはその逆）表現ができるコンポーネント。&lt;/p&gt;
&lt;p&gt;Zoom の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/zoom.gif&quot; alt=&quot;ZoomコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h3&gt;Lab&lt;/h3&gt;
&lt;p&gt;core へ組み込まれる前のコンポーネント集。&lt;/p&gt;
&lt;h4&gt;Alert(lab)&lt;/h4&gt;
&lt;p&gt;アラートコンポーネント群。ユーザへ促す注意や案内を表現する。&lt;/p&gt;
&lt;p&gt;Alert の例
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/react-material-ui/alert.png&quot; alt=&quot;Alertコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Autocomplete(lab)&lt;/h4&gt;
&lt;p&gt;サジェスト入力フォームを実現できるコンポーネント。複数選択式にすることも可能。&lt;/p&gt;
&lt;p&gt;AutoComplete の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/auto-complete.gif&quot; alt=&quot;AutoCompleteコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Data Grid(x-grid, data-grid)&lt;/h4&gt;
&lt;p&gt;データグリッドを表現できるコンポーネント群。
表示だけの Table と違い、値を直接操作できるといった機能も持ち合わせているなど多機能で、ドキュメントの項目も多い。
所属するライブラリとして、フル機能のエンタープライズ版である x-grid と、無料のコミュニティ版である data-grid がある。&lt;/p&gt;
&lt;p&gt;DataGrid の例
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/react-material-ui/data-grid.png&quot; alt=&quot;DataGridコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Pagination(lab)&lt;/h4&gt;
&lt;p&gt;ページネーションを表現できるコンポーネント群。&lt;/p&gt;
&lt;p&gt;Pagination の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/pagination.png&quot; alt=&quot;Paginationコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Rating(lab)&lt;/h4&gt;
&lt;p&gt;レーティングを表現できるコンポーネント。&lt;/p&gt;
&lt;p&gt;Rating の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/rating.gif&quot; alt=&quot;RatingコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Skeleton(lab)&lt;/h4&gt;
&lt;p&gt;コンテンツの読み込み中にスケルトンを表示するような表現ができるコンポーネント。&lt;/p&gt;
&lt;p&gt;Skeleton の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/skeleton.gif&quot; alt=&quot;SketetonコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Speed Dial(lab)&lt;/h4&gt;
&lt;p&gt;スピードダイヤルで3~6の関連アクションを表示するような表現ができるコンポーネント群。&lt;/p&gt;
&lt;p&gt;SpeedDial、SpeedDialIcon、SpeedDialAction の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;gifs/2020/react-material-ui/speed-dial.gif&quot; alt=&quot;SpeedDial、SpeedDialIcon、SpeedDialActionコンポーネントのプレビューGIF&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Timeline(lab)&lt;/h4&gt;
&lt;p&gt;タイムラインを表現できるコンポーネント群。
Icon や Paper と組み合わせると、よりリッチな表現ができる。&lt;/p&gt;
&lt;p&gt;Timeline、TimelineItem、TimelineSeparator、TimelineConnector、TimelineContent、TimelineDot の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/timeline.png&quot; alt=&quot;Timeline、TimelineItem、TimelineSeparator、TimelineConnector、TimelineContent、TimelineDotコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Toggle Buttons(lab)&lt;/h4&gt;
&lt;p&gt;トグルボタンコンポーネント群。&lt;/p&gt;
&lt;p&gt;ToggleButton、ToggleButtonGroup の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/toggle-button.png&quot; alt=&quot;ToggleButton、ToggleButtonGroupコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;h4&gt;Tree View(lab)&lt;/h4&gt;
&lt;p&gt;ツリービューを表現できるコンポーネント群。&lt;/p&gt;
&lt;p&gt;TreeView、TreeItem の例
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2020/react-material-ui/tree-view.png&quot; alt=&quot;TreeView、TreeItemコンポーネントのプレビュー画像&quot;/&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;概要だけさらっと書くつもりがすごく長くなってしまいました。
ここまで読んでくださった方、ありがとうございます！&lt;/p&gt;
&lt;p&gt;それだけ提供している機能が多いということでもあるので、味方につけるときっと頼もしい存在になってくれるのではないかと。
ちなみに自分が過去に業務で使用していた時には、バージョン少し古く対応してなくて泣く泣く使うことができなかった機能もありました（笑）&lt;/p&gt;
&lt;p&gt;この記事を書くにあたって、改めて公式ドキュメントを読んで新しい発見もあって、より使いこなせるようになりたいなと思いました。
Material UI がどんなものなのか、大まかにでも伝わっていれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mui.com&quot;&gt;Material UI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v4.mui.com/&quot;&gt;Material UI v4&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://material-ui-pickers.dev/&quot;&gt;Material UI Pickers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>JS×Reactにおけるpropsの型定義を担うPropTypes入門</title><link>https://changeofpace.site/posts/2020-04-04-react-proptypes</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-04-04-react-proptypes</guid><description>Reactにおいて必須概念である、コンポーネントのpropsの型チェックやバリデーションを行うPropTypesの使い方まとめ</description><pubDate>Sat, 04 Apr 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今回はコンポーネントに渡す props の使用を補助するものである PropTypes について書きました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/bab6845472e4d428732c&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-09-23-react-proptypes&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2021/08/26追記 全体的に少し内容を見直しました。
※2022/01/24追記 あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました（旧：React入門 ～PropTypes編～）&lt;/p&gt;
&lt;h2&gt;PropTypes とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/typechecking-with-proptypes.html&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/facebook/prop-types&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;React で登場するコンポーネントは、 props という任意の値を受け取ることができるようになっています。
受け取ったコンポーネント側でこの props の値を使い、レンダリング内容に変化をつけたり、ロジックを作ったり。&lt;/p&gt;
&lt;p&gt;一見、便利な props ではありますが、通常ではどんな値でも受け取ることができてしまいます。
そのため、props の型補完が効かなかったり。
誤って想定と違う値を渡してしまうと、予期しない動作をしてアプリがクラッシュする...いったことも起こる可能性があったり。&lt;/p&gt;
&lt;p&gt;TypeScript を使用すれば props の型定義ができるため、こういった問題に対応できるのですが...。
JavaScript を使用している場合は、このことを常に考慮する必要があります。&lt;/p&gt;
&lt;p&gt;これを補助するものとして、型定義機能を提供してくれるのが&lt;code&gt;PropTypes&lt;/code&gt;です。
元々は React 本体に組み込まれていましたが、バージョン15.5より&lt;code&gt;prop-types&lt;/code&gt;という別パッケージとして切り分けされました。&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;yarn add prop-types
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回の使用バージョンは以下のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：17.0.2&lt;/li&gt;
&lt;li&gt;prop-types：15.7.2&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;基本的な使い方&lt;/h2&gt;
&lt;pre&gt;&lt;code&gt;import PropsTypesComponent from &apos;./PropTypesComponent&apos;;

const App = () =&amp;gt; {
  return (
    &amp;lt;PropsTypesComponent name=&quot;太郎&quot; /&amp;gt;
  )
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import PropTypes from &apos;prop-types&apos;;

const PropTypesComponent = ({ name }) =&amp;gt; {
  return (
    &amp;lt;h2&amp;gt;Hello {name}&amp;lt;/h2&amp;gt;
  );
}

PropTypesComponent.propTypes = {
  name: PropTypes.string
};

export default PropTypesComponent;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ライブラリを import&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;import PropTypes from &apos;prop-types&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;props ごとの型定義を記述&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;コンポーネント名.propTypes = {
  props名: PropTypes.型定義
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PropTypesComponent.propTypes = {
  name: PropTypes.string
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで props の型定義ができました。
このコンポーネント呼び出し時に、props の入力補完が効くようになります。&lt;/p&gt;
&lt;p&gt;また、自動でバリデーションチェックが行われるようになり、無効な値が渡された場合は DevTools のコンソールに Warning が出力されます。
上記の例では問題ありませんが、例えば PropsTypesComponent に渡している name の値を「1」など、string 以外の値にしてみます。
すると、以下のような Warning がコンソールに出力されているはずです。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/react-proptypes/warning.png&quot; alt=&quot;コンソールに出力されたPropTypesによるバリデーションエラーメッセージ&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;あくまでコンソール上に Warning を出力するだけではありますが、これが出ないようにコーディングすれば、予期せぬ動作を減らすことができるわけです。
加えて props にどんな型の値が入るのか可視化される。というメリットもあります。&lt;/p&gt;
&lt;p&gt;ただ、このバリデーションチェックはパフォーマンス上の理由から&lt;strong&gt;開発モードの場合のみ動作&lt;/strong&gt;することに注意です。&lt;/p&gt;
&lt;h2&gt;型定義の種類&lt;/h2&gt;
&lt;h3&gt;数値&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.number
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
1.0
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;文字列&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.string
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;太郎&apos;
&apos;1&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;真偽値&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.bool
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;true
false
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;一応補足として、あくまで真偽値なので&lt;code&gt;&apos;true&apos;&lt;/code&gt;などはダメです。
文字列扱いなので、バリデーションに引っ掛かります。&lt;/p&gt;
&lt;h3&gt;配列&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.array
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[1, &apos;A&apos;]
[{ id: &apos;A&apos;}, { id: &apos;B&apos; }]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;配列であれば、その中の値の型までは問わないため非推奨のようです。
中の値の型までチェックする場合は&lt;code&gt;arrayOf&lt;/code&gt;を使います。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.arrayOf(型定義の種類)

// 例
PropTypes.arrayOf(PropTypes.number)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※ PropTypes.arrayOf(PropTypes.number) の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[1, 2, 3]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;オブジェクト&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.object
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ a: &apos;A&apos;, b: &apos;B&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;オブジェクトであれば、その中の値の型までは問わないため非推奨のようです。
中の値の型までチェックする場合は&lt;code&gt;objectOf&lt;/code&gt;か&lt;code&gt;shape&lt;/code&gt;、もしくは&lt;code&gt;exact&lt;/code&gt;を使います。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;objectOf&lt;/code&gt;は特定の型のみの場合。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.objectOf(型定義の種類)

// 例
PropTypes.objectOf(PropTypes.number)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※ PropTypes.objectOf(PropTypes.number) の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ a: 1, b: 2 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;shape&lt;/code&gt;は型がバラバラの場合。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.shape({
  props名: 型定義の種類,
  props名: 型定義の種類
})

// 例
PropTypes.shape({
  num: PropTypes.number,
  str: PropTypes.string
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※上の設定例の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{ num: 1, str: &apos;太郎&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;exact&lt;/code&gt;も型がバラバラの場合です。
&lt;code&gt;shape&lt;/code&gt;との違いは、型定義した以外のものがオブジェクトに追加されているとバリデーションに引っ掛かります。
こちらの方がより厳密に props をチェックする感じです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.exact({
  props名: 型定義の種類,
  props名: 型定義の種類
})

// 例
PropTypes.exact({
  num: PropTypes.number,
  str: PropTypes.string
})
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※上の設定例の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ここにこの二つ以外のキー、値があると警告
{ num: 1, str: &apos;太郎&apos; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;関数&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.func
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;() =&amp;gt; {
  console.log(&apos;func&apos;);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;シンボル&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.symbol
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Symbol()
Symbol(&apos;test&apos;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;恥ずかしながら自分はシンボルって何？状態だったので調べたのですが、ES6 で追加された新しいプリミティブのデータ型だったのですね。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/&lt;em&gt;lemon2003&lt;/em&gt;/items/55c947f29b44f79dc859&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Symbol&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;でも、いまいち使い方がよくわからない...。&lt;/p&gt;
&lt;h3&gt;特定の値のいずれかの場合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.oneOf([&apos;値１&apos;, &apos;値２&apos;])

// 例
PropTypes.oneOf([&apos;A&apos;, &apos;B&apos;, &apos;C&apos;])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※上の設定例の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&apos;A&apos;
&apos;B&apos;
&apos;C&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;いろんなデータ型が渡される可能性がある場合&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.oneOfType([
  型定義の種類,
  型定義の種類
])

// 例
PropTypes.oneOfType([
  PropTypes.number,
  PropTypes.string
])
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※上の設定例の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&apos;1&apos;
&apos;A&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;クラスオブジェクト&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.instanceOf(クラス名)

// 例
PropTypes.instanceOf(Date)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例
※上の設定例の場合.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;new Date()
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;React Element&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.element
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Test /&amp;gt; // 独自定義のコンポーネント
&amp;lt;p&amp;gt;Test&amp;lt;/p&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;React Element Type&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.elementType
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React.element と似ていますが、こちらは以下のような記述がありました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;関数、文字列、または「要素のような」オブジェクト（React.Fragment、Suspenseなど）&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;詳細は React の isValidElementType を見てね、とのこと。
&amp;lt;OG url=&quot;https://github.com/facebook/react/blob/v17.0.2/packages/shared/isValidElementType.js&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Test // 独自定義のコンポーネント名（JSX を返す関数）
React.Fragment
React.Context
React.Suspense
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;レンダリングできるもの&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.node
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;受け付ける例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;1
&apos;A&apos;
[&apos;a&apos;, &apos;b&apos;]
&amp;lt;p&amp;gt;Test&amp;lt;/p&amp;gt;
&amp;lt;Test /&amp;gt; // 独自定義のコンポーネント
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;数値、文字列、配列、React Element であればいいようです。
真偽値やオブジェクトはバリデーションに引っ掛かりました。&lt;/p&gt;
&lt;h3&gt;型は問わない&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.any
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;どんな型でも受け付けるよ。というやつです。
これを使ってしまうと PropTypes の意味があまりなくなってしまうので、どうしても使いたい時だけに留めておきましょう。&lt;/p&gt;
&lt;h3&gt;必須&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;PropTypes.型定義.isRequired

// 例
PropTypes.number.isRequired
PropTypes.any.isRequired
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;カスタムルール&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;// カスタムルールの定義
const customProp = (props, propName, componentName) =&amp;gt; {
  if (!/test/.test(props[propName])) {
    return new Error(
      &apos;Invalid prop `&apos; + propName + &apos;` supplied to&apos; +
      &apos; `&apos; + componentName + &apos;`. Validation failed.&apos;
    );
  }
}

// カスタムルールの使用
コンポーネント名.propTypes = {
  props名: customProp
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この例は props の値に&lt;code&gt;test&lt;/code&gt;という文字列が含まれているかチェックするものです（公式ドキュメントのコードをベースにしています）&lt;/p&gt;
&lt;h2&gt;props のデフォルト値&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;defaultProps&lt;/code&gt;を使って、props のデフォルト値を設定できます。
もしその props に値が渡されなかった場合は、このデフォルト値がセットされます。
また、型定義と並行して使用している場合は、このデフォルト値セットが行われた後でバリデーションチェックが行われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;コンポーネント名.defaultProps = {
  props名: 値
}

// 例
PropTypesComponent.defaultProps = {
  defaultValue: &apos;default&apos;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;2021/08/26 時点で、自分は TypeScript をメインで使うようになったので、PropTypes と少し縁遠くなりました。
ただ、JavaScript のままのアプリも一部管理しているので、そちらでは今でも継続して使用しています。&lt;/p&gt;
&lt;p&gt;最初は、渡す props が多いコンポーネントだと型定義がめんどくさいと感じる人少なくないんじゃないかなと。
その分、それ以上のメリットがあると思っていますし、JavaScript × React アプリ開発時の使用をお勧めします。
（自分の場合は慣れてくると、逆に型定義がないと不安な気持ちなるようになりました(笑)）&lt;/p&gt;
&lt;h2&gt;参考リンク&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.reactjs.org/docs/typechecking-with-proptypes.html&quot;&gt;React - Doc - PropTypes を用いた型チェック&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/facebook/prop-types&quot;&gt;GitHub - prop-types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/park-jh/items/a7dc06a478b944e59222&quot;&gt;ReactのPropTypes Validator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/_lemon2003_/items/55c947f29b44f79dc859&quot;&gt;Javascript - シ、シンボル(Symbol)って何？&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/59019287/difference-between-proptypes-exact-and-proptypes-shape&quot;&gt;Stack Overflow - Difference between PropTypes.exact and PropTypes.shape&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/park-jh/items/f06b9ce4a7a698b5c6c9&quot;&gt;Custom PropTypes Validator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Jekyll製ブログのカテゴリーページにもページネーションを導入する</title><link>https://changeofpace.site/posts/2020-03-07-jekyll-pagination-v2</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-03-07-jekyll-pagination-v2</guid><description>デフォルトで入っているjekyll-paginateではカテゴリーページに対応していなかったので、jekyll-paginate-v2を使ってページネーションを導入した備忘録</description><pubDate>Sat, 07 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以前、当ブログのカテゴリーページにページネーションを導入したのですが、記事化していなかったので、当時の記録を掘り起こしながら導入手順を書いてみます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;元々あるページネーション&lt;/h2&gt;
&lt;p&gt;現時点で、当ブログで使用しているJekyllのバージョンは&lt;code&gt;3.8.1&lt;/code&gt;です。
この Jekyll 3系ではデフォルトで&lt;code&gt;jekyll-paginate&lt;/code&gt;プラグインが導入されており、トップページではページネーションが有効になっていました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;http://jekyllrb-ja.github.io/docs/pagination&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ただ、カテゴリーページにもページネーションを有効にしたいなと思い調べると、&lt;code&gt;jekyll-paginate&lt;/code&gt;プラグインでは対応していないとのこと。
代わりに&lt;code&gt;jekyll-paginate-v2&lt;/code&gt;プラグインを使用するように公式でも案内があったため、こちらへ移行することにしました。&lt;/p&gt;
&lt;h2&gt;jekyll-paginate-v2導入&lt;/h2&gt;
&lt;p&gt;※以下導入手順を書いていきますが、使用テーマによって微妙に違いのある可能性があります。
（自分は現時点で &lt;a href=&quot;https://github.com/fongandrew/hydeout&quot;&gt;Hydeout&lt;/a&gt; の&lt;code&gt;4.0.2&lt;/code&gt;を使用しています）&lt;/p&gt;
&lt;h3&gt;プラグインのインストール&lt;/h3&gt;
&lt;p&gt;Gemfileに追記。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;group :jekyll_plugins do
  gem &quot;jekyll-feed&quot;, &quot;~&amp;gt; 0.6&quot;
  gem &quot;jekyll-sitemap&quot;
  gem &quot;jekyll-paginate-v2&quot; ←追記
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストール.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle install
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;基本設定&lt;/h3&gt;
&lt;h4&gt;_config.yml&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;使用プラグインを修正&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;plugins:
  - jekyll-feed
  - jekyll-paginate
  - jekyll-sitemap
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;plugins:
  - jekyll-feed
  - jekyll-paginate-v2 # 変更
  - jekyll-sitemap
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;旧ページネーション設定を書き換え&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;paginate: 8
paginate_path: &quot;page:num&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;↓&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pagination:
  enabled: true
  per_page: 10
  permalink: &apos;/page/:num/&apos;
  title: &apos;:title - :num&apos;
  sort_reverse: true
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;enabled：ページネーションの有効化&lt;/li&gt;
&lt;li&gt;per_page：ページネーションの1ページ当たりのリンク数&lt;/li&gt;
&lt;li&gt;permalink：ページネーションでページ送りしたときのURL（:numにはページ数が入る）&lt;/li&gt;
&lt;li&gt;title：ページネーションでページ送りしたときの表示タイトル（:titleにはそのページのタイトル、:numにはページ数）&lt;/li&gt;
&lt;li&gt;sort_reverse：リンクの並び順を記事が新しい順にするか&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;_layouts/index.html&lt;/h4&gt;
&lt;p&gt;トップページのレイアウトファイルです。
デフォルトでは&lt;code&gt;jekyll-paginate&lt;/code&gt;が導入されているため、恐らく以下のようなものが含まれていると思われます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pagination-newer.html：ページを1ページ戻す部分&lt;/li&gt;
&lt;li&gt;pagination-older.html：ページを1ページ進める部分&lt;/li&gt;
&lt;li&gt;paginator.posts：ページネーションでの1ページあたりの数の記事情報が入っている&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここについては特に変更しなくてよいです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
layout: default
---

&amp;lt;div class=&quot;content&quot;&amp;gt;
  {% include pagination-newer.html %}

  {{ content }}

  {% for post in paginator.posts %}
  &amp;lt;article class=&quot;post-body&quot;&amp;gt;
    &amp;lt;h2 class=&quot;post-title&quot;&amp;gt;
      &amp;lt;a href=&quot;{{ site.baseurl }}{{ post.url }}&quot;&amp;gt;
        {{ post.title }}
      &amp;lt;/a&amp;gt;
    &amp;lt;/h2&amp;gt;
    {% include post-meta.html post=post %}

    {% if post.excerpt %}
      {{ post.excerpt }}
    {% else %}
      {{ post.content }}
    {% endif %}

    {% if post.excerpt %}
      {% comment %}Excerpt may be equal to content. Check.{% endcomment %}
      {% capture content_words %}
        {{ post.content | number_of_words }}
      {% endcapture %}
      {% capture excerpt_words %}
        {{ post.excerpt | number_of_words }}
      {% endcapture %}

      {% if content_words != excerpt_words %}
        &amp;lt;a href=&quot;{{ site.baseurl }}{{ post.url }}&quot;&amp;gt;More &amp;amp;hellip;&amp;lt;/a&amp;gt;
      {% endif %}
    {% endif %}
  &amp;lt;/article&amp;gt;
  {% endfor %}

  {% include pagination-older.html %}
&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;index.html&lt;/h4&gt;
&lt;p&gt;トップページの html ファイルです。&lt;/p&gt;
&lt;p&gt;front matter に以下を追記します。
これがないと、レイアウトファイルの&lt;code&gt;paginator.posts&lt;/code&gt;に何も値が入らないため、トップページ表示の際に何も表示されなくなってしまいました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;pagination:
  enabled: true
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;※注意点・URL&lt;/h4&gt;
&lt;p&gt;使用テーマの影響の可能性もありますが、この設定をしたことで URL が&lt;code&gt;/&lt;/code&gt;であるかを条件とした分岐処理が一部うまく動かなくなりました。&lt;/p&gt;
&lt;p&gt;その個所については URL が&lt;code&gt;/index.html&lt;/code&gt;であるかというように修正して対応しました。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;この時点ではページネーションの基本設定をやっただけなので、カテゴリーページにページネーションはまだついていません。&lt;/p&gt;
&lt;h3&gt;カテゴリーページへの設定&lt;/h3&gt;
&lt;h4&gt;各カテゴリーページのマークダウンファイル&lt;/h4&gt;
&lt;p&gt;front matter にページネーションの設定を追記します。&lt;/p&gt;
&lt;p&gt;以下は当ブログのプログラミングカテゴリーの例です。
&lt;code&gt;permalink&lt;/code&gt;、&lt;code&gt;pagination&lt;/code&gt;が今回必要な設定です。&lt;/p&gt;
&lt;p&gt;例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
layout: category
title: プログラミング
permalink:  &apos;/プログラミング/&apos;
sidebar_sort_order: 3
pagination:
  enabled: true
  category: プログラミング
  permalink: &apos;/:num/&apos;
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この場合、URL は&lt;code&gt;(ドメイン)/プログラミング/index.html&lt;/code&gt;や&lt;code&gt;(ドメイン)/プログラミング/(ページ数)&lt;/code&gt;といったようになります。&lt;/p&gt;
&lt;h4&gt;_layouts/category.html&lt;/h4&gt;
&lt;p&gt;カテゴリーページのレイアウトファイルです。
&lt;code&gt;pagination-newer.html&lt;/code&gt;と&lt;code&gt;pagination-older.html&lt;/code&gt;の include を追記。
加えて、&lt;code&gt; {% for post in site.categories[category] %}&lt;/code&gt;となっていたところを&lt;code&gt;{% for post in paginator.posts %}&lt;/code&gt;に。
ページネーションでの1ページあたりの数の記事情報でforを回すようにします。&lt;/p&gt;
&lt;p&gt;以下は一例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;---
layout: page
---

{% unless page.content == &apos;&apos; %}
  {{ content }}
{% endunless %}

{% include pagination-newer.html %}

&amp;lt;ul class=&quot;posts-list&quot;&amp;gt;
  {% assign category = page.category | default: page.title %}
  {% for post in paginator.posts %}
    &amp;lt;li&amp;gt;
      &amp;lt;h3&amp;gt;
        &amp;lt;a href=&quot;{{ site.baseurl }}{{ post.url }}&quot;&amp;gt;
          {{ post.title }}
          &amp;lt;small&amp;gt;{{ post.date | date_to_string }}
            {% for tag in post.tags %}
              {{ tag }}
            {% endfor %}
          &amp;lt;/small&amp;gt;
        &amp;lt;/a&amp;gt;
      &amp;lt;/h3&amp;gt;
    &amp;lt;/li&amp;gt;
  {% endfor %}
&amp;lt;/ul&amp;gt;

{% include pagination-older.html %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでカテゴリーページでもページネーションが有効になります。&lt;/p&gt;
&lt;h4&gt;※注意点・カテゴリーページへのリンク&lt;/h4&gt;
&lt;p&gt;自分が使用しているテーマの場合、サイドバーにカテゴリーページへのリンクがあり、元々以下のようなコードになっていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% comment %}
  Dynamically generate list of links to all category pages
{% endcomment %}
{% assign pages_list = site.pages|sort:&quot;sidebar_sort_order&quot; %}
{% for node in pages_list %}
  {% if node.title != null %}
    {% if node.layout == &quot;category&quot; %}
      &amp;lt;a class=&quot;category-link {% if page.url == node.url %} active{% endif %}&quot;
          href=&quot;{{ node.url | relative_url }}&quot;&amp;gt;{{ node.title }}&amp;lt;/a&amp;gt;
    {% endif %}
  {% endif %}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;するとカテゴリーページのページネーションを有効にしたことで、サイドバーのリンクが以下のようになってしまいました。
ページネーションの各ページまでリンクに出てしまっています。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/jekyll-pagination-v2/category-link.png&quot; alt=&quot;サイドバーのカテゴリーページへのリンク&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;なので、_includes/category-links.html ファイルを新たに作成して、以下のような内容で上書きするようにしました。
これでページネーションの各ページが出なくなります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% comment %}
  Dynamically generate list of links to all category pages
{% endcomment %}

{% assign pages_list = site.pages|sort:&quot;sidebar_sort_order&quot; %}
{% for node in pages_list %}
  {% if node.title and node.autogen == nil and node.layout == &quot;category&quot; %}
    &amp;lt;a class=&quot;category-link {% if page.url == node.url %} active{% endif %}&quot; href=&quot;{{ node.url | relative_url }}&quot;&amp;gt;{{ node.title | escape }}&amp;lt;/a&amp;gt;
  {% endif %}
{% endfor %}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://jekyllrb-ja.github.io/&quot;&gt;Jekyll - ドキュメント&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/sverrirs/jekyll-paginate-v2&quot;&gt;GitHub - jekyll-paginate-v2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/fongandrew/hydeout&quot;&gt;GitHub - hydeout&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GitHub ActionsでReact(SPA)アプリをS3にデプロイ（+ CloudFrontのキャッシュクリア）して、Slackに通知するまで</title><link>https://changeofpace.site/posts/2020-03-01-github-actions-react</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-03-01-github-actions-react</guid><description>GitHub Actionsを使った、React(SPA)アプリの自動デプロイ+Slack通知に挑戦した記録</description><pubDate>Sun, 01 Mar 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;以前から業務で React を使用していましたが、デプロイは手動でやっていたため、自動化できたらいいなという話をチームでしていました。
そこで、正式リリースもされた GitHub Actions をせっかくなので使ってみたいなと思い、自動化に挑戦してみました。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/5b130ab87d73bd888bd9&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-10-05-github-actions-react&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2021/09/05追記 全体的に内容を更新しました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;GitHub Actions とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.co.jp/features/actions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ざっくり言うと、GitHub が提供している、ワークフロー自動化機能のことです。
あるトリガーをきっかけに、あらかじめ定義しておいた処理を自動実行する。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;ビルド&lt;/li&gt;
&lt;li&gt;静的解析&lt;/li&gt;
&lt;li&gt;テスト&lt;/li&gt;
&lt;li&gt;デプロイ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;などといったものを自動化することで、いわゆる CI/CD 環境を構築できます。&lt;/p&gt;
&lt;p&gt;はじめはベータ版で公開されていましたが、2019年11月に正式公開されました。&lt;/p&gt;
&lt;p&gt;これまで CI/CD 環境を構築するにあたって、 Circle CI などの外部サービスと連携させて行うことが多かったです。
ですが、この GitHub Actions に登場により、GitHub だけでも構築できるようになりました。&lt;/p&gt;
&lt;p&gt;こちらの記事で概要から機能まで幅広く解説されています。
&amp;lt;OG url=&quot;https://knowledge.sakura.ad.jp/23478&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;今回構築する構成&lt;/h2&gt;
&lt;p&gt;React(SPA) アプリのビルド生成物を S3 バケットに配置。
その S3 バケットをオリジンとして CDN（CloudFront ディストリビューション）を配置し、そこからコンテンツを配信する
というシンプルな構成です。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;※インフラ構築するにあたって、AWS CloudFormation や Terraform 等の IaC（Infrastructure as Code）を実現するものを使う方式もありますが、当記事では使用しませんのでご注意ください。
　また、AWS サービスを使用した Web・モバイルアプリ開発支援ツールである AWS Amplify についても同様に当記事では取り扱いません。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;OAI について&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.aws.amazon.com/ja_jp/AmazonCloudFront/latest/DeveloperGuide/private-content-restricting-access-to-s3.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Origin Access Identity&lt;/code&gt;の略称で、特別な CloudFront ユーザのことを指します。
S3 バケットへのアクセスを、特定の CloutFront ディストリビューション経由からのみにするために使用するものです。&lt;/p&gt;
&lt;p&gt;通常、S3 バケット単体でもコンテンツを静的サイトとして公開する機能を持っており、そのドメインを CloudFront ディストリビューションのオリジンとして設定も可能です。
しかし、S3 バケット単体でコンテンツを静的サイトとして公開するにあたっては、全てのユーザに S3 バケットへの読み取りアクセス許可を付与する必要があります。
どんな人でも S3 バケットへ直接アクセスできるようになるわけで、セキュリティやアクセス制御などの問題が生じてきてしまいます。&lt;/p&gt;
&lt;p&gt;それを解決するため、特定の CloutFront ディストリビューション経由からのみ、S3 バケットへのアクセスを受け付けるようにするわけです。
S3 バケット側から見て、どの CloudFront ディストリビューションからのみアクセスを受け付ければいいのか判断するものとして、この OAI が使われます。&lt;/p&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;p&gt;最初にまずこれらを準備しましょう。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS アカウント&lt;/li&gt;
&lt;li&gt;Slack ワークスペース&lt;/li&gt;
&lt;li&gt;React（SPA）アプリのコード + そのコードを管理している GitHub リポジトリ&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;S3 の準備&lt;/h2&gt;
&lt;p&gt;GitHub Actions でデプロイする先を用意します。
マネジメントコンソールで S3 の画面へ。&lt;/p&gt;
&lt;h3&gt;バケットの作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;「バケットを作成」を選択&lt;/li&gt;
&lt;li&gt;一般的な設定
&lt;ul&gt;
&lt;li&gt;バケット名を入力（全世界で一意の名前である必要があります）&lt;/li&gt;
&lt;li&gt;リージョンを選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;このバケットのブロックパブリックアクセス設定
&lt;ul&gt;
&lt;li&gt;パブリックアクセスをすべてブロックにチェック（デフォルトだとチェック済み）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;バケットのバージョニング
&lt;ul&gt;
&lt;li&gt;任意でどちらか選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;タグ - オプション
&lt;ul&gt;
&lt;li&gt;任意でタグの追加&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;デフォルトの暗号化
&lt;ul&gt;
&lt;li&gt;任意でどちらか選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;詳細設定
&lt;ul&gt;
&lt;li&gt;任意でどちらか選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;「バケットを作成」を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;作成したバケットの ARN（&lt;code&gt;arn:aws:s3:::バケット名&lt;/code&gt;） は、ポリシー作成時に使用するので控えておきましょう。
バケットのプロパティタブから確認できます。
また、バケット名も GitHub Actions ワークフロー定義時に使用します。&lt;/p&gt;
&lt;h3&gt;静的サイトとして公開に関して&lt;/h3&gt;
&lt;p&gt;S3 にデプロイしたものを直接静的サイトとして公開もできますが、今回は CloudFront を介しての公開とするので&lt;strong&gt;非公開のままで OK&lt;/strong&gt; です。&lt;/p&gt;
&lt;h2&gt;Cloud Front の準備&lt;/h2&gt;
&lt;p&gt;マネジメントコンソールで CloudFront の画面へ。&lt;/p&gt;
&lt;h3&gt;ディストリビューションの作成&lt;/h3&gt;
&lt;p&gt;最低限の設定のみ記載します。
その他の項目は、ご自身の環境に応じて設定ください。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;「ディストリビューションを作成」を選択&lt;/li&gt;
&lt;li&gt;オリジン
&lt;ul&gt;
&lt;li&gt;オリジンドメイン：AWS オリジンとして、先ほど作成したバケット（&lt;code&gt;{※バケット名}.s3.amazonaws.com&lt;/code&gt;）を選択&lt;/li&gt;
&lt;li&gt;S3 バケットアクセス：「はい、OAI を使用します」を選択
&lt;ul&gt;
&lt;li&gt;オリジンアクセスアイデンティティ：既存の OAI を選択か、新しく OAI を作成して選択&lt;/li&gt;
&lt;li&gt;バケットポリシー：「はい、バケットポリシーを更新します」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;デフォルトのキャッシュビヘイビア
&lt;ul&gt;
&lt;li&gt;※任意で設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;関数の関連付け
&lt;ul&gt;
&lt;li&gt;※任意で設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;設定
&lt;ul&gt;
&lt;li&gt;デフォルトルートオブジェクト：「index.html」と入力&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;「ディストリビューションの作成」を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これでディストリビューションが作成できました。
以前は動作するようになるまで15分ほどかかりましたが、問題なければ即座に有効化のステータスになるかと思われます（設定にもよるかも）&lt;/p&gt;
&lt;p&gt;ディストリビューションの ARN をポリシー作成で使用するので控えておきましょう。
&lt;code&gt;arn:aws:cloudfront::XXXXXXXXXXXX:distribution/{※ディストリビューションID}&lt;/code&gt; のような形式です。
また、ディストリビューションドメインも併せて確認しておきましょう。
どちらも一般タブから確認できます。&lt;/p&gt;
&lt;h3&gt;設定項目に関しての補足&lt;/h3&gt;
&lt;h4&gt;S3 バケットアクセスで OAI を使用&lt;/h4&gt;
&lt;p&gt;OAI については、&lt;a href=&quot;#oai-%E3%81%AB%E3%81%A4%E3%81%84%E3%81%A6&quot;&gt;冒頭&lt;/a&gt;に書いた通りです。&lt;/p&gt;
&lt;p&gt;バケットポリシーで「はい、バケットポリシーを更新します」を選択することで、連携した S3 バケットのバケットポリシーに以下のものが追記されます。
このポリシーによって、特定の OAI と紐づく CloudFront ディストリビューションのみ、アクセス許可を付与するようになっているわけです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2008-10-17&quot;,
    &quot;Id&quot;: &quot;PolicyForCloudFrontPrivateContent&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;1&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: {
                &quot;AWS&quot;: &quot;arn:aws:iam::cloudfront:user/CloudFront Origin Access Identity {※OAI ID}&quot;
            },
            &quot;Action&quot;: &quot;s3:GetObject&quot;,
            &quot;Resource&quot;: &quot;{S3バケットのARN}/*&quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;デフォルトルートオブジェクト&lt;/h4&gt;
&lt;p&gt;ルート階層のオブジェクトとしたいものを指定します。
今回の場合は「index.html」なので、index.html でアクセスを受け取る感じです。&lt;/p&gt;
&lt;p&gt;今回はやっていませんが、S3 バケットを直接静的サイトとして公開する設定にしている場合、その設定の中にもインデックスドキュメントを指定するところがあります。
これと同様の設定のように思えますが、CloudFront 側でも設定しておく必要がありました（設定しないとブラウザからアクセスしたとき AccessDenied になりました）&lt;/p&gt;
&lt;h2&gt;S3 と CloudFront における動作確認&lt;/h2&gt;
&lt;p&gt;ここまでで S3 と CloudFront の準備はできたので、試しに手動デプロイをして動作を見てみましょう。
ビルドして、その成果物をデプロイします。&lt;/p&gt;
&lt;p&gt;create-react-app で作成した React アプリの場合は、以下でビルド。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;build&lt;/code&gt;ディレクトリにビルド成果物ができるので、&lt;code&gt;build&lt;/code&gt;配下のものを作成した S3 バケットにアップロード。&lt;/p&gt;
&lt;p&gt;作成した CloudFront ディストリビューションのドメインにアクセスして、React アプリが問題なく動作していれば OK です。&lt;/p&gt;
&lt;h3&gt;補足：S3 のみでの動作確認&lt;/h3&gt;
&lt;p&gt;もし CloudFront ディストリビューションのドメインにアクセスしても React アプリが表示されない場合。
問題個所の切り分けとして、まず S3 バケットへのデプロイ内容が問題ないか確認したい時もあるでしょう。&lt;/p&gt;
&lt;p&gt;そういった時、一時的に S3 バケットを&lt;strong&gt;直接&lt;/strong&gt;静的サイトとして公開し、動作を確認するのも1つの方法です。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;作成したバケットを選択&lt;/li&gt;
&lt;li&gt;プロパティタブ → 静的ウェブサイトホスティングの「編集」を選択&lt;/li&gt;
&lt;li&gt;静的ウェブサイトホスティング：「有効にする」を選択&lt;/li&gt;
&lt;li&gt;ホスティングタイプ：「静的ウェブサイトをホストする」を選択&lt;/li&gt;
&lt;li&gt;インデックスドキュメント：「index.html」を入力&lt;/li&gt;
&lt;li&gt;エラードキュメント：任意で指定&lt;/li&gt;
&lt;li&gt;「変更の保存」を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;静的ウェブサイトホスティングが有効と表示されていれば公開されています。
ただ、これだけだとバケット（静的サイト）へのアクセスが許可されていないため、エンドポイントにアクセスしても403で弾かれてしまう状態です。
そのため、ブロックパブリックアクセスの制御と、バケットポリシーでアクセス許可を付与する必要があります。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ブロックパブリックアクセス (バケット設定)では、以下の2つを一時的に無効化。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;「新しいパブリックバケットポリシーまたはアクセスポイントポリシーを介して付与されたバケットとオブジェクトへのパブリックアクセスをブロックする」：バケットポリシーを編集できるようにするため&lt;/li&gt;
&lt;li&gt;「任意のパブリックバケットポリシーまたはアクセスポイントポリシーを介したバケットとオブジェクトへのパブリックアクセスとクロスアカウントアクセスをブロックする」：設定したバケットポリシーを介してのパブリックアクセスを受け付けるようにするため&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-emable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;バケットポリシー編集にあたっては、以下のアクセス許可が必要になるので注意です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;s3:GetBucketPolicy&lt;/li&gt;
&lt;li&gt;s3:PutBucketPolicy&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;アクセス許可タブ → バケットポリシーで「編集」を選択し、ポリシーを追記。
（すでに記述しているバケットポリシーはどこかに控えておいて、ちゃんと戻せるようにしておきましょう）&lt;/p&gt;
&lt;p&gt;指定の S3 バケットコンテンツ全てへ、読み取り許可をする例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Sid&quot;: &quot;PublicReadGetObject&quot;,
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Principal&quot;: &quot;*&quot;,
            &quot;Action&quot;: &quot;s3:GetObject&quot;,
            &quot;Resource&quot;: &quot;{※S3 バケットの ARN}/*&quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでエンドポイントからアクセスして、React アプリが正常に動作するか確認してみましょう。
問題なければ、&lt;strong&gt;各種設定を戻すことを忘れないように&lt;/strong&gt;。&lt;/p&gt;
&lt;h2&gt;IAM の準備&lt;/h2&gt;
&lt;p&gt;GitHub Actions でデプロイする際に使用する、デプロイ用の IAM ユーザを作成します。
マネジメントコンソールで IAM の画面へ。&lt;/p&gt;
&lt;h3&gt;ポリシーの作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;ポリシー一覧から「ポリシーを作成」を選択&lt;/li&gt;
&lt;li&gt;JSON に以下を記述して「次のステップ：タグ」を選択
&lt;code&gt;{}&lt;/code&gt;の部分はこれまで控えたものに適宜置き換えてください。&lt;/li&gt;
&lt;li&gt;タグを追加
&lt;ul&gt;
&lt;li&gt;任意でタグを追加し、「次のステップ：確認」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ポリシーの確認
&lt;ul&gt;
&lt;li&gt;任意の名前と説明を入力して「ポリシーの作成」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;2 で記述するポリシーの JSON.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
    &quot;Version&quot;: &quot;2012-10-17&quot;,
    &quot;Statement&quot;: [
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;s3:PutObject&quot;,
                &quot;s3:GetObject&quot;,
                &quot;s3:ListBucket&quot;,
                &quot;s3:DeleteObject&quot;
            ],
            &quot;Resource&quot;: [
                &quot;{※S3バケットのARN}&quot;,
                &quot;{※S3バケットのARN}/*&quot;
            ]
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;cloudfront:GetDistribution&quot;,
                &quot;cloudfront:GetDistributionConfig&quot;,
                &quot;cloudfront:CreateInvalidation&quot;,
                &quot;cloudfront:ListInvalidations&quot;,
                &quot;cloudfront:GetInvalidation&quot;
            ],
            &quot;Resource&quot;: &quot;{※CloudFrontディストリビューションのARN}&quot;
        },
        {
            &quot;Effect&quot;: &quot;Allow&quot;,
            &quot;Action&quot;: [
                &quot;cloudfront:ListDistributions&quot;,
                &quot;cloudfront:ListStreamingDistributions&quot;
            ],
            &quot;Resource&quot;: &quot;*&quot;
        }
    ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ユーザの作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;ユーザ一覧から「ユーザを追加」を選択&lt;/li&gt;
&lt;li&gt;ユーザ名は任意、アクセスの種類は「プログラムによるアクセス」のみ選択して「次のステップ：アクセス権限」を選択&lt;/li&gt;
&lt;li&gt;アクセス許可の設定
&lt;ul&gt;
&lt;li&gt;「既存のポリシーを直接アタッチ」を選択&lt;/li&gt;
&lt;li&gt;先ほど作成したポリシーを選択&lt;/li&gt;
&lt;li&gt;アクセス権限の境界の設定は任意でどちらかを選択し、「次のステップ：タグ」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;タグの追加
&lt;ul&gt;
&lt;li&gt;任意（そのまま次のステップでも OK）で追加して、「次のステップ：確認」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;確認して「ユーザの作成」を選択&lt;/li&gt;
&lt;li&gt;アクセスキーとシークレットキーを控えておく&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Slack の準備&lt;/h2&gt;
&lt;p&gt;GitHub Actions の結果を通知するための Webhook URL を用意します。&lt;/p&gt;
&lt;h3&gt;Slack App の作成&lt;/h3&gt;
&lt;p&gt;作成時に From scratch と From an app nanifest の2パターンありますが、今回は前者で作成しています。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Slack ワークスペースのワークスペース名のところからメニューを開く&lt;/li&gt;
&lt;li&gt;ビルド画面へ
&lt;ul&gt;
&lt;li&gt;※ワークスペースのオーナーの場合
設定と管理 → アプリを管理する → 右上の「ビルド」&lt;/li&gt;
&lt;li&gt;※オーナーでない場合
その他管理項目 → アプリを管理する → 右上の「ビルド」&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;「Create New App」を選択&lt;/li&gt;
&lt;li&gt;「From scratch」を選択
&lt;ul&gt;
&lt;li&gt;App Name：任意の名前&lt;/li&gt;
&lt;li&gt;Development Slack Workspace：任意のワークスペース&lt;/li&gt;
&lt;li&gt;「Create App」を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;以下、作成した App の画面で進めます。&lt;/p&gt;
&lt;h3&gt;Incomming Webhook URL の作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Building Apps for Slack の Add features and functionality を開いて、Incomming Webhooks を選択&lt;/li&gt;
&lt;li&gt;Activate Incoming Webhooks を ON にして、「Add New Webhook to Workspace」&lt;/li&gt;
&lt;li&gt;投稿するチャンネルを選択して、「許可する」を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これで Webhook URL が生成されるので控えておきます。
投稿を担う Bots もあわせて作成されました。名前やアイコンを変えたければ編集しましょう。&lt;/p&gt;
&lt;h2&gt;GitHub の準備&lt;/h2&gt;
&lt;h3&gt;React アプリ&lt;/h3&gt;
&lt;p&gt;React アプリをリポジトリに push して用意しておきます。&lt;/p&gt;
&lt;p&gt;ちなみに自分は&lt;code&gt;create-react-app&lt;/code&gt;にて作れる雛形でやりました。
npx や yarn コマンドでサクッと作れますが、自分は yarn を使っているので、以降は yarn コマンドで書いていきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# TS
yarn create react-app (アプリ名) --template typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;それと GitHub Actions の中で ESLint を使いたいので、使いやすいように package.json の scripts に以下を追加しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  .
  .
  .
  &quot;lint&quot;: &quot;yarn run -s eslint &apos;./src/**/*.{js,jsx,ts,tsx}&apos;&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;GitHub Secrets&lt;/h3&gt;
&lt;p&gt;GitHub Actions で使用する環境変数を設定します。
公開したくない秘匿情報などを定義するのに向いており、GitHub Secrets に登録した変数の値は後から確認できないようになっています。&lt;/p&gt;
&lt;p&gt;リポジトリの Settings → Secrets で以下の情報を登録しておきましょう。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;AWS_ACCESS_KEY_ID&lt;/li&gt;
&lt;li&gt;AWS_SECRET_ACCESS_KEY&lt;/li&gt;
&lt;li&gt;AWS_REGION&lt;/li&gt;
&lt;li&gt;AWS_S3_BUCKET_NAME&lt;/li&gt;
&lt;li&gt;SLACK_WEBHOOK_URL&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なお、GITHUB_TOKEN については自動で値がセットされるので、設定しなくて大丈夫です。&lt;/p&gt;
&lt;h3&gt;GitHub Actions のワークフロー定義ファイル作成&lt;/h3&gt;
&lt;p&gt;ワークフロー定義ファイルは yml で記述して、リポジトリルート直下に&lt;code&gt;.github/workflows/ワークフロー定義ファイル名.yml&lt;/code&gt;で配置します。
配置する場所さえ合っていれば、ファイル名は何でも OK です。&lt;/p&gt;
&lt;p&gt;今回は Lint などのチェックを行うものと、デプロイを行うものとで2種類のワークフローを作成します。
内容の説明に関しては、&lt;a href=&quot;#%E3%83%AF%E3%83%BC%E3%82%AF%E3%83%95%E3%83%AD%E3%83%BC%E5%AE%9A%E7%BE%A9%E3%83%95%E3%82%A1%E3%82%A4%E3%83%AB%E3%81%AE%E7%B0%A1%E5%8D%98%E3%81%AA%E8%A7%A3%E8%AA%AC&quot;&gt;ワークフロー定義ファイルの簡単な解説&lt;/a&gt;を参照ください。&lt;/p&gt;
&lt;h4&gt;Lint などのチェック用ワークフロー例&lt;/h4&gt;
&lt;p&gt;ブランチを問わず、push されたら動作するようにしてあります。
今回は Lint チェックくらいしかやっておらず、もし失敗した時のみ Slack 通知をするようにしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Develop Check
on: push

env:
  project-name: ga-test

jobs:
  dev-check:
    name: Lint
    runs-on: ubuntu-20.04
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v2
        with:
          node-version: 14.17.3

      - name: Get Yarn Cache Directory Path
        id: yarn-cache-dir-path
        run: echo &quot;::set-output name=dir::$(yarn cache dir)&quot;

      - name: Cache Node Modules
        uses: actions/cache@v2
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format(&apos;{0}{1}&apos;, github.workspace, &apos;/yarn.lock&apos;)) }}
          restore-keys: |
            ${{ runner.os }}-${{ env.project-name }}-

      - name: Package Install
        run: yarn install

      - name: Lint
        run: yarn lint

      - name: Slack Notification by NonSuccess
        uses: 8398a7/action-slack@v3
        if: success() != true
        with:
          status: ${{ job.status }}
          fields: repo,message,commit,author,action,eventName,ref,workflow
          author_name: &apos;check&apos;
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;デプロイ用のワークフロー例&lt;/h4&gt;
&lt;p&gt;workflow_run を使って、main ブランチにおいて、チェック用ワークフローが終わった後、動作するようにしています。
チェック用ワークフローの成功・失敗問わず動作するため、ジョブの実行条件として事前のワークフローが成功した時という条件を設定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;name: Deploy
on:
  workflow_run:
    workflows:
      - &quot;Develop Check&quot;
    branches:
      - main
    types:
      - completed

env:
  project-name: ga-test

jobs:
  deploy:
    name: Build &amp;amp; Deploy
    runs-on: ubuntu-20.04
    if: ${{ github.event.workflow_run.conclusion == &apos;success&apos; }}
    timeout-minutes: 5

    steps:
      - name: Checkout
        uses: actions/checkout@v2

      - name: Setup Node
        uses: actions/setup-node@v2
        with:
          node-version: 14.17.3

      - name: Get Yarn Cache Directory Path
        id: yarn-cache-dir-path
        run: echo &quot;::set-output name=dir::$(yarn cache dir)&quot;

      - name: Cache Node Modules
        uses: actions/cache@v2
        with:
          path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
          key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format(&apos;{0}{1}&apos;, github.workspace, &apos;/yarn.lock&apos;)) }}
          restore-keys: |
            ${{ runner.os }}-${{ env.project-name }}-

      - name: Package Install
        run: yarn install

      - name: Build
        run: yarn build

      - name: Configure AWS Credentials
        uses: aws-actions/configure-aws-credentials@v1
        with:
          aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
          aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
          aws-region: ${{ secrets.AWS_REGION }}

      - name: Deploy to S3
        run: |
          aws s3 sync ./build s3://${{ secrets.AWS_S3_BUCKET_NAME }}

      - name: CloudFront Cache Clear
        run: |
          CFID=$(aws cloudfront list-distributions --query &quot;DistributionList.Items[].{Id:Id,Origin:Origins.Items[0].DomainName}[?contains(Origin,&apos;${{ secrets.AWS_S3_BUCKET_NAME }}.s3&apos;)] | [0].Id&quot; | sed &apos;s/&quot;//g&apos;)
          echo &quot;aws cloudfront create-invalidation ${CFID}&quot;
          aws cloudfront create-invalidation --distribution-id ${CFID} --paths &quot;/*&quot;

      - name: Slack Notification
        uses: 8398a7/action-slack@v3
        if: always()
        with:
          status: ${{ job.status }}
          fields: repo,message,commit,author,action,eventName,ref,workflow
          author_name: &apos;deploy&apos;
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;workflow_run イベントの注意点&lt;/h4&gt;
&lt;p&gt;この workflow_run イベントによって実行されるワークフローは、&lt;strong&gt;デフォルトブランチ&lt;/strong&gt;の内容をベースとして動作することに注意が必要です。&lt;/p&gt;
&lt;p&gt;まず、&lt;strong&gt;デフォルトブランチ&lt;/strong&gt;上のワークフロー定義ファイルの内容で実行されるということと。
上記のワークフローの場合、main ブランチで push すれば、チェック用ワークフロー → デプロイ用ワークフロー と動作するようになっているわけですが...。
例えば、デフォルトブランチが develop だったとすると checkout してくるコードが develop ブランチのものになります。
そのため、デプロイする内容も develop ブランチのものになるわけです。&lt;/p&gt;
&lt;p&gt;デフォルトブランチは develop で運用しているけど、main ブランチの内容をデプロイしたい
などといった場合は、明示的に参照する ref を指定することで対応できるようです。
こちらの記事が参考になります。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://blog.kengo-toda.jp/entry/2021/07/06/222400&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;この仕様を理解していないと予期せぬ事故を引き起こす可能性があるので、取り扱いに十分注意しましょう。&lt;/p&gt;
&lt;h4&gt;補足：node_modules キャッシュの新しいやり方（※2021/10/24）&lt;/h4&gt;
&lt;p&gt;setup-node アクションだけで、node_modules のキャッシュ使用定義ができるようになりました。
使用しているパッケージマネージャに合わせて with.cache を追加するだけで OK。
こちらの方がすっきりしますね。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Setup Node
  uses: actions/setup-node@v2
  with:
    node-version: 14.17.3
    cache: yarn
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;いざ、GitHub Actions でデプロイ&lt;/h2&gt;
&lt;p&gt;今回の定義内容では、 main ブランチに push する（もしくは main ブランチへのプルリクをマージする）ことで、デプロイ用のワークフローまで動作します。
ちゃんと動作していれば、リポジトリの Actions からワークフローの状況を確認できるようになっているはずです。
それぞれのステップのログも確認できるので、もしどこかでエラーになって失敗しても原因調査に役立ちます。&lt;/p&gt;
&lt;p&gt;無事最後まで成功していれば、CloudFront ディストリビューションドメインからアクセスしてみましょう。
React アプリが問題なく表示されているでしょうか？&lt;/p&gt;
&lt;p&gt;そして、Slack にもちゃんと通知できているでしょうか？
通知の内容は以下のような感じになります。&lt;/p&gt;
&lt;p&gt;成功
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/github-actions-react/github-actions-slack-success.png&quot; alt=&quot;ワークフロー成功時のSlack通知&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;キャンセル
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/github-actions-react/github-actions-slack-canceled.png&quot; alt=&quot;ワークフローキャンセル時のSlack通知&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;失敗
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/github-actions-react/github-actions-slack-failed.png&quot; alt=&quot;ワークフロー失敗時のSlack通知&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;また、プルリクを出した時、そのリクエスト元ブランチのワークフロー実行状況が表示されるようになります。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/github-actions-react/pull-request.png&quot; alt=&quot;ワークフロー実行ブランチでのプルリクエスト&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;ワークフロー定義ファイルの簡単な解説&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://docs.github.com/en/actions/reference/context-and-expression-syntax-for-github-actions&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;ワークフロー名&lt;/h3&gt;
&lt;p&gt;GitHub の Actions 上では、この名前が表示されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# チェック用ワークフロー
name: Develop Check
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
name: Deploy
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;トリガー&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;on&lt;/code&gt;でトリガーになるイベントを指定。
指定できるイベントについては以下を参照ください。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.github.com/en/actions/reference/events-that-trigger-workflows&quot; /&amp;gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# チェック用ワークフロー
on: push
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これだけだと、全ての push をトリガーとするので、ブランチやタグの指定で絞り込むことも出来ます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
on:
  workflow_run:
    workflows:
      - &quot;Develop Check&quot;
    branches:
      - main
    types:
      - completed
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;workflow_run は他のワークフロー実行をトリガーとするものです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;workflows：依存させるワークフロー名&lt;/li&gt;
&lt;li&gt;branches：どのブランチで依存させるワークフローが実行された時か&lt;/li&gt;
&lt;li&gt;types：実行するタイミング
&lt;ul&gt;
&lt;li&gt;completed：依存させるワークフローが終了した時（成功・失敗は関係なし）&lt;/li&gt;
&lt;li&gt;requested：依存させるワークフローがリクエストされた時&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;1ファイルのワークフロー定義の中に複数のジョブがあり、その依存関係を定義する際は&lt;code&gt;needs&lt;/code&gt;で出来ますが、別ファイルのワークフローに依存させたい時はこの workflow_run を使います。&lt;/p&gt;
&lt;h3&gt;ワークフローの環境変数&lt;/h3&gt;
&lt;p&gt;ワークフロー全体で使用できる環境変数の指定。
ここで設定したものは&lt;code&gt;env.〇〇&lt;/code&gt;で使用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;env:
  project-name: ga-test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ジョブ&lt;/h3&gt;
&lt;p&gt;ワークフローで実行するジョブ定義を記述。複数指定可能です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# チェック用ワークフロー
jobs:
  dev-check: # ジョブID
    name: Lint # ジョブ名
    runs-on: ubuntu-20.04 # 実行環境
    timeout-minutes: 5 # タイムアウト時間（分）

    steps:
      # 各種ステップの定義
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
jobs:
  deploy:
    name: Build &amp;amp; Deploy
    runs-on: ubuntu-20.04
    if: ${{ github.event.workflow_run.conclusion == &apos;success&apos; }} # 実行条件（今回の場合は、事前のワークフローが成功していること）
    timeout-minutes: 5

    steps:
      # 各種ステップの定義
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行環境の種類については以下を参照。
あらかじめ導入されている CLI ツールなども確認できます。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;どの実行環境にするとしても、なるべくバージョンまで指定した方がいいです。
latest にしていると、その対象バージョンが更新されたときに予期せず動作しなくなる可能性があるので...。&lt;/p&gt;
&lt;p&gt;それとタイムアウト時間も設定しておいた方が無難です。
ステップ定義ミスで、実行されたコマンドが入力応答待ちのままになるなど止まってしまったときに、どんどん無料枠の時間を食いつぶしてしまうことを避けられます。
実際の実行時間を計測して、それに少し余裕を持たせるくらいで設定しておきましょう。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;jobs.&amp;lt;job_id&amp;gt;.steps&lt;/code&gt;で、そのジョブで実行するステップ処理を記述していきます。&lt;/p&gt;
&lt;h4&gt;ステップ・リポジトリのチェックアウト&lt;/h4&gt;
&lt;p&gt;リポジトリのコードを落としてきます。
どのブランチの内容を参照するかは、トリガーイベントによって異なることに注意です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Checkout
  uses: actions/checkout@v2
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;uses&lt;/code&gt;で指定している actions は、あらかじめ実行する処理を定義しているもので、これを使用することでステップを組み立てやすくなります。
実行環境のバージョンと同様に、このアクションのバージョンもなるべく指定しておいた方がいいです。&lt;/p&gt;
&lt;h4&gt;ステップ・Node.js のセットアップ&lt;/h4&gt;
&lt;p&gt;ちなみに Node.js だけでなく、いろんな言語のセットアップ action が提供されています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Setup Node
  uses: actions/setup-node@v2
  with:
    node-version: 14.17.3
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・yarn のキャッシュを格納するディレクトリのパスを取得&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- name: Get Yarn Cache Directory Path
  id: yarn-cache-dir-path
  run: echo &quot;::set-output name=dir::$(yarn cache dir)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・キャッシュの復元もしくは作成&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;key&lt;/code&gt;の完全一致で既存のキャッシュを探し、マッチすればキャッシュを&lt;code&gt;path&lt;/code&gt;に復元。
なければ&lt;code&gt;restore-key&lt;/code&gt;の完全一致で既存のキャッシュを探し、マッチすればキャッシュを&lt;code&gt;path&lt;/code&gt;に復元。
さらになければ&lt;code&gt;restore-key&lt;/code&gt;の部分一致で既存のキャッシュを探し、見つければ最新のキャッシュを&lt;code&gt;path&lt;/code&gt;に復元。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;key&lt;/code&gt;の完全一致がなかった場合、このワークフローが成功して完了した際にキャッシュを作成します。&lt;/p&gt;
&lt;p&gt;今回の場合は key の一部として yarn.lock の内容をハッシュ化しているため、もしその内容に変更があった時に key が変わるようになっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Cache Node Modules
  uses: actions/cache@v2
  with:
    path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
    key: ${{ runner.os }}-${{ env.project-name }}-${{ hashFiles(format(&apos;{0}{1}&apos;, github.workspace, &apos;/yarn.lock&apos;)) }}
    restore-keys: |
      ${{ runner.os }}-${{ env.project-name }}-
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;キャッシュのあるなしで実行時間を比較してみたところ、こんな感じになりました。&lt;/p&gt;
&lt;p&gt;キャッシュなし・50s
&amp;lt;ImageWrapper src=&quot;screenshots/2020/github-actions-react/github-actions-no-cache.png&quot; alt=&quot;キャッシュなしの場合のワークフロー実行結果&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;キャッシュあり・26s
&amp;lt;ImageWrapper src=&quot;screenshots/2020/github-actions-react/github-actions-cache.png&quot; alt=&quot;キャッシュありの場合のワークフロー実行結果&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;ステップ・yarn でライブラリのインストール&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;- name: Package Install
  run: yarn install
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・ESLint で静的解析チェック&lt;/h4&gt;
&lt;p&gt;あらかじめ&lt;code&gt;package.json&lt;/code&gt;に定義しておいた、ESLint 実行コマンドを実行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Lint
  run: yarn lint
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・yarn でビルド&lt;/h4&gt;
&lt;p&gt;ビルドの成果物は&lt;code&gt;./build&lt;/code&gt;に格納されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;- name: Build
  run: yarn build
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・AWS Credentials の設定&lt;/h4&gt;
&lt;p&gt;元々この記事では以下のアクションを使って、S3 デプロイ + CloudFront のキャッシュクリアを行う方法を紹介していました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/opspresso/action-s3-sync&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;2021/09/05 時点で最終更新が 2020/06/05 であることと、内部的に使用されている AWS CLI が1系であることから、記事更新にあたって手動でコマンド実行する形式に変更しました。
（このアクションで内部的に実行されているコマンドを参考にしています）
今回使用している実行環境の Ubuntu 20.04 では AWS CLI の2系が導入済みなので、それを使っていきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
- name: Configure AWS Credentials
  uses: aws-actions/configure-aws-credentials@v1
  with:
    aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
    aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
    aws-region: ${{ secrets.AWS_REGION }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・S3 バケットにデプロイ&lt;/h4&gt;
&lt;p&gt;ビルド生成物を S3 バケットに同期させる形で反映させています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
- name: Deploy to S3
  run: |
    aws s3 sync ./build s3://${{ secrets.AWS_S3_BUCKET_NAME }}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・CloudFront ディストリビューションのキャッシュクリア&lt;/h4&gt;
&lt;p&gt;ディストリビューション ID も GitHub Secrets に設定して、そこから使う手もありますが、今回は S3 バケット名から辿って特定する形式にしています。
特定した ID をそのまま渡すと、余分なダブルクォートがついて&lt;code&gt;&quot;ディストリビューションID&quot;&lt;/code&gt;というディストリビューション ID でアクセスしに行くため、アクセス許可がないよと弾かれてしまいました。
そのため、sed を使ってダブルクォートを削除したうえで使用しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
- name: CloudFront Cache Clear
  run: |
    CFID=$(aws cloudfront list-distributions --query &quot;DistributionList.Items[].{Id:Id,Origin:Origins.Items[0].DomainName}[?contains(Origin,&apos;${{ secrets.AWS_S3_BUCKET_NAME }}.s3&apos;)] | [0].Id&quot; | sed &apos;s/&quot;//g&apos;)
    echo &quot;aws cloudfront create-invalidation ${CFID}&quot;
    aws cloudfront create-invalidation --distribution-id ${CFID} --paths &quot;/*&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;ステップ・Slackに通知&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/8398a7/action-slack&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://839.hateblo.jp/entry/2019/08/16/104624&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;if で通知する条件を指定。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;success()&lt;/code&gt;：ジョブの前のステップが成功した場合&lt;/li&gt;
&lt;li&gt;&lt;code&gt;always()&lt;/code&gt;：ジョブのこのステップが実行された場合&lt;/li&gt;
&lt;li&gt;&lt;code&gt;cancelled()&lt;/code&gt;：ワークフローがキャンセルされた場合&lt;/li&gt;
&lt;li&gt;&lt;code&gt;failure()&lt;/code&gt;：ジョブの前のステップが失敗した場合、もしくはジョブが失敗した場合&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;status で指定している &lt;code&gt;job.status&lt;/code&gt;にはそのジョブの成功、失敗、キャンセルに応じた値が入ります。
fields は通知内容です。デフォルトでは repo と message のみとなっていますので、お好みで設定。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# チェック用ワークフロー
- name: Slack Notification by NonSuccess
  uses: 8398a7/action-slack@v3
  if: success() != true
  with:
    status: ${{ job.status }}
    fields: repo,message,commit,author,action,eventName,ref,workflow
    author_name: &apos;check&apos;
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# デプロイ用ワークフロー
- name: Slack Notification
  uses: 8398a7/action-slack@v3
  if: always()
  with:
    status: ${{ job.status }}
    fields: repo,message,commit,author,action,eventName,ref,workflow
    author_name: &apos;deploy&apos;
  env:
    GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # 自動で設定される
    SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} # Slack投稿用のWebhook URL
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;とりあえずデプロイまで無事到達できてよかったのですが、色々検証しながらやっていたら、かなり時間を使ってました(苦笑)
まぁ、はじめてやることに時間かかるのはつきものでしょうか。
GitHub Actions を使おうとしている方の何かの参考になれば幸いです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://help.github.com/ja/actions&quot;&gt;GitHub Docs - GitHub Actions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://knowledge.sakura.ad.jp/23478/&quot;&gt;GitHubの新機能「GitHub Actions」で試すCI/CD&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/actions/cache&quot;&gt;GitHub - actions/cache&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/ngmr_mo/items/0576bfcea8c4487a6b47&quot;&gt;GitHub Actionsでキャッシュが使えるようになって嬉しい！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/8398a7/action-slack&quot;&gt;GitHub - 8398a7/action-slack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://839.hateblo.jp/entry/2019/08/16/104624&quot;&gt;GitHub Actions(beta)向けにslack通知プラグインを作った&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.honai.me/post/github-actions-aws-s3&quot;&gt;GitHub Actions を使って AWS S3 に自動デプロイする&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/anti-pattern-engineering/github-actions%E3%81%A7s3%E3%81%AB%E3%83%87%E3%83%97%E3%83%AD%E3%82%A4%E3%81%99%E3%82%8B-ef58a019aa1e&quot;&gt;GitHub ActionsでS3にデプロイする！&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.classmethod.jp/cloud/aws/cloudfront_invalidation_onlyuser/&quot;&gt;CloudFrontエッジキャッシュクリアのみを行うユーザーの作成&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/dogwood008/items/a92abae789f4b0466f38&quot;&gt;AWS S3で静的Webページをホスティングする&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.putise.com/cloudfront%E3%81%AB%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E3%81%97%E3%81%9F%E3%82%89accessdenied%E3%81%A7%E3%82%A2%E3%82%AF%E3%82%BB%E3%82%B9%E5%87%BA%E6%9D%A5%E3%81%AA%E3%81%84/&quot;&gt;CloudfrontにアクセスしたらAccessDeniedでアクセス出来ない&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://dev.classmethod.jp/cloud/aws/cloudfront-oai-for-cross-acount-s3/&quot;&gt;CloudFront オリジンアクセスアイデンティティでクロスアカウントなS3オリジンへのアクセスを制限する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://hacknote.jp/archives/43218/&quot;&gt;【S3】Web アクセス制限いろいろ (CloudFront もあるよ)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/ooxif/items/d28b7caf72bf5290588c&quot;&gt;CloudFront + S3 で静的サイトを運用する際の注意点&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/hiroyuki7/items/611d6e66831523c9ec64&quot;&gt;S3のバケットポリシーを書こうとしてもアクセス拒否される件について&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://zenn.dev/dzeyelid/articles/0dc97f76a7f33f2c27ca&quot;&gt;Check! GitHub Actions: 別のワークフローの実行リクエストまたは完了を契機にワークフローを実行する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.kengo-toda.jp/entry/2021/07/06/222400&quot;&gt;GitHub Actionsのworkflow_runイベントでテストを回すときの要点&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>ルーティングライブラリ、React Router(v5)入門</title><link>https://changeofpace.site/posts/2020-02-08-react-router</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-02-08-react-router</guid><description>Reactアプリケーションにルーティングを設定するデファクトのライブラリである、React Router(v5)のおおまかな使い方まとめ</description><pubDate>Sat, 08 Feb 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回は React の基礎的なことをまとめましたが、今回は React アプリのルーティング設定をするうえでよく使われている React Router についてまとめました。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/aa78b6c7cd1ef43549bf&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-09-22-react-router&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2021/08/22追記 TypeScript ベースで、全体的に大幅加筆修正を行いました。
※2022/01/24追記  あくまでライブラリの記事であるとわかりやすくするために、記事タイトルを変更しました（旧：React入門 ～React Router(v5)編～）&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;React Router とは？&lt;/h2&gt;
&lt;p&gt;最新
&amp;lt;OG url=&quot;https://reacttraining.com/react-router&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;5系
&amp;lt;OG url=&quot;https://v5.reactrouter.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;React で SPA を書くにあたって、DOM を書き換えて複数ページがあるように見せても URL が変わらないため、ブラウザからは1つのページとしてしか認識されません。
そこで、SPA の画面状態と URL とを紐づけ、さらにブラウザ履歴の同期を行います。
そうすることで、疑似的なページ遷移を実現できます。&lt;/p&gt;
&lt;p&gt;これにより URL を指定して直接特定の画面にいけたり、ブラウザバックを利用できるようになるわけです。
また、クライアントサイドでのページ遷移となるため、高速に遷移します。&lt;/p&gt;
&lt;p&gt;これを行ってくれるデファクトのルーティングライブラリが&lt;code&gt;React Router&lt;/code&gt;です。
ブラウザ履歴を管理する History API を React Router を通して操作していく形になります。&lt;/p&gt;
&lt;h2&gt;インストール&lt;/h2&gt;
&lt;p&gt;React Router は Web とネイティブともに対応しています。
今回は Web アプリに導入するので、react-router-dom とその型定義を追加。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add react-router-dom @types/react-router-dom
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;react-router も必要になりますが、react-router-dom の依存関係にあるので、一緒に追加されます。&lt;/p&gt;
&lt;p&gt;今回の使用バージョンは以下のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：17.0.2&lt;/li&gt;
&lt;li&gt;react-router-dom：5.2.0&lt;/li&gt;
&lt;li&gt;react-router：5.2.0&lt;/li&gt;
&lt;li&gt;@types/react-router-dom：5.1.8&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;※2022/01/24追加
2021/11に6系がリリースされましたが、&lt;strong&gt;当記事では5系を扱っていることに注意です。&lt;/strong&gt;
5系と6系とでは、破壊的な変更も行われています。&lt;/p&gt;
&lt;h2&gt;基本的な使い方&lt;/h2&gt;
&lt;p&gt;これ以降のコードは公式ドキュメントをコードを引用、もしくは元にしています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import {
  BrowserRouter,
  Switch,
  Route,
  Link
} from &apos;react-router-dom&apos;;

const Home: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;;
}

const About: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;About&amp;lt;/h2&amp;gt;;
}

const Users: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;Users&amp;lt;/h2&amp;gt;;
}

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;BrowserRouter&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;nav&amp;gt;
          &amp;lt;ul&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;Link to=&apos;/&apos;&amp;gt;Home&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;Link to=&apos;/about&apos;&amp;gt;About&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
            &amp;lt;li&amp;gt;
              &amp;lt;Link to=&apos;/users&apos;&amp;gt;Users&amp;lt;/Link&amp;gt;
            &amp;lt;/li&amp;gt;
          &amp;lt;/ul&amp;gt;
        &amp;lt;/nav&amp;gt;

        &amp;lt;Switch&amp;gt;
          &amp;lt;Route path=&apos;/about&apos;&amp;gt;
            &amp;lt;About /&amp;gt;
          &amp;lt;/Route&amp;gt;
          &amp;lt;Route path=&apos;/users&apos;&amp;gt;
            &amp;lt;Users /&amp;gt;
          &amp;lt;/Route&amp;gt;
          &amp;lt;Route path=&apos;/&apos;&amp;gt;
            &amp;lt;Home /&amp;gt;
          &amp;lt;/Route&amp;gt;
        &amp;lt;/Switch&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/BrowserRouter&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;gifs/2020/react-router/router-standard.gif&quot; alt=&quot;React Routerの基本動作GIF&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;ルーティングに必要なものを import&lt;/h3&gt;
&lt;p&gt;使用するコンポーネントを import します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  BrowserRouter,
  Switch,
  Route,
  Link
} from &apos;react-router-dom&apos;;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ルート階層で、コンポーネント全体をプロバイダーコンポーネントで囲う&lt;/h3&gt;
&lt;p&gt;React Router が提供しているプロバイダーコンポーネントは複数存在しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;BrowserRouter：HTML の History API（pushState、replaceState、popstate イベント）を使用して UI を URL と同期させるルーター&lt;/li&gt;
&lt;li&gt;HashRouter：URL のハッシュ部分（window.location.hash）を使用して UI を URL と同期させるルーター&lt;/li&gt;
&lt;li&gt;StaticRouter：location を変更しないルーター&lt;/li&gt;
&lt;li&gt;MemoryRouter：URL の履歴をメモリに保持するルーター（アドレスバーの読み取りまたは書き込みは行わない）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この中で、今回は&lt;code&gt;BrowserRouter&lt;/code&gt;を使っていきます。
現在の一般的な SPA 開発では、この&lt;code&gt;BrowserRouter&lt;/code&gt;を使われることが多いようです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;BrowserRouter&amp;gt;
    .
    .
    .
    &amp;lt;/BrowserRouter&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロバイダーコンポーネントとはなんぞや？という方は、このコンポーネントの子コンポーネント全体に特定の値や機能を提供するものと、まずは思ってもらえればいいかなと。
（context の話が出てくることになりますが、ここでは書きません）&lt;/p&gt;
&lt;p&gt;上記のプロバイダーコンポーネントでは、ルーティングに関する機能を提供してくれます。
ルート階層で囲っておくことで、その下層のコンポーネントでルーティングに関する機能が使えるようになるわけです。&lt;/p&gt;
&lt;h3&gt;ルーティングの定義&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Switch&lt;/code&gt;コンポーネントで囲い、その中に&lt;code&gt;Route&lt;/code&gt;コンポーネントでそれぞれのルートを定義。
この&lt;code&gt;Switch&lt;/code&gt;コンポーネントは、現在の URL と一致する Route を上から順に探し、最初に一致するルートの内容を返すようになっています。&lt;/p&gt;
&lt;p&gt;注意点として、通常では前方一致で比較します。
例として path が&lt;code&gt;/about&lt;/code&gt;の場合、&lt;code&gt;/about/a&lt;/code&gt;などでも一致とみなされます。
精度を変えたい場合は、&lt;a href=&quot;#%E3%83%AB%E3%83%BC%E3%83%88%E7%85%A7%E5%90%88%E3%81%AE%E7%B2%BE%E5%BA%A6%E6%8C%87%E5%AE%9A&quot;&gt;ルート照合の精度指定&lt;/a&gt;参照。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Home: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;;
}

const About: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;About&amp;lt;/h2&amp;gt;;
}

const Users: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;Users&amp;lt;/h2&amp;gt;;
}
.
.
.
&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/about&apos;&amp;gt;
    &amp;lt;About /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/users&apos;&amp;gt;
    &amp;lt;Users /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/&apos;&amp;gt;
    &amp;lt;Home /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、Route の path は一度に複数定義も可能です。
以下の場合は、&lt;code&gt;/about&lt;/code&gt;、&lt;code&gt;/profile&lt;/code&gt;両方で About コンポーネントをレンダリングします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path={[&apos;/about&apos;, &apos;/profile&apos;]}&amp;gt;
  &amp;lt;About /&amp;gt;
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;リンクの作成&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Link&lt;/code&gt;コンポーネントでリンクを作成。to でリンク先情報を指定。
後に a タグへ変換されることになりますが、通常の a タグと違い、クライアントサイドで遷移が動作します。
通常の a タグを使うと React Router の管理外となり、アプリ全体が再読み込みされて履歴が消えてしまうので注意。
内部リンクは Link コンポーネント、外部リンクは a タグと使い分けるイメージです。&lt;/p&gt;
&lt;p&gt;なお、この例では同じコンポーネント内に共存していますが、&lt;code&gt;Link&lt;/code&gt;コンポーネントは&lt;code&gt;Switch&lt;/code&gt;や&lt;code&gt;Route&lt;/code&gt;コンポーネントを使用しているコンポーネント内でしか使用できないということはありません。
ルーティングプロバイダーコンポーネントの配下であれば、どこでも使用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;nav&amp;gt;
  &amp;lt;ul&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;Link to=&apos;/&apos;&amp;gt;Home&amp;lt;/Link&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;Link to=&apos;/about&apos;&amp;gt;About&amp;lt;/Link&amp;gt;
    &amp;lt;/li&amp;gt;
    &amp;lt;li&amp;gt;
      &amp;lt;Link to=&apos;/users&apos;&amp;gt;Users&amp;lt;/Link&amp;gt;
    &amp;lt;/li&amp;gt;
  &amp;lt;/ul&amp;gt;
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみに、リンク URL と現在の URL が一致した時に、スタイルを追加できる NavLink コンポーネントという拡張バージョンがあったりもします。
&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/api/NavLink&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これで基本的なルーティングが作成できました。&lt;/p&gt;
&lt;h2&gt;Hooks API&lt;/h2&gt;
&lt;p&gt;React Router が提供しているフックは以下の4種類です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;useHistory&lt;/li&gt;
&lt;li&gt;useLocation&lt;/li&gt;
&lt;li&gt;useParams&lt;/li&gt;
&lt;li&gt;useRouteMatch&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;useHistory&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&apos;https://v5.reactrouter.com/web/api/history&apos; /&amp;gt;
React Router が提供する history オブジェクトを返すフック。&lt;/p&gt;
&lt;p&gt;履歴の数である length
最後に実行されたアクションである action（PUSH・REPLACE・POP）
location オブジェクトとブラウザ履歴に関する関数などを持っているものです。&lt;/p&gt;
&lt;p&gt;この history オブジェクトは HTML の History API と完全一致ではありませんが、おおよそ似たようなブラウザ履歴に関する処理ができるようになっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 履歴の追加
history.push(&apos;/about&apos;)

// 履歴の追加 + ユーザ定義データの受け渡し
history.push(&apos;/about&apos;, { someState: &apos;foo&apos; });

// 履歴の書き換え
history.replace(&apos;/about&apos;);

// 履歴の書き換え + ユーザ定義データの受け渡し
history.replace(&apos;/about&apos;, { someState: &apos;foo&apos; });

// 履歴を2つ（引数の値分）進める
history.go(2);

// 履歴を1つ戻る
history.goBack();

// 履歴を1つ進める
history.goForward();

// 上記の履歴変更の前に記述しておくと、遷移前にアラートを出す
history.block(&apos;このページを離れますか？&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特定の処理後に遷移させたい（例：ログアウト処理後にログイン画面へリダイレクト）時や、特定の要素をクリックしたときに遷移させたい時などに活用できます。&lt;/p&gt;
&lt;p&gt;ボタンを押した時に遷移させる例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { useHistory } from &apos;react-router-dom&apos;;

const Home: VFC = () =&amp;gt; {
  const history = useHistory();

  const handleClick = () =&amp;gt; {
    history.push(&apos;/about&apos;);
  };

  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;
      &amp;lt;button type=&quot;button&quot; onClick={handleClick}&amp;gt;
        Go about
      &amp;lt;/button&amp;gt;
    &amp;lt;/&amp;gt;
  );
};

export default Home;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;history オブジェクトの中身（history.push で /about に遷移した後の例）
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/react-router/history.png&quot; alt=&quot;コンソールに出力したhistoryオブジェクトの内容&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;あくまで処理の中でブラウザ履歴の操作をしているので、場合によっては、ユーザから見てクリックするまで挙動がわからない。遷移なのに別タブで開くことができない。となってしまう可能性があります。
特定の要素をクリックで遷移させたい時は、&lt;code&gt;Link&lt;/code&gt;コンポーネントの使用も検討してみましょう。&lt;/p&gt;
&lt;h3&gt;useLocation&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/api/location&quot; /&amp;gt;
React Router が提供する location オブジェクトを返すフック。&lt;/p&gt;
&lt;p&gt;location オブジェクトは以下の情報を持っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;pathname：URL&lt;/li&gt;
&lt;li&gt;search：クエリパラメータ&lt;/li&gt;
&lt;li&gt;hash：URL ハッシュ&lt;/li&gt;
&lt;li&gt;state：ユーザ定義のデータ&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらの情報は&lt;code&gt;Link&lt;/code&gt;コンポーネントや、history オブジェクトで履歴操作時に渡すことができます。
その情報を遷移先のコンポーネント側で使いたい場合に活用します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import {
  BrowserRouter,
  Switch,
  Route,
  Link,
} from &apos;react-router-dom&apos;;

const to = {
  pathname: &apos;/users&apos;,
  search: &apos;?class=A&apos;,
  hash: &apos;#user-hash&apos;,
  state: { test: &apos;test-state&apos; }
};
.
.
.
&amp;lt;Link to={to}&amp;gt;Users&amp;lt;/Link&amp;gt;
.
.
.
&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/users&apos;&amp;gt;
    &amp;lt;Users /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import { useLocation } from &apos;react-router-dom&apos;;

const Users: VFC = () =&amp;gt; {
  const location = useLocation();
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h2&amp;gt;Users&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;pathname：{location.pathname}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;search：{location.search}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;hash：{location.hash}&amp;lt;/p&amp;gt;
      &amp;lt;p&amp;gt;state：{(location.state as { test: string }).test}&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
}

export default Users
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;location オブジェクトの中身
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/react-router/location.png&quot; alt=&quot;コンソールに出力したlocationオブジェクトの内容&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;動作
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;gifs/2020/react-router/router-uselocation.gif&quot; alt=&quot;useLocationを使用した場合の動作GIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみにクエリパラメータを取り出して扱いたい場合は、&lt;code&gt;query-string&lt;/code&gt;というライブラリを使うと便利です。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/sindresorhus/query-string&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;location オブジェクトのクエリパラメータに対して、以下のようにすると&lt;code&gt;{ class: &apos;A&apos; }&lt;/code&gt;のようにオブジェクト形式に変換してくれるため、扱いやすくなります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import queryString from &apos;query-string&apos;;
.
.
.
queryString.parse(location.search)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;クエリパラメータを頻繁に扱う場合は、あらかじめ&lt;code&gt;useLocation&lt;/code&gt;と&lt;code&gt;query-string&lt;/code&gt;を組み合わせたカスタムフックを作っておく手もありです。&lt;/p&gt;
&lt;h3&gt;useParams&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/api/match&quot; /&amp;gt;
React Router が提供する match オブジェクトの中から、 パスパラメータ部分のみ返すフック。
遷移元から受け取ったパスパラメータを保持しているため、その情報を遷移先のコンポーネント側で使いたい場合に活用します。&lt;/p&gt;
&lt;p&gt;パスパラメータを受け付けるようにするには、Route コンポーネントの path で&lt;code&gt;:aboutId&lt;/code&gt;のように&lt;code&gt;:&lt;/code&gt;をつけて定義。
この状態で、リンク時にパスパラメータを指定するようにすれば、遷移先の方で取り出せます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useParams
} from &apos;react-router-dom&apos;;
.
.
.
&amp;lt;Link to=&apos;/about/1&apos;&amp;gt;About&amp;lt;/Link&amp;gt;
.
.
.
&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/about/:aboutId&apos;&amp;gt;
    &amp;lt;About /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;

const About: VFC = () =&amp;gt; {
  // { aboutId: &apos;1&apos; } からの、分割代入 + ショートハンド
  const { aboutId } = useParams&amp;lt;{aboutId: string}&amp;gt;();
  return &amp;lt;h2&amp;gt;About：{aboutId}&amp;lt;/h2&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;動作
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;gifs/2020/react-router/router-useparam.gif&quot; alt=&quot;usePamramを使用した場合の動作GIF&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみにパスパラメータ定義に&lt;code&gt;?&lt;/code&gt;をつけると任意パラメータにもできたりします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&apos;/about/:aboutId?&apos;&amp;gt;
  &amp;lt;About /&amp;gt;
&amp;lt;/Route&amp;gt;

const About: VFC = () =&amp;gt; {
  const { aboutId } = useParams&amp;lt;{aboutId?: string}&amp;gt;();
  return &amp;lt;h2&amp;gt;About：{aboutId ?? &apos;none&apos; }&amp;lt;/h2&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;useRouteMatch&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/api/match&quot; /&amp;gt;
React Router が提供する match オブジェクトを返すフック。&lt;/p&gt;
&lt;p&gt;match オブジェクトは以下の情報を持っています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;path：ルートパス&lt;/li&gt;
&lt;li&gt;url：URL&lt;/li&gt;
&lt;li&gt;isExact：URL がルートパスと一致するか&lt;/li&gt;
&lt;li&gt;params：パスパラメータのオブジェクト&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Route コンポーネントが現在の URL を照合するのと同じ方法で、照合するようになっているようです。&lt;/p&gt;
&lt;p&gt;活用例の1つとして、ネストしたルーティングを実現できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC } from &apos;react&apos;;
import {
  BrowserRouter,
  Switch,
  Route,
  Link,
  useRouteMatch
} from &apos;react-router-dom&apos;;

const Home: VFC = () =&amp;gt; {
  return &amp;lt;h2&amp;gt;Home&amp;lt;/h2&amp;gt;;
}

const Topics: VFC = () =&amp;gt; {
  const match = useRouteMatch();

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;Topics&amp;lt;/h2&amp;gt;

      &amp;lt;ul&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;Link to={`${match.url}/components`}&amp;gt;Components&amp;lt;/Link&amp;gt;
        &amp;lt;/li&amp;gt;
        &amp;lt;li&amp;gt;
          &amp;lt;Link to={`${match.url}/props-v-state`}&amp;gt;
            Props v. State
          &amp;lt;/Link&amp;gt;
        &amp;lt;/li&amp;gt;
      &amp;lt;/ul&amp;gt;

      &amp;lt;Switch&amp;gt;
        &amp;lt;Route path={`${match.path}/components`}&amp;gt;
          &amp;lt;h3&amp;gt;Components&amp;lt;/h3&amp;gt;
        &amp;lt;/Route&amp;gt;
        &amp;lt;Route path={`${match.path}/props-v-state`}&amp;gt;
          &amp;lt;h3&amp;gt;props-v-state&amp;lt;/h3&amp;gt;
        &amp;lt;/Route&amp;gt;
      &amp;lt;/Switch&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

const App: VFC = () =&amp;gt; {
  return (
    &amp;lt;BrowserRouter&amp;gt;
      &amp;lt;div&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;Link to=&apos;/&apos;&amp;gt;Home&amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;
            &amp;lt;Link to=&apos;/topics&apos;&amp;gt;Topics&amp;lt;/Link&amp;gt;
          &amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;

        &amp;lt;Switch&amp;gt;
          &amp;lt;Route path=&apos;/topics&apos;&amp;gt;
            &amp;lt;Topics /&amp;gt;
          &amp;lt;/Route&amp;gt;
          &amp;lt;Route path=&apos;/&apos;&amp;gt;
            &amp;lt;Home /&amp;gt;
          &amp;lt;/Route&amp;gt;
        &amp;lt;/Switch&amp;gt;
      &amp;lt;/div&amp;gt;
    &amp;lt;/BrowserRouter&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;match オブジェクトの中身
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/react-router/match.png&quot; alt=&quot;コンソールに出力したmatchオブジェクトの内容&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;動作
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;gifs/2020/react-router/router-nest.gif&quot; alt=&quot;ネストしたルーティングの動作GIF&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;その他の使い方&lt;/h2&gt;
&lt;h3&gt;ルート定義方法の種類&lt;/h3&gt;
&lt;h4&gt;Switch コンポーネント + Route コンポーネントで囲う形で子要素として渡す&lt;/h4&gt;
&lt;p&gt;現在推奨されるスタンダードなやり方。
&lt;code&gt;Switch&lt;/code&gt;コンポーネントを使用しているため、配下の Route を上から順に照合していって、&lt;strong&gt;最初に一致&lt;/strong&gt;したものがレンダリングされます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/about&apos;&amp;gt;
    &amp;lt;About /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/users&apos;&amp;gt;
    &amp;lt;Users /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/&apos;&amp;gt;
    &amp;lt;Home /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この他にも指定方法はありますが、それらは主に Hooks API が導入される前のバージョンで使われていた方法で、以下のようなものがあります。
これらを&lt;code&gt;Switch&lt;/code&gt;コンポーネントと併用しない場合は、&lt;strong&gt;一致するルート全て&lt;/strong&gt;がレンダリングされることになるため、扱いには注意が必要です。&lt;/p&gt;
&lt;h4&gt;Route コンポーネントの component に渡す&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&apos;/about&apos; component={About} /&amp;gt;
&amp;lt;Route path=&apos;/users&apos; component={Users} /&amp;gt;
&amp;lt;Route path=&apos;/home&apos; component={Home} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この方法の特徴として、React Router は React.createElement を使用して、指定されたコンポーネントから新しい React 要素を作成します。
レンダリングごとに新しいコンポーネントを作成する形になるようです。
そのため、既存コンポーネントを更新するだけでなく、既存コンポーネントをアンマウント → 新しいコンポーネントをマウントのような挙動になるとのこと。&lt;/p&gt;
&lt;p&gt;また、この方法では、渡すコンポーネントに props を指定できません。
その代わりに、&lt;code&gt;RouteComponentProps&lt;/code&gt;型のオブジェクトが props へ渡されるようになっています。&lt;/p&gt;
&lt;p&gt;この&lt;code&gt;RouteComponentProps&lt;/code&gt;には以下のものが含まれています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;history オブジェクト&lt;/li&gt;
&lt;li&gt;location オブジェクト&lt;/li&gt;
&lt;li&gt;match オブジェクト&lt;/li&gt;
&lt;li&gt;staticContext&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常これらは Hooks API で取得できるものです。&lt;/p&gt;
&lt;p&gt;component、render、children が同時に定義されていた場合、component は2番目の優先度となります。&lt;/p&gt;
&lt;h4&gt;Route コンポーネントの render に渡す&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&quot;/about&quot; render={() =&amp;gt; &amp;lt;About /&amp;gt;} /&amp;gt;
&amp;lt;Route path=&quot;/users&quot; render={() =&amp;gt; &amp;lt;Users /&amp;gt;} /&amp;gt;
&amp;lt;Route path=&quot;/home&quot; render={() =&amp;gt; &amp;lt;Home /&amp;gt;} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ルートが一致したときに呼び出される関数として定義するやり方。
この render の引数には、&lt;code&gt;RouteComponentProps&lt;/code&gt;型のオブジェクトが渡されるようになっているので、それを利用した処理が可能です。&lt;/p&gt;
&lt;p&gt;component、render、children が同時に定義されていた場合、render は3番目の優先度となります。&lt;/p&gt;
&lt;h4&gt;Route コンポーネントの children に渡す&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&quot;/about&quot; children={() =&amp;gt; &amp;lt;About /&amp;gt;} /&amp;gt;
&amp;lt;Route path=&quot;/users&quot; children={() =&amp;gt; &amp;lt;Users /&amp;gt;} /&amp;gt;
&amp;lt;Route path=&quot;/home&quot; children={() =&amp;gt; &amp;lt;Home /&amp;gt;} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;render の時と似ていますね。
挙動としても render と似ていますが、children に渡している関数は、ルートが一致したかどうかに関係なく呼び出されます。
ルートが一致した時のみ、同様に引数へ&lt;code&gt;RouteComponentProps&lt;/code&gt;型のオブジェクトが渡されるようになっているため、ルートが一致するかどうかで UI を動的に切り替えるようなことも可能とのこと。&lt;/p&gt;
&lt;p&gt;component、render、children が同時に定義されていた場合、children は1番目の優先度となります。&lt;/p&gt;
&lt;h3&gt;リンク定義方法の種類&lt;/h3&gt;
&lt;h4&gt;URL 形式&lt;/h4&gt;
&lt;p&gt;URL のみのシンプルなパターン。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Link to=&apos;/about&apos;&amp;gt;About&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;オブジェクト形式&lt;/h4&gt;
&lt;p&gt;location オブジェクト形式のパターン。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Link
  to={{
    pathname: &apos;/about&apos;,
    search: &apos;?class=A&apos;,
    hash: &apos;#user-hash&apos;,
    state: { test: &apos;test-state&apos; }
  }}
&amp;gt;
  About
&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;関数形式&lt;/h4&gt;
&lt;p&gt;現在の location 情報を引数として、location オブジェクト形式か URL 形式を返すパターン。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// オブジェクト形式
&amp;lt;Link to={location =&amp;gt; ({ ...location, pathname: &apos;/about&apos; })} &amp;gt;About&amp;lt;/Link&amp;gt;

// URL 形式
&amp;lt;Link to={location =&amp;gt; `${location.pathname}?sort=name`} &amp;gt;About&amp;lt;/Link&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ルート照合の精度指定&lt;/h3&gt;
&lt;p&gt;デフォルトは前方一致です。&lt;/p&gt;
&lt;h4&gt;完全一致&lt;/h4&gt;
&lt;p&gt;exact をつけます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&apos;/about&apos; exact&amp;gt;
  ...
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;末尾スラッシュ有無確認&lt;/h4&gt;
&lt;p&gt;path の末尾にスラッシュをつける + strict をつけます。
&lt;code&gt;/abount/&lt;/code&gt;に対して&lt;code&gt;/about&lt;/code&gt;は一致とみなされません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&apos;/about/&apos; strict&amp;gt;
  ...
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;大文字・小文字確認&lt;/h4&gt;
&lt;p&gt;sensitive をつけます。
大文字小文字を厳密に照合するようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Route path=&apos;/about&apos; sensitive&amp;gt;
  ...
&amp;lt;/Route&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ルート定義していない URL にアクセスされた場合のルート定義&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;Route&lt;/code&gt;コンポーネントの path で&lt;code&gt;*&lt;/code&gt;を指定すると全受けにできるのを利用します。
&lt;code&gt;Switch&lt;/code&gt;コンポーネントの仕様上、上から Route を照合していくので、最後に追加しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/about&apos;&amp;gt;
    &amp;lt;About /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/users&apos;&amp;gt;
    &amp;lt;Users /&amp;gt;
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;*&apos;&amp;gt;
    &amp;lt;Error /&amp;gt;
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;アプリ内リダイレクト&lt;/h3&gt;
&lt;h4&gt;history.replace&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;useHistory&lt;/code&gt;の項でも書いた通り、&lt;code&gt;history.replace&lt;/code&gt;で履歴の書き換えができるので、これで対応できます。
ロジックの中でリダイレクトさせたい時など。&lt;/p&gt;
&lt;h4&gt;Redirect コンポーネント&lt;/h4&gt;
&lt;p&gt;to にリダイレクト先情報（URL 形式、location オブジェクト形式）を指定することで、そちらにリダイレクトさせられます。
何らかのフラグ変数によって、リダイレクトさせるか切り分ける時など。&lt;/p&gt;
&lt;p&gt;以下は&lt;code&gt;authenticated&lt;/code&gt;変数によって遷移する先を変化させている例です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Switch&amp;gt;
  &amp;lt;Route path=&apos;/mypage&apos;&amp;gt;
    {authenticated ? &amp;lt;MyPage /&amp;gt; : &amp;lt;Redirect to=&quot;/&quot; /&amp;gt;}
  &amp;lt;/Route&amp;gt;
  &amp;lt;Route path=&apos;/login&apos;&amp;gt;
    {authenticated ? &amp;lt;Redirect to=&quot;/mypage&quot; /&amp;gt; : &amp;lt;Login /&amp;gt;}
  &amp;lt;/Route&amp;gt;
&amp;lt;/Switch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ログインしている時にログイン画面へアクセスすると、マイページ画面にリダイレクト。
ログインしていない時にマイページ画面へアクセスすると、ログイン画面にリダイレクト。
といった感じ。&lt;/p&gt;
&lt;p&gt;毎回、分岐を書くのがめんどくさければ、認証ルート用と非認証用ルートで Route コンポーネントの薄いラッパーを作るのもありです。
公式の example - Redirects(Auth) が参考になります。
&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/example/auth-workflow&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;また、以下のような書き方もできます。
こちらの場合は from を使用していて、from の URL にアクセスされたら、to の location にリダイレクトします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;Switch&amp;gt;
  &amp;lt;Redirect from=&apos;/test&apos; to=&apos;/other&apos; /&amp;gt;
  &amp;lt;Route path=&apos;/other&apos;&amp;gt;
    &amp;lt;Other /&amp;gt;
  &amp;lt;/Route&amp;gt;
  .
  .
  .
&amp;lt;/Switch&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;フレンドリーフォワーディング&lt;/h3&gt;
&lt;p&gt;ログイン時にしかアクセスできない画面に、未ログイン状態でアクセスしたとします。
大体の Web アプリはログイン画面にリダイレクトして、ログインを要求されるでしょう。
そうやってログインした後、ログイン後のアプリトップ画面でなく、元々アクセスしようとしていた画面に遷移してくれると嬉しいよね。というやつのことです。&lt;/p&gt;
&lt;p&gt;クエリパラメータに URL を保持しておいて、ログイン後にその URL へリダイレクトさせる。
というのを割と見かける気がしますが、以下の例では location オブジェクトの state を活用してみています。&lt;/p&gt;
&lt;p&gt;まず以下のような認証ルート用のラッパーコンポーネントを用意。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Props = ComponentPropsWithRef&amp;lt;typeof Route&amp;gt;;

const AuthRoute: VFC&amp;lt;Props&amp;gt; = ({ children, ...rest }) =&amp;gt; {
  // 認証情報を取得
  const { authenticated } = useAuth();

  return (
    &amp;lt;Route
      {...rest}
      render={({ location }) =&amp;gt;
        authenticated ? (
          children
        ) : (
          &amp;lt;Redirect
            to={{
              pathname: &apos;/login&apos;,
              state: { from: location }
            }}
          /&amp;gt;
        )
      }
    /&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;認証していれば、子要素のコンポーネントを返す。
認証しなければ、ログイン画面へリダイレクトする。
というものです。
リダイレクト時に、その時の location 情報（元々アクセスしようとしていた画面情報）を state にセットしておきます。&lt;/p&gt;
&lt;p&gt;あとは Login コンポーネント側でそれを取得しておいて、ログイン後にその Location 情報でリダイレクトさせれば OK です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { Location } from &apos;history&apos;;
.
.
.
const Login: VFC = () =&amp;gt; {
  const history = useHistory();
  const location = useLocation();
  const { login } = useAuth();

  const { from } = (location.state as { from: Location }) || { from: { pathname: &apos;/&apos; } };

  const handleLogin = () =&amp;gt; {
    login(); // ログイン処理
    history.replace(from);
  };

  return (
    &amp;lt;div&amp;gt;
      &amp;lt;form&amp;gt;
        {/* フォーム要素 */}
        .
        .
        .
        &amp;lt;button type=&quot;submit&quot; onClick={handleLogin}&amp;gt;ログイン&amp;lt;/button&amp;gt;
      &amp;lt;/form&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これも先ほど触れた公式の &lt;a href=&quot;https://v5.reactrouter.com/web/example/auth-workflow&quot;&gt;example - Redirects(Auth)&lt;/a&gt; が参考になります。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;※2021/08/21追記
全体加筆修正・更新にあたり、現在は Hooks API を使うことが一般的になっているため、HOC 式の項は削除しました。
HOC 式が気になるという方は、公式ドキュメント withRouter をご参照ください。
&amp;lt;OG url=&quot;https://v5.reactrouter.com/web/api/withRouter&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://reacttraining.com/react-router/&quot;&gt;React Router&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.hypertextcandy.com/react-router-for-vuejs-developer&quot;&gt;Hookにも対応！Vue.jsエンジニアのためのReact Router v5入門&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot;&gt;GitHub - oukayuka/Riakuto-StartingReact-ja3.1 ※書籍&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GAS + スプレッドシート + Slackで当番決めに使えるガチャを作ってみた</title><link>https://changeofpace.site/posts/2020-01-19-duty-gacha</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-01-19-duty-gacha</guid><description>スプレッドシートのデータと連携させたGASを使って、Slackから呼び出せるガチャを作ってみた</description><pubDate>Sun, 19 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;GAS こと Google Apps Script は、手軽に bot や他の Google サービスと連携したツールを作れるから、ちょっと楽しいですよね。
今回はスプレッドシートと組み合わせて、Slack から呼び出して使えるガチャを作ってみました。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/b5a0f2fe81fe0e9a45c2&quot;&gt;Qiita&lt;/a&gt; からの転載です。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;作ろうと思った背景&lt;/h2&gt;
&lt;p&gt;自分が所属している事業部では、毎月月末にとある当番を決めるのに Slack Bot を使ってガチャみたいにやっていました。&lt;/p&gt;
&lt;p&gt;ただ、これだと設定された文言のいずれかをランダムで返しているだけなので、先月に担当した人がまた抽選されたりといった問題がありました。&lt;/p&gt;
&lt;p&gt;そこで当番履歴をスプレッドシートに記録しておいて、抽選のたびに当番履歴のメンバーを除外して、抽選するようにすればいいんじゃないか？と思い、思い切って作ってみました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2020/01/27追記&lt;/strong&gt;
当月の当番抽選がすでに行われており、当月の当番履歴がすでにある場合は、新たに抽選せずにその履歴情報から当番を通知するように改修しました。&lt;/p&gt;
&lt;h2&gt;構成図&lt;/h2&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/duty-gacha/architecture.png&quot; alt=&quot;当番ガチャの構成図&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;全体の処理のおおまかな流れとしてはこんな感じです。&lt;/p&gt;
&lt;p&gt;※&lt;a href=&quot;#webhook-%E3%81%AE%E3%82%84%E3%82%8A%E6%96%B9%E3%81%8C%E5%A4%89%E3%82%8F%E3%81%A3%E3%81%9F&quot;&gt;下部&lt;/a&gt;でも書いているのですが、今回は&lt;strong&gt;旧方式&lt;/strong&gt;の Webhook を使用しているので注意です。&lt;/p&gt;
&lt;h2&gt;作り方&lt;/h2&gt;
&lt;h3&gt;Incoming Webhook の用意&lt;/h3&gt;
&lt;p&gt;まずは Slack に投稿する部分を担う、Incoming Webhook を用意します。
（Slack のワークスペースはすでにあるものとして、進めていきます）&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Slack ワークスペースのワークスペース名のところからメニューを開く&lt;/li&gt;
&lt;li&gt;App 管理画面へ&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;※ワークスペースのオーナーの場合
その他管理項目 → App 管理&lt;/li&gt;
&lt;li&gt;※オーナーでない場合
Slackをカスタマイズ → 左上のメニューを開く → App管理&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Incoming Webhook 設定の追加画面へ&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;※Incoming Webhook をまだ導入していない場合
「App ディレクトリを検索」のところに Incoming Webhook で検索し選択 → Slack へ追加&lt;/li&gt;
&lt;li&gt;※すでに Incoming Webhook を導入済みの場合
カスタムインテグレーション → Incoming Webhook → Slack に追加&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;チャンネルへの投稿で、投稿したいチャンネルを選択し、Incoming Webhook インテグレーションの追加を選択&lt;/li&gt;
&lt;li&gt;インテグレーションの設定&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Webhook URL を控えておく（GAS 側で使います）&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;名前やアイコンを必要に応じてカスタマイズ&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;設定を保存を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;スプレッドシートの用意&lt;/h3&gt;
&lt;p&gt;スプレッドシートを作成、以下のようなシートを用意します。ここではシートの名称を&lt;code&gt;当番履歴表&lt;/code&gt;としてください。
&amp;lt;ImageWrapper src=&quot;screenshots/2020/duty-gacha/sheet.png&quot; alt=&quot;用意するスプレッドシートの内容&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;メンバー表&lt;/h4&gt;
&lt;p&gt;ガチャ対象は〇か×で選択できるようにしておき、メンバーがガチャに参加するかどうかをここで制御できるようにします。
ガチャ対象列のデータ範囲を選択 → データ → データの入力規則
以下のように設定しておきます。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/duty-gacha/gacha-select.png&quot; alt=&quot;メンバー表のデータの入力規則設定&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Slack member_id は Slack にガチャ結果を投稿する際に、メンションをつけるために使用します。
こちらは Slack 上で確認できます。
チャンネルのメンバーリストなどから、メンバーを選択し簡易プロフィールを表示。
アイコン画像を選択してプロフィールを表示し、三点リーダから確認。
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/duty-gacha/slack-profile.png&quot; alt=&quot;Slackアカウントのプロフィール画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;当番履歴表&lt;/h4&gt;
&lt;p&gt;履歴がある場合は記述しておきます。
ない場合は、表のヘッダー部分のみ記述で問題ありません。&lt;/p&gt;
&lt;h3&gt;GAS の用意&lt;/h3&gt;
&lt;p&gt;スプレッドシート上で ツール → スクリプトエディタ から GAS エディタへ行けます。
以下のコードを用意します（&lt;s&gt;なお、現時点で GAS では ES6 記法は使えません&lt;/s&gt;）&lt;/p&gt;
&lt;p&gt;※2020/2/16 追記
v8 ランタイムに対応したため、let やアロー関数などが新たに使用できるようになりました。
これに伴いリファクタリングを行っています。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ランタイムの変更は、GAS エディタを開いたときに表示される案内（&lt;code&gt;Enable new Apps Script runtime powered by Chrome V8 for this project.&lt;/code&gt;）から変更する。
もしくは、実行 → Chrome V8 を搭載した新しい Apps Script ランタイムを有効にする からできます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// スプレッドシートのデータを元に当番ガチャを行い、結果を Slack に投稿する
function dutyGacha() {
  // メンバー表のデータ部分のセル範囲
  const memberDataCellRange = &apos;A4:C23&apos;;
  // 当番履歴表の開始列
  const historyDataColumnRangeStart = &apos;E&apos;;
  // 当番履歴表の終了列
  const historyDataColumnRangeEnd = &apos;G&apos;;
  // 当番履歴表の列範囲
  const historyDataColumnRange = historyDataColumnRangeStart + &apos;:&apos; + historyDataColumnRangeEnd;
  // ガチャから除外する履歴件数
  const historyDataTargetNum = 5;
  // 抽選する当番の人数
  const dutyMemberNum = 2;
  let msg;
  try {
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&apos;当番履歴表&apos;);
    const filterHistoryData = getFilterHistoryData(sheet, historyDataColumnRange);
    const lastIndex = filterHistoryData.length - 1;
    const thisMonthMemberNameList = getThisMonthMemberNameList(filterHistoryData, lastIndex, dutyMemberNum);
    // 当月の当番履歴情報がすでにある場合はそのメンバーの情報を取得
    if (thisMonthMemberNameList !== null) {
      const thisMonthMemberList = getThisMonthMemberList(sheet, memberDataCellRange, thisMonthMemberNameList, dutyMemberNum);
      msg = createNoticeMsg(thisMonthMemberList, false);
    // ない場合は抽選に必要な情報を取得し、抽選を行い、スプレッドシートに書き込み
    } else {
      const joinMemberList = getJoinMemberList(sheet, memberDataCellRange, dutyMemberNum);
      const historyMenberList = getHistoryMemberList(filterHistoryData, lastIndex, historyDataTargetNum, dutyMemberNum);
      const gachaMemberList = getGachaMemberList(joinMemberList, historyMenberList, dutyMemberNum);
      const dutyMemberList = getDutyMemberList(gachaMemberList, dutyMemberNum);
      msg = createNoticeMsg(dutyMemberList, true);

      const insertRow = sheet.getRange(historyDataColumnRangeStart + sheet.getMaxRows()).getNextDataCell(SpreadsheetApp.Direction.UP).getRow() + 1;
      const insertRange = historyDataColumnRangeStart + insertRow + &apos;:&apos; + historyDataColumnRangeEnd + insertRow;
      const insertDataArr = createInsertData(dutyMemberList);
      // スプレッドシートに当月当番データを書き込み
      sheet.getRange(insertRange).setValues(insertDataArr);
      // スプレッドシートに書き込んだ行に罫線を引く
      sheet.getRange(insertRange).setBorder(false, true, true, true, true, false);
    }
  } catch (e) {
    msg = &apos;エラーが発生しました：&apos; + &apos;\nstack：\n&apos; + e.stack;
    Logger.log(msg);
  }

  try {
    const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty(&apos;WEBHOOK_URL&apos;);
    const jsonData =
        {
          &apos;text&apos;: msg
        };
    const payload = JSON.stringify(jsonData);
    const options =
        {
          &apos;method&apos;: &apos;post&apos;,
          &apos;contentType&apos;: &apos;application/json&apos;,
          &apos;payload&apos;: payload
        };
    UrlFetchApp.fetch(WEBHOOK_URL, options);
  } catch (e) {
    Logger.log(&apos;送信エラー：&apos; + &apos;\nstack：\n&apos; + e.stack);
  }
}

// 当番履歴表の列のデータから空白セルを除いたものを返す
function getFilterHistoryData(sheet, historyDataColumnRange) {
  const range = sheet.getRange(historyDataColumnRange);
  const historyData = range.getValues();
  return historyData.filter(data =&amp;gt; data[0] !== &apos;&apos;);
}

// 当番履歴表に当月当番の情報があればそのメンバー名を、なければ null を返す
function getThisMonthMemberNameList(filterHistoryData, lastIndex, dutyMemberNum) {
  if (Utilities.formatDate(filterHistoryData[lastIndex][0], &apos;JST&apos;, &apos;yyyy/MM&apos;)
                           === Utilities.formatDate(new Date(), &apos;JST&apos;, &apos;yyyy/MM&apos;)) {
    const historyDataTargetNum = 1;
    return getHistoryMemberList(filterHistoryData, lastIndex, historyDataTargetNum, dutyMemberNum);
  } else {
    return null;
  }
}

// メンバー表から当月当番メンバーの slack_id と名前を抽出して、オブジェクトの配列で返す
function getThisMonthMemberList(sheet, memberDataCellRange, thisMonthMemberNameList, dutyMemberNum) {
  const memberData = sheet.getRange(memberDataCellRange).getValues();
  let thisMonthMemberList = [];
  memberData.map(data =&amp;gt; {
    if (thisMonthMemberNameList.includes(data[2])) {
      thisMonthMemberList.push({id: data[1], name: data[2]});
    }
  });
  if (thisMonthMemberList.length &amp;lt; dutyMemberNum) {
    throw new Error(&apos;当月当番メンバーの情報がメンバー表に不足しています。&apos;);
  }
  return thisMonthMemberList;
}

// メンバー表からガチャ参加メンバーの slack_id と名前を抽出して、オブジェクトの配列で返す
function getJoinMemberList(sheet, memberDataCellRange, dutyMemberNum) {
  const memberData = sheet.getRange(memberDataCellRange).getValues();
  let joinMemberList = [];
  memberData.map(data =&amp;gt; {
    if (data[0] === &apos;〇&apos;) {
      joinMemberList.push({id: data[1], name: data[2]});
    }
  });
  if (joinMemberList.length &amp;lt; dutyMemberNum) {
    throw new Error(&apos;ガチャ参加メンバーが抽選する当番の人数より少ないです。ガチャ参加メンバーを増やしてください。&apos;);
  }
  return joinMemberList;
}

// 当番履歴表から指定件数分の履歴メンバーを抽出して配列で返す
function getHistoryMemberList(filterHistoryData, lastIndex, historyDataTargetNum, dutyMemberNum) {
  let historyMenberList = [];
  for (let i = 0; i &amp;lt; historyDataTargetNum; i++) {
    for (let l = 1; l &amp;lt;= dutyMemberNum; l++) {
      if (filterHistoryData[lastIndex - i][l] === &apos;&apos;) {
        continue;
      }
      if (filterHistoryData[lastIndex - i][l].match(/当番.*/)) {
        return historyMenberList;
      }
      historyMenberList.push(filterHistoryData[lastIndex - i][l]);
    }
  }
  return historyMenberList;
}

// ガチャ参加メンバーから履歴メンバーを除外したものを配列で返す
function getGachaMemberList(joinMemberList, historyMenberList, dutyMemberNum) {
  const gachaMemberList = joinMemberList.filter(data =&amp;gt; historyMenberList.indexOf(data.name) === -1);
  if (gachaMemberList.length &amp;lt; dutyMemberNum) {
    throw new Error(&apos;ガチャ参加メンバーから履歴メンバーを除外した数が、抽選する当番の人数より少ないです。ガチャ参加メンバーを増やしてください。&apos;);
  }
  return gachaMemberList;
}

// 抽選する当番人数分、当番抽選を行い、当月の当番メンバーの配列を返す
function getDutyMemberList(gachaMenberList, dutyMemberNum) {
  let dutyMemberList = [];
  let index;
  for (let i = 0; i &amp;lt; dutyMemberNum; i++) {
    do {
      index = random(gachaMenberList.length);
    } while (dutyMemberList.includes(gachaMenberList[index]));
    dutyMemberList[i] = gachaMenberList[index];
  }

  return dutyMemberList;
}

// 0～(length-1)の乱数を返す
function random(length) {
  //例 5人の場合 0~0.9999… * 5 の小数点切り捨てで、0~4になる
  return Math.floor(Math.random() * length);
}

// Slack 通知メッセージを作成して返す
function createNoticeMsg(noticeMemberList, gachaFlg) {
  let noticeMsg = &apos;今月の当番\n&apos;;
  // 新たにガチャを行った場合の通知メッセージ
  if (gachaFlg) {
    noticeMemberList.forEach(memberData =&amp;gt; {
      noticeMsg += &apos;:penguin:&amp;lt;ﾃﾞﾚﾚﾚﾚﾚﾃﾞﾃﾞﾝ!!　&amp;lt;&apos; + memberData.id + &apos;&amp;gt; さん\n&apos;;
    });
  // 新たにガチャを行わなかった場合の通知メッセージ
  } else {
    noticeMemberList.forEach(memberData =&amp;gt; {
      noticeMsg += &apos;:penguin:&amp;lt;ﾃﾞﾃﾞﾝ!!　&amp;lt;&apos; + memberData.id + &apos;&amp;gt; さん\n&apos;;
    });
    noticeMsg += &apos;(すでに今月ガチャ済み)\n&apos;;
  }
  return noticeMsg;
}

// スプレッドシートに記録する当月データの二次元配列を返す
function createInsertData(dutyMemberList) {
  let insertData = [Utilities.formatDate(new Date(), &apos;JST&apos;, &apos;yyyy/MM&apos;)];
  dutyMemberList.forEach(memberData =&amp;gt; insertData.push(memberData.name));
  const insertDataArr = [insertData];
  return insertDataArr;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;大まかな流れとしては以下のような感じです。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※シートデータの当番履歴表に当月の当番情報がすでにある場合.&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;スプレッドシートの当番履歴表シートのデータをまるごと取得
↓
当番履歴表に当月の当番情報があるかチェック
↓
シートデータのメンバー表から当月の当番メンバー情報のみ取得
↓
Slack 投稿メッセージの作成
↓
投稿
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;※シートデータの当番履歴表に当月の当番情報がなく、新たに抽選する場合&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;スプレッドシートの当番履歴表シートのデータをまるごと取得
↓
当番履歴表に当月の当番情報があるかチェック
↓
シートデータのメンバー表からガチャ対象が〇になっているメンバー情報のみ取得
↓
シートデータの当番履歴表から指定した件数分、履歴データを取得
↓
取得したメンバー情報から、履歴にあるメンバー情報を除外
↓
残りのメンバーで、指定当番人数分ガチャ抽選を行う
↓
Slack 投稿メッセージの作成
↓
ガチャ抽選結果をシートの当番履歴表に記述（＋罫線をひく）
↓
投稿
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;変数&lt;/h4&gt;
&lt;p&gt;dutyGacha 関数の最初で初期化している変数については、スプレッドシートからデータを取得するにあたってのデータ範囲の指定であったり、ガチャの機能を制御したりするものです。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;特にデータ範囲に関しては、スプレッドシートの状態とあっていないと予期しない動作を起こすので注意してください。&lt;/strong&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// メンバー表のデータ部分のセル範囲
const memberDataCellRange = &apos;A4:C23&apos;;
// 当番履歴表の開始列
const historyDataColumnRangeStart = &apos;E&apos;;
// 当番履歴表の終了列
const historyDataColumnRangeEnd = &apos;G&apos;;
// 当番履歴表の列範囲
const historyDataColumnRange = historyDataColumnRangeStart + &apos;:&apos; + historyDataColumnRangeEnd;
// ガチャから除外する履歴件数
const historyDataTargetNum = 5;
// 抽選する当番の人数
const dutyMemberNum = 2;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;プロパティストア&lt;/h4&gt;
&lt;p&gt;GAS にはプロパティストアという機能があります。
これは、プロジェクトやドキュメントに紐付けて、キーと値形式でデータを格納できる機能で、コードにべた書きしたくない情報などを格納しておくのに向いています。いわば環境変数を別ファイルで管理するような感じです。
プロパティストアには3種類あるのですが、今回はその中でプロジェクトに紐づくデータを管理できる、スクリプトプロパティを使用しています。&lt;/p&gt;
&lt;p&gt;設定の仕方
GAS エディタ上で ファイル → プロジェクトのプロパティ を選択。
スクリプトのプロパティ タブで 行を追加 から。
&lt;strong&gt;今回の場合は、キーを&lt;code&gt;WEBHOOK_URL&lt;/code&gt;、値を&lt;code&gt;（先ほど控えたIncomming WebhookのURL）&lt;/code&gt;を追加します&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※注意点&lt;/strong&gt;
&lt;strong&gt;スクリプトプロパティの編集は、そのGASコードおよび連携している Google サービスのオーナーのアカウントでないとできません。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;スクリプトプロパティを取得するには以下のようにして、キーを指定します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const webHookUrl = PropertiesService.getScriptProperties().getProperty(&apos;WEBHOOK_URL&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;投稿メッセージ&lt;/h4&gt;
&lt;p&gt;Imcoming Webhook URL へ送信する際に、以下のようにパラメータとして渡せば、それが投稿メッセージになります。（msg 変数に投稿メッセージが入っています）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Slack 通知メッセージを作成して返す
function createNoticeMsg(noticeMemberList, gachaFlg) {
  let noticeMsg = &apos;今月の当番\n&apos;;
  // 新たにガチャを行った場合の通知メッセージ
  if (gachaFlg) {
    noticeMemberList.forEach(function(memberData) {
      noticeMsg += &apos;:penguin:&amp;lt;ﾃﾞﾚﾚﾚﾚﾚﾃﾞﾃﾞﾝ!!　&amp;lt;&apos; + memberData.id + &apos;&amp;gt; さん\n&apos;;
    });
  // 新たにガチャを行わなかった場合の通知メッセージ
  } else {
    noticeMemberList.forEach(function(memberData) {
      noticeMsg += &apos;:penguin:&amp;lt;ﾃﾞﾃﾞﾝ!!　&amp;lt;&apos; + memberData.id + &apos;&amp;gt; さん\n&apos;;
    });
    noticeMsg += &apos;(すでに今月ガチャ済み)\n&apos;;
  }
  return noticeMsg;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;投稿メッセージに関して、絵文字も使用できます。&lt;code&gt;:penguin:&lt;/code&gt;のようにnameで指定すればOKです。
また、個人へのメンションについては&lt;code&gt;&amp;lt;member_id&amp;gt;&lt;/code&gt;の形式で指定すると、自動的に&lt;code&gt;@name&lt;/code&gt;形式に変換してくれます。
全体メンションの場合は&lt;code&gt;&amp;lt;!here&amp;gt;&lt;/code&gt;と&lt;code&gt;&amp;lt;!channel&amp;gt;&lt;/code&gt;で、それぞれ&lt;code&gt;@here&lt;/code&gt;、&lt;code&gt;@channel&lt;/code&gt;になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// Slack 通知メッセージを作成して返す
function createNoticeMsg(gachaMenber, dutyMemberList) {
  let noticeMsg = &apos;&apos;;
  dutyMemberList.forEach(function(memberData) {
    noticeMsg += &apos;:penguin:&amp;lt;ﾃﾞﾚﾚﾚﾚﾚﾃﾞﾃﾞﾝ!!　&amp;lt;&apos; + memberData.id + &apos;&amp;gt; さん\n&apos;;
  });
  return noticeMsg;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Slack 投稿のテスト&lt;/h3&gt;
&lt;p&gt;ここまできたら、ガチャを行って、その内容を Slack へ投稿できるようになります。
GAS エディタ上から実行してみましょう。
GAS エディタ上で 実行 → 関数を実行 → dutyGacha を選択。
初回実行時はアクセス権の許可がいるので、確認して許可してください。
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/duty-gacha/gas-run-account1.png&quot; alt=&quot;アクセス権の許可確認モーダル１&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/duty-gacha/gas-run-account2.png&quot; alt=&quot;アクセス権の許可確認モーダル２&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;なお、案内にある通り、後でこのアクセス権を確認・削除は以下から行えます。
&amp;lt;OG url=&quot;https://myaccount.google.com/permissions&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;当番履歴表に当月の当番情報がなく、新たに抽選した場合、
以下のようにメッセージが投稿されていて、スプレッドシートの当番履歴表に履歴が追加されていたらOKです。
上が member_id に該当するアカウントがある場合。
下がない場合で、member_id がそのまま出力されます。
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/duty-gacha/result-gacha.png&quot; alt=&quot;Slack投稿結果(新たにガチャを行った場合)&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;当番履歴表に当月の当番情報がすでにある場合は、その当月の当番がSlackに投稿されていれば OK です。
こちらは新たにスプレッドシートに書き込みは行いません。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2020/duty-gacha/result-no-gacha.png&quot; alt=&quot;Slack投稿結果(新たにガチャを行わなかった場合)&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;うまく投稿できない場合は、ここまでの設定を見直してみてください。
また、以下の場合はエラーとなるようにしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;すでに履歴がある当月の当番メンバーの情報がメンバー表に存在しない&lt;/li&gt;
&lt;li&gt;ガチャ参加メンバーが抽選する当番の人数より少ない&lt;/li&gt;
&lt;li&gt;ガチャ参加メンバーから履歴メンバーを除外した数が、抽選する当番の人数より少ない&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;あとは、当番履歴表の下に関係ないデータを入れると、当番履歴表の最終行を取得する時におかしくなります。&lt;/p&gt;
&lt;p&gt;ちなみに historyDataTargetNum 変数の値（ガチャから除外する履歴件数）より、当番履歴表のデータ件数が少ない場合でもエラーにはならず、存在する履歴データで処理します。&lt;/p&gt;
&lt;h3&gt;Outgoing Webhook との連携&lt;/h3&gt;
&lt;p&gt;今度は、このガチャを Slack の指定のチャンネルから実行できるようにしていきます。&lt;/p&gt;
&lt;h4&gt;Slack 側&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;Slack ワークスペースのワークスペース名のところからメニューを開く&lt;/li&gt;
&lt;li&gt;App 管理画面へ&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;※ワークスペースのオーナーの場合
その他管理項目 → App 管理&lt;/li&gt;
&lt;li&gt;※オーナーでない場合
Slack をカスタマイズ → 左上のメニューを開く → App 管理&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;Outgoing Webhook 設定の追加画面へ&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;※Outgoing Webhook をまだ導入していない場合
「App ディレクトリを検索」のところに Outgoing Webhook で検索し選択 → Slackへ追加&lt;/li&gt;
&lt;li&gt;※すでに Outgoing Webhook を導入済みの場合
カスタムインテグレーション → Outgoing Webhook → Slack に追加&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;ガチャを呼び出せるようにするチャンネルを選択し、Outgoing Webhook インテグレーションの追加を選択&lt;/li&gt;
&lt;li&gt;インテグレーションの設定&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;トークンを控えておく（GAS 側で使います）&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;この段階ではまだ設定を保存としないで、そのまま設定画面のままにしておきます。&lt;/strong&gt;&lt;/p&gt;
&lt;h4&gt;GAS 側&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;スクリプトプロパティに先ほど控えたトークンを登録。（ここではキーを&lt;code&gt;SLACK_OUTGOING_TOKEN&lt;/code&gt;としています）&lt;/li&gt;
&lt;li&gt;以下のコードを dutyGacha 関数の上に追加します。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// POST リクエスト時に当番ガチャを実行する
// （トークン認証を行い、特定の Slack チャンネルからのリクエストのみ受付）
function doPost(e) {
  const verifyToken = PropertiesService.getScriptProperties().getProperty(&apos;SLACK_OUTGOING_TOKEN&apos;);

  if (verifyToken !== e.parameter.token) {
    throw new Error(&quot;トークンが違います。&quot;);
  }
  dutyGacha();
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Outgoing Webhook で GAS にアクセスがあった時の処理は&lt;code&gt;doPost&lt;/code&gt;関数で記述できます。
最初にトークンで認証し、予期しないアクセスははじくようにしておき、その後に dutyGacha 関数を実行します。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;GASのコードを公開
GAS エディタ上から 公開 → ウェブアプリケーションとして導入.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Project version...公開するバージョンを指定。新しくバージョンを作る場合はNewを選択&lt;/li&gt;
&lt;li&gt;Execute the app as...実行時にどのアカウントで実行するか。&lt;code&gt;Me&lt;/code&gt;を選択&lt;/li&gt;
&lt;li&gt;Who has access to the app...このアプリケーションへのアクセス権限。Outgoing Webhookからのアクセスを受け入れるために&lt;code&gt;Anyone, even anonymous&lt;/code&gt;を選択&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;公開すると&lt;code&gt;Current web app URL&lt;/code&gt;が表示されるので控えておきます。&lt;/p&gt;
&lt;h4&gt;再度 Slack 側&lt;/h4&gt;
&lt;p&gt;インテグレーションの設定の続きを行います。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;インテグレーションの設定&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;チャンネル...どのチャンネルから実行できるようにするか選択&lt;/li&gt;
&lt;li&gt;引き金となる言葉...トリガーの言葉を入力（例：がちゃる）&lt;/li&gt;
&lt;li&gt;URL...先ほど控えた GAS アプリケーションのURLを記述&lt;/li&gt;
&lt;li&gt;名前やアイコンはそのままで問題なし&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;Slack 側から呼び出し&lt;/h3&gt;
&lt;p&gt;ここまで来たら、Slack の指定したチャンネルからトリガーワードで呼び出せるようになっているはずです。
呼び出してみましょう。うまくメッセージが投稿され、スプレッドシートに履歴が記述されたでしょうか。&lt;/p&gt;
&lt;h3&gt;スプレッドシート上でガチャ実行&lt;/h3&gt;
&lt;p&gt;スプレッドシート上でもガチャを実行したいという場合は、適当な図形を作成して、それにスクリプトを割り当てると実現できます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;スプレッドシート上で 挿入 → 図形描画 で適当な図形を作成してシートに挿入&lt;/li&gt;
&lt;li&gt;挿入した図形を右クリックで選択し、三点リーダからスクリプトの割り当てを選択&lt;/li&gt;
&lt;li&gt;dutyGacha 関数を指定&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;後から設定をカスタマイズしたい時は&lt;/h3&gt;
&lt;p&gt;例として抽選する当番の人数を増やしたい時はこんな感じ。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;スプレッドシートの当番履歴表の右側に列を追加する&lt;/li&gt;
&lt;li&gt;GAS の dutyGacha 関数の最初で初期化している以下の変数の値を合わせる&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;historyDataColumnRangeEnd&lt;/code&gt;...変数の値を、追加した列に合わせる&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dutyMemberNum&lt;/code&gt;...新しい当番人数を指定&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;GAS を再度公開する（公開 → ウェブアプリケーションとして導入 → New バージョンで指定して公開）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;特に、3の GAS の再度公開は忘れがちなので注意して下さい。&lt;/p&gt;
&lt;p&gt;基本的には、何かしらGASのコードを更新したら再度公開するようにすれば大丈夫かと。&lt;/p&gt;
&lt;h2&gt;Webhook のやり方が変わった？&lt;/h2&gt;
&lt;p&gt;このガチャを作成した後に知ったのですが、今回使用した Webhook の方式がどうやら旧方式になるらしく...。
新方式として Slack App を個別に作成し、そのなかで Webhook の設定をするものになったそうです。
&amp;lt;OG url=&quot;https://qiita.com/kshibata101/items/0e13c420080a993c5d16&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;すぐに旧方式が使えなくなるようではないですが、将来的にはなくなるようなので、いずれは方式を新しくしておきたいですね。&lt;/p&gt;
&lt;p&gt;新方式もちょっとやってみたところ、Incomming Webhook はすんなりできました。
ただ、Outgoing Webhookの代わりとなると思われる Event Subscriptions がよくわかりませんでした。
トリガーワードが設定できないっぽい...？
&lt;code&gt;message.channels&lt;/code&gt;のイベントを設定したら、メッセージ投稿で反応するっぽい？ですが、投稿の度に毎回 GAS へリクエストされるとしたら微妙なような...。
スラッシュコマンドとかでやった方がいいんですかね、う～ん。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;連携させることで、GAS からスプレッドシートの操作ができるのは便利ですが、元となるスプレッドシートの状態がおかしかったりすると、うまく動作しなくなる問題があります。
スプレッドシートの変化に対応できるような GAS を書くことも考えたものの、どこまで対応すべきなのか？というところを悩み。
結果、dutyGacha 関数の最初で初期化することで、せめて修正しやすくしようとしてみました。
ここらへんってどうすべきなんでしょうね...。悩ましいところです。&lt;/p&gt;
&lt;p&gt;また何か作るネタを思いついたら GAS で作ってみますー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tonari-it.com/gas-property-store/&quot;&gt;【初心者向けGAS】プロパティストアの概要とスクリプトプロパティの編集方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tonari-it.com/gas-properties-script-property/&quot;&gt;【初心者向けGAS】スクリプトプロパティを操作してそのデータを取り出す方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://moripro.net/gas-get-specified-lastcol-lastrow&quot;&gt;【GAS】1行で書ける！特定列の最終行・特定行の最終列を取得する方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/ryo-yamaoka/items/7677ee4486cf395ce9bc&quot;&gt;SlackのIncoming Webhooksでメンションを飛ばす方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tonari-it.com/gas-spreadsheet-insertrows-splice/&quot;&gt;Google Apps Scriptでスプレッドシートにデータ行を挿入する２つの方法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://caymezon.com/gas-border/&quot;&gt;【GAS】スプレッドシートの枠線設定機能まとめ【サンプルソース付】&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/kshibata101/items/0e13c420080a993c5d16&quot;&gt;slackのIncoming webhookが新しくなっていたのでまとめてみた&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>勉強した内容をDocusaurus(v2)でドキュメント化してみる</title><link>https://changeofpace.site/posts/2020-01-13-docusaurus-ver-2</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-01-13-docusaurus-ver-2</guid><description>TILリポジトリにコミットしていた、勉強したコードやメモなどをドキュメント化するべくDocusaurusを使ってみた</description><pubDate>Mon, 13 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;勉強したことは何かしらメモしたり、記録を残したりすることが多いでしょうが、どうせ後から見直すなら見やすい方がいいですよね。
ドキュメント化して整理するべく Docusaurus を使ってみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Docusaurus とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2020/docusaurus-ver-2/docusaurus.png&quot; alt=&quot;Docusaurus 公式ドキュメント トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://v1.docusaurus.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://docusaurus.io&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Facebook 製の、React をベースとしたドキュメント特化の静的サイトジェネレータです。
その特性の通り、主に OSS のドキュメントサイトの作成に使用されています。&lt;/p&gt;
&lt;h2&gt;なぜ Docusaurus？&lt;/h2&gt;
&lt;p&gt;昨年末の記事で書いていたので以下、引用します。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;個人勉強のコードは学習記録の可視化にもなるということで、上記にも書いている TIL リポジトリにコミットしています。これは今後も継続していきます。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TILとは
「Today I Learned」の略で、Github 上に TIL というリポジトリを作成してそこに今日覚えたことを書いていくというものです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;※TIL リポジトリに関する過去記事はこちら&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2019-03-25-til&quot;&gt;TILリポジトリで小さなアウトプット&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2019-08-17-til-150&quot;&gt;TILリポジトリが150コミット突破&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ただ、途中まではいい感じかなーと思っていたのですが、だんだん散らかってきたといいますが、勉強した内容をドキュメントみたいにちゃんとまとめたいという思いが出てきました。&lt;/p&gt;
&lt;p&gt;そこで、OSS のドキュメントがどうなっているか見てみようということで、create-react-app のドキュメントのコードを見ていたところ、どうも&lt;code&gt;Docusaurus&lt;/code&gt;というドキュメント特化の静的サイトジェネレータを使っていることがわかりました。&lt;/p&gt;
&lt;p&gt;今後、これを導入して勉強したことのドキュメントを作ろうか検討中です。
言語のバージョンアップで仕様が変わるなど、ドキュメントの更新や整備が大変では...という懸念点もありますが、まずはやってみようかと。
自己満足みたいなところがあるので、このドキュメントを評価材料にしてもらおうとかはあんまり考えていません。というか、評価材料になりえるものなんでしょうか...？&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;簡潔に言ってしまえば、冒頭に書いた通り、勉強したことを後から見返しやすいようにしたいからドキュメント化してみよう。といった感じです。&lt;/p&gt;
&lt;p&gt;ということで、早速導入してみました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※追記 2021年末くらいからは Notion で TIL 活動をすることにしたので、リポジトリの活動停止してます。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Docusaurus の導入&lt;/h2&gt;
&lt;h3&gt;使用バージョンについて&lt;/h3&gt;
&lt;p&gt;安定版としては執筆時点で&lt;code&gt;1.14.4&lt;/code&gt;が最新です。
&lt;code&gt;docusaurus-init&lt;/code&gt;という雛形ドキュメントを作成してくれる CLI ツールがあるので、比較的楽に導入できます。
また、これで作成された雛形ドキュメントの中には Docker に関するファイルも含まれ、ローカルで Docker でサーバを立ち上げて作業するということができるようになっています。&lt;/p&gt;
&lt;p&gt;便利だなーと思いながら導入したのですが、実際に使ってみるとちょっと気になる点がありました。&lt;/p&gt;
&lt;p&gt;Docusaurus の1系には i18n による翻訳機能が搭載されているのですが、どうも英語でドキュメントを書いていく前提の作りになっているようでした。
最初に英語で書いて、それを多言語に翻訳していくような。&lt;/p&gt;
&lt;p&gt;各ドキュメントのタイトルといった、翻訳対象になりそうなものを検知して&lt;code&gt;i18n/en.json&lt;/code&gt;ファイルにまとめてくれる機能があり。
便利ではあるものの、このファイルはあくまで英語用のファイルです。
ドキュメントを日本語で書いていようと&lt;code&gt;i18n/en.json&lt;/code&gt;ファイルにまとめられます。&lt;/p&gt;
&lt;p&gt;GitHub のコードを見たりもしましたが、ドキュメントを書いていく上でのデフォルト言語は変えられなさそうでした。&lt;/p&gt;
&lt;p&gt;そこでいろいろと調べているうちに、開発中の alpha 版ではあるものの、2系も使えることに気づきました。
2系は一から作り直されているようで、翻訳機能はまだ搭載されていませんが、それ以外の1系の主な機能は移植されているとのこと。
現状日本語でしか作らないつもりなので十分です。&lt;/p&gt;
&lt;p&gt;すでに2系を使われている OSS ドキュメントもありますし、むしろ、2系を使った方がいいのではないか？ということで、今回は2系を使うことにしました。
なお、執筆時点での最新バージョンは&lt;code&gt;2.0.0-alpha.40&lt;/code&gt;です。
↓
※2020/05/03追記  &lt;code&gt;2.0.0-alpha.54&lt;/code&gt;に対応して一部修正。
※2020/10/12追記  &lt;code&gt;2.0.0-alpha.65&lt;/code&gt;に対応して一部修正。
※2021/04/02追記  &lt;code&gt;2.0.0-alpha.72&lt;/code&gt;に対応して一部修正。&lt;/p&gt;
&lt;h3&gt;導入手順&lt;/h3&gt;
&lt;p&gt;前提として以下が必要です。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Node.js：12.13.0以上&lt;/li&gt;
&lt;li&gt;yarn：1.5以上&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;npx&lt;/code&gt;コマンドを使って、Docusaurus 導入 + 雛形作成の CLI ツールを直接利用します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx @docusaurus/init@latest init [name] [template]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;今回は classic テンプレートでの導入で進めます。
（以降の内容は&lt;strong&gt;classic テンプレートでの内容である&lt;/strong&gt;ことに注意です）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx @docusaurus/init@latest init website classic
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、&lt;code&gt;classic&lt;/code&gt;以外のテンプレートとしては&lt;code&gt;facebook&lt;/code&gt;、&lt;code&gt;bootstrap&lt;/code&gt;などがあります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;website
├ blog
│  ├ 2019-05-28-hola.md
│  ├ 2019-05-29-hello-world.md
│  └ 2020-05-30-welcome.md
├ docs
│  ├ create-a-blog-post.md
│  ├ create-a-document.md
│  ├ create-a-page.md
│  ├ getting-started.md
│  ├ markdown-features.mdx
│  └ thank-you.md
├ node_modules
├ src
│  ├ css
│  │  └ custom.css
│  └ pages
│      ├ index.js
│      ├ markdown-page.md
│      └ styles.module.css
├ static
│  ├ img
│  │  ├ docusaurus.png
│  │  ├ favicon.ico
│  │  ├ logo.svg
│  │  ├ undraw_docusaurus_mountain.svg
│  │  ├ undraw_docusaurus_react.svg
│  │  └ undraw_docusaurus_tree.svg
│  └ .nojekyll
├ .gitignore
├ babel.config.js
├ docusaurus.config.js
├ package.json
├ README.md
├ sidebars.js
└ yarn.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この classic テンプレートには、基本的なテーマやプラグインをまとめたプリセットである&lt;code&gt;@docusaurus/preset-classic&lt;/code&gt;が含まれています。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;@docusaurus/preset-classic&lt;/code&gt;に含まれているものはこちら.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;@docusaurus/theme-classic&lt;/li&gt;
&lt;li&gt;@docusaurus/theme-search-algolia&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-content-docs&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-content-blog&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-content-pages&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-debug&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-google-analytics&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-google-gtag&lt;/li&gt;
&lt;li&gt;@docusaurus/plugin-sitemap&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;サーバの起動.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;サーバを起動すると、自動でブラウザが立ち上がり、&lt;code&gt;localhost:3000&lt;/code&gt;が開きます。&lt;/p&gt;
&lt;p&gt;以下のような画面が表示されればOKです。
&amp;lt;ImageWrapper src=&quot;screenshots/2020/docusaurus-ver-2/docusaurus-init-top.png&quot; alt=&quot;Docusaurus 2系の雛形ドキュメントのトップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみにホットリロードに対応しているので、基本的には変更が即座に反映されます（一部設定はサーバを再起動しないといけないときもありました）&lt;/p&gt;
&lt;h2&gt;Docusaurus の基本的な構成&lt;/h2&gt;
&lt;h3&gt;ページ&lt;/h3&gt;
&lt;p&gt;Docusaurus では通常のページやドキュメントページが作れるほか、ブログを書くこともできます。
確かに OSS だと開発ブログみたいなものがあったりしますよね。&lt;/p&gt;
&lt;p&gt;ページの種別ごとにディレクトリ階層が決まっています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;通常のページ：&lt;code&gt;src/pages&lt;/code&gt;配下&lt;/li&gt;
&lt;li&gt;ドキュメントページ：&lt;code&gt;docs&lt;/code&gt;配下&lt;/li&gt;
&lt;li&gt;ブログページ：&lt;code&gt;blog&lt;/code&gt;配下&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;通常のページは React 式にも書けますし、マークダウンファイルでも書けます。
また、mdx にも対応しています。&lt;/p&gt;
&lt;h3&gt;サイトの基本設定&lt;/h3&gt;
&lt;p&gt;docusaurus.config.js にまとめられています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;/** @type {import(&apos;@docusaurus/types&apos;).DocusaurusConfig} */
module.exports = {
  title: &apos;My Site&apos;,
  tagline: &apos;The tagline of my site&apos;,
  url: &apos;https://your-docusaurus-test-site.com&apos;,
  baseUrl: &apos;/&apos;,
  onBrokenLinks: &apos;throw&apos;,
  onBrokenMarkdownLinks: &apos;warn&apos;,
  favicon: &apos;img/favicon.ico&apos;,
  organizationName: &apos;facebook&apos;, // Usually your GitHub org/user name.
  projectName: &apos;docusaurus&apos;, // Usually your repo name.
  themeConfig: {
    navbar: {
      title: &apos;My Site&apos;,
      logo: {
        alt: &apos;My Site Logo&apos;,
        src: &apos;img/logo.svg&apos;,
      },
      items: [
        {
          to: &apos;docs/&apos;,
          activeBasePath: &apos;docs&apos;,
          label: &apos;Docs&apos;,
          position: &apos;left&apos;,
        },
        {to: &apos;blog&apos;, label: &apos;Blog&apos;, position: &apos;left&apos;},
        {
          href: &apos;https://github.com/facebook/docusaurus&apos;,
          label: &apos;GitHub&apos;,
          position: &apos;right&apos;,
        },
      ],
    },
    footer: {
      style: &apos;dark&apos;,
      links: [
        {
          title: &apos;Docs&apos;,
          items: [
            {
              label: &apos;Getting Started&apos;,
              to: &apos;docs/&apos;,
            },
          ],
        },
        {
          title: &apos;Community&apos;,
          items: [
            {
              label: &apos;Stack Overflow&apos;,
              href: &apos;https://stackoverflow.com/questions/tagged/docusaurus&apos;,
            },
            {
              label: &apos;Discord&apos;,
              href: &apos;https://discordapp.com/invite/docusaurus&apos;,
            },
            {
              label: &apos;Twitter&apos;,
              href: &apos;https://twitter.com/docusaurus&apos;,
            },
          ],
        },
        {
          title: &apos;More&apos;,
          items: [
            {
              label: &apos;Blog&apos;,
              to: &apos;blog&apos;,
            },
            {
              label: &apos;GitHub&apos;,
              href: &apos;https://github.com/facebook/docusaurus&apos;,
            },
          ],
        },
      ],
      copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
    },
  },
  presets: [
    [
      &apos;@docusaurus/preset-classic&apos;,
      {
        docs: {
          sidebarPath: require.resolve(&apos;./sidebars.js&apos;),
          // Please change this to your repo.
          editUrl:
            &apos;https://github.com/facebook/docusaurus/edit/master/website/&apos;,
        },
        blog: {
          showReadingTime: true,
          // Please change this to your repo.
          editUrl:
            &apos;https://github.com/facebook/docusaurus/edit/master/website/blog/&apos;,
        },
        theme: {
          customCss: require.resolve(&apos;./src/css/custom.css&apos;),
        },
      },
    ],
  ],
};

&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ナビゲーションバー&lt;/h3&gt;
&lt;p&gt;docusaurus.config.js の themeConfig のなかに設定があります。&lt;/p&gt;
&lt;p&gt;react-router-dom の Link タグの書き方に似ていますね。
to には相対パスで URL を指定します。
内部リンクだけでなく、外部リンクも設定できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ※一部抜粋
navbar: {
  title: &apos;My Site&apos;,
  logo: {
    alt: &apos;My Site Logo&apos;,
    src: &apos;img/logo.svg&apos;,
  },
  items: [
    {
      to: &apos;docs/&apos;,
      activeBasePath: &apos;docs&apos;,
      label: &apos;Docs&apos;,
      position: &apos;left&apos;,
    },
    {to: &apos;blog&apos;, label: &apos;Blog&apos;, position: &apos;left&apos;},
    {
      href: &apos;https://github.com/facebook/docusaurus&apos;,
      label: &apos;GitHub&apos;,
      position: &apos;right&apos;,
    },
  ],
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画面側
&amp;lt;ImageWrapper src=&quot;screenshots/2020/docusaurus-ver-2/navbar.png&quot; alt=&quot;ナビゲーションバー&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;サイドバー&lt;/h3&gt;
&lt;p&gt;sidebars.js に設定があります。&lt;/p&gt;
&lt;p&gt;ここで指定している&lt;code&gt;create-a-page&lt;/code&gt;などは各ドキュメントの id です。
この id は、デフォルトでドキュメントのマークダウンファイル名から拡張子を抜いたものが割り当てられますが、マークダウンファイル側で別途 id を指定することも出来ます。&lt;/p&gt;
&lt;p&gt;ここでサイドバーとドキュメントの紐づけが行われ、表示上ではデフォルトで title が使われます。
（ドキュメントタイトルとサイドバー表示タイトルを分けたい場合は、マークダウンファイル側で sidebar_label を設定することで対応できます）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  docs: [
    {
      type: &apos;category&apos;,
      label: &apos;Docusaurus Tutorial&apos;,
      items: [
        &apos;getting-started&apos;,
        &apos;create-a-page&apos;,
        &apos;create-a-document&apos;,
        &apos;create-a-blog-post&apos;,
        &apos;markdown-features&apos;,
        &apos;thank-you&apos;,
      ],
    },
  ],
};

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ドキュメントのマークダウンファイル.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;※Frontmatter 部分
---
title: Create a Blog Post
---
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画面側
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2020/docusaurus-ver-2/sidebar.png&quot; alt=&quot;サイドバー&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;階層構造にしたい場合は、以下のように書けば OK。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;module.exports = {
  docs: {
    Guides: [
      &apos;creating-pages&apos;,
      {
        type: &apos;category&apos;,
        label: &apos;Docs&apos;,
        collapsed: false,
        items: [&apos;markdown-features&apos;, &apos;sidebar&apos;],
      },
    ],
  },
};
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画面側
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2020/docusaurus-ver-2/sidebar-subcategory.png&quot; alt=&quot;階層構造にしたサイドバー&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;フッター&lt;/h3&gt;
&lt;p&gt;docusaurus.config.js の themeConfig のなかに設定があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// ※一部抜粋
footer: {
  style: &apos;dark&apos;,
  links: [
    {
      title: &apos;Docs&apos;,
      items: [
        {
          label: &apos;Getting Started&apos;,
          to: &apos;docs/&apos;,
        },
      ],
    },
    {
      title: &apos;Community&apos;,
      items: [
        {
          label: &apos;Stack Overflow&apos;,
          href: &apos;https://stackoverflow.com/questions/tagged/docusaurus&apos;,
        },
        {
          label: &apos;Discord&apos;,
          href: &apos;https://discordapp.com/invite/docusaurus&apos;,
        },
        {
          label: &apos;Twitter&apos;,
          href: &apos;https://twitter.com/docusaurus&apos;,
        },
      ],
    },
    {
      title: &apos;More&apos;,
      items: [
        {
          label: &apos;Blog&apos;,
          to: &apos;blog&apos;,
        },
        {
          label: &apos;GitHub&apos;,
          href: &apos;https://github.com/facebook/docusaurus&apos;,
        },
      ],
    },
  ],
  copyright: `Copyright © ${new Date().getFullYear()} My Project, Inc. Built with Docusaurus.`,
},
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;画面側
&amp;lt;ImageWrapper src=&quot;screenshots/2020/docusaurus-ver-2/footer.png&quot; alt=&quot;フッター&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;URL&lt;/h3&gt;
&lt;h4&gt;通常のページ&lt;/h4&gt;
&lt;p&gt;基本的には&lt;code&gt;/{拡張子なしファイル名}&lt;/code&gt;になります。
&lt;code&gt;src/pages&lt;/code&gt;配下でディレクトリ階層が分かれていれば、&lt;code&gt;/{dir}/{拡張子なしファイル名}&lt;/code&gt;といった感じです。&lt;/p&gt;
&lt;p&gt;ただ、ファイル名が&lt;code&gt;index.js&lt;/code&gt;だった場合は&lt;code&gt;/&lt;/code&gt;になります。
まとめると以下のような感じです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;/src/pages/index.js → &lt;code&gt;/&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;/src/pages/foo.js → &lt;code&gt;/foo&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;/src/pages/foo/test.js → &lt;code&gt;/foo/test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;/src/pages/foo/index.js → &lt;code&gt;/foo/&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;docs 配下のドキュメント&lt;/h4&gt;
&lt;p&gt;マークダウンファイルの FrontMatter に slug があるか、docs 配下でディレクトリ階層になっているかで変わってきます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;slug 設定あり：&lt;code&gt;/docs/{slug}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;slug 設定なし： &lt;code&gt;/docs/{dir}/{id}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;docs
├ greeting.md
└ guide
    └ hello.md
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;docs/greeting&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;docs/guide/hello&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;となります。&lt;/p&gt;
&lt;h4&gt;ブログ&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;トップ：&lt;code&gt;/blog&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ブログ記事の URL も FrontMatter に slug があるかで変化します。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;slug 設定あり：&lt;code&gt;/blog/{slug}&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;slug 設定なし：&lt;code&gt;/blog/{YYYY}/{MM}/{dd}/{id}&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;デプロイ&lt;/h2&gt;
&lt;p&gt;今回は Netlify でデプロイして、いつでもサイトとして見られるようにしておきます。&lt;/p&gt;
&lt;h3&gt;初回デプロイ&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;docusaurus.config.js の内容を確認&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;url と baseUrl の設定を確認してください&lt;/li&gt;
&lt;li&gt;url は Netlify で公開する上での URL（Netlify のサブドメインは&lt;code&gt;{プロジェクト名}.netlify.app&lt;/code&gt;）&lt;/li&gt;
&lt;li&gt;baseUrl は&lt;code&gt;/&lt;/code&gt;のままでも問題なかったです&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;netlify.toml を用意&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;[build]
  publish = &quot;build&quot;
  command = &quot;yarn build&quot;

[build.environment]
  NODE_ENV = &quot;production&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もしサブディレクトリにサイトのソースがある場合は、base で指定すれば対応できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;[build]
  base    = &quot;website&quot;
  publish = &quot;build&quot;
  command = &quot;yarn build&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Netlify とリポジトリを連携させてデプロイ
基本的なやり方は過去記事に書いているので、こちらをご覧ください。&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;develop/2019/02/10/netlify-deploy&quot;&gt;Netlifyでブログをデプロイ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ビルド・デプロイの設定例
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2020/docusaurus-ver-2/docusaurus-netlify.png&quot; alt=&quot;Netlifyのビルド設定&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これでうまくいっていればデプロイされて、サイトとして閲覧できるようになれるはずです。&lt;/p&gt;
&lt;h3&gt;2回目からのデプロイ&lt;/h3&gt;
&lt;p&gt;初回デプロイ以降は、デプロイブランチが更新されるたびに自動でデプロイを行ってくれます。
そのほか、デプロイブランチに対するプルリク（マジリク）を出したときに、デプロイのプレビューを生成してくれるので、マージした後にどうなるか事前に確認ができます。&lt;/p&gt;
&lt;h3&gt;補足：Node.js のバージョン指定&lt;/h3&gt;
&lt;p&gt;バージョン指定をしたい場合は、以下の2通りのやり方があります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;環境変数に&lt;code&gt;NODE_VERSION&lt;/code&gt;でバージョンを設定する（設定方法は以下のいずれか）
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;netlify.toml&lt;/code&gt;に記述する&lt;/li&gt;
&lt;li&gt;Netlify の管理画面の Settings → Build &amp;amp; Deploy → Environment で設定&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.node-version&lt;/code&gt;か&lt;code&gt;.nvmrc&lt;/code&gt;をリポジトリ直下に作成して、バージョンを記述する。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Textlint の導入（2020/1/19追記）&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/textlint/textlint&quot; /&amp;gt;
Textlint は文書向けの Lint ツールです。
せっかくなので使用してみることにしました。&lt;/p&gt;
&lt;h3&gt;導入手順&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D textlint textlint-rule-preset-ja-technical-writing
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Textlint 単体だと何もルールを持っていないので、技術書向けのルールセットである&lt;code&gt;textlint-rule-preset-ja-technical-writing&lt;/code&gt;を一緒にインストールしています。
&amp;lt;OG url=&quot;https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;設定ファイルの作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;yarn run -s textlint --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のような&lt;code&gt;.textlintrc&lt;/code&gt;ファイルが作られます。
すでにインストールしているルールを認識して作ってくれるようです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;filters&quot;: {},
  &quot;rules&quot;: {
    &quot;preset-ja-technical-writing&quot;: true
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;コマンドで実行&lt;/h3&gt;
&lt;p&gt;この時点でコマンドが使えるようになっています。
設定ファイルで設定したルールに沿ってチェックされます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn run -s textlint (ファイルパス)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また、以下のコマンドではルールにもよりますが、自動修正できるものは修正してくれるようです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn run -s textlint --fix (ファイルパス)
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;VSCode 上で実行&lt;/h3&gt;
&lt;p&gt;毎回コマンド実行するのも手間なので、VSCode のエディタ上で確認できるようにしておきます。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://marketplace.visualstudio.com/items?itemName=taichi.vscode-textlint&quot; /&amp;gt;
インストールして、必要に応じて VSCode を再起動。
プロジェクトルート直下に&lt;code&gt;.textlintrc&lt;/code&gt;や&lt;code&gt;node_modules&lt;/code&gt;がある場合は、この時点で動作します。&lt;/p&gt;
&lt;p&gt;もしサブディレクトリにサイトのソースがある場合は、この拡張の設定で別途&lt;code&gt;.textlintrc&lt;/code&gt;や&lt;code&gt;node_modules&lt;/code&gt;のパスの指定が必要になります。&lt;/p&gt;
&lt;p&gt;設定例.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Textlint: Config Path → website/.textlintrc&lt;/li&gt;
&lt;li&gt;Textlint: Node Path → website/node_modules&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;その他の設定には以下のようなものがあります。この辺はお好みで。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Textlint: Auto Fix On Save（保存時に自動修正できるものを修正するか）&lt;/li&gt;
&lt;li&gt;Textlint: Run（入力時にチェックするか、保存時にチェックするか）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;こんな感じで動いてくれるようになります。
&amp;lt;ImageWrapper src=&quot;screenshots/2020/docusaurus-ver-2/textlint.png&quot; alt=&quot;VSCode上で動作するTextlint&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;設定のカスタマイズ&lt;/h3&gt;
&lt;p&gt;今回使用している&lt;code&gt;textlint-rule-preset-ja-technical-writing&lt;/code&gt;は複数のルールから構成されており、ルールそれぞれのカスタマイズが可能です。
デフォルト値が厳しめに設定されているため、自分に合った設定へカスタマイズしてみましょう。&lt;/p&gt;
&lt;p&gt;デフォルトルール一覧.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;1文の長さは100文字以下とする&lt;/li&gt;
&lt;li&gt;カンマは1文中に3つまで&lt;/li&gt;
&lt;li&gt;読点は1文中に3つまで&lt;/li&gt;
&lt;li&gt;連続できる最大の漢字長は6文字まで&lt;/li&gt;
&lt;li&gt;漢数字と算用数字を使い分けます&lt;/li&gt;
&lt;li&gt;「ですます調」、「である調」を統一します&lt;/li&gt;
&lt;li&gt;文末の句点記号として「。」を使います&lt;/li&gt;
&lt;li&gt;二重否定は使用しない&lt;/li&gt;
&lt;li&gt;ら抜き言葉を使用しない&lt;/li&gt;
&lt;li&gt;逆接の接続助詞「が」を連続して使用しない&lt;/li&gt;
&lt;li&gt;同じ接続詞を連続して使用しない&lt;/li&gt;
&lt;li&gt;同じ助詞を連続して使用しない&lt;/li&gt;
&lt;li&gt;UTF8-MAC 濁点を使用しない&lt;/li&gt;
&lt;li&gt;不必要な制御文字を使用しない&lt;/li&gt;
&lt;li&gt;感嘆符!！、感嘆符?？を使用しない&lt;/li&gt;
&lt;li&gt;半角カナを使用しない&lt;/li&gt;
&lt;li&gt;弱い日本語表現の利用を使用しない&lt;/li&gt;
&lt;li&gt;同一の単語を間違えて連続しているのをチェックする&lt;/li&gt;
&lt;li&gt;よくある日本語の誤用をチェックする&lt;/li&gt;
&lt;li&gt;冗長な表現をチェックする&lt;/li&gt;
&lt;li&gt;入力ミスで発生する不自然なアルファベットをチェックする&lt;/li&gt;
&lt;li&gt;対になっていない括弧をチェックする&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;設定例.&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;filters&quot;: {},
  &quot;rules&quot;: {
    &quot;preset-ja-technical-writing&quot;: {
      &quot;sentence-length&quot;: {
        &quot;max&quot; : 90
      },
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この場合、sentence-length（1文の長さ）を90以下にして、それ以外のルールはデフォルト値が使われます。
他の設定や例外の設定の仕方などは textlint-rule-preset-ja-technical-writing の README を参考にしてください。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;ここまで簡単に概要を書きました。
開発版とはいえ、2系の方が扱いやすい印象をうけました。
これから少しずつ、TIL リポジトリにあげた勉強内容をドキュメント化していきますー。&lt;/p&gt;
&lt;p&gt;また、プログラマーとしての経歴や経験をまとめたページなんかも作ったり、当ブログとリンクさせたりしていきたいですね。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docusaurus.io/&quot;&gt;Docusaurus（1系）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://v2.docusaurus.io/&quot;&gt;Docusaurus（2系）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.netlify.com/configure-builds/manage-dependencies/&quot;&gt;Netlify docs - Manage build dependencies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/textlint-ja/textlint-rule-preset-ja-technical-writing&quot;&gt;GitHub - textlint-rule-preset-ja-technical-writing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/azu/items/2c565a38df5ed4c9f4e1&quot;&gt;VS Codeでtextlintを使って文章をチェックする&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://marketplace.visualstudio.com/items?itemName=taichi.vscode-textlint&amp;amp;ssr=false#overview&quot;&gt;vscode-textlint&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Jekyll製ブログの各見出しに自動アンカー生成を導入する</title><link>https://changeofpace.site/posts/2020-01-04-jekyll-anthor-heading</link><guid isPermaLink="true">https://changeofpace.site/posts/2020-01-04-jekyll-anthor-heading</guid><description>Jekyllで各hタグに自動的にアンカーをつけてくれる拡張の導入手順</description><pubDate>Sat, 04 Jan 2020 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Qiita や GitHubのマークダウンって、各見出しに自動的にアンカーがついていて、すぐにその見出しのリンク取得できるから便利ですよね。
あれを Jekyll で導入する方法を調査して、よいものを見つけたので早速導入してみました。&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;調査したきっかけ&lt;/h2&gt;
&lt;p&gt;自分はとあるエンジニアのコミュニティに参加しています。
そのコミュニティを立ち上げられた方を Twitter でフォローさせていただいているのですが...。
あるとき、その方が「Jekyll で見出しにアンカーを自動で付与する方法はないか？」というつぶやきをされていました。&lt;/p&gt;
&lt;p&gt;最初は目次から見出しへ飛べるようにするのかな？と思ってやりとりをしたのですが、そこで Qiita や GitHub の見出しの話になりました。
Qiita や GitHub のマークダウンの見出しって、自動的にアンカーリンクが付与されていて、すぐにその見出しのアンカーリンクが取得できるんですよね。
あれってどうやってるんだろうと。&lt;/p&gt;
&lt;p&gt;そこで何かプラグイン等がないか調査してみることにしました。
何か見つかれば当ブログにも導入できるなと。&lt;/p&gt;
&lt;h2&gt;jekyll-anchor-headings&lt;/h2&gt;
&lt;p&gt;調査したところ、こちらのリポジトリを見つけました。
&amp;lt;OG url=&quot;https://github.com/allejo/jekyll-anchor-headings&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;概要&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;GitHub Pages can&apos;t run custom Jekyll plug-ins so when generating anchors for your headings (i.e. h1 - h6), you&apos;re stuck with JavaScript solutions that will inject anchors. But what if your users don&apos;t have JavaScript enabled on their browsers? If you&apos;re building a static website, why not make your anchors static as well?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;簡単に静的サイト構築のできる GitHub Pages でも Jekyll が使われているのですが、あちらはプラグインが使用できないという制約があります。
そこで JavaScript で見出しをカスタマイズするという選択肢が出ますが、JavaScriptが無効化されていると、利用できません。
静的サイトでは、アンカーも静的にしましょうといったことが書いてありました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;A GitHub Pages compatible way of adding anchors to your headings without a plug-in or JavaScript&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;こちらに書いてあるとおり、プラグインや JavaScript を使わずとも、このスニペットを使えば、見出しに自動的にアンカーを付与できますよとのことです。
プラグインでないので GitHub Pages でも問題なく使用できます。&lt;/p&gt;
&lt;h3&gt;導入実績&lt;/h3&gt;
&lt;p&gt;こちらも README に記述がありました。
比較的有名なサービスや OSS のドキュメントに使われているそうです。
すごいですね！&lt;/p&gt;
&lt;blockquote&gt;
&lt;ul&gt;
&lt;li&gt;Travis CI Docs (fixed in #1960)&lt;/li&gt;
&lt;li&gt;Bitrise&apos;s Documentation&lt;/li&gt;
&lt;li&gt;di&apos;s personal website&lt;/li&gt;
&lt;li&gt;sitespeed.io&lt;/li&gt;
&lt;li&gt;Duality&apos;s developer docs&lt;/li&gt;
&lt;li&gt;Australia&apos;s Vote Flux campaign&lt;/li&gt;
&lt;li&gt;mlpack.org&lt;/li&gt;
&lt;li&gt;Riot.js website&lt;/li&gt;
&lt;li&gt;&quot;Just the Docs&quot; Jekyll theme&lt;/li&gt;
&lt;li&gt;Microsoft&apos;s TypeScript website&lt;/li&gt;
&lt;li&gt;VMWare&apos;s Octant documentation&lt;/li&gt;
&lt;/ul&gt;
&lt;/blockquote&gt;
&lt;h3&gt;導入方法&lt;/h3&gt;
&lt;p&gt;導入はとても簡単です。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;リポジトリから最新の&lt;code&gt;anchor_headings.html&lt;/code&gt;ファイルをダウンロードする&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;ダウンロードしたファイルを、自ブログコードの&lt;code&gt;_includes&lt;/code&gt;配下に格納&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;レイアウトファイルで&lt;code&gt;{{ content }}&lt;/code&gt;のところを以下のように置き換える&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;{% include anchor-headings.html html=content %}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自分は&lt;code&gt;_layouts/default.html&lt;/code&gt;の該当箇所を置き換えました。
他のレイアウトファイルもこのレイアウトファイルをベースにしているので、まとめて適用できました。&lt;/p&gt;
&lt;h3&gt;オプション&lt;/h3&gt;
&lt;p&gt;include 文にオプションを渡すことができます。&lt;/p&gt;
&lt;p&gt;一例はこちら.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;beforeHeading
見出しの文字の前にアンカーを生成するか（bool で指定、デフォルトは false）&lt;/li&gt;
&lt;li&gt;anchorBody
アンカー内に配置するコンテンツ（string で指定、デフォルトは&apos;&apos;）
文字列で指定ですが、HTML の i タグで指定もできました。&lt;/li&gt;
&lt;li&gt;anchorClass
アンカーに指定するクラス（string で指定、デフォルトは&apos;&apos;）
このクラスに CSS をあてて、カスタマイズもできます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;詳しくはリポジトリの README でご確認ください。
&amp;lt;OG url=&quot;https://github.com/allejo/jekyll-anchor-headings#parameters&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;自分の場合は以下のようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{% include anchor-headings.html html=content anchorBody=&apos;#&apos; anchorClass=&apos;anchor-heading&apos; %}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;とても簡単に見出しに自動アンカーを導入できました！
Jekyll を利用している方はぜひ導入してみてはどうでしょうか。&lt;/p&gt;
&lt;p&gt;README を読んでて気づいたのですが、この製作者の方は、以前目次導入の調査をしていた時に見つけた jekyll-toc を作った方でもあったそうです。
&amp;lt;OG url=&quot;https://github.com/allejo/jekyll-toc&quot; /&amp;gt;
jekyll-toc に関しても、同様に導入でき、GitHub Pages でも使えるようになっていました。&lt;/p&gt;
&lt;p&gt;こういう便利なものを作れる方ってすごいですね！
自分もいつか挑戦してみようかな...。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;遅ればせながら、あけましておめでとうございます。
今年も少しずつブログ書いていきますので、どうぞよろしくお願い致します。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/allejo/jekyll-anchor-headings&quot;&gt;GitHub - jekyll-anchor-headings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/allejo/jekyll-toc&quot;&gt;GitHub - jekyll-toc&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>2019年振り返り ～Gitコミット、ブログ編～</title><link>https://changeofpace.site/posts/2019-12-31-looking-back-2019-git</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-12-31-looking-back-2019-git</guid><description>2019年のGitのコミット履歴から、プログラミングに取り組んだ時間の振り返り</description><pubDate>Tue, 31 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ついに大晦日になりましたね。どうも、よしです。
先日は精神疾患に関して振り返りを行いましたが、今度は Git のコミット履歴、ブログに関する振り返りをやってみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;2019年の活動履歴&lt;/h2&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;まずは各アカウントのプロフィール画面から、コミット履歴を確認してみます。&lt;/p&gt;
&lt;h4&gt;個人 - GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/looking-back-2019-git/github-contributions-personal.png&quot; alt=&quot;GitHubの個人アカウントのcontributions履歴&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;4月後半から7月にかけてが多いですね。このあたりは精神疾患の症状が比較的落ち着いていて、勉強していた時期でもあります。
6月に復職した頃は、通勤リハビリ～午前中勤務だったので、自分の時間に余裕があったというのもありました。
なので、勤務時間が戻っていくにつれてコミットが少なくなっていっています。
大半は勉強用のTILリポジトリにコミットしていたものです。&lt;/p&gt;
&lt;h4&gt;仕事 - GitHub&lt;/h4&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/looking-back-2019-git/github-contributions-work.png&quot; alt=&quot;GitHubの仕事アカウントのcontributions履歴&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;年始の頃は当時の案件をやっていましたが、プライベートリポジトリでしたし、そのリポジトリからは抜けたので、ここには表示されていないようです。
2月中頃～5月末まで休職。
6月に復職して、React の勉強～React 案件参加となってから少しずつコミットが増えていっています。&lt;/p&gt;
&lt;h4&gt;個人 - GitLab&lt;/h4&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/looking-back-2019-git/gitlab-contributions-personal.png&quot; alt=&quot;GitLabの個人アカウントのcontributions履歴&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;個人の GitLab に関しては、当ブログに関する活動履歴になります。
年始に立てた、毎月1記事は投稿するという目標の元やっていましたが、後半は1か月あたり2～5記事投稿できました。
他にブログ自体の機能の追加といった改修や過去記事の見直しなどをやっていたため、このような履歴になりました。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;p&gt;2019年の大まかなブログの履歴は以下のような感じでした。
（Google Analytics のスクショは貼れる、貼れないで意見が分かれるようなので、貼らずにデータを抜粋しました）&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;月&lt;/th&gt;
&lt;th&gt;記事数&lt;/th&gt;
&lt;th&gt;月のアクセスユーザ数&lt;/th&gt;
&lt;th&gt;月のページビュー数&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1月&amp;lt;br /&amp;gt;(1/1 ブログ公開)&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2月&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3月&lt;/td&gt;
&lt;td&gt;9&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4月&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5月&lt;/td&gt;
&lt;td&gt;12&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;td&gt;※データなし&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;6月&amp;lt;br /&amp;gt;(6/5 GA導入)&lt;/td&gt;
&lt;td&gt;14&lt;/td&gt;
&lt;td&gt;7&lt;/td&gt;
&lt;td&gt;24&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;7月&lt;/td&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;21&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;8月&lt;/td&gt;
&lt;td&gt;18&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;61&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;9月&lt;/td&gt;
&lt;td&gt;23&lt;/td&gt;
&lt;td&gt;80&lt;/td&gt;
&lt;td&gt;247&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10月&lt;/td&gt;
&lt;td&gt;26&lt;/td&gt;
&lt;td&gt;118&lt;/td&gt;
&lt;td&gt;196&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;11月&lt;/td&gt;
&lt;td&gt;29&lt;/td&gt;
&lt;td&gt;163&lt;/td&gt;
&lt;td&gt;246&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;12月&lt;/td&gt;
&lt;td&gt;33&lt;/td&gt;
&lt;td&gt;291&lt;/td&gt;
&lt;td&gt;449&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Google Analytics を導入したのが6月なので、およそ7か月の履歴になりますが、一気に5記事書いた9月に急激に伸びましたね。
約7か月でユーザ数が約41倍、ページビュー数が約18倍になりました。&lt;/p&gt;
&lt;p&gt;自分はブロガーではないのでアクセス数を伸ばすことに注力しているわけではないですが、それでもちょっと嬉しいですね。&lt;/p&gt;
&lt;h2&gt;2020年はどうしていきたいか&lt;/h2&gt;
&lt;h3&gt;個人勉強&lt;/h3&gt;
&lt;p&gt;個人勉強のコードは学習記録の可視化にもなるということで、上記にも書いている TIL リポジトリにコミットしています。これは今後も継続していきます。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;TILとは
「Today I Learned」の略で、Github上にTILというリポジトリを作成してそこに今日覚えたことを書いていくというものです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;※TILリポジトリに関する過去記事はこちら。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2019-03-26-til&quot;&gt;TILリポジトリで小さなアウトプット&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;/posts/2019-08-17-til-150&quot;&gt;TILリポジトリが150コミット突破&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ただ、途中まではいい感じかなーと思っていたものの、だんだん散らかってきたといいますが、勉強した内容をドキュメントみたいにちゃんとまとめたいという思いが出てきました。&lt;/p&gt;
&lt;p&gt;そこで、OSS のドキュメントがどうなっているか見てみようということで、create-react-app のドキュメントのコードを見ていたところ...。
どうも&lt;code&gt;Docusaurus&lt;/code&gt;というドキュメント特化の静的サイトジェネレータを使っていることがわかりました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docusaurus.io&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;今後、これを導入して勉強したことのドキュメントを作ろうか検討中です。
言語のバージョンアップで仕様が変わるなど、ドキュメントの更新や整備が大変では...という懸念点もありますが、まずはやってみようかと。
自己満足みたいなところがあるので、このドキュメントを評価材料にしてもらおうとかはあんまり考えていません。というか、評価材料になりえるものなんでしょうか...？&lt;/p&gt;
&lt;p&gt;仕事としては、フロントとバックエンドで1つずつ自分の技術の軸になるものがほしいということで。
JavaScript（React）と Java（Spring）をメインにしようかと思ってはいるのですが、興味のあることは個人勉強で色々やってみたいと思っています。&lt;/p&gt;
&lt;p&gt;個人勉強で特にこれをやりたい！というものは現状決めていません。これが吉と出るか、凶とでるか...。&lt;/p&gt;
&lt;h3&gt;仕事&lt;/h3&gt;
&lt;p&gt;年明けから週5日勤務に戻るので、まずはそれを維持したいなと。
維持できていれば自然とコードを書く量も増えていくと思います。&lt;/p&gt;
&lt;h3&gt;ブログ&lt;/h3&gt;
&lt;p&gt;1か月に複数記事も意外といけるなーと思ったのですが、変に無理せず最低1記事書くというのを継続していきます。書けたら複数記事書こうくらいの緩い感じで。&lt;/p&gt;
&lt;p&gt;アクセス数が伸びたら、それはそれで嬉しいですが、短期的なアクセス数目当てだけの質の悪い記事を書かないよう気を付けたいところです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;前回の記事でも書いたのですが、精神疾患を抱えている分ハンデもあります。ただ、それでもできることはやっていきたいと思っています。&lt;/p&gt;
&lt;p&gt;身体の調子を見ながら、緩やかな目標の中で2020年は過ごしていきたいですね。&lt;/p&gt;
&lt;p&gt;皆様、よいお年をお迎えくださいー。
ではでは。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
</content:encoded></item><item><title>React入門 ～基礎編～</title><link>https://changeofpace.site/posts/2019-12-18-react-basics</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-12-18-react-basics</guid><description>React入門記事。Reactについてコンポーネントやpropやstateなど、公式チュートリアルで触れる部分を中心とした基礎的な概念や書き方のまとめ</description><pubDate>Wed, 18 Dec 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;現在業務で使用している React について、より理解を深めていくために勉強したことを記事として書くことにしました。
ということで、まずは基礎編です。&lt;/p&gt;
&lt;p&gt;※この記事は元々 &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/4cde7fd891783c396a32&quot;&gt;Qiita&lt;/a&gt; からの転載です。
現在は Qiita でなく &lt;a href=&quot;https://zenn.dev/h_yoshikawa0724/articles/2020-09-21-react-basics&quot;&gt;Zenn&lt;/a&gt; の方で更新しています。&lt;/p&gt;
&lt;p&gt;※2021/09/13追記  全体的に内容を見直しました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;記事を書くにあたって&lt;/h2&gt;
&lt;p&gt;2019年秋頃から Qiita の記事を書いてきましたが、環境構築系ばかりで、いまだ技術の中身についての記事を書いたことがなかったので、そろそろ書いてみたいなーと思っていました。（ブログで多少技術記事は書いていました）&lt;/p&gt;
&lt;p&gt;技術記事となると、すでに同じテーマで記事を書かれている方がいてクオリティも高くてと、自分なんかが書いてもなと尻込みをしていたのですが、&lt;strong&gt;自分の理解を深める&lt;/strong&gt;という目的で思い切って書くことにしました。
記事を書く上で自然とまとめるようになるので、頭の中が整理できるので。&lt;/p&gt;
&lt;p&gt;もし自分がその技術から離れてしまって、また戻ってきたときに記事を見返して助けになればとも。
まだまだ勉強中の身であるため内容に間違い等ありましたら、コメントいただけると幸いです。&lt;/p&gt;
&lt;h2&gt;React とは&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Facebook およびコミュニティによって開発されている、ユーザインターフェース構築のための JavaScript ライブラリです。&lt;/p&gt;
&lt;p&gt;これだけ聞いてもなんのこっちゃとなりそうなので、公式ドキュメントにある特徴を引用してみます。&lt;/p&gt;
&lt;h3&gt;宣言的な View&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;React は、インタラクティブなユーザインターフェイスの作成にともなう苦痛を取り除きます。アプリケーションの各状態に対応するシンプルな View を設計するだけで、React はデータの変更を検知し、関連するコンポーネントだけを効率的に更新、描画します。&lt;/p&gt;
&lt;p&gt;宣言的な View を用いてアプリケーションを構築することで、コードはより見通しが立ちやすく、デバッグのしやすいものになります。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;通常の JavaScript のみであったり jQuery を使っての「命令型」の UI 構築となると、操作したい DOM 要素を取得して、その要素を操作していく形になるでしょう。
もちろんこれでも UI 構築は出来るわけですし、そう複雑なものでなければ問題はないこともあります。
ただ、より複雑なことをしようとすると、ロジックの複雑さは当然増してきます。
保守性が落ちて、バグが発生しやすくなったり。最終的にどんな UI になるのかイメージしにくくなったり。&lt;/p&gt;
&lt;p&gt;React では主に JSX という JavaScript の拡張構文を使って UI を記述していきます。
こちらは「宣言型」で UI を記述するため、基本的には最終的な UI の状態を記述（宣言）すればいいだけです。
その UI にするための DOM 操作は React の方でやってくれます。&lt;/p&gt;
&lt;p&gt;また、React における DOM 構築に関して、&lt;code&gt;仮想DOM&lt;/code&gt;というものがあります。
React は DOM 操作時に、すぐにブラウザ上の DOM に反映させるのではなく、まずはメモリ上に仮想 DOM として構築し記録。
そして、前回の記録されている仮想 DOM と比較して差分を特定し、その&lt;strong&gt;差分のみ&lt;/strong&gt;をブラウザ上の DOM に同期させる。という仕組みになっています。
従来の JavScript、jQuery による DOM 操作では、DOM 全体が再構築されていたため、それに比べると高速で動作するようになっているわけです。&lt;/p&gt;
&lt;h3&gt;コンポーネントベース&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;自分自身の状態を管理するカプセル化されたコンポーネントをまず作成し、これらを組み合わせることで複雑なユーザインターフェイスを構築します。&lt;/p&gt;
&lt;p&gt;コンポーネントのロジックは、Template ではなく JavaScript そのもので書くことができるので、様々なデータをアプリケーション内で簡単に取り回すことができ、かつ DOM に状態を持たせないようにすることができます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;React ではコンポーネントベースの設計思想が取り入れられています。
インタフェースさえ分かっていれば内部の実装を知らなくても使える、独立したコンポーネントを組み合わせて、UI を構築していく形です。
あるコンポーネントを呼び出して使うほか、複数のコンポーネントを組み合わせて新たなコンポーネントを作るということもできます。&lt;/p&gt;
&lt;p&gt;前項でも書いた通り、React では JSX という JavaScript の拡張構文を使って UI を記述していくわけですが、JSX の中で JavaScript の処理を書くということも可能です。
コンポーネント自体が持つロジックも JavaScript で書けるため、UI 部分とロジック部分とでデータのやり取りも楽にできたりします。&lt;/p&gt;
&lt;h3&gt;一度学習すれば、どこでも使える&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;React と組み合わせて使用する技術に制限はありません。React を使って新しい機能を追加する際に、既存のソースコードを書き換える必要はありません。&lt;/p&gt;
&lt;p&gt;React は Node を使ったサーバ上でもレンダーできますし、React Native を使うことでモバイルアプリケーションの中でも動きます。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;React は、本体と、仮想 DOM の内容を実際に UI へ反映させるレンダラー（react-dom など）とが分かれています。
このレンダラーの部分は、様々なプラットフォームのものが提供されているため、いろんなものが作れるよーという話です。
react-dom 以外の代表的なものとして、クロスプラットフォームのモバイルアプリ向けである React Native があります。&lt;/p&gt;
&lt;h2&gt;環境構築&lt;/h2&gt;
&lt;p&gt;環境構築のやり方は色々あるのですが、パパっと作りたい場合は、Facebook 製の Create React App を使うと楽です。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://create-react-app.dev&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;初期セットアップが済んだ React プロジェクトを作成してくれます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npx create-react-app ※アプリ名
# or
yarn create react-app ※アプリ名
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;任意ではありますが、オプションで&lt;code&gt;--template&lt;/code&gt;を指定することで、テンプレートを利用出来たりもします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# TypeScript テンプレートによるセットアップ例
npx create-react-app ※アプリ名 --template typescript
# or
yarn create react-app ※アプリ名 --template typescript
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;セットアップ後、ローカルサーバを立ち上げて開発をしていく形です。
なお、webpack-dev-server によりホットリロードが自動で動作します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm start
# or
yarn start
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;react-scripts&lt;/h3&gt;
&lt;p&gt;Create React App によるセットアップで作成された React アプリは、以下のライブラリが導入されています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;react：React 本体&lt;/li&gt;
&lt;li&gt;react-dom：DOM を抽象化して React から操作できるようにするレンダラー&lt;/li&gt;
&lt;li&gt;react-scripts：React アプリ開発に必要な様々なライブラリを管理するもの&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;手動で React 環境を作ろうとすると、自分でいろんな設定をしなくてはならなくなるため、なかなか大変なのですが...。
それを react-scripts がまとめて管理してくれているのです。
lock ファイルで react-scripts の依存関係を見てもらうとわかりますが、実にたくさんのライブラリが依存関係となっています。&lt;/p&gt;
&lt;p&gt;代表的な例。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Babel：新仕様の JavaScript や JSX、TypeScript のコードを、古いブラウザでも実行できるように、旧式の JavaScript コードに変換するトランスコンパイラ&lt;/li&gt;
&lt;li&gt;Webpack：コンパイラと連携しながら、依存関係を解決し、ソースコードファイルをまとめて最適化を行うモジュールバンドラー&lt;/li&gt;
&lt;li&gt;webpack-dev-server：ファイル変更を検知して再ビルドとリロードを行う、開発用の webpack サーバ&lt;/li&gt;
&lt;li&gt;dotenv：環境ごとに異なる環境変数を管理&lt;/li&gt;
&lt;li&gt;ESLint：JavaScript および TypeScript 向け静的解析&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Babel や Webpack の設定は react-scripts によって隠蔽されており、これにより設定を意識することなく、React アプリ開発が可能になっているわけです。
とはいえ、内部で何が動いているかは一応意識しておいた方がいいかと思われます。&lt;/p&gt;
&lt;p&gt;ただ、場合によっては、自分で設定をカスタマイズしたいということもあるでしょう。
そんな時は eject することで、react-scripts の依存関係を解除し、隠蔽されていた設定を直接カスタマイズできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm eject
# or
yarn eject
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この eject は一度やってしまうと元に戻せないため、取り扱いには十分注意しましょう。&lt;/p&gt;
&lt;p&gt;部分的なカスタマイズであれば、こちらを使うのもありです。
&amp;lt;OG url=&quot;https://github.com/gsoft-inc/craco&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;なお、ローカルに環境作るのすらめんどくさい場合は、オンラインエディタを使う手もあります。
自分もパパっと検証をしたい時などに、CodeSandbox をよく活用しています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://codesandbox.io&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://stackblitz.com&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;React の基本&lt;/h2&gt;
&lt;p&gt;以下、記述しているコードは以下を引用、もしくはベースにしています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Create React App で生成されたもの&lt;/li&gt;
&lt;li&gt;React 公式ドキュメント&lt;/li&gt;
&lt;li&gt;React 公式チュートリアル&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;今回の使用バージョンは以下のとおりです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React：17.0.2&lt;/li&gt;
&lt;li&gt;react-scripts：4.0.3&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;起点&lt;/h3&gt;
&lt;h4&gt;index.html&lt;/h4&gt;
&lt;p&gt;まずはおなじみの起点となる index.html。
この例において、body タグの中身としては、root という id を持つ div 要素だけです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;!DOCTYPE html&amp;gt;
&amp;lt;html lang=&quot;en&quot;&amp;gt;
  &amp;lt;head&amp;gt;
    &amp;lt;meta charset=&quot;utf-8&quot; /&amp;gt;
    &amp;lt;link rel=&quot;icon&quot; href=&quot;%PUBLIC_URL%/favicon.ico&quot; /&amp;gt;
    &amp;lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1&quot; /&amp;gt;
    &amp;lt;meta name=&quot;theme-color&quot; content=&quot;#000000&quot; /&amp;gt;
    &amp;lt;meta
      name=&quot;description&quot;
      content=&quot;Web site created using create-react-app&quot;
    /&amp;gt;
    &amp;lt;link rel=&quot;apple-touch-icon&quot; href=&quot;%PUBLIC_URL%/logo192.png&quot; /&amp;gt;
    &amp;lt;link rel=&quot;manifest&quot; href=&quot;%PUBLIC_URL%/manifest.json&quot; /&amp;gt;
    &amp;lt;title&amp;gt;React App&amp;lt;/title&amp;gt;
  &amp;lt;/head&amp;gt;
  &amp;lt;body&amp;gt;
    &amp;lt;noscript&amp;gt;You need to enable JavaScript to run this app.&amp;lt;/noscript&amp;gt;
    &amp;lt;div id=&quot;root&quot;&amp;gt;&amp;lt;/div&amp;gt;
  &amp;lt;/body&amp;gt;
&amp;lt;/html&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;index.js&lt;/h4&gt;
&lt;p&gt;React アプリ側の起点となる部分。&lt;/p&gt;
&lt;p&gt;レンダラーである&lt;code&gt;ReactDOM&lt;/code&gt;の&lt;code&gt;render&lt;/code&gt;関数で以下を指定。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;第1引数：レンダリングするコンポーネント&lt;/li&gt;
&lt;li&gt;第2引数：どの DOM 要素にレンダリンクするか&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この例では、App というコンポーネントに定義された React 要素を、（index.html の）id が root の要素にレンダリングするよう定義しています。
ここで HTML の DOM 要素との関連付けが行われているわけです。
この関連付けされた React 要素全てが React DOM によって管理されることになるため、この関連付けされた DOM 要素を&lt;code&gt;ルート DOM ノード&lt;/code&gt;と呼んだりもします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import ReactDOM from &apos;react-dom&apos;;
import &apos;./index.css&apos;;
import App from &apos;./App&apos;;

ReactDOM.render(
  &amp;lt;App /&amp;gt;,
  document.getElementById(&quot;root&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;紐づけだけをパパっと試したいのであれば、第1引数に直接 React 要素を書いて試すことも出来ます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ReactDOM.render(
  &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;,
  document.getElementById(&apos;root&apos;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロバイダーコンポーネントという、子孫コンポーネントに特定の値や機能を提供するコンポーネントでアプリ全体を囲うというときに触ることはありますが、基本的にはあまり触ることのないファイルです。&lt;/p&gt;
&lt;h4&gt;App.js&lt;/h4&gt;
&lt;p&gt;今回の例において、index.js から呼ばれているコンポーネント。&lt;/p&gt;
&lt;p&gt;コンポーネントが return で返している React 要素が、そのコンポーネントのレンダリングする内容になります。
（※関数コンポーネントのため省略されていますが、正確には&lt;code&gt;render&lt;/code&gt;メソッドが返す React 要素のこと）
エクスポートすると、他ファイルからこのコンポーネントを呼び出すことができるようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import logo from &apos;./logo.svg&apos;;
import &apos;./App.css&apos;;

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&amp;gt;
        &amp;lt;p&amp;gt;
          Edit &amp;lt;code&amp;gt;src/App.js&amp;lt;/code&amp;gt; and save to reload.
        &amp;lt;/p&amp;gt;
        &amp;lt;a
          className=&quot;App-link&quot;
          href=&quot;https://reactjs.org&quot;
          target=&quot;_blank&quot;
          rel=&quot;noopener noreferrer&quot;
        &amp;gt;
          Learn React
        &amp;lt;/a&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App; // コンポーネントをエクスポート
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あとは、この App コンポーネントをカスタマイズしていき React アプリ開発を進めていくイメージです。
このコンポーネントにルーティングを定義したり。他のコンポーネントを呼び出したり。
（React 本体自体はルーティング機能を持っていないため、別途ルーティングライブラリが必要になります）&lt;/p&gt;
&lt;h4&gt;ビルド&lt;/h4&gt;
&lt;p&gt;ソースコードをビルドすると、React 部分のコードは Babel でトランスコンパイルされ、Webpack で依存関係を解決したのちにファイルをまとめられることになります。
そのビルド結果のファイルを、HTML から読み込んで使用している。といった形です。&lt;/p&gt;
&lt;p&gt;試しにローカルサーバを立ち上げて、ブラウザから DevTools で Elements を確認すると、ビルド成果物を読み込む script タグが追加されているはずです。&lt;/p&gt;
&lt;p&gt;なお、ローカルサーバ起動時は、webpack-dev-server がファイル変更を検知して自動で再ビルド実行（開発モード）
デプロイ時など本番モードでビルドしたい時は、ビルドコマンドを実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;npm build
# or
yarn build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;実行結果のビルド生成物は&lt;code&gt;/build&lt;/code&gt;配下へ作成されるようになっています。&lt;/p&gt;
&lt;h4&gt;React.StrictMode&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/strict-mode.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Create React App で作成した React アプリの場合、index.js が以下のようになっているでしょう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ReactDOM.render(
  &amp;lt;React.StrictMode&amp;gt;
    &amp;lt;App /&amp;gt;
  &amp;lt;/React.StrictMode&amp;gt;,
  document.getElementById(&quot;root&quot;)
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;strict モードというのは、アプリケーションの潜在的な問題点を洗い出すためのツールです。
この React.StrictMode 自体は特に何も UI を提供しませんが、子孫要素において不可的な問題を検知した時に、コンソールに警告を出してくれるようになります。
コード品質の向上に役立ってくれるでしょう。&lt;/p&gt;
&lt;p&gt;検知する例（それぞれの詳細は公式ドキュメントを参照）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;安全でないライフサイクルの特定&lt;/li&gt;
&lt;li&gt;レガシーな文字列 ref API の使用に対する警告&lt;/li&gt;
&lt;li&gt;非推奨な findDOMNode の使用に対する警告&lt;/li&gt;
&lt;li&gt;意図しない副作用の検出&lt;/li&gt;
&lt;li&gt;レガシーなコンテクスト API の検出&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;なお、この strict モードは、開発モードでのみ動作するようになっています。
本番ビルドには影響がありません。&lt;/p&gt;
&lt;h3&gt;コンポーネント&lt;/h3&gt;
&lt;p&gt;冒頭にも書いた通り、React ではコンポーネントベースの設計思想が取り入れられています。&lt;/p&gt;
&lt;h4&gt;クラスコンポーネント&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;React.Component&lt;/code&gt;を継承したクラスで定義されたコンポーネント。
後述する状態（state）やライフサイクルを元から持つことができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;

class App extends React.Component {  // React.component を継承するクラスの定義
  render() {  // React 要素 を返り値とする render メソッドを定義
    return (
      &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
    );
  }
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;React Hooks の登場により、クラスコンポーネント特有機能の大部分が関数コンポーネントでも使用できるようになったため、ほとんど使わなくなってきています。&lt;/p&gt;
&lt;h4&gt;関数コンポーネント&lt;/h4&gt;
&lt;p&gt;クラスコンポーネントと違い、状態（state）やライフサイクルを持たないコンポーネントをよりシンプルに記述したもの。
const で変数定義などはできますが、基本的に&lt;code&gt;render&lt;/code&gt;メソッドのみであるため、&lt;code&gt;render()&lt;/code&gt;の記述が省略できます。&lt;/p&gt;
&lt;p&gt;元々、状態（state）やライフサイクルを持つことができませんでしたが、関数コンポーネントでもそれらが使えるようにと様々な試みが行われていました。
その後、React 公式から React Hooks という機能が提供され、現在では React Hooks と関数コンポーネントの組み合わせが主流となっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 関数宣言方式
function App() {
  return (
    &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;関数式 + アロー関数方式で以下のようにも書くことができます。
個人的にはこちらが好みです（これ以降のコードはこちらの方式で書いています）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 関数式 + アロー関数方式
const App = () =&amp;gt; {
  return (
    &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;また変数定義などがなく、純粋に&lt;code&gt;render&lt;/code&gt;メソッドのみの場合は、さらに&lt;code&gt;return&lt;/code&gt;も省略できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const App = () =&amp;gt; (
 &amp;lt;h1&amp;gt;Hello World&amp;lt;/h1&amp;gt;
)

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;JSX&lt;/h3&gt;
&lt;p&gt;コンポーネントの&lt;code&gt;render&lt;/code&gt;メソッド内に記述する JavaScript の拡張構文。
XML っぽく記述ができるような作りになっています。
テンプレート言語に似ていますが、完全に JavaScript だけで動作するものです。&lt;/p&gt;
&lt;p&gt;Babel でのトランスコンパイル時に&lt;code&gt;React.createElement&lt;/code&gt;形式へ変換されます。
そのため、以下のコードは等価のものになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const element = (
  &amp;lt;h1 className=&quot;title&quot;&amp;gt;
    Hello, world!
  &amp;lt;/h1&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const element = React.createElement(
  &apos;h1&apos;,
  {className: &apos;title&apos;},
  &apos;Hello, world!&apos;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このことから、React において JSX を使うことは必須でありません。
また、JSX は React 特有のものでもありません。
ただ、どちらが扱いやすいかといえば、多くの方が JSX の方になるのではないでしょうか。&lt;/p&gt;
&lt;p&gt;この JSX は、必ず&lt;strong&gt;1つのReact要素&lt;/strong&gt;を返す必要があります。
複数の要素から構成される場合は、div で囲うなどして、1つの React 要素を返すようにしましょう。
（※div などでは支障が出る場合は、&lt;a href=&quot;#reactfragment&quot;&gt;React.Fragment&lt;/a&gt; の欄を参照）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// NG な例
render() {
  return (
    &amp;lt;h1&amp;gt;h1です&amp;lt;/h1&amp;gt;
    &amp;lt;h2&amp;gt;h2です&amp;lt;/h2&amp;gt;
    &amp;lt;p&amp;gt;pです&amp;lt;/p&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// OK な例
render() {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h1&amp;gt;h1です&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;h2です&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;pです&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;JSX は一見 HTML とも似ていますが、閉じタグに関しては注意が必要です。
img タグのように HTML では閉じタグが必要なかったものについては、末尾に&lt;code&gt;/&lt;/code&gt;とつけて閉じる必要があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;img src=&apos;画像URL&apos; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;React.Fragment&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/fragments.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;React 要素をまとめたい時、場合によっては div などを使いたくないこともあるでしょう。
そういったときは&lt;code&gt;React.Fragment&lt;/code&gt;というものを使うことができ、DOM に余分なノードを追加することなく子要素をまとめられます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;render() {
  return (
    &amp;lt;React.Fragment&amp;gt;
      &amp;lt;h1&amp;gt;h1です&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;h2です&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;pです&amp;lt;/p&amp;gt;
    &amp;lt;/React.Fragment&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;短縮記法で以下のようにも書くことができますが、こちらでは後述する key や props を持つことができないので注意が必要です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;render() {
  return (
    &amp;lt;&amp;gt;
      &amp;lt;h1&amp;gt;h1です&amp;lt;/h1&amp;gt;
      &amp;lt;h2&amp;gt;h2です&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;pです&amp;lt;/p&amp;gt;
    &amp;lt;/&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;変数や関数の埋め込み&lt;/h4&gt;
&lt;p&gt;JSX 内に変数を埋め込む際は&lt;code&gt;{}&lt;/code&gt;で囲って記述します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const name = &apos;Yamada Taro&apos;;
const element = &amp;lt;h1&amp;gt;Hello, {name}&amp;lt;/h1&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;なお、デフォルトでは、React DOM は JSX に埋め込まれた値をレンダリングされる前にエスケープします。
そのため、以下のようにユーザーの入力を受け付ける場合でも、XSS などインジェクション攻撃を防ぐことができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const title = response.potentiallyMaliciousInput;
const element = &amp;lt;h1&amp;gt;{title}&amp;lt;/h1&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;関数の場合はこんな感じ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const formatName = (user) =&amp;gt; {
  return user.firstName + &apos; &apos; + user.lastName;
}

const user = {
  firstName: &apos;Taro&apos;,
  lastName: &apos;Yamada&apos;
};

const element = (
  &amp;lt;h1&amp;gt;
    Hello, {formatName(user)}!
  &amp;lt;/h1&amp;gt;
);

&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;式としての JSX&lt;/h4&gt;
&lt;p&gt;前述したとおり、JSX はトランスコンパイルされると&lt;code&gt;React.createElement()&lt;/code&gt;形式に変換されます。
つまり、通常の JavaScript 関数呼び出しと同じようになるわけです。&lt;/p&gt;
&lt;p&gt;このことから、JSX を if 分や for ループの中で使うことも可能です。
変数に代入したり、引数として受け取ったり、関数から返すということもできます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const getGreeting = (user) =&amp;gt; {
  if (user) {
    return &amp;lt;h1&amp;gt;Hello, {formatName(user)}!&amp;lt;/h1&amp;gt;;
  }
  return &amp;lt;h1&amp;gt;Hello, Stranger.&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;条件付きレンダー&lt;/h4&gt;
&lt;p&gt;論理値 or 論理式と &amp;amp;&amp;amp; の組み合わせで、条件によってレンダリングする内容の切り替えができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;render() {
  const flg = true;
  return (
    &amp;lt;div&amp;gt;
      {/* flg変数がtrueの時のみレンダリング */}
      {flg &amp;amp;&amp;amp; &amp;lt;p&amp;gt;フラグ：true&amp;lt;/p&amp;gt;}
      {/* flg変数がfalseの時のみレンダリング */}
      {!flg &amp;amp;&amp;amp; &amp;lt;p&amp;gt;フラグ：false&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 三項演算子を使用した例
render() {
  const flg = true;
  return (
    &amp;lt;div&amp;gt;
      {flg ? &amp;lt;p&amp;gt;フラグ：true&amp;lt;/p&amp;gt; : &amp;lt;p&amp;gt;フラグ：false&amp;lt;/p&amp;gt;}
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;props&lt;/h3&gt;
&lt;p&gt;React のコンポーネントには、props という任意の値を渡すことができるようになっています。
親コンポーネントから子コンポーネントにデータを渡すイメージ。
これにより、同じコンポーネントでも、渡された props によって UI やロジックに変化をつけることができます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// コンポーネント呼び出し側
&amp;lt;Message name=&quot;Taro&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// コンポーネント側（関数コンポーネントの例）
const Message = (props) =&amp;gt; {
  return &amp;lt;h1&amp;gt;Hello, {props.name}&amp;lt;/h1&amp;gt;;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// コンポーネント側（クラスコンポーネントの例）
class Message extends React.Component {
  render() {
    return &amp;lt;h1&amp;gt;Hello, {this.props.name}&amp;lt;/h1&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;children&lt;/h4&gt;
&lt;p&gt;子要素を意味する props。
要素やコンポーネントの中身を children で指定する書き方もできます。
そのため、以下のコードは等価になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1&amp;gt;test&amp;lt;/h1&amp;gt;

&amp;lt;Message&amp;gt;test&amp;lt;/Message&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1 children=&quot;test&quot; /&amp;gt;

&amp;lt;Message children=&quot;test&quot; /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;props として渡されるので、コンポーネント側で使用できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const Message = (props) =&amp;gt; {
  return &amp;lt;h1&amp;gt;{props.children}&amp;lt;/h1&amp;gt;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;HTML の属性名&lt;/h4&gt;
&lt;p&gt;React DOM においても、HTML の属性を使うことができますが、キャメルケースの命名規則を使用します。
例として、tabindex は&lt;code&gt;tabIndex&lt;/code&gt;など。
変わった例として、class は&lt;code&gt;className&lt;/code&gt;となりますが、これは class が JavaScript において予約語であるためです。&lt;/p&gt;
&lt;p&gt;また、style でインラインスタイルを指定する時は、文字列形式でなくオブジェクト形式で渡します。
（疑似要素やメディアクエリは指定できません）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;h1 style={{ color: &apos;red&apos; }}&amp;gt;test title&amp;lt;/h1&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;読み取り専用&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;全ての React コンポーネントは、自己の props に対して純関数のように振る舞わねばなりません。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;React にはこのようなルールがあります。&lt;/p&gt;
&lt;p&gt;つまり、受け取った props を変更するようなことはしてはいけません。
同じ入力に対し同じ結果を返す「純関数」のような振る舞いをする必要があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 純関数でない例
const withdraw = (account, amount) =&amp;gt; {
  account.total -= amount;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 純関数の例
const sum = (a, b) =&amp;gt; {
  return a + b;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;props の型定義&lt;/h4&gt;
&lt;p&gt;一見、便利な props ではありますが、デフォルトではどんな値も受け取れてしまうという特徴があります。
そのため、ぱっと見どんな値を渡せばいいかわからなかったり。
誤って予期しない値を渡してエラーとなり、アプリをクラッシュさせてしまったり。&lt;/p&gt;
&lt;p&gt;もし TypeScript を使用しているのであれば、そのまま型定義が可能です。
しかし、JavaScript だとそうはいかないので、別途&lt;code&gt;PropTypes&lt;/code&gt;というライブラリを導入する必要があります。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/typechecking-with-proptypes.html&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/facebook/prop-types&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;自記事もありますので、よかったらこちらも参照ください。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://changeofpace.site/develop/2020/04/04/react-proptypes&quot;&gt;JS×Reactにおけるpropsの型定義を担うPropTypes入門&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;state&lt;/h3&gt;
&lt;p&gt;コンポーネントに持たせられる「状態」のことを指します。&lt;/p&gt;
&lt;p&gt;デフォルトでは、クラスコンポーネントのみ state を持つことができます。
設定するには、まずクラスコンポーネントにコンストラクタを追加して state を初期化。
(クラスコンポーネントのコンストラクタは、常に props を引数として、親クラスのコンストラクタを呼び出す必要があります)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Square extends React.Component {
  constructor(props) {
    super(props);
    this.state = {
      value: &apos;no set&apos;,
    };
  }

  render() {
    return (
      &amp;lt;div&amp;gt;
        &amp;lt;h1&amp;gt;Hello, world!&amp;lt;/h1&amp;gt;
        &amp;lt;h2&amp;gt;state：{this.state.value}.&amp;lt;/h2&amp;gt;
      &amp;lt;/div&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;state の更新&lt;/h4&gt;
&lt;p&gt;読み取り専用の props と違い、state は値の更新が可能です。&lt;/p&gt;
&lt;p&gt;更新するには、直接 state に再代入するのでなく、&lt;code&gt;this.setState&lt;/code&gt;を使用して更新するようにします。
なお、一度の&lt;code&gt;setState&lt;/code&gt;で複数の state の値更新も可能です。&lt;/p&gt;
&lt;p&gt;state が更新されると、そのコンポーネントは再度レンダリングされます。
（&lt;code&gt;setState&lt;/code&gt;を使用せずに state を直接変更した場合は再レンダリングされないのでやらないこと）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;class Square extends React.Component {
  ※一部省略

  render() {
    return (
      // ボタンクリックで state に値をセットする
      &amp;lt;button className=&quot;square&quot; onClick={() =&amp;gt; this.setState({value: &apos;X&apos;})}&amp;gt;
        {this.state.value}
      &amp;lt;/button&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;setState&lt;/code&gt;を使った更新では、引数のオブジェクトを現在の state にマージするというやり方で更新されます。
例えば、複数の state を持っていた場合。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;constructor(props) {
  super(props);
  this.state = {
    posts: null,
    comments: null
  };
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;this.setState({ posts: &apos;posts value&apos;})&lt;/code&gt;のように posts だけの更新をしても、comments の現在の値には影響しません。
逆もまた然りです。
マージ時における比較の仕方としては shallow （浅く）で行われます。&lt;/p&gt;
&lt;h4&gt;state の更新タイミング&lt;/h4&gt;
&lt;p&gt;this.props と this.state は非同期に更新されるため、&lt;code&gt;setState&lt;/code&gt;時にそれらの値に依存してはいけません。
予期しない動作を引き起こす可能性があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// NG な例
this.setState({
  counter: this.state.counter + this.props.increment,
});
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;子コンポーネント側から親コンポーネントが持つ state を更新&lt;/h4&gt;
&lt;p&gt;子コンポーネントから親コンポーネントが持つ state を変更したいという時、&lt;code&gt;this.state&lt;/code&gt;はその親コンポーネント内でのプライベートになるため、子コンポーネントから直接呼び出しはできません。
この場合は、親コンポーネントから子コンポーネントに state を更新する関数を渡すようにして、その関数を子コンポーネントから呼び出すことで対処できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 子コンポーネント側
class Square extends React.Component {
  ※一部省略

  render() {
    return (
      &amp;lt;button className=&quot;square&quot; onClick={() =&amp;gt; this.props.onClick()}&amp;gt;
        {this.props.value}
      &amp;lt;/button&amp;gt;
    );
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// 親コンポーネント側
class Board extends React.Component {
  ※一部省略

  handleClick(i) {
      // 配列のコピーを作成
      const squares = this.state.squares.slice();
      squares[i] = &apos;X&apos;;
      this.setState({squares: squares});
  }

  renderSquare(i) {
    return &amp;lt;Square value={this.state.squares[i]} onClick={() =&amp;gt; this.handleClick(i)}/&amp;gt;;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;イベント&lt;/h3&gt;
&lt;p&gt;通常の DOM 要素でのイベント処理とほとんど同じですが、主な違いとしては、以下の2つです。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;React のイベントは小文字でなく、キャメルケース（onclick → onClick）&lt;/li&gt;
&lt;li&gt;JSX ではイベントハンドラとして、文字列でなく関数を渡す&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;button onClick={() =&amp;gt; {console.log(&apos;Hello World&apos;)}}&amp;gt;こんにちは&amp;lt;/button&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;イベントの例。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;onClick：クリックされた時、button タグなど&lt;/li&gt;
&lt;li&gt;onSubmit：送信された時、form タグなど&lt;/li&gt;
&lt;li&gt;onChange：入力や削除が行われた時、input タグなど&lt;/li&gt;
&lt;li&gt;onMouseOver：マウスカーソルが上に置かれた時&lt;/li&gt;
&lt;li&gt;onMouseOut：マウスカーソルが外れた時&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;onChange の例
&lt;code&gt;event&lt;/code&gt;は合成 (synthetic) イベントです。&lt;code&gt;event.target.value&lt;/code&gt;で入力された値を取得できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;input onChange={(event) =&amp;gt; {console.log(event.target.value)}} /&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;preventDefault&lt;/h4&gt;
&lt;p&gt;a タグのリンクや form の送信など、その要素が持つデフォルトのイベントをキャンセルしたい時。
HTML では false を返すようにすることで、デフォルトのイベントを抑制できましたが、React では&lt;code&gt;preventDefault&lt;/code&gt;を使う必要があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ActionLink = () =&amp;gt; {
  const handleClick = (e) =&amp;gt; {
    e.preventDefault();
    console.log(&apos;The link was clicked.&apos;);
  }

  return (
    &amp;lt;a href=&quot;#&quot; onClick={handleClick}&amp;gt;
      Click me
    &amp;lt;/a&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;リストと key&lt;/h3&gt;
&lt;p&gt;要素のリストをレンダリングする際、リストの項目それぞれについて、React は情報を保持します。
リストに変更、追加、削除などがあった時に、どのアイテムが変更になったのかを React へ伝えるために key を与えるようにしましょう。&lt;/p&gt;
&lt;p&gt;リストが再レンダリングされる際、React はそれぞれのリスト項目の key について、前回のリスト項目内に同じ key を持つものがないか探します。
その結果によってリスト項目を追加したり、削除したりするわけです。
もし key が設定されていないリスト項目を変更した場合、React は正しく再レンダリングできなくなります。&lt;/p&gt;
&lt;p&gt;ちなみに key の値は兄弟要素の中で一意であれば問題ないようです。&lt;/p&gt;
&lt;p&gt;なお、リスト項目に key プロパティを設定していないと、コンソールで警告が表示されます。
また、key プロパティは props の一部のようにも見えますが、&lt;code&gt;this.props.key&lt;/code&gt;で参照はできません。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const numbers = [1, 2, 3, 4, 5];
const listItems = numbers.map((number) =&amp;gt;
  &amp;lt;li key={number.toString()}&amp;gt;
    {number}
  &amp;lt;/li&amp;gt;
);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;リスト項目をコンポーネント化した際は、呼び出し時に key を設定するようにしましょう。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const ListItem = (props) =&amp;gt; {
  // ここでは key を指定しない
  return &amp;lt;li&amp;gt;{props.value}&amp;lt;/li&amp;gt;;
}

const NumberList = (props) =&amp;gt; {
  const numbers = props.numbers;
  const listItems = numbers.map((number) =&amp;gt;
    // ここで key を指定
    &amp;lt;ListItem
      key={number.toString()}
      value={number}
    /&amp;gt;
  );
  return (
    &amp;lt;ul&amp;gt;
      {listItems}
    &amp;lt;/ul&amp;gt;
  );
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ライフサイクル&lt;/h3&gt;
&lt;p&gt;React において、以下のようなライフサイクルがあります。
これらは、デフォルトではクラスコンポーネントのみ使用できるようになっています。&lt;/p&gt;
&lt;p&gt;ライフサイクルの流れ図
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/react-basics/react-lifecycle.png&quot; alt=&quot;Reactコンポーネントのライフサイクル図&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;http://projects.wojtekmaj.pl/react-lifecycle-methods-diagram&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/wojtekmaj/react-lifecycle-methods-diagram&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;マウント&lt;/h4&gt;
&lt;p&gt;コンポーネントのインスタンスが作成されて DOM に挿入される時、以下のメソッドが次の順序で呼び出されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;constructor()&lt;/li&gt;
&lt;li&gt;static getDerivedStateFromProps()&lt;/li&gt;
&lt;li&gt;render()&lt;/li&gt;
&lt;li&gt;componentDidMount()&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;更新&lt;/h4&gt;
&lt;p&gt;props や state の変更によって発生します。
コンポーネントが再レンダリングされるときに、以下のメソッドが次の順序で呼び出されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;static getDerivedStateFromProps()&lt;/li&gt;
&lt;li&gt;shouldComponentUpdate()&lt;/li&gt;
&lt;li&gt;render()&lt;/li&gt;
&lt;li&gt;getSnapshotBeforeUpdate()&lt;/li&gt;
&lt;li&gt;componentDidUpdate()&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;アンマウント&lt;/h4&gt;
&lt;p&gt;コンポーネントが DOM から削除されるときに呼び出されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;componentWillUnmount()&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;エラーハンドリング&lt;/h4&gt;
&lt;p&gt;任意の子コンポーネントのレンダリング中、ライフサイクルメソッド内、またはコンストラクタ内でエラーが発生したときに呼び出されます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;static getDerivedStateFromError()&lt;/li&gt;
&lt;li&gt;componentDidCatch()&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;それぞれのメソッドの解説&lt;/h4&gt;
&lt;p&gt;長くなりそうなので省略します。&lt;/p&gt;
&lt;p&gt;こちらで詳しく解説されていますのでご参照ください。
&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/react-component.html&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;React Hooks&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/hooks-intro.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;React 16.8 より React Hooks という機能が導入されました。
これにより、今までクラスコンポーネントでしか使うことのできなかった state や ライフサイクルなどの機能を、関数コンポーネントへ付与できるようになりました。
&lt;code&gt;use〇〇&lt;/code&gt;という名称が特徴です。&lt;/p&gt;
&lt;h4&gt;フックのルール&lt;/h4&gt;
&lt;p&gt;フックのルールとしては、以下の2つがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;フックは関数の&lt;strong&gt;トップレベル&lt;/strong&gt;のみで呼び出すこと（ループや条件分岐、ネストした関数の中では呼び出さない）&lt;/li&gt;
&lt;li&gt;フックは React の&lt;strong&gt;関数コンポーネントの内部&lt;/strong&gt;もしくは&lt;strong&gt;カスタムフック&lt;/strong&gt;の内部でのみ呼び出すこと&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらのルールを強制するための ESLint ルールがあるため、それを利用すると変な使い方をせずにすみます。
（Create React App で作った環境の場合は、react-scripts の依存関係として導入されています）
&amp;lt;OG url=&quot;https://github.com/facebook/react/blob/main/packages/eslint-plugin-react-hooks/README.md&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;フックの種類&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/hooks-reference.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;フックだけでも記事を書けるくらいのボリュームなので、ここでは簡単な紹介のみ記載します。
基本的には、最後の3つはあまり使いません。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;useState：state 機能を提供するフック（現在の state の値と、state を更新するための関数を返す）&lt;/li&gt;
&lt;li&gt;useEffect：副作用のためのフック（マウント時・アンマウント時の処理、依存配列にある特定の値変更を検知して特定の処理実行など）&lt;/li&gt;
&lt;li&gt;useContext：コンテクスト（props を使わず、コンポーネント間でデータを共有できるもの）オブジェクトを受け取り、そのコンテクストの現在値を返すフック&lt;/li&gt;
&lt;li&gt;useReducer：useState の代替となるフック（より複雑な state ロジックがある場合などに有効）&lt;/li&gt;
&lt;li&gt;useCallback：メモ化されたコールバックを返すフック（依存配列にある値が変化した時のみ再生成するようにするため、パフォーマンス最適化に活用）&lt;/li&gt;
&lt;li&gt;useMemo：メモ化された値を返すフック（計算結果などをメモ化。依存配列にある値が変化した時のみ再計算するようにするため、パフォーマンス最適化に活用）&lt;/li&gt;
&lt;li&gt;useRef：ミュータブルな ref オブジェクトを返すフック（特定の DOM 要素へのアクセスや、置き換え可能な値を保持に使える）&lt;/li&gt;
&lt;li&gt;useImperativeHandle：親コンポーネントから渡された ref をカスタマイズするフック（ref を通して、子コンポーネントで定義した関数を親コンポーネントから呼び出すのに使える）&lt;/li&gt;
&lt;li&gt;useLayoutEffect：useEffect と類似しているが、呼び出されるタイミングが微妙に異なるフック（基本的には useEffect を使うのを推奨している）&lt;/li&gt;
&lt;li&gt;useDebugValue：React DevTools でカスタムフックのラベル表示ができるフック&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;項の初めに以下のように書きました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;今までクラスコンポーネントでしか使うことのできなかった state や ライフサイクルなどの機能を、関数コンポーネントに付与できるようになりました。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;クラスコンポーネントで使える機能が関数コンポーネントでも使えるというのは、おおよそ間違ってはいないのですが、厳密には微妙に仕様が異なる部分もあることに注意です。
詳細は公式ドキュメントを読んでみてください。&lt;/p&gt;
&lt;h4&gt;カスタムフック&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.reactjs.org/docs/hooks-custom.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;フックは、自分で独自のものを作ることも出来ます。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;use〇〇&lt;/code&gt;の命名規則&lt;/li&gt;
&lt;li&gt;カスタムフックのトップレベルで他のフックを呼び出せる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これ以外は、通常の関数と同じです。&lt;/p&gt;
&lt;p&gt;コンポーネントファイルにロジックを書くとごちゃごちゃしがちなので、カスタムフックに処理を切り出して、そのロジックを使いたいコンポーネントで適宜呼び出すという使い方をします。&lt;/p&gt;
&lt;h3&gt;TypeScript での React&lt;/h3&gt;
&lt;p&gt;現在、新たに React アプリを作成するとすれば、TypeScript を使うのがデファクトになっているような印象を受けます。
自分も、この記事を最初に投稿した頃は JavaScript を使用していましたが、現在は TypeScript をすっかり使うようになりました。&lt;/p&gt;
&lt;p&gt;各種型定義が必要になるので、慣れないうちは面倒に感じることもあるでしょう。
しかし、型定義がある分、強力な入力補完や型補完が効くようになります。
とても心強いものになってくれることでしょう。&lt;/p&gt;
&lt;p&gt;TypeScript での React について、ここでは少しだけ紹介します。&lt;/p&gt;
&lt;h4&gt;関数コンポーネントの型定義&lt;/h4&gt;
&lt;p&gt;&lt;code&gt;FC&lt;/code&gt;もしくは&lt;code&gt;VFC&lt;/code&gt;を使います。
&lt;code&gt;FC&lt;/code&gt;は、関数コンポーネントの型インタフェース&lt;code&gt;FunctionComponent&lt;/code&gt;のエイリアス。
&lt;code&gt;VFC&lt;/code&gt;も同様に、関数コンポーネントの型インタフェースである&lt;code&gt;VoidFunctionComponent&lt;/code&gt;のエイリアス。&lt;/p&gt;
&lt;p&gt;その違いをざっくりいうと、暗黙的に children props を許容しているかです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const MyButton: FC = ({ children }) =&amp;gt; {
  return (
    &amp;lt;button&amp;gt;{children}&amp;lt;/button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;const MyButton: VFC = () =&amp;gt; {
  return (
    &amp;lt;button&amp;gt;VFC Button&amp;lt;/button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;独自 props の型定義をするとなると以下のような感じになるのですが...。
&lt;code&gt;FC&lt;/code&gt;の場合、デフォルトで children props の定義が含まれているので、独自で children props の型定義を追加しなくても受け取って使えます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;type Props = {
  title: string;
}

const TextBlock: FC&amp;lt;Props&amp;gt; = ({ title, children }) =&amp;gt; {
  return (
    &amp;lt;div&amp;gt;
      &amp;lt;h2&amp;gt;{title}&amp;lt;/h2&amp;gt;
      &amp;lt;p&amp;gt;{children}&amp;lt;/p&amp;gt;
    &amp;lt;/div&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;もし children を使用しない場合でも children を許容する形となるため、それってどうなの？
という議論がありまして。
元々、&lt;code&gt;FC&lt;/code&gt;から children を外す動きがあったようで、将来的になくす予定のようです。
ただ、破壊的な変更となってしまうため、移行措置として children を含まない&lt;code&gt;VFC&lt;/code&gt;が導入されたとのこと。&lt;/p&gt;
&lt;p&gt;現状では&lt;code&gt;VFC&lt;/code&gt;を使用して、children を使いたい時は明示的に型定義を追加。
&lt;code&gt;FC&lt;/code&gt;から children がなくなったのちに、&lt;code&gt;VFC&lt;/code&gt;だったものを&lt;code&gt;FC&lt;/code&gt;に置換する。という流れがよさそうです。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/tttocklll/items/c78aa33856ded870e843&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://ikesyo.hatenablog.com/entry/2020/12/18/141737&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみにかつては&lt;code&gt;SFC&lt;/code&gt;（Stateless Functional Component）というものが使われていましたが、現在は非推奨となっています。&lt;/p&gt;
&lt;h4&gt;既定の型を使った型定義&lt;/h4&gt;
&lt;p&gt;props の型定義をするにあたって、既存要素の型定義や、既存コンポーネントの型定義を使いたい時があります。
そんな時は&lt;code&gt;ComponentPropsWithRef&lt;/code&gt;か&lt;code&gt;ComponentPropsWithoutRef&lt;/code&gt;で取得できます。
両者の違いがよくわかってなかったのですが、どうも ref を含むかどうかの違いのようです（前者が ref を含む）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { VFC, ComponentPropsWithRef } from &apos;react&apos;;

// button 要素が持つ props を取得
type Props = ComponentPropsWithRef&amp;lt;&apos;button&apos;&amp;gt;;

const MyButton: VFC&amp;lt;Props&amp;gt; = ({ children, ...props }) =&amp;gt; {
  return (
    &amp;lt;button {...props}&amp;gt;{children}&amp;lt;/button&amp;gt;
  )
}
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// コンポーネントの props 型定義取得
type TestButtonProps = ComponentPropsWithRef&amp;lt;typeof MyButton&amp;gt;;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;特定のプロパティを除外する&lt;code&gt;Omit&lt;/code&gt;や、特定のプロパティを抽出する&lt;code&gt;Pick&lt;/code&gt;などと組み合わせると、より柔軟な型定義ができます。
&lt;code&gt;&amp;amp;&lt;/code&gt;で独自のプロパティを追加して拡張したり。&lt;/p&gt;
&lt;p&gt;その他、style 属性の型定義について触れてみます。
style 属性の型定義としては&lt;code&gt;CSSProperties&lt;/code&gt;となっているので、個別の CSS プロパティの型定義を使う際は、そのプロパティにアクセスすれば OK です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import { CSSProperties } from &apos;react&apos;;

type Props = {
  width: CSSProperties[&apos;width&apos;];
  textAlign: CSSProperties[&apos;textAlign&apos;];
}
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;React の基礎としてどこまで書くか迷いましたが、公式チュートリアルで扱われたものを中心に今回書きました。
続編として、フォームや Router、Recompose や Redux、React Hooks などについて書いていけたらと。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/ja-no-weak-phrase --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;※2021/09/13 追記
この記事は、自分が React 記事を書くようになったきっかけの記事なので、少々思い入れがありまして。
当時の React 歴としては、3か月程度。
今の自分として読み返すと、色々説明不足なところあるなぁとか思いながら、今回記事内容を見直してみました。
それだけ、当時と比べて成長できているということかもしれませんね。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;引き続き、React 周辺の知識を深めていきますー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.reactjs.org/docs/getting-started.html&quot;&gt;React - Doc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.reactjs.org/tutorial/tutorial.html&quot;&gt;React - Tutorial&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot;&gt;GitHub - oukayuka/Riakuto-StartingReact-ja3.1 ※書籍&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/wojtekmaj/react-lifecycle-methods-diagram&quot;&gt;GitHub - react-lifecycle-methods-diagram&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/kotaroito/items/e36ebac185b6b1d8538d&quot;&gt;Stateless な React Component の記法をまとめてみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/Julia0709/items/3c3fc8d29fd2e56ed7a9&quot;&gt;React(v16.4) コンポーネントライフサイクルメソッドまとめ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/otanu/items/994fdf9d8fb7327d41d5&quot;&gt;【React+TypeScript】React Hooks useImperativeHandleのサンプル&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/tttocklll/items/c78aa33856ded870e843&quot;&gt;【TypeScript】React.VFCとは何ぞや&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ikesyo.hatenablog.com/entry/2020/12/18/141737&quot;&gt;Reactコンポーネントの定義にFCではなくVFCを使う&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Jekyll製ブログの記事に目次を追加する</title><link>https://changeofpace.site/posts/2019-11-10-jekyll-toc</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-11-10-jekyll-toc</guid><description>Jekyllのマークダウン、kramdownのAutomatic “Table of Contents” Generation機能を使って目次を追加する方法</description><pubDate>Sun, 10 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;より良いブログ記事を書くために...と解説されている記事などを見ていて、そういえば自分の記事には目次がなかったなと気づきました。
記事が長くなってくると目次ありの方が見やすいですよね。
Jekyll の記事に目次を追加する方法を調べて、導入してみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;目次をどうやって導入する？&lt;/h2&gt;
&lt;p&gt;まずはどうすれば追加できるのか、楽に追加できるプラグインなどはあるのかと調べました。&lt;/p&gt;
&lt;h3&gt;プラグイン&lt;/h3&gt;
&lt;p&gt;プラグインは主に以下の2つがあるようでした。
&amp;lt;OG url=&quot;https://github.com/dafi/jekyll-toc-generator&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://github.com/toshimaru/jekyll-toc&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;記事で使用するレイアウトファイルに記述しておけば、すべての記事に適用される点は便利そうだなと思いました。
1つのファイルに記述しておけばいいだけなので、各記事のマークダウンファイルに記述を追加する必要がありません。&lt;/p&gt;
&lt;p&gt;ただ、自分が目次を入れたい場所は記事の中（contentの中）の&lt;strong&gt;導入文の後&lt;/strong&gt;でした。
プラグインを使用すると、これは実現できそうになさそうでした。&lt;/p&gt;
&lt;h3&gt;kramdown の機能&lt;/h3&gt;
&lt;p&gt;Jekyll のマークダウンには&lt;code&gt;kramdown&lt;/code&gt;が使用されています。&lt;/p&gt;
&lt;p&gt;この&lt;code&gt;kramdown&lt;/code&gt;には目次を追加する機能、&lt;code&gt;Automatic &quot;Table of Contents: Generation&lt;/code&gt;が元々搭載されており。
各記事にコードを記述する必要がありますが、自由に表示場所を指定できるということで今回はこちらを使用することにしました。&lt;/p&gt;
&lt;h2&gt;目次の追加&lt;/h2&gt;
&lt;h3&gt;直接マークダウンファイルに追記&lt;/h3&gt;
&lt;p&gt;使用するには、各記事のマークダウンファイルで目次を挿入したい箇所に以下のコード追加するだけです。
なお、&lt;code&gt;*&lt;/code&gt;以降の文言はなんでもOK。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;* A markdown unordered list which will be replaced with the ToC, excluding the &quot;Contents header&quot; from above
{:toc}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これだけで、記事内の見出しタグを検知して自動で目次を作成してくれます。
コードとしては以下のようになります。（前回の記事の例）
各要素に id が設定されるので、見た目のカスタマイズも自由に行えます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;ul id=&quot;markdown-toc&quot;&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;a href=&quot;#eslint&quot; id=&quot;markdown-toc-eslint&quot;&amp;gt;ESLint&amp;lt;/a&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;a href=&quot;#導入&quot; id=&quot;markdown-toc-導入&quot;&amp;gt;導入&amp;lt;/a&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#手順&quot; id=&quot;markdown-toc-手順&quot;&amp;gt;手順&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#error対応&quot; id=&quot;markdown-toc-error対応&quot;&amp;gt;Error対応&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#warning対応&quot; id=&quot;markdown-toc-warning対応&quot;&amp;gt;Warning対応&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;a href=&quot;#設定のカスタマイズ&quot; id=&quot;markdown-toc-設定のカスタマイズ&quot;&amp;gt;設定のカスタマイズ&amp;lt;/a&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#設定ファイルについて&quot; id=&quot;markdown-toc-設定ファイルについて&quot;&amp;gt;設定ファイルについて&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#ウィザードで設定ファイルを作成して使用&quot; id=&quot;markdown-toc-ウィザードで設定ファイルを作成して使用&quot;&amp;gt;ウィザードで設定ファイルを作成して使用&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#既存の設定ファイルを使用&quot; id=&quot;markdown-toc-既存の設定ファイルを使用&quot;&amp;gt;既存の設定ファイルを使用&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;
    &amp;lt;a href=&quot;#prettier&quot; id=&quot;markdown-toc-prettier&quot;&amp;gt;Prettier&amp;lt;/a&amp;gt;
    &amp;lt;ul&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#コマンドラインとしてeslint連携&quot; id=&quot;markdown-toc-コマンドラインとしてeslint連携&quot;&amp;gt;コマンドラインとしてESLint連携&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#vscode上でeslint連携&quot; id=&quot;markdown-toc-vscode上でeslint連携&quot;&amp;gt;VSCode上でESLint連携&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
      &amp;lt;li&amp;gt;
        &amp;lt;a href=&quot;#設定のカスタマイズ-1&quot; id=&quot;markdown-toc-設定のカスタマイズ-1&quot;&amp;gt;設定のカスタマイズ&amp;lt;/a&amp;gt;
        &amp;lt;ul&amp;gt;
          &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#設定ファイル&quot; id=&quot;markdown-toc-設定ファイル&quot;&amp;gt;設定ファイル&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
        &amp;lt;/ul&amp;gt;
      &amp;lt;/li&amp;gt;
    &amp;lt;/ul&amp;gt;
  &amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#ファイル保存時に自動整形&quot; id=&quot;markdown-toc-ファイル保存時に自動整形&quot;&amp;gt;ファイル保存時に自動整形&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
  &amp;lt;li&amp;gt;&amp;lt;a href=&quot;#参考&quot; id=&quot;markdown-toc-参考&quot;&amp;gt;参考&amp;lt;/a&amp;gt;&amp;lt;/li&amp;gt;
&amp;lt;/ul&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;HTML ファイルに分離して追記&lt;/h3&gt;
&lt;p&gt;目次生成のコードを直接マークダウンファイルに記述してもいいのですが、より柔軟にカスタマイズできるよう HTML ファイルに分離して、それを読み込むようにもできます。&lt;/p&gt;
&lt;p&gt;こちらの方がよりスマートな感じに思ったので、自分はこちらの方法にしました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;_includes&lt;/code&gt;配下にファイルを作成して、目次生成コードを記述
自分は&lt;code&gt;toc.html&lt;/code&gt;というファイルを作成しました。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;nav class=&quot;post-nav-links&quot;&amp;gt;
  &amp;lt;h4&amp;gt;目次&amp;lt;/h4&amp;gt;
  * table
  {:toc}
&amp;lt;/nav&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;sassファイルを用意して、目次のレイアウトを整える
この辺はお好みで。
※補足：以下で使用している&lt;code&gt;$gray-1&lt;/code&gt;、&lt;code&gt;$gray-6&lt;/code&gt;については使用テーマで定義されている色を指定しています。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;// 例
.post-nav-links {
  border: 1px solid $gray-6;
  background-color: $gray-1;
  margin-top: 2rem;
}

.post-nav-links h4 {
  font-weight: bold;
  text-align: center;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;各記事のマークダウンファイルにて、1で作成したファイルを読み込む
&lt;code&gt;include&lt;/code&gt;を使用して読み込みます。&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;_config.yml&lt;/code&gt;に設定を追記&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;kramdown:
  parse_block_html: true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;1で記述した目次生成コードについては、あくまで kramdown の機能であるため、通常はマークダウンファイル内でしか使用できません。
上記設定を追記することで、ブロックレベルの要素を含むテキストとしてブロック HTML タグのコンテンツのマークダウン構文を処理できるようになり、目次が生成できるようになります。
(2020/1/3 一部説明を修正)&lt;/p&gt;
&lt;p&gt;これらで生成された目次が以下のようになります（前回の記事の例）
↓
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/jekyll-toc/toc.png&quot; alt=&quot;kramdownの機能で生成した目次の例&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これでいくらか記事が見やすくなりましたね。
これからもこういったブログ改善をやっていきますー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/dafi/jekyll-toc-generator&quot;&gt;GitHub - jekyll-toc-generator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/toshimaru/jekyll-toc&quot;&gt;GitHub - jekyll-toc&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.kotet.jp/2018/04/toc-on-github-pages/&quot;&gt;#jekyll GitHub Pagesでも目次(ToC)は作れる&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.tamesuu.com/2018/09/23/75/&quot;&gt;jekyllで目次を設定する (jekyll-toc-generator)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://kramdown.gettalong.org/converter/html.html&quot;&gt;kramdown - HTML Converter&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/38417624/table-of-contents-using-jekyll-and-kramdown&quot;&gt;Table of contents using Jekyll and Kramdown&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>create-react-appで作成した雛形 + VSCodeにESLintとPrettierを導入する</title><link>https://changeofpace.site/posts/2019-11-06-eslint-prettier</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-11-06-eslint-prettier</guid><description>create-react-appで作成した雛形プロジェクトに対して、コード品質を上げるために、VSCodeでESLintとPrettierを使えるようにする手順</description><pubDate>Wed, 06 Nov 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回、&lt;a href=&quot;/posts/2019-10-24-docker-react-swagger&quot;&gt;DockerでReact + Swagger 環境を作ろう&lt;/a&gt;という記事を書いたのですが、VSCode へ ESLint 導入時に少し手間取ったので別記事にしました。
あわせてコード整形ツールである Prettier も導入したので、その手順を書いておきます。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/14a114da903fb0eff886&quot;&gt;Qiita&lt;/a&gt; からの転載です。&lt;/p&gt;
&lt;p&gt;※2021/03/22追記　&lt;code&gt;eslint-plugin-prettier&lt;/code&gt;が非推奨になったりと情報が古くなったので、全体的に書き直しました。
※2021/08/22追記　&lt;code&gt;--template typescript&lt;/code&gt;で TypeScript にした場合にも対応しました。&lt;/p&gt;
&lt;p&gt;import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;ESLint&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://eslint.org&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;静的解析ツールです。
コーディングする上でのルールを決められるので、チーム開発するうえでコードのスタイルを統一できます。&lt;/p&gt;
&lt;h3&gt;セットアップの確認&lt;/h3&gt;
&lt;p&gt;create-react-app で作成された雛形では、すでに ESLint に関するパッケージが導入されており。
yarn.lock で&lt;code&gt;react-scripts&lt;/code&gt;の依存関係となっているのを確認できます。
create-react-app を&lt;code&gt;--template typescript&lt;/code&gt;としていても、同様です。&lt;/p&gt;
&lt;p&gt;以下は&lt;code&gt;create-react-app@4.0.3&lt;/code&gt;で作成した React プロジェクトの例です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;※一部抜粋
react-scripts@4.0.3:
  version &quot;4.0.3&quot;
  resolved &quot;https://registry.yarnpkg.com/react-scripts/-/react-scripts-4.0.3.tgz#b1cafed7c3fa603e7628ba0f187787964cb5d345&quot;
  integrity sha512-S5eO4vjUzUisvkIPB7jVsKtuH2HhWcASREYWHAQ1FP5HyCv3xgn+wpILAEWkmy+A+tTNbSZClhxjT3qz6g4L1A==
  dependencies:
    &quot;@babel/core&quot; &quot;7.12.3&quot;
    &quot;@pmmmwh/react-refresh-webpack-plugin&quot; &quot;0.4.3&quot;
    &quot;@svgr/webpack&quot; &quot;5.5.0&quot;
    &quot;@typescript-eslint/eslint-plugin&quot; &quot;^4.5.0&quot;
    &quot;@typescript-eslint/parser&quot; &quot;^4.5.0&quot;
    babel-eslint &quot;^10.1.0&quot;
    babel-jest &quot;^26.6.0&quot;
    babel-loader &quot;8.1.0&quot;
    babel-plugin-named-asset-import &quot;^0.3.7&quot;
    babel-preset-react-app &quot;^10.0.0&quot;
    bfj &quot;^7.0.2&quot;
    camelcase &quot;^6.1.0&quot;
    case-sensitive-paths-webpack-plugin &quot;2.3.0&quot;
    css-loader &quot;4.3.0&quot;
    dotenv &quot;8.2.0&quot;
    dotenv-expand &quot;5.1.0&quot;
    eslint &quot;^7.11.0&quot;
    eslint-config-react-app &quot;^6.0.0&quot;
    eslint-plugin-flowtype &quot;^5.2.0&quot;
    eslint-plugin-import &quot;^2.22.1&quot;
    eslint-plugin-jest &quot;^24.1.0&quot;
    eslint-plugin-jsx-a11y &quot;^6.3.1&quot;
    eslint-plugin-react &quot;^7.21.5&quot;
    eslint-plugin-react-hooks &quot;^4.2.0&quot;
    eslint-plugin-testing-library &quot;^3.9.2&quot;
    eslint-webpack-plugin &quot;^2.5.2&quot;
    file-loader &quot;6.1.1&quot;
    fs-extra &quot;^9.0.1&quot;
    html-webpack-plugin &quot;4.5.0&quot;
    identity-obj-proxy &quot;3.0.0&quot;
    jest &quot;26.6.0&quot;
    jest-circus &quot;26.6.0&quot;
    jest-resolve &quot;26.6.0&quot;
    jest-watch-typeahead &quot;0.6.1&quot;
    mini-css-extract-plugin &quot;0.11.3&quot;
    optimize-css-assets-webpack-plugin &quot;5.0.4&quot;
    pnp-webpack-plugin &quot;1.6.4&quot;
    postcss-flexbugs-fixes &quot;4.2.1&quot;
    postcss-loader &quot;3.0.0&quot;
    postcss-normalize &quot;8.0.1&quot;
    postcss-preset-env &quot;6.7.0&quot;
    postcss-safe-parser &quot;5.0.2&quot;
    prompts &quot;2.4.0&quot;
    react-app-polyfill &quot;^2.0.0&quot;
    react-dev-utils &quot;^11.0.3&quot;
    react-refresh &quot;^0.8.3&quot;
    resolve &quot;1.18.1&quot;
    resolve-url-loader &quot;^3.1.2&quot;
    sass-loader &quot;^10.0.5&quot;
    semver &quot;7.3.2&quot;
    style-loader &quot;1.3.0&quot;
    terser-webpack-plugin &quot;4.2.3&quot;
    ts-pnp &quot;1.2.0&quot;
    url-loader &quot;4.1.1&quot;
    webpack &quot;4.44.2&quot;
    webpack-dev-server &quot;3.11.1&quot;
    webpack-manifest-plugin &quot;2.2.0&quot;
    workbox-webpack-plugin &quot;5.1.4&quot;
  optionalDependencies:
    fsevents &quot;^2.1.3&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定に関しては&lt;code&gt;package.json&lt;/code&gt;に記述があり、デフォルトではこの設定で ESLint が動作します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;eslintConfig&quot;: {
    &quot;extends&quot;: [
      &quot;react-app&quot;,
      &quot;react-app/jest&quot;
    ]
  },
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最低限のセットアップはすでに行われているので、あとはそれをカスタマイズしていくだけです。&lt;/p&gt;
&lt;h3&gt;CLI で動かしてみる&lt;/h3&gt;
&lt;p&gt;チェックを行いたいファイルパスを指定して実行します。&lt;/p&gt;
&lt;p&gt;コマンド例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# JS
yarn run -s eslint &apos;./src/**/*.{js,jsx}&apos;

# TS
yarn run -s eslint &apos;./src/**/*.{js,jsx,ts,tsx}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自動整形までやりたい場合は&lt;code&gt;--fix&lt;/code&gt;をつけて実行します。&lt;/p&gt;
&lt;p&gt;コマンド例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# JS
yarn run -s eslint &apos;./src/**/*.{js,jsx}&apos; --fix

# TS
yarn run -s eslint &apos;./src/**/*.{js,jsx,ts,tsx}&apos; --fix
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;注意点として、自動整形は必ずしも全ての問題個所を直してくれるわけではありません。
自動整形で対応できない箇所は、自分たちで直す必要があります。&lt;/p&gt;
&lt;p&gt;VSCode エディタ上で ESLint を動作させる際は、あまりコマンドを使用しなくなりそうですが、CI でのテスト時に使用したりということがあります。
使いやすいように&lt;code&gt;package.json&lt;/code&gt;の scripts にコマンドを設定しておくとよいです。&lt;/p&gt;
&lt;p&gt;TypeScript 向けの例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  .
  .
  .
  &quot;lint:eslint&quot;: &quot;eslint &apos;./src/**/*.{js,jsx,ts,tsx}&apos;&quot;,
  &quot;fix:eslint&quot;: &quot;yarn lint:eslint --fix&quot;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;VSCode 上で動かす&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://marketplace.visualstudio.com/items?itemName=dbaeumer.vscode-eslint&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;インストールして再起動。
特に問題なければこれだけでチェックが動作します。&lt;/p&gt;
&lt;p&gt;ファイル保存時に自動整形を走らせたい場合は、VSCode の設定ファイルにその旨追記します。
デフォルトのフォーマッタは無効にして、ESLint によるフォーマットをかけるイメージです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;editor.codeActionsOnSave&quot;: {
    &quot;source.fixAll.eslint&quot;: true
  },
  &quot;editor.formatOnSave&quot;: false,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ESLint の設定ファイル&lt;/h3&gt;
&lt;p&gt;設定の定義の仕方としては、以下の4パターンがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;の eslintConfig に書く&lt;/li&gt;
&lt;li&gt;JavaScript ファイル（例　&lt;code&gt;.eslintrc.js&lt;/code&gt;）に書く&lt;/li&gt;
&lt;li&gt;JSON ファイル（例　&lt;code&gt;.eslintrc.json&lt;/code&gt;）に書く&lt;/li&gt;
&lt;li&gt;YAML ファイル（例　&lt;code&gt;.eslintrc.yml&lt;/code&gt;）に書く&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個人的な好みとしては2番目です。
別ファイルにしておいた方が、一度作成した設定を別プロジェクトで使いまわしたりしやすいです。&lt;/p&gt;
&lt;p&gt;設定の記述方式は公式をご参照ください。
&amp;lt;OG url=&quot;https://eslint.org/docs/latest/use/configure/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;それと、ESLint のチェックから除外したいファイルがある場合は、&lt;code&gt;.eslintignore&lt;/code&gt; ファイルを作って書いておくことで対応できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;node_modules/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;ESLint の設定ファイルを対話式で作成&lt;/h3&gt;
&lt;p&gt;ESLint の設定ファイルは対話式で作成も可能です。
まずこれで雛形を作成して、そこからカスタマイズしていくとよいです。&lt;/p&gt;
&lt;p&gt;以下は回答の流れの例です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn run -s eslint --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESLint をどのように使用しますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? How would you like to use ESLint?
  To check syntax only
  To check syntax and find problems
❯ To check syntax, find problems, and enforce code style
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;どんなモジュールを使用していますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? What type of modules does your project use?
❯ JavaScript modules (import/export)
  CommonJS (require/exports)
  None of these
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;どのフレームワークを使用していますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Which framework does your project use?
❯ React
  Vue.js
  None of these
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;TypeScriptは使用していますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Does your project use TypeScript? No / Yes
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コードはどこで実行されますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Where does your code run? (Press &amp;lt;space&amp;gt; to select, &amp;lt;a&amp;gt; to toggle all, &amp;lt;i&amp;gt; to invert selection)
❯ Browser
  Node
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;プロジェクトのスタイルをどのように定義しますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? How would you like to define a style for your project?
❯ Use a popular style guide
  Answer questions about your style
  Inspect your JavaScript file(s)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;どのスタイルガイドをフォローしますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Which style guide do you want to follow? (Use arrow keys)
❯ Airbnb (https://github.com/airbnb/javascript)
  Standard (https://github.com/standard/standard)
  Google (https://github.com/google/eslint-config-google)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定ファイルのフォーマットはどれにしますか？&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? What format do you want your config file to be in? (Use arrow keys)
❯ JavaScript
  YAML
  JSON
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;npm でこれらのパッケージをインストールしますか？
（yarn でインストールしたい場合は No を選択して、案内されたパッケージを手動でインストールする必要があります）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;eslint-plugin-react@^7.14.3 eslint-config-airbnb@latest eslint@^5.16.0 || ^6.1.0 eslint-plugin-import@^2.18.2 eslint-plugin-jsx-a11y@^6.2.3 eslint-plugin-react-hooks@^1.7.0
? Would you like to install them now with npm? No / Yes
  No
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この回答例だと、以下のような設定ファイルが作られます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// TypeScript を使わないと回答した場合
module.exports = {
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2021&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:react/recommended&quot;,
        &quot;airbnb&quot;
    ],
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 12,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;react&quot;
    ],
    &quot;rules&quot;: {
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;// TypeScript を使うと回答した場合
module.exports = {
    &quot;env&quot;: {
        &quot;browser&quot;: true,
        &quot;es2021&quot;: true
    },
    &quot;extends&quot;: [
        &quot;plugin:react/recommended&quot;,
        &quot;airbnb&quot;
    ],
    &quot;parser&quot;: &quot;@typescript-eslint/parser&quot;,
    &quot;parserOptions&quot;: {
        &quot;ecmaFeatures&quot;: {
            &quot;jsx&quot;: true
        },
        &quot;ecmaVersion&quot;: 12,
        &quot;sourceType&quot;: &quot;module&quot;
    },
    &quot;plugins&quot;: [
        &quot;react&quot;,
        &quot;@typescript-eslint&quot;
    ],
    &quot;rules&quot;: {
    }
};
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;スタイルガイドの導入&lt;/h3&gt;
&lt;p&gt;ESLint の設定ファイルを対話式で作成時の回答にもありましたが、企業などのスタイルガイドを準拠した ESLint の共有設定が存在します。
全く一からルール設定をするのは大変なので、この共有設定を適用して、適宜カスタマイズするとよいです。&lt;/p&gt;
&lt;p&gt;以下は Airbnb の共有設定を使用する例です。&lt;/p&gt;
&lt;p&gt;パッケージをインストール。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D eslint-config-airbnb
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;設定の extends に追加（&lt;code&gt;.eslintrc.js&lt;/code&gt;での例）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extends: [
  &apos;plugin:react/recommended&apos;,
  &apos;airbnb&apos;, // 追記
]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;トラブルシューティング&lt;/h3&gt;
&lt;h4&gt;ルート階層に設定ファイルがない場合&lt;/h4&gt;
&lt;p&gt;VSCode の ESLint 拡張は、ワークスペースに追加したフォルダの直下に設定ファイル（&lt;code&gt;.eslintrc.js&lt;/code&gt;、&lt;code&gt;package.json&lt;/code&gt;など）がある前提で動作します。
直下にない場合は、VSCode の設定ファイルで別途指定する必要があります。&lt;/p&gt;
&lt;p&gt;※例（app 直下に設定ファイルがある場合）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;eslint.workingDirectories&quot;: [
	  &quot;./app&quot;
  ]
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;React のバージョン検知に問題がある場合&lt;/h4&gt;
&lt;p&gt;ESLint 実行時に
eslint-plugin-react の設定で React のバージョンが指定されていません
と表示されることがあります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Warning: React version not specified in eslint-plugin-react settings. See https://github.com/yannickcr/eslint-plugin-react#configuration .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESLint の設定ファイルに以下を追記することで、バージョンを自動検知させることが可能です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;settings: {
  react: {
    version: &apos;detect&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しかし、Docker コンテナの中に Node.js 環境があるが、ローカルの VSCode 上で ESLint 拡張を動かしている（ローカルに Node.js 環境がない）。
などといった、環境によって次の Warning が出ることもあります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Warning: React version was set to &quot;detect&quot; in eslint-plugin-react settings, but the &quot;react&quot; package is not installed. Assuming latest React version for linting.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;上記のような環境においては、さすがにバージョン検知ができないようです。&lt;/p&gt;
&lt;p&gt;この場合は ESLint の設定に、明示的にバージョンを指定することで対応できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;settings: {
  react: {
    version: &apos;17.0.2&apos;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;@typescript-eslint/typescript-estree のサポートバージョン外の場合&lt;/h4&gt;
&lt;p&gt;TypeScript のバージョンにより、@typescript-eslint/typescript-estree がサポートしているバージョンでないとの Warning が表示されることがあります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;=============

WARNING: You are currently running a version of TypeScript which is not officially supported by @typescript-eslint/typescript-estree.

You may find that it works just fine, or you may not.

SUPPORTED TYPESCRIPT VERSIONS: &amp;gt;=3.3.1 &amp;lt;4.2.0

YOUR TYPESCRIPT VERSION: 4.3.5

Please only submit bug reports when using the officially supported version.

=============
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;あくまで Warning のため、ESLint 自体は動作します。
気になる方は、バージョンを合わせる。
そのままのバージョンを使う方は、そのバージョンではバグレポート受付に対応していないことを理解したうえで使用しましょう。&lt;/p&gt;
&lt;h2&gt;Prettier&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://prettier.io&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;幅広いファイル形式に対応したコードフォーマッタです。
ESLint とあわせて導入しておくと、さらなるコードの品質向上に繋がります。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;react-scripts&lt;/code&gt;の依存関係に Prettier に関するものはないので、追加でライブラリをインストールしていく必要があります。&lt;/p&gt;
&lt;h3&gt;連携セットアップ&lt;/h3&gt;
&lt;p&gt;ライブラリインストール。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;yarn add -D prettier eslint-config-prettier
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESLint の設定ファイルに追記。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;extends: [
  &apos;plugin:react/recommended&apos;,
  &apos;airbnb&apos;,
  &apos;prettier&apos;, // 追記
],
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESLint と Prettier はルール競合することがあるので、eslint-config-prettier を適用することで競合するルールを無効化して調整します。
その性質上、追加するのは extends の最後にしてください。&lt;/p&gt;
&lt;p&gt;ちなみに eslint-config-prettier は、競合しているルールがないかチェックする CLI ツールを持っていたりします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# JS
yarn run -s eslint-config-prettier &apos;./src/**/*.{js,jsx}&apos;

# TS
yarn run -s eslint-config-prettier &apos;./src/**/*.{js,jsx,ts,tsx}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;問題なければ以下のような表示になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;No rules that are unnecessary or conflict with Prettier were found.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;CLI で動かしてみる&lt;/h3&gt;
&lt;p&gt;チェックを行いたいファイルパスを指定して実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# JS
yarn run -s prettier --check &apos;./{public,src}/**/*.{js,jsx,html,gql,graphql,json}&apos;

# TS
yarn run -s prettier --check &apos;./{public,src}/**/*.{js,jsx,ts,tsx,html,gql,graphql,json}&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;自動整形までやりたい場合は&lt;code&gt;--write&lt;/code&gt;をつけて実行します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# JS
yarn run -s prettier --check &apos;./{public,src}/**/*.{js,jsx,html,gql,graphql,json}&apos; --write

# TS
yarn run -s prettier --check &apos;./{public,src}/**/*.{js,jsx,ts,tsx,html,gql,graphql,json}&apos; --write
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このコマンドも ESLint の時と同様に、&lt;code&gt;package.json&lt;/code&gt;の scripts に追加しておくとよいです。&lt;/p&gt;
&lt;p&gt;TypeScript 向けの例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  .
  .
  .
  &quot;check:prettier&quot;: &quot;prettier --check &apos;./{public,src}/**/*.{js,jsx,ts,tsx,html,gql,graphql,json}&apos;&quot;,
  &quot;fix:prettier&quot;: &quot;yarn check:prettier --write&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ESLint とセットで1コマンド実行したい場合は、下記のようにする手もあります。&lt;/p&gt;
&lt;p&gt;TypeScript 向けの例。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  .
  .
  .
  &quot;lint-check&quot;: &quot;yarn lint:eslint &amp;amp;&amp;amp; yarn check:prettier&quot;,
  &quot;lint:eslint&quot;: &quot;eslint &apos;./src/**/*.{js,jsx,ts,tsx}&apos;&quot;,
  &quot;check:prettier&quot;: &quot;prettier --check &apos;./{public,src}/**/*.{js,jsx,ts,tsx,html,gql,graphql,json}&apos;&quot;,
  &quot;fix&quot;: &quot;yarn fix:eslint &amp;amp;&amp;amp; yarn fix:prettier&quot;,
  &quot;fix:eslint&quot;: &quot;yarn lint:eslint --fix&quot;,
  &quot;fix:prettier&quot;: &quot;yarn check:prettier --write&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;VSCode 上で動かしてみる&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://marketplace.visualstudio.com/items?itemName=esbenp.prettier-vscode&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;インストールして必要に応じて再起動。&lt;/p&gt;
&lt;p&gt;VSCode の設定ファイルに以下を追記。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;editor.defaultFormatter&quot;: &quot;esbenp.prettier-vscode&quot;,
  &quot;[javascript]&quot;: {
    &quot;editor.formatOnSave&quot;: true
  },
  &quot;[javascriptreact]&quot;: {
    &quot;editor.formatOnSave&quot;: true
  },
  &quot;[json]&quot;: {
    &quot;editor.formatOnSave&quot;: true
  },
  // TypeScript の場合は以下も
  &quot;[typescript]&quot;: {
    &quot;editor.formatOnSave&quot;: true
  },
  &quot;[typescriptreact]&quot;: {
    &quot;editor.formatOnSave&quot;: true
  },
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;VSCode のデフォルトのフォーマッタとして Prettier を設定。
加えて、上記3つのファイル形式において、保存時に自動整形が動作するようにしています。&lt;/p&gt;
&lt;h3&gt;Prettier の設定ファイル&lt;/h3&gt;
&lt;p&gt;設定の定義の仕方としては、以下の6パターンがあります。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;package.json&lt;/code&gt;の prettier に書く&lt;/li&gt;
&lt;li&gt;&lt;code&gt;.prettierrc&lt;/code&gt;ファイルに書く&lt;/li&gt;
&lt;li&gt;JavaScript ファイル（例　&lt;code&gt;.prettierrc.js&lt;/code&gt;）に書く&lt;/li&gt;
&lt;li&gt;JSON ファイル（例　&lt;code&gt;.prettierrc.json&lt;/code&gt;）に書く&lt;/li&gt;
&lt;li&gt;YAML ファイル（例　&lt;code&gt;.prettierrc.yml&lt;/code&gt;）に書く&lt;/li&gt;
&lt;li&gt;TOML ファイル（例　&lt;code&gt;.prettierrc.toml&lt;/code&gt;）に書く&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;個人的な好みとしては2番目です。
ESLint と同様に別ファイルにしておいた方が、一度作成した設定を別プロジェクトで使いまわしたりしやすいです。&lt;/p&gt;
&lt;p&gt;設定の記述方式は公式をご参照ください。
&amp;lt;OG url=&quot;https://prettier.io/docs/en/configuration.html&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;それと、Prettier のチェックから除外したいファイルがある場合は、&lt;code&gt;.prettierignore&lt;/code&gt;ファイルを作って書いておくことで対応できます。&lt;/p&gt;
&lt;h2&gt;補足：glob を Windows にも対応させる（※2021/11/17追記）&lt;/h2&gt;
&lt;p&gt;上記では ESLint、Prettier のコマンドで、実行パスである glob 部分をシングルクォートで囲うようにしていました。
Mac や Linux といった Unix 環境であれば、これで問題ないのですが、Windows だとうまく動作しません。&lt;/p&gt;
&lt;p&gt;Windows にも対応させる場合は、シングルクォートでなくダブルクォートで囲う必要があります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&quot;scripts&quot;: {
  .
  .
  .
  &quot;lint-check&quot;: &quot;yarn lint:eslint &amp;amp;&amp;amp; yarn check:prettier&quot;,
  &quot;lint:eslint&quot;: &quot;eslint \&quot;./src/**/*.{js,jsx,ts,tsx}\&quot;&quot;,
  &quot;check:prettier&quot;: &quot;prettier --check \&quot;./{public,src}/**/*.{js,jsx,ts,tsx,html,gql,graphql,json}\&quot;&quot;,
  &quot;fix&quot;: &quot;yarn fix:eslint &amp;amp;&amp;amp; yarn fix:prettier&quot;,
  &quot;fix:eslint&quot;: &quot;yarn lint:eslint --fix&quot;,
  &quot;fix:prettier&quot;: &quot;yarn check:prettier --write&quot;,
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://eslint.org/&quot;&gt;ESLint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://prettier.io/&quot;&gt;Prettier&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/oukayuka/Riakuto-StartingReact-ja3.1&quot;&gt;GitHub - oukayuka/Riakuto-StartingReact-ja3.1 ※書籍&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>DockerでReact + Swagger 環境を作ろう</title><link>https://changeofpace.site/posts/2019-10-24-docker-react-swagger</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-10-24-docker-react-swagger</guid><description>DockerでReact + Swaggerの環境を作って、React側からAPIリクエストを出すまでの手順</description><pubDate>Thu, 24 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;現在業務で React を使用しているのですが、事前勉強する際に Docker 環境を作成していたので、それを共有します。
ちなみに Swagger 環境に関しては、自分で作成してうまくいかなかったので、上司が作成した環境を参考にさせてもらいました。感謝です。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/962712623376fd40e1c5&quot;&gt;Qiita&lt;/a&gt; からの転載です。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker 導入済み&lt;/li&gt;
&lt;li&gt;docker-compose コマンドが使用できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;※2020/02/29
&lt;code&gt;create-react-app&lt;/code&gt;がグローバルインストールをサポートしなくなったことに伴い、React 用のコンテナ周りを見直しました。&lt;/p&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;p&gt;今回は4つのコンテナを使用。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;node...React のコードを実行するコンテナ&lt;/li&gt;
&lt;li&gt;swagger-editor...Swagger Editor のコンテナ&lt;/li&gt;
&lt;li&gt;swagger-api...Swagger 定義ファイルから生成されたコードを実行するコンテナ&lt;/li&gt;
&lt;li&gt;swagger-nginx...swagger-api の内容を配信するアプリケーションサーバのコンテナ（nginx をさらに高性能に使えるようにした openresty を使用）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;これらに必要な設定ファイルを用意していきます。
ちなみに作成後のディレクトリは以下のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(root)
├ docker
│ ├ nginx
│ │  └ swagger.conf
│ └ swagger-api
│     ├ Dockerfile
│     └ swagger.json
└ docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;nginx/swagger.conf&lt;/h3&gt;
&lt;p&gt;nginx（openresty） の設定を記述します。
ここで CORS に関する設定をしておかないと、Reac t側から axios でリクエストする際にエラーとなってしまいます。
（自分が最初環境を作ろうとしたときに、ここでつまづいていました。今も設定に関しては疎いです...。）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;server {
  listen 80;
  server_name localhost;

  location /docs/ {
    proxy_pass http://swagger-api:8000/docs/;
  }

  location / {
    if ($request_method = &apos;OPTIONS&apos;) {
      add_header Access-Control-Allow-Origin $http_origin;
      add_header Access-Control-Allow-Headers &apos;X-Requested-With, Authorization, Origin, Accept, Content-Type&apos;;
      add_header Access-Control-Allow-Methods &apos;GET, POST, PUT, DELETE&apos;;
      add_header Access-Control-Max-Age 86400;
      add_header Access-Control-Allow-Credentials true;
      add_header Access-Control-Expose-Headers Set-Cookie;
      add_header Content-Type &apos;text/plain charset=UTF-8&apos;;
      add_header Content-Length 0;
      return 204;
    }
    proxy_pass http://swagger-api:8000;
    add_header Access-Control-Allow-Origin $http_origin always;
    add_header Access-Control-Allow-Credentials true always;
    add_header Access-Control-Expose-Headers Content-Disposition;
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;swagger-api/Dockerfile&lt;/h3&gt;
&lt;p&gt;Swagger 定義ファイルをもとにコードを生成する&lt;code&gt;Swagger Codegen&lt;/code&gt;をインストールして、実際に Node.js のコードを生成しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;FROM openjdk:8-jdk-alpine

ARG CLI_VERSION=2.4.5

RUN wget http://central.maven.org/maven2/io/swagger/swagger-codegen-cli/${CLI_VERSION}/swagger-codegen-cli-${CLI_VERSION}.jar -O /swagger-codegen-cli.jar

RUN apk --update add bash nodejs npm &amp;amp;&amp;amp; rm -rf /var/cache/apk/*

ADD swagger.json swagger.json

RUN java -jar /swagger-codegen-cli.jar generate -l nodejs-server -i swagger.json -o src &amp;amp;&amp;amp; \
    cd src &amp;amp;&amp;amp; npm install

CMD [&quot;java&quot;, &quot;-jar&quot;, &quot;/swagger-codegen-cli.jar&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;swagger-api/swagger.json&lt;/h3&gt;
&lt;p&gt;Swagger 定義ファイルです。
ここでは簡単に1つだけ API を定義しています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;{
  &quot;swagger&quot;: &quot;2.0&quot;,
  &quot;info&quot;: {
    &quot;description&quot;: &quot;テスト用API&quot;,
    &quot;version&quot;: &quot;1.0.0&quot;,
    &quot;title&quot;: &quot;Swagger test API設計書&quot;,
    &quot;contact&quot;: {},
    &quot;license&quot;: {
      &quot;name&quot;: &quot;&quot;
    }
  },
  &quot;host&quot;: &quot;localhost:8000&quot;,
  &quot;basePath&quot;: &quot;/&quot;,
  &quot;tags&quot;: [
    {
      &quot;name&quot;: &quot;User&quot;,
      &quot;description&quot;: &quot;User Controller&quot;
    }
  ],
  &quot;paths&quot;: {
    &quot;/user&quot;: {
      &quot;get&quot;: {
        &quot;tags&quot;: [
          &quot;User&quot;
        ],
        &quot;summary&quot;: &quot;ユーザ情報取得&quot;,
        &quot;description&quot;: &quot;ユーザ情報を取得する。&quot;,
        &quot;operationId&quot;: &quot;doGetUsingGET&quot;,
        &quot;consumes&quot;: [
          &quot;application/json&quot;
        ],
        &quot;produces&quot;: [
          &quot;application/json&quot;
        ],
        &quot;responses&quot;: {
          &quot;200&quot;: {
            &quot;description&quot;: &quot;OK&quot;,
            &quot;schema&quot;: {
              &quot;$ref&quot;: &quot;#/definitions/UserGetOut&quot;
            }
          },
          &quot;400&quot;: {
            &quot;description&quot;: &quot;バリデーションエラー&quot;,
            &quot;schema&quot;: {
              &quot;$ref&quot;: &quot;#/definitions/ValidationErrorsOut&quot;
            }
          },
          &quot;500&quot;: {
            &quot;description&quot;: &quot;想定外のエラー&quot;,
            &quot;schema&quot;: {
              &quot;$ref&quot;: &quot;#/definitions/ErrorInfo&quot;
            }
          }
        }
      }
    }
  },
  &quot;definitions&quot;: {
    &quot;UserGetOut&quot;: {
      &quot;type&quot;: &quot;object&quot;,
      &quot;required&quot;: [
        &quot;userId&quot;,
        &quot;name&quot;,
        &quot;kana&quot;
      ],
      &quot;properties&quot;: {
        &quot;userId&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;abc001&quot;,
          &quot;description&quot;: &quot;ユーザID&quot;
        },
        &quot;name&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;山田 太郎&quot;,
          &quot;description&quot;: &quot;名前&quot;
        },
        &quot;kana&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;ヤマダ タロウ&quot;,
          &quot;description&quot;: &quot;カナ&quot;
        }
      }
    },
    &quot;ValidationError&quot;: {
      &quot;type&quot;: &quot;object&quot;,
      &quot;required&quot;: [
        &quot;itemName&quot;
        &quot;code&quot;,
        &quot;content&quot;
      ],
      &quot;properties&quot;: {
        &quot;itemName&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;userId&quot;,
          &quot;description&quot;: &quot;項目名&quot;
        },
        &quot;code&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;NotBlank&quot;,
          &quot;description&quot;: &quot;code&quot;
        },
        &quot;content&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;必須項目が設定されていません。&quot;,
          &quot;description&quot;: &quot;content&quot;
        }
      }
    },
    &quot;ValidationErrorsOut&quot;: {
      &quot;type&quot;: &quot;object&quot;,
      &quot;required&quot;: [
        &quot;validationInfo&quot;
      ],
      &quot;properties&quot;: {
        &quot;validationInfo&quot;: {
          &quot;type&quot;: &quot;array&quot;,
          &quot;description&quot;: &quot;バリデーション情報&quot;,
          &quot;items&quot;: {
            &quot;$ref&quot;: &quot;#/definitions/ValidationError&quot;
          }
        }
      }
    },
    &quot;ErrorInfo&quot;: {
      &quot;type&quot;: &quot;object&quot;,
      &quot;required&quot;: [
        &quot;code&quot;,
        &quot;content&quot;
      ],
      &quot;properties&quot;: {
        &quot;code&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;exception.errors.unexpeced.exception&quot;,
          &quot;description&quot;: &quot;エラーコード&quot;
        },
        &quot;content&quot;: {
          &quot;type&quot;: &quot;string&quot;,
          &quot;example&quot;: &quot;システムエラーが発生しました。誠に恐れ入りますが、初めからやり直して下さい。&quot;,
          &quot;description&quot;: &quot;メッセージ&quot;
        }
      }
    }
  }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;docker-compose.yml&lt;/h3&gt;
&lt;p&gt;今回使用する4つのコンテナ定義を書いていきます。
なお、node の environment の&lt;code&gt;CHOKIDAR_USEPOLLING=true&lt;/code&gt;はホットリロードを有効化にするものです。&lt;/p&gt;
&lt;p&gt;node の command については一旦コメントアウトしておきます。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※node_modules について補足説明&lt;/strong&gt;
node_modules は volume マウントすることが多いとは思われます。
コンテナ内のnpm or yarn の速度的にも、volume マウントの方がいいそうです。&lt;/p&gt;
&lt;p&gt;ただ、そうするとローカルに node_modules ができなくなり、VSCode の ESLint 拡張を動かす時などに支障が出るので、あえてやっていません。
（VSCode の&lt;code&gt;Remote - Containers&lt;/code&gt;拡張を使えばコンテナ内で作業できるので、解決する話ではあるのですが今回はやってません）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3&quot;
services:
  node:
    image: node:12-alpine
    environment:
      - NODE_ENV=development
      - CHOKIDAR_USEPOLLING=true
    volumes:
      - ./:/usr/app
    tty: true
    stdin_open: true
    working_dir: /usr/app
    # command: /bin/sh -c &quot;yarn install &amp;amp;&amp;amp; yarn start&quot;
    ports:
      - &quot;3000:3000&quot;

  swagger-editor:
    image: swaggerapi/swagger-editor
    ports:
      - &quot;8081:8080&quot;

  swagger-api:
    build: ./docker/swagger-api
    working_dir: /src
    command: node index.js

  swagger-nginx:
    image: openresty/openresty:alpine
    ports:
      - 8000:80
    depends_on:
      - swagger-api
    volumes:
      - ./docker/nginx/swagger.conf:/etc/nginx/conf.d/default.conf:ro
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;コンテナの作成と起動&lt;/h2&gt;
&lt;h3&gt;コンテナのビルド&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;docker-compose build
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;コンテナ起動確認&lt;/h3&gt;
&lt;p&gt;State が Up になっていれば立ち上がっています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;React プロジェクト作成&lt;/h2&gt;
&lt;h3&gt;プロジェクトの雛形作成&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;create-react-app&lt;/code&gt;を使用して雛形を作成します。
なお、ここで使用しているバージョンは&lt;code&gt;3.4.0&lt;/code&gt;です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec node yarn create react-app my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;yarn create v1.19.1
[1/4] Resolving packages...
[2/4] Fetching packages...
[3/4] Linking dependencies...
[4/4] Building fresh packages...
warning Your current version of Yarn is out of date. The latest version is &quot;1.22.0&quot;, while you\&apos;re on &quot;1.19.1&quot;.
info To upgrade, run the following command:
$ curl --compressed -o- -L https://yarnpkg.com/install.sh | bash
success Installed &quot;create-react-app@3.4.0&quot; with binaries:
      - create-react-app
[####################################################################################################] 100/100
Creating a new React app in /usr/app/my-app.

Installing packages. This might take a couple of minutes.
Installing react, react-dom, and react-scripts with cra-template...

yarn add v1.19.1
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@1.2.11: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@1.2.11&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@2.1.2: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@2.1.2&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning &quot;react-scripts &amp;gt; eslint-config-react-app@5.2.0&quot; has incorrect peer dependency &quot;eslint-plugin-flowtype@3.x&quot;.
warning &quot;react-scripts &amp;gt; @typescript-eslint/eslint-plugin &amp;gt; tsutils@3.17.1&quot; has unmet peer dependency &quot;typescript@&amp;gt;=2.8.0 || &amp;gt;= 3.2.0-dev || &amp;gt;= 3.3.0-dev || &amp;gt;= 3.4.0-dev || &amp;gt;= 3.5.0-dev || &amp;gt;= 3.6.0-dev || &amp;gt;= 3.6.0-beta || &amp;gt;= 3.7.0-dev || &amp;gt;= 3.7.0-beta&quot;.
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 9 new dependencies.
info Direct dependencies
├─ cra-template@1.0.2
├─ react-dom@16.13.0
├─ react-scripts@3.4.0
└─ react@16.13.0
info All dependencies
├─ cra-template@1.0.2
├─ is-docker@2.0.0
├─ open@7.0.2
├─ react-dev-utils@10.2.0
├─ react-dom@16.13.0
├─ react-error-overlay@6.0.6
├─ react-scripts@3.4.0
├─ react@16.13.0
└─ scheduler@0.19.0
Done in 161.91s.
Git repo not initialized Error: Command failed: git --version
    at checkExecSyncError (child_process.js:621:11)
    at execSync (child_process.js:657:15)
    at tryGitInit (/usr/app/my-app/node_modules/react-scripts/scripts/init.js:46:5)
    at module.exports (/usr/app/my-app/node_modules/react-scripts/scripts/init.js:267:7)
    at [eval]:3:14
    at Script.runInThisContext (vm.js:116:20)
    at Object.runInThisContext (vm.js:306:38)
    at Object.&amp;lt;anonymous&amp;gt; ([eval]-wrapper:9:26)
    at Module._compile (internal/modules/cjs/loader.js:956:30)
    at evalScript (internal/process/execution.js:80:25) {
  status: 127,
  signal: null,
  output: [ null, null, null ],
  pid: 104,
  stdout: null,
  stderr: null
}

Installing template dependencies using yarnpkg...
yarn add v1.19.1
[1/4] Resolving packages...
[2/4] Fetching packages...
info fsevents@2.1.2: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@2.1.2&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.11: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@1.2.11&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
[3/4] Linking dependencies...
warning &quot;react-scripts &amp;gt; eslint-config-react-app@5.2.0&quot; has incorrect peer dependency &quot;eslint-plugin-flowtype@3.x&quot;.
warning &quot;react-scripts &amp;gt; @typescript-eslint/eslint-plugin &amp;gt; tsutils@3.17.1&quot; has unmet peer dependency &quot;typescript@&amp;gt;=2.8.0 || &amp;gt;= 3.2.0-dev || &amp;gt;= 3.3.0-dev || &amp;gt;= 3.4.0-dev || &amp;gt;= 3.5.0-dev || &amp;gt;= 3.6.0-dev || &amp;gt;= 3.6.0-beta || &amp;gt;= 3.7.0-dev || &amp;gt;= 3.7.0-beta&quot;.
warning &quot; &amp;gt; @testing-library/user-event@7.2.1&quot; has unmet peer dependency &quot;@testing-library/dom@&amp;gt;=5&quot;.
[4/4] Building fresh packages...
success Saved lockfile.
success Saved 18 new dependencies.
info Direct dependencies
├─ @testing-library/jest-dom@4.2.4
├─ @testing-library/react@9.4.1
├─ @testing-library/user-event@7.2.1
├─ react-dom@16.13.0
└─ react@16.13.0
info All dependencies
├─ @sheerun/mutationobserver-shim@0.3.2
├─ @testing-library/dom@6.12.2
├─ @testing-library/jest-dom@4.2.4
├─ @testing-library/react@9.4.1
├─ @testing-library/user-event@7.2.1
├─ @types/prop-types@15.7.3
├─ @types/react-dom@16.9.5
├─ @types/react@16.9.23
├─ @types/testing-library__dom@6.12.1
├─ @types/testing-library__react@9.1.2
├─ css.escape@1.5.1
├─ csstype@2.6.9
├─ min-indent@1.0.0
├─ react-dom@16.13.0
├─ react@16.13.0
├─ redent@3.0.0
├─ strip-indent@3.0.0
└─ wait-for-expect@3.0.2
Done in 41.98s.
Removing template package using yarnpkg...

yarn remove v1.19.1
[1/2] Removing module cra-template...
[2/2] Regenerating lockfile and installing missing dependencies...
info fsevents@2.1.2: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@2.1.2&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
info fsevents@1.2.11: The platform &quot;linux&quot; is incompatible with this module.
info &quot;fsevents@1.2.11&quot; is an optional dependency and failed compatibility check. Excluding it from installation.
warning &quot; &amp;gt; @testing-library/user-event@7.2.1&quot; has unmet peer dependency &quot;@testing-library/dom@&amp;gt;=5&quot;.
warning &quot;react-scripts &amp;gt; eslint-config-react-app@5.2.0&quot; has incorrect peer dependency &quot;eslint-plugin-flowtype@3.x&quot;.
warning &quot;react-scripts &amp;gt; @typescript-eslint/eslint-plugin &amp;gt; tsutils@3.17.1&quot; has unmet peer dependency &quot;typescript@&amp;gt;=2.8.0 || &amp;gt;= 3.2.0-dev || &amp;gt;= 3.3.0-dev || &amp;gt;= 3.4.0-dev || &amp;gt;= 3.5.0-dev || &amp;gt;= 3.6.0-dev || &amp;gt;= 3.6.0-beta || &amp;gt;= 3.7.0-dev || &amp;gt;= 3.7.0-beta&quot;.
success Uninstalled packages.
Done in 48.44s.

Success! Created my-app at /usr/app/my-app
Inside that directory, you can run several commands:

  yarn start
    Starts the development server.

  yarn build
    Bundles the app into static files for production.

  yarn test
    Starts the test runner.

  yarn eject
    Removes this tool and copies build dependencies, configuration files
    and scripts into the app directory. If you do this, you can’t go back!

We suggest that you begin by typing:

  cd my-app
  yarn start

Happy hacking!
Done in 267.52s.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでプロジェクトの雛形が作成されます。
この時点でのディレクトリ構成は以下のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(root)
├ my-app
│  ├ node_modules
│  │  └ （各種パッケージ）
│  ├ public
│  │  ├ favicon.ico
│  │  ├ index.html
│  │  ├ logo192.png
│  │  ├ logo512.png
│  │  ├ manifest.json
│  │  └ rebots.txt
│  ├ src
│  │  ├ App.css
│  │  ├ App.js
│  │  ├ App.test.js
│  │  ├ index.css
│  │  ├ index.js
│  │  ├ logo.svg
│  │  ├ serviceWorker.js
│  │  └ setupTests.js
│  ├ .gitignore
│  ├ package.json
│  ├ README.md
│  ├ yarn.lock
├ docker
│  ├ nginx
│  │  └ swagger.conf
│  └ swagger-api
│      ├ Dockerfile
│      └ swagger.json
└ docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;作成したプロジェクトの雛形を移動&lt;/h3&gt;
&lt;p&gt;ここで自分の場合は、プロジェクトルート直下に&lt;code&gt;my-app&lt;/code&gt;の中身を置きたかったので移動させました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec node mv ./my-app/* .
docker-compose exec node mv ./my-app/.gitignore .
docker-compose exec node rm -rf my-app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;移動後のディレクトリ構成。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(root)
├ node_modules
│  └ （各種パッケージ）
├ public
│  ├ favicon.ico
│  ├ index.html
│  ├ logo192.png
│  ├ logo512.png
│  ├ manifest.json
│  └ rebots.txt
├ src
│  ├ App.css
│  ├ App.js
│  ├ App.test.js
│  ├ index.css
│  ├ index.js
│  ├ logo.svg
│  ├ serviceWorker.js
│  └ setupTests.js
├ .gitignore
├ package.json
├ README.md
├ yarn.lock
├ docker
│  ├ nginx
│  │  └  swagger.conf
│  └  swagger-api
│      ├ Dockerfile
│      └  swagger.json
└  docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;後から移動させるなら、元からカレントディレクトリに雛形を作成すればいいのでは？と思われそうですが...。
カレントディレクトリだと docker ディレクトリや docker-compose.yml があるため、雛形作成の邪魔になるらしく、エラーになります。&lt;/p&gt;
&lt;h3&gt;コンテナ起動時のコマンドの有効化&lt;/h3&gt;
&lt;p&gt;docker-compose.yml の node の command のコメントアウトを解除します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;command: /bin/sh -c &quot;yarn install &amp;amp;&amp;amp; yarn start&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;コンテナの再ビルド&lt;/h3&gt;
&lt;p&gt;有効化したコマンドを反映させるために再ビルドします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up -d --build
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;うまくいっていれば、コマンドが実行されてサーバが起動しているはずです。&lt;/p&gt;
&lt;h3&gt;ブラウザでアクセス&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;localhost:3000&lt;/code&gt;にアクセスして、以下のような画面になればOKです。
ちなみにホットリロードを有効化にしているため、コードを変更して保存すると即座に反映されます。
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;gifs/2019/docker-react-swagger/react-app.gif&quot; alt=&quot;Reactアプリ トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;Swagger&lt;/h2&gt;
&lt;h3&gt;Swagger Editor&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;localhost:8081&lt;/code&gt;にアクセス。
左側がエディタ、右側が定義をもとにしたドキュメントとなっています。&lt;/p&gt;
&lt;p&gt;デフォルトではサンプルの API 定義が表示されます。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-swagger/swagger-editor.png&quot; alt=&quot;Swagger Editor画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Swagger 定義ファイルを読み込む場合は、File → Import File からインポートできます。
なお、エディタ上から定義をもとにしたコードを生成もできます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※注意点&lt;/strong&gt;
&lt;strong&gt;エディタ上の変更は自動で保存されますが、インポートしたファイル自体を保存までは行ってくれません。&lt;/strong&gt;
エディタで変更した定義ファイルをアプリケーションサーバに反映させたい場合は、File → Convert and save as JSON でファイルをダウンロード。
(root)/docker/swagger-api 配下に設置して、コンテナをビルドしなおす必要があります。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;Swagger UI&lt;/h3&gt;
&lt;p&gt;Swagger Codegen を使用してコードを生成すると、あわせてドキュメントも作成してくれています。
&lt;code&gt;localhost:8000/docs&lt;/code&gt;にアクセスすることで確認できます。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-swagger/swagger-ui.png&quot; alt=&quot;Swagger UI画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ちなみに最初は Swagger UIのコンテナをまた別で立ち上げていました。
ただ、参照する Swagger 定義ファイルを編集しているうちに、ファイルに間違いがない状態でもなぜかエラーが出てしまい、いまいち使えなくなってしまいました。
（Swagger Editor 上で何もエラーが出ていない状態で出力したファイルでもエラーになりました）&lt;/p&gt;
&lt;h3&gt;Swagger のサーバ&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;localhost:8000&lt;/code&gt;でアクセスできます。
今回は Swagger 定義ファイルで定義している API にリクエストしてみます。&lt;/p&gt;
&lt;h4&gt;ブラウザからアクセス&lt;/h4&gt;
&lt;p&gt;GET の API なので、直接ブラウザからアクセスしてもレスポンスが返ってきます。
&lt;code&gt;localhost:8000/user&lt;/code&gt;にアクセスすると以下のようになります。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-swagger/swagger-api-browser.png&quot; alt=&quot;Swaggerにブラウザでアクセスした画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;axios でアクセス&lt;/h4&gt;
&lt;p&gt;yarn で axios をインストール。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec node yarn add axios
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;src/App.js を以下のように修正。
（あまりいい書き方ではないでしょうが、とりあえず動作確認用のコードです）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;import React from &apos;react&apos;;
import logo from &apos;./logo.svg&apos;;
import &apos;./App.css&apos;;
import axios from &apos;axios&apos;; // 追記

function App() {
  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&amp;gt;
        &amp;lt;p&amp;gt;
          Edit &amp;lt;code&amp;gt;src/App.js&amp;lt;/code&amp;gt; and save to reload.
        &amp;lt;/p&amp;gt;
        &amp;lt;a
          className=&quot;App-link&quot;
          href=&quot;https://reactjs.org&quot;
          target=&quot;_blank&quot;
          rel=&quot;noopener noreferrer&quot;
        &amp;gt;
          Learn React
        &amp;lt;/a&amp;gt;
        {/* 以下を追記 */}
        &amp;lt;button
          onClick={
            () =&amp;gt; axios.get(&apos;http://localhost:8000/user&apos;)
                       .then((result) =&amp;gt; {
                         console.log(result)
                       })
                       .catch((error) =&amp;gt; {
                         console.log(error)
                       })
          }
        &amp;gt;
          APIリクエスト
        &amp;lt;/button&amp;gt;
        {/* ここまで追記 */}
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;localhost:3000&lt;/code&gt;にアクセスし、API リクエストのボタンを押すと axios のリクエストが実行されます。
コンソールに出力しているので、Developer Tools で確認すると以下のようにデータが取得できているのが確認できるはず。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/docker-react-swagger/swagger-api-axios.png&quot; alt=&quot;Swaggerにaxiosでアクセスしてコンソールに出力した画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;その他必要なもの&lt;/h2&gt;
&lt;p&gt;使用しているエディタに React に関する拡張を導入するとより開発しやすくなります。&lt;/p&gt;
&lt;h3&gt;VSCode 拡張&lt;/h3&gt;
&lt;h4&gt;Full React/React Native/React Router/Redux/GraphQL/ES7/Testing/PropTypes snippets&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://marketplace.visualstudio.com/items?itemName=walter-ribeiro.full-react-snippets&quot; /&amp;gt;
名称にある通り、各種のスニペットが使用でき、慣れてくるとコーディングが捗るかと思います。
（自分はあまり使いこなせてなかったりします...）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;h4&gt;ESLint + Prettier（2019/11/06追記）&lt;/h4&gt;
&lt;p&gt;&lt;s&gt;静的解析ツールであるESLintも導入した方がいいでしょうが、VSCode 上で動かすのがうまくいかなかったので今回は書いていません。&lt;/s&gt;
&lt;s&gt;（いずれちゃんと設定できたら書くかも...）&lt;/s&gt;
↓
長くなったので別記事に書きました。
&lt;a href=&quot;/posts/2019-11-06-eslint-prettier&quot;&gt;create-react-appで作成した雛形 + VSCodeにESLintとPrettierを導入する&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;h3&gt;Chrome 拡張&lt;/h3&gt;
&lt;h4&gt;React Developer Tools&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://chrome.google.com/webstore/detail/react-developer-tools/fmkadmapgofadopljbjfkapdkoienihi/related?hl=ja&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Developer Tools に Components と Profiler タブが追加されます。
コンポーネントの階層や、props や state の中身を確認できデバッグがしやすくなります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Chromeウェブストアからインストール&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Chrome の React Developer Tools アイコンを右クリックして、「拡張機能を管理」へ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;「ファイルの URL へのアクセスを許可する」をONにする&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;設定のカスタマイズ（2019/11/06追記）&lt;/h2&gt;
&lt;p&gt;crate-react-app で作成した雛形に Babel、Webpack、ESLint などの設定が見当たりませんが、内部的に設定が行われており、一から手動で設定する必要がありません。&lt;/p&gt;
&lt;p&gt;yarn.lock で&lt;code&gt;react-script&lt;/code&gt;の中に依存関係となっていることが確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;※一部抜粋
react-scripts@3.4.0:
  version &quot;3.4.0&quot;
  resolved &quot;https://registry.yarnpkg.com/react-scripts/-/react-scripts-3.4.0.tgz#f413680f0b5b937c8879ba1ffdae9b8c5b364bf5&quot;
  integrity sha512-pBqaAroFoHnFAkuX+uSK9Th1uEh2GYdGY2IG1I9/7HmuEf+ls3lLCk1p2GFYRSrLMz6ieQR/SyN6TLIGK3hKRg==
  dependencies:
    &quot;@babel/core&quot; &quot;7.8.4&quot;
    &quot;@svgr/webpack&quot; &quot;4.3.3&quot;
    &quot;@typescript-eslint/eslint-plugin&quot; &quot;^2.10.0&quot;
    &quot;@typescript-eslint/parser&quot; &quot;^2.10.0&quot;
    babel-eslint &quot;10.0.3&quot;
    babel-jest &quot;^24.9.0&quot;
    babel-loader &quot;8.0.6&quot;
    babel-plugin-named-asset-import &quot;^0.3.6&quot;
    babel-preset-react-app &quot;^9.1.1&quot;
    camelcase &quot;^5.3.1&quot;
    case-sensitive-paths-webpack-plugin &quot;2.3.0&quot;
    css-loader &quot;3.4.2&quot;
    dotenv &quot;8.2.0&quot;
    dotenv-expand &quot;5.1.0&quot;
    eslint &quot;^6.6.0&quot;
    eslint-config-react-app &quot;^5.2.0&quot;
    eslint-loader &quot;3.0.3&quot;
    eslint-plugin-flowtype &quot;4.6.0&quot;
    eslint-plugin-import &quot;2.20.0&quot;
    eslint-plugin-jsx-a11y &quot;6.2.3&quot;
    eslint-plugin-react &quot;7.18.0&quot;
    eslint-plugin-react-hooks &quot;^1.6.1&quot;
    file-loader &quot;4.3.0&quot;
    fs-extra &quot;^8.1.0&quot;
    html-webpack-plugin &quot;4.0.0-beta.11&quot;
    identity-obj-proxy &quot;3.0.0&quot;
    jest &quot;24.9.0&quot;
    jest-environment-jsdom-fourteen &quot;1.0.1&quot;
    jest-resolve &quot;24.9.0&quot;
    jest-watch-typeahead &quot;0.4.2&quot;
    mini-css-extract-plugin &quot;0.9.0&quot;
    optimize-css-assets-webpack-plugin &quot;5.0.3&quot;
    pnp-webpack-plugin &quot;1.6.0&quot;
    postcss-flexbugs-fixes &quot;4.1.0&quot;
    postcss-loader &quot;3.0.0&quot;
    postcss-normalize &quot;8.0.1&quot;
    postcss-preset-env &quot;6.7.0&quot;
    postcss-safe-parser &quot;4.0.1&quot;
    react-app-polyfill &quot;^1.0.6&quot;
    react-dev-utils &quot;^10.2.0&quot;
    resolve &quot;1.15.0&quot;
    resolve-url-loader &quot;3.1.1&quot;
    sass-loader &quot;8.0.2&quot;
    semver &quot;6.3.0&quot;
    style-loader &quot;0.23.1&quot;
    terser-webpack-plugin &quot;2.3.4&quot;
    ts-pnp &quot;1.1.5&quot;
    url-loader &quot;2.3.0&quot;
    webpack &quot;4.41.5&quot;
    webpack-dev-server &quot;3.10.2&quot;
    webpack-manifest-plugin &quot;2.2.0&quot;
    workbox-webpack-plugin &quot;4.3.1&quot;
  optionalDependencies:
    fsevents &quot;2.1.2&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これらの設定をカスタマイズしたい場合は、&lt;code&gt;yarn eject&lt;/code&gt;することで内部的に設定されていた内容で config 配下に設定ファイルが作られ、カスタマイズが可能です。
また、あわせて&lt;code&gt;react-script&lt;/code&gt;の依存関係のパッケージと設定が package.json に記述されます。
※ESLint に関しては&lt;code&gt;yarn eject&lt;/code&gt;しなくてもカスタマイズが可能でした。&lt;/p&gt;
&lt;p&gt;注意点として一度&lt;code&gt;yarn eject&lt;/code&gt;すると、元に戻すことはできません。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/mii288/items/aac597bc02575831ea90&quot;&gt;Docker環境内でcreate-react-app&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/matsuda_chikara/items/a4119a972535a4b69201&quot;&gt;Swagger EditorとSwagger UIとSwaggerのモックAPIサーバーをdocker-compose化してみた&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/geek_duck/items/6f99a3da15dd39658fff&quot;&gt;知っていると捗るcreate-react-appの設定&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WSLでexpo startして20～30分後にネットワーク不具合が起こる現象について調べたこと</title><link>https://changeofpace.site/posts/2019-10-09-wsl-expo-network</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-10-09-wsl-expo-network</guid><description>WSLでexpo startした際にネットワークが繋がらなくなる現象について、対応策などを調査したことまとめ</description><pubDate>Wed, 09 Oct 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回、&lt;a href=&quot;/posts/2019-09-28-wsl-react-native&quot;&gt;WSLでReact Native + Expo環境を作ろう&lt;/a&gt;という記事を書き、その末尾にも書いていた件。
&lt;code&gt;expo start&lt;/code&gt;したときに起こる謎のネットワーク不具合について調べたこと、わかったことを書いておきます。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/518e8f6e0b700b65b0fa&quot;&gt;Qiita&lt;/a&gt; からの転載です。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;ネットワーク不具合&lt;/h2&gt;
&lt;h3&gt;起こる問題&lt;/h3&gt;
&lt;p&gt;WSL で&lt;code&gt;expo start&lt;/code&gt;した後、20～30分後くらいにネットワークが繋がらなくなってしまう。&lt;/p&gt;
&lt;p&gt;どこのページにアクセスしようとしても以下のようにエラーが表示され、アクセスできません。
ここに表示されている Windows ネットワーク診断ツールを実行してみても、異常がないといわれます。
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2019/wsl-expo-network/network-error.png&quot; alt=&quot;ネット上のページにアクセスできないエラー画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;CONNECTION を Tunnel にした時も、&lt;code&gt;expo start --offline&lt;/code&gt;とオプションをつけた時も、同様の現象が起きます。&lt;/p&gt;
&lt;p&gt;一応、Windows 自体を再起動すれば、また繋がるようにはなるのですが...。
短時間の間に何度も再起動するのはかなり手間ですし、ストレスにもなります。&lt;/p&gt;
&lt;h3&gt;対応策&lt;/h3&gt;
&lt;p&gt;この現象について調べていても、なかなか情報が見つけられませんでしたが、GitHub にほぼ同じ現象が報告されていた Issue を見つけました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/expo/expo-cli/issues/99&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ここで緩和策として、以下の手順を踏めばいいとありました。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;I found a palliative solution not to restart the machine:
1 - WIN+R -&amp;gt; services.msc
2 - Find LxssManager
3 - Right-click -&amp;gt; Restart
This should restart the WSL service, normalizing the network.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Windows + R でファイル名を指定して実行の画面を出し、&lt;code&gt;services.msc&lt;/code&gt;と入力してOK
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/wsl-expo-network/file-run.png&quot; alt=&quot;ファイル名を指定して実行画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一覧から LxssManager を見つける
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/wsl-expo-network/lxss-manager.png&quot; alt=&quot;サービスの一覧画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;右クリックを押し、再起動&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;LxssManager is the service that manages the layer that communicates between Windows and Linux. Restarting it is basically equivalent to restarting Windows regarding everything that happens in WSL.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;LxssManager は、Windows と Linux の間で通信するレイヤーを管理するサービスらしく。
これを再起動することで、Windows を再起動したときと同じことが WSL 上で起こるそうです。&lt;/p&gt;
&lt;p&gt;この手順を踏めば即座に WSL が再起動され、ネットワークが繋がるようになります。&lt;/p&gt;
&lt;h3&gt;課題&lt;/h3&gt;
&lt;p&gt;上記の手順でネットワークが復旧する...のですが、そこからまた&lt;code&gt;expo start&lt;/code&gt;してしばらくすると、またネットワークが繋がらなくなります...。&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;This is likely an issue with WSL. There is an issue for this at &lt;a href=&quot;https://github.com/microsoft/WSL/issues/2913&quot;&gt;microsoft/WSL#2913&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;どうもこの現象は Expo CLI というより、WSL の問題らしく、Issue が上がっているそうです。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/microsoft/WSL/issues/2913&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Can confirm that Windows Insider Build 18890 fixes this issue.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Windows 10 Insider Build 18890では、この問題が修正されたとのこと。
調べてみると、このバージョンは2020年前半にリリース予定の大型アップデート「20H1」のプレビュービルドとなるそうです。
もう少しの間我慢になりますが、正式にリリースがくれば解決できそうですね（Windows Insider Programに参加する度胸はありません...）&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Obs: When I run the expo-cli start in CMD and then run on WSL, a network does not fail anymore....&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ちなみにこんなことも書いてありましたが、CMD ってことは Windows 側に Expo を入れるってことなのかな？と思い、あまり入れたくないので試していません。
直接 Windows に Expo（及びNode.js）を入れてもいいという方は、そうするのも1つの手ですね。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/expo/expo-cli/issues/99&quot;&gt;expo-cli/issues - Host remains without network after a while&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.softantenna.com/wp/windows/windows-10-insider-preview-build-18890/&quot;&gt;Windows 10 Insider Preview Build 18890がリリース - Fastリング向けFriendly Dates終了&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://taisy0.com/2019/05/02/109885.html&quot;&gt;Microsoft、｢Windows 10 Insider Preview build 18890 (20H1)｣をFastリング向けにリリース&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WSLでReact Native + Expo環境を作ろう</title><link>https://changeofpace.site/posts/2019-09-28-wsl-react-native</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-09-28-wsl-react-native</guid><description>WSLでExpoを使用して、React Native環境及びエミュレータ環境を作成する手順</description><pubDate>Sat, 28 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回、Docker で React Native 環境を作成した記事 を Qiita で書いて多くの方に見ていただけたようでした。
ただ、結局ホットリロードがうまく働かない、エミュレータ環境との連携がうまくいかない。等、使い勝手がイマイチだったので WSL で環境を作り直しました。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/610ffea888f13275cde8&quot;&gt;Qiita&lt;/a&gt; からの転載です。
前回の記事：&lt;a href=&quot;/posts/2019-09-14-docker-react-native&quot;&gt;DockerでReact Native環境作成から、Expo Clientで実機確認するまで&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;基本的な作成手順は一緒なので内容が重複するところもありますが、エミュレータに関する記述も少しあわせて書いておきます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Node.js をインストール済み（自分は anyenv + nodenv でいれてます）&lt;/li&gt;
&lt;li&gt;npm or yarn コマンドが実行できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;p&gt;React Native の開発支援サービスである Expo を使用します。
Expo に関する説明は前回も書いたので省略します。&lt;/p&gt;
&lt;h3&gt;Expo アカウントを作成&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://expo.io&quot; /&amp;gt;
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/wsl-react-native/expo.png&quot; alt=&quot;Expoの公式サイト トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://expo.io/&quot;&gt;expo.io&lt;/a&gt;にいき、「Create an account」を選択。&lt;/li&gt;
&lt;li&gt;e-mail、ユーザ名、パスワードを入力して「Create your account」を選択。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Expo Client をインストール&lt;/h3&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/wsl-react-native/expo-client.png&quot; alt=&quot;Expo ClientのApp Store画面&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用する iOS/Android 端末に App Store / Google Play からインストール
&amp;lt;OG url=&quot;https://apps.apple.com/jp/app/expo-client/id982107779&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=ja&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;作成した Expo アカウントでログインしておく。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Expo CLI のインストール&lt;/h3&gt;
&lt;p&gt;npm もしくは yarn でインストールします。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# npm
npm install -g expo-cli

# yarn
yarn global add expo-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Expo プロジェクト&lt;/h2&gt;
&lt;h3&gt;作成&lt;/h3&gt;
&lt;p&gt;app の部分は任意の Expo プロジェクトフォルダ名を指定します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;expo init app
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テンプレートを選択します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# expo init app
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank                 a minimal app as clean as an empty canvas
  blank (TypeScript)    same as blank but with TypeScript configuration
  tabs                  several example screens and tabs using react-navigation
  ----- Bare workflow -----
  minimal               bare and minimal, just the essentials to get you started
  minimal (TypeScript)  same as minimal but with TypeScript configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo プロジェクトの表示名を聞かれます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Choose a template: expo-template-blank
? Please enter a few initial configuration values.
  Read more: https://docs.expo.io/versions/latest/workflow/configuration/ ‣ 0% completed
 {
   &quot;expo&quot;: {
     &quot;name&quot;: &quot;&amp;lt;The name of your app visible on the home screen&amp;gt;&quot;,
     &quot;slug&quot;: &quot;app&quot;
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yarn を使ってパッケージをインストールするか聞かれます。
Y でインストール実行。（n を選択すると npm でパッケージをインストールします）&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Yarn v1.17.3 found. Use Yarn to install dependencies? (Y/n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで Expo プロジェクトのひな型が作成されました。
ちなみに Expo プロジェクト作成後のディレクトリ構成は以下のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;例
ReactNative（大元のプロジェクトフォルダ）
└ app（Expo プロジェクトフォルダ）
   ├ .expo
   ├ .expo-shared
   ├ assets
   ├ node_moodules
   ├ .gitignore
   ├ .watchmanconfig
   ├ App.js
   ├ app.json
   ├ babel.config.js
   ├ package.json
   └ yarn.lock
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;起動&lt;/h3&gt;
&lt;h4&gt;環境変数&lt;/h4&gt;
&lt;p&gt;Expo サーバ起動時の IP を設定します。
デフォルトでは Ubuntu の方の IP を使用し、CONNECTION を LAN で接続するため、Expo Client から接続できません。
（一応、サーバ起動後に CONNECTION を Tunnel にすることで接続は可能です）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;Ubuntu側で以下を&lt;code&gt;~/.profile&lt;/code&gt;など各種 profile のいずれかに追記&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;export REACT_NATIVE_PACKAGER_HOSTNAME=(Windows側のIP)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;各種 profile の読み込み&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 例
source ~/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Expo サーバ起動&lt;/h4&gt;
&lt;p&gt;以下のいずれかで Expo サーバとして起動。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;expo start

npm start

yarn start
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;しばらくするとQRコードが表示されるとともに、Expo DevTools（localhost:19002）がブラウザで自動的に立ち上がります。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/wsl-react-native/expo-start.png&quot; alt=&quot;expo start時のQR表示画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;動作確認&lt;/h2&gt;
&lt;h3&gt;実機確認&lt;/h3&gt;
&lt;p&gt;Expo サーバ起動時に表示された QR を、iOS/Android 端末で読み込むことで、Expo Client 起動。ビルド開始。&lt;/p&gt;
&lt;p&gt;なお、注意点として、&lt;strong&gt;Expo サーバになる PC と iOS/Android 端末は同じネットワークにつないでいる必要があります。&lt;/strong&gt;
しばらくしてビルドが終わると初期ガイドが表示されます。
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/wsl-react-native/app-guide.png&quot; alt=&quot;Expoアプリのガイド画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ガイドを消すと、Expo アプリの画面が表示されます。これが実行結果になります。
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/wsl-react-native/app-preview.png&quot; alt=&quot;Expoアプリの実行結果画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;なお、この状態でコードを変更すると、即座に再読み込みが走り画面に反映されます。
ライブリロードが走っているそうです（ホットリロードとの違いがよくわかってません）&lt;/p&gt;
&lt;h3&gt;エミュレータでの確認&lt;/h3&gt;
&lt;p&gt;Windows なので、Android のエミュレータのみを書いておきます。&lt;/p&gt;
&lt;h4&gt;Android Studio のインストール&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://developer.android.com/studio?gclid=CjwKCAjwvpCkBhB4EiwAujULMhT4FlcbucI9A6orIx6JaS8MGdt-v1Pj7FM3rTiPk6l9J7pSnvrWaBoCf9cQAvD_BwE&amp;amp;gclsrc=aw.ds&quot; /&amp;gt;
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/wsl-react-native/android-studio.png&quot; alt=&quot;Android Studioの公式サイト トップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;公式からダウンロード。&lt;/li&gt;
&lt;li&gt;ダウンロードしたインストーラを起動して、インストール（少し時間がかかります）&lt;/li&gt;
&lt;li&gt;インストールした Android Studio を起動&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;初回はセットアップウィザードがあるので、案内に沿って進めていきます&lt;/li&gt;
&lt;li&gt;必要に応じてインストールする SDK を選択
（&lt;code&gt;Android SDK Build-Tools&lt;/code&gt;や&lt;code&gt;Android Emulator&lt;/code&gt;、&lt;code&gt;Android SDK Platform-Tools&lt;/code&gt;、&lt;code&gt;Android SDK Tools&lt;/code&gt;などは最初からチェックが入っているかと思われます）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;あとから SDK を追加したい場合は、トップ画面の右下の「Configure」→「SDK Manager」から SDK の一覧画面へ行けます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable  --&amp;gt; */}&lt;/p&gt;
&lt;h4&gt;環境変数の設定&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Android Studio のトップ画面の右下の「Configure」→「SDK Manager」から SDK の一覧画面へ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Android SDK Location を控えておく&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;環境変数を追記
Windows のシステム環境変数に追記します。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;ANDROID_SDK ... (※2でコピーしたパス)
PATH ... %ANDROID_SDK%\emulator と %ANDROID_SDK%\pratform_tools を追加
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;emulator&lt;/code&gt;コマンドと&lt;code&gt;adb&lt;/code&gt;コマンドが使えるか確認&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Virtual Deviceの用意&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Android Studioトップ画面の「Configure」→「AVD Manager」を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;「Create Virtual Device」を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;デバイスを選択して、システムイメージを選択して作成&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;エミュレータの起動&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Android Studioトップ画面の「Configure」→「AVD Manager」からデバイス一覧へ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Actions のプレイボタンを選択して起動&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;なお、コマンドでエミュレータを起動する場合は以下のコマンドでいけます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# デバイス名の一覧確認
emulator -list-avds

# 指定デバイスのエミュレータの起動
emulator -avd (デバイス名)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Expo サーバを起動し、Expo DevTools（localhost:19002）の「Run on Android device/emulator」を選択
初回のみエミュレータに Expo Client のインストールがあります。
また、エミュレータでも同様に、コードを変更すると再読み込みが走ります。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;WSL で無事環境が作れたのはよかったのですが、Expo サーバを起動後にしばらくしてネットが繋がらなくなる？（一応繋がってはいるが、ネット上のページにアクセスできない）状態になるのが謎です...。PCを再起動したら直ります。
もしかしたら Expo 関係ない可能性がありますが、Expo のサーバ立ち上げたときしかその現象起きないんですよね。これさえなければいいのになぁ。
↓
2019/10/09追記
これについて調べたことを記事に書きました。
&lt;a href=&quot;/posts/2019-10-09-wsl-expo-network&quot;&gt;WSLでexpo startして20～30分後にネットワーク不具合が起こる現象について調べたこと&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://expo.io/&quot;&gt;Expo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://developer.android.com/studio?gclid=CjwKCAjwvpCkBhB4EiwAujULMhT4FlcbucI9A6orIx6JaS8MGdt-v1Pj7FM3rTiPk6l9J7pSnvrWaBoCf9cQAvD_BwE&amp;amp;gclsrc=aw.ds&quot;&gt;Android Studio&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>WSLでWindowsの中にLinuxの開発環境を作ろう</title><link>https://changeofpace.site/posts/2019-09-26-wsl-develop</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-09-26-wsl-develop</guid><description>WSLでUbuntuを導入し、Linuxbrew、anyenvで開発環境を作成する手順</description><pubDate>Thu, 26 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;個人 PC を新しくしてから、WSL（Windows Subsystem for Linux）をいれてなかったので改めていれてみました。
基本的には参考記事通りに進めましたが、追加でインストールするものも含めて備忘録として手順を書いておきます。&lt;/p&gt;
&lt;p&gt;※この記事は &lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/15653d08f917ad6e39f8&quot;&gt;Qiita&lt;/a&gt; からの転載です。
※2021/04/22追記 またセットアップする機会があったので全体的に少し内容を更新しました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;WSL&lt;/h2&gt;
&lt;h3&gt;WSLとは？&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux&quot; /&amp;gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Windows Subsystem for Linux (WSL) とは、Linux の（ELF フォーマット形式の）バイナリ実行ファイルを Windows 10 および Windows Server 上でネイティブ実行するための互換レイヤーである。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;ざっくりいえば、その名の通り、Windows のサブシステムとして Linux を動かす仕組みという解釈でいいかなと思っています。
WSL では完全な Linux を動かすことはできませんが、5月に発表された強化版の WSL2 では100%互換が実現されてるみたいですね。
まだこちらはベータ版のみの提供のようなので、今回は素直に WSL を使うことにしました。&lt;/p&gt;
&lt;p&gt;自分はなるべく Windows に直接言語をいれたりしたくない派なので、Docker 環境を作ったり、WSL 側に言語をいれたりしてます。&lt;/p&gt;
&lt;p&gt;※2021/04/22追記
ちなみに WSL2 が正式にリリースされてからも、自分は WSL を使っています。
WSL2 の良さはあるでしょうが、どうも WSL2 → Windows のファイルアクセスが遅いとのことだったので、このままでいいかとそのままにしています。&lt;/p&gt;
&lt;h3&gt;有効化&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;左下の Windows ボタンを右クリックし、「アプリと機能」を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;下部の方にある関連設定の「プログラムと機能」を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;左メニューの「Windows の機能の有効化または無効化」を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;機能一覧の中から「Linux 用 Windows サブシステム」にチェックをつけて「OK」
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2019/wsl-develop/enable-wsl.png&quot; alt=&quot;Windowsの機能一覧&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;設定の変更が行われた後、Windows を再起動するよう促されるので、再起動する&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Ubuntu のインストール&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Microsoft Store を開く&lt;/li&gt;
&lt;li&gt;「Ubuntu」で検索する
3つの種類（Ubuntu、Ubuntu 20.04 LTS、Ubuntu 18.04 LTS）がありますがお好みで。&lt;/li&gt;
&lt;li&gt;Ubuntu をインストール&lt;/li&gt;
&lt;li&gt;Ubuntu を起動して、初回起動時の設定として username と password を設定する
（ここで決めたパスワードが sudo コマンド使用時に必要となります）&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;これで WSL が利用できるようになりました。
ちなみにバージョンは&lt;code&gt;cat /etc/os-release&lt;/code&gt;で確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;cat /etc/os-release

NAME=&quot;Ubuntu&quot;
VERSION=&quot;20.04.2 LTS (Focal Fossa)&quot;
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME=&quot;Ubuntu 20.04.2 LTS&quot;
VERSION_ID=&quot;20.04&quot;
HOME_URL=&quot;https://www.ubuntu.com/&quot;
SUPPORT_URL=&quot;https://help.ubuntu.com/&quot;
BUG_REPORT_URL=&quot;https://bugs.launchpad.net/ubuntu/&quot;
PRIVACY_POLICY_URL=&quot;https://www.ubuntu.com/legal/terms-and-policies/privacy-policy&quot;
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;初期設定&lt;/h3&gt;
&lt;h4&gt;パッケージリストの更新&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;日本語化対応とタイムゾーン&lt;/h4&gt;
&lt;p&gt;※2021/04/22追記 この辺はお好みで。英語のままで問題ない方はスキップ。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;日本語言語パックのインストールとロケールの設定&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install -y language-pack-ja
sudo update-locale LANG=ja_JP.UTF-8
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで locale が変更されます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;locale

LANG=ja_JP.UTF-8
LANGUAGE=
LC_CTYPE=&quot;ja_JP.UTF-8&quot;
LC_NUMERIC=&quot;ja_JP.UTF-8&quot;
LC_TIME=&quot;ja_JP.UTF-8&quot;
LC_COLLATE=&quot;ja_JP.UTF-8&quot;
LC_MONETARY=&quot;ja_JP.UTF-8&quot;
LC_MESSAGES=&quot;ja_JP.UTF-8&quot;
LC_PAPER=&quot;ja_JP.UTF-8&quot;
LC_NAME=&quot;ja_JP.UTF-8&quot;
LC_ADDRESS=&quot;ja_JP.UTF-8&quot;
LC_TELEPHONE=&quot;ja_JP.UTF-8&quot;
LC_MEASUREMENT=&quot;ja_JP.UTF-8&quot;
LC_IDENTIFICATION=&quot;ja_JP.UTF-8&quot;
LC_ALL=
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;タイムゾーンの設定、日本語の manpage をインストール
タイムゾーンに関してはアジア → 東京を選択してください。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo dpkg-reconfigure tzdata
sudo apt install -y manpages-ja manpages-ja-dev
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これでタイムゾーンが JST になります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;date

2019年 9月 22日 日曜日 12:13:56 JST
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※2021/04/22追記
この設定をしなくても一応 JST になっていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;date

Wed Apr 21 16:44:54 JST 2021
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;※ここ以降で載せている各種パスの&lt;code&gt;{USER}&lt;/code&gt;部分は、人それぞれのユーザ名になりますのでご注意ください。&lt;/strong&gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;h3&gt;Windows・WSL それぞれから見た互いのパス&lt;/h3&gt;
&lt;h4&gt;Windows から見た WSL&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;C:\Users\{USER}\AppData\Local\Packages\CanonicalGroupLimited.{ディストリビューション固有の文字列}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このパスからさらに&lt;code&gt;\LocalState\rootfs&lt;/code&gt;で&lt;code&gt;bin&lt;/code&gt;、&lt;code&gt;dev&lt;/code&gt;、&lt;code&gt;etc&lt;/code&gt;、&lt;code&gt;mnt&lt;/code&gt;、&lt;code&gt;opt&lt;/code&gt;などがあります。&lt;/p&gt;
&lt;p&gt;以前は Windows 側から WSL 側のファイルを触るとファイル破損の可能性があったため推奨されていなかったようですが、現在は大丈夫になったみたいです。
&amp;lt;OG url=&quot;https://forest.watch.impress.co.jp/docs/news/1170221.html&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;WSL から見た Windows&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;/mnt/c
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;/mnt&lt;/code&gt;から C ドライブが参照できます。&lt;/p&gt;
&lt;h2&gt;追加でインストールするもの&lt;/h2&gt;
&lt;h3&gt;Linuxbrew&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://brew.sh/ja/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Mac ではおなじみのパッケージ管理ツールである Homebrew の Linux 版です。
Homebrew 2.0.0 からは Homebrew に統合され、正式に Linux をサポートするようになったそうです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;事前に必要なもののインストール
中にはすでに入っているものもあるかと思われます。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo apt install build-essential procps curl file git
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Linuxbrew のインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;/bin/bash -c &quot;$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールが終わると次のステップに関する記述が表示されますので、その通りに進めていきます。
build-essential のインストールは先に済ませているのでスキップ。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;==&amp;gt; Next steps:
- Add Homebrew to your PATH in /home/{USER}/.profile:
    echo &apos;eval &quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&quot;&apos; &amp;gt;&amp;gt; /home/{USER}/.profile
    eval &quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&quot;
- Run `brew help` to get started
- Further documentation:
    https://docs.brew.sh
- Install the Homebrew dependencies if you have sudo access:
    sudo apt-get install build-essential
    See https://docs.brew.sh/linux for more information
- We recommend that you install GCC:
    brew install gcc
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;パスを通す&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;echo &apos;eval &quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&quot;&apos; &amp;gt;&amp;gt; /home/{USER}/.profile
eval &quot;$(/home/linuxbrew/.linuxbrew/bin/brew shellenv)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;gcc のインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install gcc
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/max-comma --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;依存関係のあるものも一緒にインストールされます。（&lt;code&gt;gmp&lt;/code&gt;, &lt;code&gt;isl&lt;/code&gt;, &lt;code&gt;mpfr&lt;/code&gt;, &lt;code&gt;libmpc&lt;/code&gt;, &lt;code&gt;zlib&lt;/code&gt;, &lt;code&gt;binutils&lt;/code&gt;）&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;brew が正常に動作するか確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew doctor
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のような表示が出れば OK です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Your system is ready to brew.
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;anyenv&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/anyenv/anyenv&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;言語を複数バージョンで管理できる、〇〇env を一元管理できるものです。
今回は Linuxbrew を使用してインストールしていきます。&lt;/p&gt;
&lt;h4&gt;本体のインストール&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install anyenv
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;anyenv のセットアップ&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらを実行すると以下のように、&lt;code&gt;~/.bash_profile&lt;/code&gt;に追記するように表示が出ます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Load anyenv automatically by adding
# the following to ~/.bash_profile:

eval &quot;$(anyenv init -)&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;各種 profile のいずれかに追記
（自分は&lt;code&gt;~/.profile&lt;/code&gt;に追記しました）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;echo -e &apos;eval &quot;$(anyenv init -)&quot;&apos; &amp;gt;&amp;gt; ~/.profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみにこの段階で Ubuntu を再起動すると以下のような警告が出ますが、次の手順で解消できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ANYENV_DEFINITION_ROOT(/home/{USER}/.config/anyenv/anyenv-install) doesn\&apos;t exist. You can initialize it by:
&amp;gt; anyenv install --init
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;マニフェストディレクトリの用意
チェックアウトしますか？と聞かれるので、y で進みます。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv install --init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで anyenv で各種 env がインストールできるようになりました。
なお、インストールできる各種 env は以下のコマンドで確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anyenv install -l

Renv
crenv
denv
erlenv
exenv
goenv
hsenv
jenv
jlenv
luaenv
nodenv
phpenv
plenv
pyenv
rbenv
sbtenv
scalaenv
swiftenv
tfenv
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;anyenv-update のインストール&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/znz/anyenv-update&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;anyenv で管理している各種 env とそれらのプラグインのアップデートをまとめて行えるものです。
あわせて入れておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p $(anyenv root)/plugins
git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで以下のコマンドでアップデートを実行できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anyenv update
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;nodenv のインストール&lt;/h4&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/nodenv/nodenv&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ここでは試しに anyenv を使って nodenv をインストールしてみます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv install nodenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールが終わると各種 profile をリロードするか、Ubuntu を再起動するように言われます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Install nodenv succeeded!
Please reload your profile (exec $SHELL -l) or open a new session.
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;各種 profile をリロード&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;exec $SHELL -l
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで nodenv が使えるようになりました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodenv

nodenv 1.4.0+3.631d0b6
Usage: nodenv &amp;lt;command&amp;gt; [&amp;lt;args&amp;gt;]

Some useful nodenv commands are:
   commands    List all available nodenv commands
   local       Set or show the local application-specific Node version
   global      Set or show the global Node version
   shell       Set or show the shell-specific Node version
   install     Install a Node version using node-build
   uninstall   Uninstall a specific Node version
   rehash      Rehash nodenv shims (run this after installing executables)
   version     Show the current Node version and its origin
   versions    List installed Node versions
   which       Display the full path to an executable
   whence      List all Node versions that contain the given executable

See `nodenv help &amp;lt;command&amp;gt;` for information on a specific command.
For full documentation, see: https://github.com/nodenv/nodenv#readme
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールできる Node.js のバージョン一覧は以下で確認できます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodenv install --list
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Node.js をインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;nodenv install (バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;使用する Node.js のバージョンを指定&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# グローバルの場合
nodenv global (バージョン)

# ローカルの場合
nodenv local (バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;インストールしているバージョンの一覧は以下で確認できます。
なお、ここで*がついているのが使用しているバージョンです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;nodenv versions
* 14.2.0 (set by /home/{USER}/.anyenv/envs/nodenv/version)
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://brew.sh/index_ja&quot;&gt;Homebrew&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/anyenv/anyenv&quot;&gt;GitHub - anyenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/znz/anyenv-update&quot;&gt;GitHub - anyenv-update&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ja.wikipedia.org/wiki/Windows_Subsystem_for_Linux&quot;&gt;Windows Subsystem for Linux - wikipedia&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.aise.ics.saitama-u.ac.jp/~gotoh/HowToEnableWSL.html&quot;&gt;WSLの有効化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/rubytomato@github/items/fdfc0a76&quot;&gt;WSL (Windows Subsystem for Linux)の基本メモ&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://forest.watch.impress.co.jp/docs/news/1170221.html&quot;&gt;WindowsからLinuxファイルへのアクセスが可能に ～「Windows 10 19H1」におけるWSLの改善&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>DockerでReact Native環境作成から、Expo Clientで実機確認するまで</title><link>https://changeofpace.site/posts/2019-09-14-docker-react-native</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-09-14-docker-react-native</guid><description>DockerとExpoを使って、React Nativeプロジェクトのひな型を作り、実機確認するまでの手順</description><pubDate>Sat, 14 Sep 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;iOS、Android 両方に対応するアプリを作成できる React Native。
最近、個人的に興味がわいたので、Docker環境作成にチャレンジしてみたのですが、あまり記事を見かけなかったので構築手順を書いておきます。&lt;/p&gt;
&lt;p&gt;※この記事は&lt;a href=&quot;https://qiita.com/h-yoshikawa44/items/51b631ec0a6beabdce2d&quot;&gt;Qiita&lt;/a&gt;からの転載です。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2019/9/21追記&lt;/strong&gt;
&lt;strong&gt;後述しますが、自分がやった時にホットリロードが効かなかったり、エミュレータ環境との連動がうまくできなかったりしてます...。&lt;/strong&gt;
&lt;strong&gt;自分のやり方が悪いだけかもしれませんが、あまり Docker で構築するメリットがないかもしれません。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2019/9/28追記&lt;/strong&gt;
WSL で環境作り直しました。
→ &lt;a href=&quot;/posts/2019-09-28-wsl-react-native&quot;&gt;WSLでReact Native + Expo環境を作ろう&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;docker 導入済み&lt;/li&gt;
&lt;li&gt;docker-compose 導入済み&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;環境構築手順&lt;/h2&gt;
&lt;h3&gt;事前準備&lt;/h3&gt;
&lt;p&gt;React Native で開発をしていく上で Expo を使うと便利です。
環境構築も比較的楽に行え、アプリの内容を QR コードで発行して簡単に実機確認ができます。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://expo.io&quot; /&amp;gt;
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/docker-react-native/expo.png&quot; alt=&quot;Expoの公式サイト&quot; /&amp;gt;&lt;/p&gt;
&lt;h4&gt;Expo アカウントを作成&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;&lt;a href=&quot;https://expo.io/&quot;&gt;expo.io&lt;/a&gt;にいき、「Create an account」を選択&lt;/li&gt;
&lt;li&gt;e-mail、ユーザ名、パスワードを入力して「Create your account」を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;h4&gt;Expo Client をインストール&lt;/h4&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/docker-react-native/expo-client.png&quot; alt=&quot;Expo ClientのApp Store画面&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;使用する iOS/Android 端末に App Store / Google Play からインストール
&amp;lt;OG url=&quot;https://apps.apple.com/jp/app/expo-client/id982107779&quot; /&amp;gt;
&amp;lt;OG url=&quot;https://play.google.com/store/apps/details?id=host.exp.exponent&amp;amp;hl=ja&quot; /&amp;gt;&lt;/li&gt;
&lt;li&gt;作成した Expo アカウントでログインしておく&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Docker環境作成&lt;/h3&gt;
&lt;p&gt;自分の場合は以下のディレクトリ構成で行っています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;React Native(プロジェクトフォルダ)
├ docker
│ ├ node
│ └ Dockerfile
├ .env
└ docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Dockerfile&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;FROM &quot;node:10-alpine&quot;

WORKDIR /usr/src/app/

RUN apk update &amp;amp;&amp;amp; apk add bash

RUN yarn global add expo-cli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;後ほど&lt;code&gt;expo start&lt;/code&gt;する際に&lt;code&gt;bash&lt;/code&gt;が必要になりますが、使用しているイメージの alpine linuxには&lt;code&gt;bash&lt;/code&gt;が入っていないため、インストールしています。
はじめから&lt;code&gt;bash&lt;/code&gt;が入っているイメージを使用するのもありです。&lt;/p&gt;
&lt;p&gt;React Native のプロジェクトを作成する CLI ツールとして、以前は&lt;code&gt;create-react-native-app&lt;/code&gt;があったそうです。
現在は&lt;code&gt;expo-cli&lt;/code&gt;に統合されたそうなのでこちらをインストール。&lt;/p&gt;
&lt;h4&gt;.env&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;REACT_NATIVE_PACKAGER_HOSTNAME=(自分のローカルPCのIPアドレス)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;コンテナに渡す環境変数を定義します。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;REACT_NATIVE_PACKAGER_HOSTNAME&lt;/code&gt;は、後ほど&lt;code&gt;expo start&lt;/code&gt;で Expo アプリをホスティングする際のIPになります。
デフォルトではコンテナの IP を使用してしまうために、Expo Client から接続ができません。
そのためローカル PC 自体の IP を指定しておきます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# Windows
ipconfig

# Mac
ifconfig
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;docker-compose.yml&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;version: &quot;3&quot;
services:
  node:
    build: ./docker/node
    volumes:
      - ./app/:/usr/src/app
    tty: true
    stdin_open: true
    environment:
      - REACT_NATIVE_PACKAGER_HOSTNAME=${REACT_NATIVE_PACKAGER_HOSTNAME}
    ports:
      - &quot;19000:19000&quot;
      - &quot;19001:19001&quot;
      - &quot;19002:19002&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;tty、stdin_open：&lt;code&gt;docker-compose up&lt;/code&gt;したコンテナを起動させたままにするため設定&lt;/li&gt;
&lt;li&gt;environment：.envファイルで定義した環境変数を設定&lt;/li&gt;
&lt;li&gt;port：Expo で使用する3つのポートを設定&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;起動&lt;/h4&gt;
&lt;p&gt;バックグラウンドで起動。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;RUN yarn global add expo-cli&lt;/code&gt;の個所で WARN がたくさん出ますが、ERROR がなければ動いてくれると思われます。
（すみません、WARNの内容まではちゃんと見られていません）&lt;/p&gt;
&lt;h2&gt;Expo プロジェクト&lt;/h2&gt;
&lt;h3&gt;作成&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;# コンテナの中に入る
docker-compose exec node bash

# expo プロジェクト作成
expo init .
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;テンプレートが選択できます。
とりあえずは blank を選択して Enter。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bash-4.4# expo init .
? Choose a template: (Use arrow keys)
  ----- Managed workflow -----
❯ blank                 a minimal app as clean as an empty canvas
  blank (TypeScript)    same as blank but with TypeScript configuration
  tabs                  several example screens and tabs using react-navigation
  ----- Bare workflow -----
  minimal               bare and minimal, just the essentials to get you started
  minimal (TypeScript)  same as minimal but with TypeScript configuration
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Expo プロジェクトの表示名を聞かれます。
入力して Enter。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Choose a template: expo-template-blank
? Please enter a few initial configuration values.
  Read more: https://docs.expo.io/versions/latest/workflow/configuration/ ‣ 0% completed
 {
   &quot;expo&quot;: {
     &quot;name&quot;: &quot;&amp;lt;The name of your app visible on the home screen&amp;gt;&quot;,
     &quot;slug&quot;: &quot;app&quot;
   }
 }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;yarn を使ってパッケージをインストールするか聞かれます。Y でインストール実行。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;? Yarn v1.17.3 found. Use Yarn to install dependencies? (Y/n)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで Expo プロジェクトのひな型が作成されました。&lt;/p&gt;
&lt;h3&gt;起動&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;expo start&lt;/code&gt;もしくは&lt;code&gt;yarn start&lt;/code&gt;で Expo サーバとして起動。
しばらくするとQRコードが表示されます。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2019/docker-react-native/expo-start.png&quot; alt=&quot;expo startの画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Expo Client で実機確認&lt;/h3&gt;
&lt;p&gt;表示された QR コードを iOS/Android 端末から読み込むと、Expo Client 起動。ビルド開始。
なお、注意点として、Expo サーバになる PC と iOS/Android 端末は同じネットワークにつないでいる必要があります。
しばらくしてビルドが終わると初期ガイドが表示されます。
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/docker-react-native/app-guide.png&quot; alt=&quot;Expoアプリのガイド&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;ガイドを消すと、Expo アプリの画面が表示されます。これが実行結果です。
blank テンプレートを選択したのでシンプルな画面ですね。
一連の手順で比較的楽に実機確認できました。
&lt;strong&gt;ただ、ホットリロードがうまく動作していないのか、コードの変更が即座に反映されませんでした。なぜ...。&lt;/strong&gt;
&amp;lt;ImageWrapper className=&quot;w-[40%]&quot; src=&quot;screenshots/2019/docker-react-native/app-preview.png&quot; alt=&quot;Expoアプリの実行画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;補足&lt;/h2&gt;
&lt;h3&gt;http://localhost:19002&lt;/h3&gt;
&lt;p&gt;アクセスすると、Expo DevTool が使用できます。
ここからシミュレータを起動したりできるみたいです。
（X Code、Android Studio のインストールや設定が別途必要になるようです）
末尾にも書いていますが、自分はうまくいきませんでした...。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-native/expo-devtool.png&quot; alt=&quot;Expo DevTool画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;http://localhost:19001&lt;/h3&gt;
&lt;p&gt;HTML が表示されます。
&lt;code&gt;expo start&lt;/code&gt;時の表示で Metro Bundler だと書いてありましたが、なんなのかはよくわかってません。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-native/metro-bundler.png&quot; alt=&quot;metro bundler画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;http://localhost:19000&lt;/h3&gt;
&lt;p&gt;Expo プロジェクトの情報？が表示されます。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-react-native/expo-server.png&quot; alt=&quot;Expoサーバの情報&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;エミュレータについて(2019/9/18追記)&lt;/h3&gt;
&lt;p&gt;自分の環境は Windows なので、Docker 環境の中に Android のエミュレータ環境を作ろうとしましたがうまくいきませんでした...。&lt;/p&gt;
&lt;p&gt;一応やってみたこととしてはこんな感じ。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;openjdk をインストール&lt;/li&gt;
&lt;li&gt;Android 公式からコマンドラインの zip ファイルをダウンロード&lt;/li&gt;
&lt;li&gt;Andoid SDK を格納するディレクトリを作成し、zip ファイルを解凍して配置&lt;/li&gt;
&lt;li&gt;Android SDK に関するパスを設定&lt;/li&gt;
&lt;li&gt;sdkmanager、avdmanager コマンドが実行できることを確認&lt;/li&gt;
&lt;li&gt;sdkmanager コマンドで必要な SDK をインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;ここでエミュレータをインストールしたのですが、なぜか emulator コマンドが実行できませんでした。
パスはちゃんと通しているはずなのに、ひたすら&lt;code&gt;No such file or directory&lt;/code&gt;が出てしまい...。一体なんなのか。&lt;/p&gt;
&lt;p&gt;WSL で環境構築した方が、早そうです。
だから Docker で構築している記事がほとんどなかったんでしょうか...。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://medium.com/@hmajid2301/running-expo-react-native-in-docker-ff9c4f2a4388&quot;&gt;Running Expo/React Native in Docker&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/hitotch/items/ea4de1ed408a9ca14fce#expo%E3%83%97%E3%83%AD%E3%82%B8%E3%82%A7%E3%82%AF%E3%83%88%E3%82%92%E4%BD%9C%E6%88%90%E3%81%99%E3%82%8B&quot;&gt;Expoのアカウントを作ってReact Nativeの開発環境を整える&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>TILリポジトリが150コミット突破</title><link>https://changeofpace.site/posts/2019-08-17-til-150</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-08-17-til-150</guid><description>勉強用リポジトリである、TILリポジトリのコミット数が150突破</description><pubDate>Sat, 17 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、6月に復職して少しずつ復帰していっているよしです。
地道にコミットしていた、勉強用リポジトリのコミット数が150を超えていました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2021年末くらいからは Notion で TIL 活動をすることにしたので、リポジトリの活動停止してます。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;TIL リポジトリ&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/til-engineer&quot; /&amp;gt;
※2021/02/14追記...アカウント名、リポジトリ名を変えました。&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/til-150/github-til.png&quot; alt=&quot;GitHub TILリポジトリ&quot; /&amp;gt;
※contributors が2になっているのは、個人アカウントと仕事アカウントで利用しているからです。&lt;/p&gt;
&lt;p&gt;3月に投稿した&lt;a href=&quot;/posts/2019-03-26-til&quot;&gt;TILリポジトリで小さなアウトプット&lt;/a&gt;の記事。
ここから開始して5か月ほどになりましたが、意外とあっという間でしたね。
ちゃんと続けられているなーと。&lt;/p&gt;
&lt;h3&gt;コミット内容&lt;/h3&gt;
&lt;p&gt;内容に関しては、勉強のアウトプットということで、言語やフレームワークの公式チュートリアル、参考書、講習サイトなどのコードをあげています。それとメモ書きとか。&lt;/p&gt;
&lt;p&gt;さすがに勉強したすべてを覚えているわけではないですが。
元々あとから振り返られるような、自分の引き出しが増えるようなという意味合いではじめたので、まぁそうなっていればいいです。&lt;/p&gt;
&lt;h3&gt;コミット履歴&lt;/h3&gt;
&lt;p&gt;GitHub のアカウントのコミット履歴を見るのも少し楽しみになりました。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/til-150/github-commit.png&quot; alt=&quot;GitHub コミット履歴&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;5月頑張ってたみたいですね。
休職期間だったので家にいたっていうのもありますが。&lt;/p&gt;
&lt;p&gt;毎日1コミットしようかと考えていた時もありましたが、最近はペースが落ちてます。
著名なエンジニアの方だとほぼ毎日コミットしていてすごいなと。
どうにか時間を捻出しておられるんだろうなーと。毎日コミットは自分にはまだハードルが高いようです。&lt;/p&gt;
&lt;h2&gt;余談&lt;/h2&gt;
&lt;p&gt;そういえば仮に転職活動をしていたとして、こういう勉強用リポジトリってどういう見方されるんでしょうね。
コミット数見て少しは評価されるのか、実際に何か作っているわけではないので何も評価されないのか。
評価されないのならやらないなんてことはしないですけど、ちょっと気になりました。&lt;/p&gt;
&lt;p&gt;個人開発をやろうかと考えていた時もありましたが、結局しょうもないものしかできなくてモチベーションが持ちませんでした。
でも、いずれは自分で何か作ってみたいと思っています。というか作れるくらいにならないといけないなと。
しょうもないものとか気にしていると、いつまでも作れないでしょうし、恥ずかしからず最初はハードルを下げて取り組めたらいいなと。&lt;/p&gt;
&lt;p&gt;さていつになるやら。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;次は300、500とかになったら、また雑談記事でも書こうかと構想中です。
1000コミットになる頃には、自分はどこにいて何をしているかなぁ。
プログラマーの仕事は好きなのでちゃんと続けられているといいなと思いつつも、体調崩して休職することになってしまった仕事でもあるので、無理は禁物ですね。&lt;/p&gt;
&lt;p&gt;まだまだ暑いので、皆様もお身体お気をつけて。ではではー。&lt;/p&gt;
</content:encoded></item><item><title>RailsのDocker環境作成</title><link>https://changeofpace.site/posts/2019-08-14-docker-rails</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-08-14-docker-rails</guid><description>Docker公式手順で構築するなかで遭遇したエラーと対処法の記録</description><pubDate>Wed, 14 Aug 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Rails チュートリアルを勉強したかったので、Docker 公式を参考に Docker 環境を作成しました。
その際に少しやり方を変えたのと、いくつかエラーに遭遇したので、そのメモを書いておきます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker 導入済み&lt;/li&gt;
&lt;li&gt;docker-compose コマンドが使用できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;なお、この記事の手順では Rails 5系の環境となる点に注意です。&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;Docker 環境&lt;/h2&gt;
&lt;h3&gt;作成手順&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/docker/awesome-compose/tree/master/official-documentation-samples/rails/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※2021/02/07追記 少し公式の手順が変わっていたので見直しを行いました。&lt;/p&gt;
&lt;p&gt;公式では、すべてのファイルをルートに置いていますが、自分は以下のようなディレクトリ構造にしたかったので変えました。&lt;/p&gt;
&lt;p&gt;自分の場合のディレクトリ構造はこちら。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(root)
├ docker
│ └ ruby
│     └ Dockerfile
├ ※Railsのソース
├ docker-compose.yml
├ entrypoint.sh
├ Gemfile
├ Gemfile.lock
└ README.md
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Dockerfile を作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;FROM ruby:2.5
RUN apt-get update -qq &amp;amp;&amp;amp; apt-get install -y nodejs postgresql-client
RUN mkdir /src
WORKDIR /src
COPY Gemfile /src/Gemfile
COPY Gemfile.lock /src/Gemfile.lock
RUN bundle install
COPY . /src

# Add a script to be executed every time the container starts.
COPY entrypoint.sh /usr/bin/
RUN chmod +x /usr/bin/entrypoint.sh
ENTRYPOINT [&quot;entrypoint.sh&quot;]
EXPOSE 3000

# Start the main process.
CMD [&quot;rails&quot;, &quot;server&quot;, &quot;-b&quot;, &quot;0.0.0.0&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Gemfile を作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;source &apos;https://rubygems.org&apos;
gem &apos;rails&apos;, &apos;~&amp;gt;5&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;Gemfile.lock を作成(中身は空で OK)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;entrypoint.sh を作成&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;#!/bin/bash
set -e

# Remove a potentially pre-existing server.pid for Rails.
rm -f /src/tmp/pids/server.pid

# Then exec the container&apos;s main process (what&apos;s set as CMD in the Dockerfile).
exec &quot;$@&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;docker-compose.yml を作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.9&apos;
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql/data
    environment:
      POSTGRES_PASSWORD: password
  web:
    build:
      context: ./
      dockerfile: ./docker/ruby/Dockerfile
    command: bash -c &quot;rm -f tmp/pids/server.pid &amp;amp;&amp;amp; bundle exec rails s -p 3000 -b &apos;0.0.0.0&apos;&quot;
    volumes:
      - .:/src
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      - db
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※Windows の場合は、&lt;a href=&quot;#windows-%E3%81%AE%E3%81%BF%E3%81%A7%E7%99%BA%E7%94%9F%E3%81%99%E3%82%8B%E3%82%A8%E3%83%A9%E3%83%BC&quot;&gt;下記の注意点&lt;/a&gt;も参照。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ビルドして、Rails プロジェクトを作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose run --no-deps web rails new . --force --database=postgresql
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;もう一度、コンテナをビルド(rails new で Gemfile の内容が変わったため)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose build
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;データベースの接続設定を記述(src/config/database.yml)
データベース名は任意の名前で OK。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;default: &amp;amp;default
  adapter: postgresql
  encoding: unicode
  host: db
  username: postgres
  password: password
  pool: &amp;lt;%= ENV.fetch(&quot;RAILS_MAX_THREADS&quot;) { 5 } %&amp;gt;

development:
  &amp;lt;&amp;lt;: *default
  database: myapp_development


test:
  &amp;lt;&amp;lt;: *default
  database: myapp_test
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;Docker の起動&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;コンテナの起動&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up -d
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;初期 DB の作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec web rails db:create
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のように表示されれば無事データベースが作成されています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Created database &apos;myapp_develop&apos;
Created database &apos;myapp_test&apos;
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;ブラウザでアクセス&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;localhost:3000
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のような画面が表示されればOKです。
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2019/docker-rails/rails.png&quot; alt=&quot;Railsトップ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;遭遇したエラー&lt;/h2&gt;
&lt;h3&gt;ビルドコンテキスト外のファイルを COPY しようとしてエラー&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/mk-tool/items/1c7e4929055bb3b7aeda&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;docker-compose.yml の以下の部分。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;web:
  build:
    context: ./
    dockerfile: ./docker/ruby/Dockerfile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初はこうしていました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;web:
  build: ./docker/ruby
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;すると、Dockerfile の中で、Gemfile を COPY するところでエラーになりました。&lt;/p&gt;
&lt;p&gt;原因としては、ビルドコンテキスト外のファイルで COPY や ADD ができないとのことでした。
つまりビルドコンテキストに&lt;code&gt;./docker/ruby&lt;/code&gt;を指定しているので、この配下にあるファイルしか指定できないようです。
なので、&lt;code&gt;build&lt;/code&gt;の個所を&lt;code&gt;context&lt;/code&gt;と&lt;code&gt;dockerfile&lt;/code&gt;にわけて記述することで対応しました。&lt;/p&gt;
&lt;p&gt;なお、補足として、Dockerfile の COPY などで指定するホスト側のパスは、ビルドコンテキストを基準としたパスになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;COPY Gemfile /src/Gemfile ← ビルドコンテキスト（./）からのパス
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;コンテナ起動時に exited with code 1 になる&lt;/h3&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/nrk_baby/items/d872e8f051a96a313601&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;docker-compose up で起動するとコンテナがうまく起動しませんでした。
原因としては、コンテナ起動時に実行している&lt;code&gt;entrypoint.sh&lt;/code&gt;の改行コードが&lt;code&gt;CRLF（Windows標準）&lt;/code&gt;になっていたことでした。
ここは改行コードを&lt;code&gt;LF（Linux標準）&lt;/code&gt;に変更して対応しました。&lt;/p&gt;
&lt;h3&gt;Windows のみで発生するエラー&lt;/h3&gt;
&lt;h4&gt;postgres コンテナの起動に失敗する&lt;/h4&gt;
&lt;p&gt;コンテナ起動に失敗し、ログを見ると&lt;code&gt;FATAL: data directory &quot;/var/lib/postgresql/data&quot; has wrong ownership&lt;/code&gt;と表示されて
いました。
どうも volume マウントに失敗していたようです。&lt;/p&gt;
&lt;p&gt;docker-compose.yml でマウントのパスを&lt;code&gt;/var/lib/postgresql/data&lt;/code&gt;から&lt;code&gt;/var/lib/postgresql/&lt;/code&gt;に修正すると、なぜか起動するようになりました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;db:
  image: postgres
  volumes:
    - ./tmp/db:/var/lib/postgresql/ ← パス末尾の data をなくす
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;Rails アプリが File exists @ dir_s_mkdir - /src/tmp/cache/assets/sprockets/v3.0/2D でエラー落ちする&lt;/h4&gt;
&lt;p&gt;一度はちゃんと動作していた Rails アプリがこのエラーになり、動作しなくなったことがありました。
どうも Windows の場合は、ファイルの大文字小文字を区別しないことにより、ファイル名称がバッティングしてしまい起こるそうです。&lt;/p&gt;
&lt;p&gt;参考記事の通りに対応することで解決しました。&lt;/p&gt;
&lt;p&gt;src/config/initializers/assets.rb に以下を追加。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Rails.application.config.assets.configure do |env|
  env.cache = Sprockets::Cache::FileStore.new(
    ENV.fetch(&quot;SPROCKETS_CACHE&quot;, &quot;#{env.root}/tmp/cache/assets&quot;),
    Rails.application.config.assets.cache_limit,
    env.logger
  )
end
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;docker-compose.yml を以下のように修正。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3.9&apos;
services:
  db:
    image: postgres
    volumes:
      - ./tmp/db:/var/lib/postgresql
    environment:
      POSTGRES_PASSWORD: password
  web:
    build:
      context: ./
      dockerfile: ./docker/ruby/Dockerfile
    command: bash -c &quot;rm -f tmp/pids/server.pid &amp;amp;&amp;amp; bundle exec rails s -p 3000 -b &apos;0.0.0.0&apos;&quot;
    environment:
      - SPROCKETS_CACHE=/cach
    volumes:
      - .:/src
      - cache:/cache
    ports:
      - &quot;3000:3000&quot;
    depends_on:
      - db

volumes:
  cache:
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;Docker 公式で案内があったのはありがたかったのですが、ディレクトリ構造を変更したこともあり、いくつかエラーに遭遇しました。
とりあえず無事起動できてよかったです。&lt;/p&gt;
&lt;p&gt;Rails チュートリアルは一応一通り終わったので、一旦 AWS の勉強に戻ります。
資格取らねば...。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.docker.com/compose/rails/&quot;&gt;Docker 公式&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/mk-tool/items/1c7e4929055bb3b7aeda&quot;&gt;docker-composeでADDやCOPYをする際に注意すること&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/nrk_baby/items/d872e8f051a96a313601&quot;&gt;docker-compose upするとコンテナが一瞬でexited with code 1する話&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://crieit.net/posts/Windows-Docker-Rails-sprockets&quot;&gt;Windows+Docker+Railsでsprocketsのエラーが出る時&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>QiitaいいねランキングをSlackに投稿</title><link>https://changeofpace.site/posts/2019-06-23-qiita-ranking</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-06-23-qiita-ranking</guid><description>GASを使ってAPIでデータを取得し、Slackに投稿するまでの手順</description><pubDate>Sun, 23 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Qiitaのアカウントがあると、定期的にいいねランキングがメールで来ますよね。
あれをSlackに投稿出来たらいいなと思って、チャレンジしてみました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※追記 現在、当記事で使用していた API は使用できなくなったため、当記事のやり方はできなくなりました。&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;どうやって投稿する？&lt;/h2&gt;
&lt;p&gt;最初にいいねランキングを取得するところですが、Qiita の API を使えばできるのかなと思い見てみたところ、単純に Qiita の API だけでは難しそうでした。
いろいろ工夫すればできるのかなとか考えていたなか、自作で API を作られている方の記事を見つけました。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/zonbitamago/items/1027b532b174e5ee04b3&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;Qiita API をスクレイピングしてランキングデータを構築しているそうです。
仕組みに関しても記事に記述がありますが、今の自分にはちょっと難しく感じました...。すごい。&lt;/p&gt;
&lt;p&gt;こちらの方の API を利用させていただくとして、投稿までの手順は以下の流れにしました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;API で Qiita いいねランキングデータを取得する&lt;/li&gt;
&lt;li&gt;取得したデータを使って、Slack に投稿する文言を組み立てる&lt;/li&gt;
&lt;li&gt;Webhookを利用して、Slackに投稿する&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;なお、今回は GAS を使用します。&lt;/p&gt;
&lt;h2&gt;準備&lt;/h2&gt;
&lt;h3&gt;Webhook&lt;/h3&gt;
&lt;p&gt;まずは、Slack に投稿するための Webhook の設定をしていきます。
下記の&lt;code&gt;App管理&lt;/code&gt;から設定画面へ。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/qiita-ranking/slack.png&quot; alt=&quot;SlackでApp管理に行くまで&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Webhookが追加されていない場合&lt;/strong&gt;
ヘッダの&lt;code&gt;検索&lt;/code&gt;から&lt;code&gt;webhook&lt;/code&gt;で検索して、&lt;code&gt;Incoming Webhook&lt;/code&gt;を選択。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;すでにWebhookが追加されている場合&lt;/strong&gt;
ヘッダの&lt;code&gt;管理&lt;/code&gt;から&lt;code&gt;カスタムインテグレーション&lt;/code&gt;を選択して、&lt;code&gt;Incoming Webhook&lt;/code&gt;を選択。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;設定の追加&lt;/code&gt;を選択して、投稿するチャンネルを決めて、&lt;code&gt;Incoming Webhookインテグレーションの追加&lt;/code&gt;を選択。
ここでは、投稿時のアイコンや名称などが設定できます。WebhookURL は GAS 側で使用するので控えておきましょう。&lt;/p&gt;
&lt;h3&gt;GAS&lt;/h3&gt;
&lt;p&gt;Google ドライブ上でGASプロジェクトを作成して、手順通りにスクリプトを書いていきます。&lt;/p&gt;
&lt;p&gt;※2020/2/16 追記
v8 ランタイムが使えるようになったので、それに伴いリファクタリングを行いました。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length, ja-technical-writing/ja-no-mixed-period --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ランタイム変更は、GAS エディタを開いて表示される案内（&lt;code&gt;Enable new Apps Script runtime powered by Chrome V8 for this project.&lt;/code&gt;）から変更する。
もしくは、実行 → Chrome V8 を搭載した新しい Apps Script ランタイムを有効にする からできます。&lt;/p&gt;
&lt;p&gt;主な変更点&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;var で定義していた変数を、その後値を変更するかどうかで let か const に変更&lt;/li&gt;
&lt;li&gt;ログの出力方法の変更（stack にファイル名や行数などが含まれるので、stack のみにしました）&lt;/li&gt;
&lt;li&gt;HTTP リクエスト時に使用するリクエスト URL は、スクリプトプロパティで設定して使用するよう変更
&lt;ul&gt;
&lt;li&gt;（ファイル→プロジェクトのプロパティ→スクリプトのプロパティ から設定できますが、オーナー権限のアカウントでないと編集できません）&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ダブルクォートをシングルクォートに変更&lt;/li&gt;
&lt;li&gt;getYear() は非推奨のため、getFullYear() に変更&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function noticeQiitaRanking() {
  let msg;
  try {
    let response = getQiitaRanking();
    if (response == &apos;&apos;) {
      msg = &apos;※最新のQiitaいいねランキングが登録されていません。\n違う取得日を指定してください。&apos;;
    } else {
      // Slack 投稿内容の組み立て
      msg = &apos;*■週間いいねランキング■* \n\n&apos;;
      // JSON をオブジェクトに変換
      response = JSON.parse(response);
      const dataList = response[&apos;data&apos;];
      let count = 1;
      dataList.forEach(data =&amp;gt; {
        // &amp;lt;url|文字列&amp;gt; の形式で文字列でのリンクを作成
        msg += count + &apos;位：&amp;lt;&apos; + data[&apos;url&apos;] + &apos;|&apos; + data[&apos;title&apos;] + &apos;&amp;gt; \n&apos;;
        count++;
      })
    }
  } catch (e) {
    Logger.log(&apos;Qiitaいいねランキング取得エラー：&apos; + &apos;\nstack：\n&apos; + e.stack);
  }

  try {
    // Slack 側 Incoming WebHook のURL
    const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty(&apos;WEBHOOK_URL&apos;);
    // Incoming WebHook に渡すパラメータ
    const jsonData =
        {
          &apos;text&apos;: msg
        };
    //パラメータを JSON に変換
    const payload = JSON.stringify(jsonData);
    // 送信オプション
    const options =
        {
          &apos;method&apos;: &apos;post&apos;,
          &apos;contentType&apos;: &apos;application/json&apos;,
          &apos;payload&apos;: payload
        };
    //指定 URL、オプションでリクエスト
    UrlFetchApp.fetch(WEBHOOK_URL, options);
  } catch (e) {
    Logger.log(&apos;Slack送信エラー：&apos; + e.message + &apos;\nstack：\n&apos; + e.stack);
  }
}

function getQiitaRanking() {
  const type = &apos;weekly&apos;;
  const date = new Date();
  // リクエスト時に今日を指定するとなぜかデータが取得できないので、前日を指定する
  const yesterday = new Date(date.getFullYear(), date.getMonth(), date.getDate() - 1);
  const formatYesterday = Utilities.formatDate(yesterday, &apos;JST&apos;, &apos;yyyy-MM-dd&apos;);
  const QIITA_SCRAIPING_URL = PropertiesService.getScriptProperties().getProperty(&apos;QIITA_SCRAIPING_URL&apos;) + type + &apos;/&apos; + formatYesterday;
  return UrlFetchApp.fetch(QIITA_SCRAIPING_URL);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Qiita いいねランキングAPIを使用する際に、実行日当日の日付を指定すると、なぜかデータが取得できませんでした。
そのため、前日を指定するようにしています。&lt;/p&gt;
&lt;h2&gt;投稿&lt;/h2&gt;
&lt;p&gt;上記 GAS スクリプトを実行することで、以下のような投稿になります。
ちゃんと、タイトルが記事リンクとして作成されています。
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2019/qiita-ranking/ranking.png&quot; alt=&quot;Slackにランキング投稿結果&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;GAS は定期実行するようにトリガーの設定ができるので、週に1回投稿するなどもできますよー。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;実は最初は GAS ではなく、Serverless Framework + Python + Lambda で作成しました。
(Docker で実行環境も作りました)
ただ、こちらの場合、事前に準備が多いので GAS の方が手軽に作成できていいですね。
需要があれば、会社でも使ってもらおうかと構想中ですー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/zonbitamago/items/1027b532b174e5ee04b3&quot;&gt;Qiitaいいね数ランキングAPIの作成&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>LaradockでLaravel環境構築</title><link>https://changeofpace.site/posts/2019-06-03-laravel-laradock</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-06-03-laravel-laradock</guid><description>Laradockを使ってLaravel環境を作る手順</description><pubDate>Mon, 03 Jun 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;Laravel を勉強するために Laradock を使用してみました。
いろんなサービスの Dockerfile が用意されていますが、まずは最低限の構成でチャレンジ。
Laradock 便利ー。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;注意書き（※2021/02/24 追記）&lt;/h2&gt;
&lt;p&gt;Laravel の Docker 環境を作成するにあたって、
現在の自分のスタンスとしては、Laradock よりもこちらの環境を使用するのをお勧めしています。
Laradock と比べて圧倒的に早く構築できます。
&amp;lt;OG url=&quot;https://github.com/ucan-lab/docker-laravel&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;また、ご本人による解説記事もあり、非常に勉強になります。
特に Docker の知識に自信がない方はぜひ読んでみてください。
&amp;lt;OG url=&quot;https://qiita.com/ucan-lab/items/5fc1281cd8076c8ac9f4&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;このスタンスのため、この記事は今後&lt;strong&gt;更新停止&lt;/strong&gt;します。
ご了承ください。&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker 導入済み&lt;/li&gt;
&lt;li&gt;docker-compose コマンドが使用できる&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://liginc.co.jp/364089&quot; /&amp;gt;
こちらの記事をベースに進めました。&lt;/p&gt;
&lt;h2&gt;Laradock&lt;/h2&gt;
&lt;h3&gt;導入&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;任意のプロジェクトフォルダを作成（ここが git の管理対象になるイメージ）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;プロジェクトフォルダに移動して、Git の設定&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git init
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;プロジェクトフォルダの中に Laravel プロジェクト用のフォルダを作成&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;プロジェクトフォルダの中で Laradock をサブモジュールとして登録&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git submodule add https://github.com/Laradock/laradock.git (※Laradock 用のフォルダ名)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この時点のフォルダ構成はこちら。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(git プロジェクトフォルダ)
├ (Laravel プロジェクトフォルダ)
└ (Laradock フォルダ)
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;env ファイル準備&lt;/h4&gt;
&lt;p&gt;チームメンバーと環境変数の共有のしやすさとして、シェルを作成します。
(※2019/8/14追記 環境変数ファイルを Git にあげることでもあるので、パブリックリポジトリではやらないほうがよさそうです)&lt;/p&gt;
&lt;p&gt;シェル作成のあたりは、以下の記事を参考にしました。
&amp;lt;OG url=&quot;https://qiita.com/dev_satsuki/items/e2769925da33bfa77df5&quot; /&amp;gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;シェル用のフォルダとファイルを作成（以下の&lt;code&gt;.sh/&lt;/code&gt;）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;(git プロジェクトフォルダ)
├ (Laravel プロジェクトフォルダ)
├ (Laradock フォルダ)
└ .sh/
   ├ conf/
   └ setup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;conf/&lt;/code&gt;のなかに&lt;code&gt;.laradock-env&lt;/code&gt;を作成（laradock の中の&lt;code&gt;env-example&lt;/code&gt;をコピー）
(以降、Laradock を更新した際は&lt;code&gt;env-example&lt;/code&gt;の内容が更新されている場合があるので、その際は再度合わせるようにしましょう)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.laradock-env&lt;/code&gt;を編集&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;APP_CODE_PATH_HOST&lt;/code&gt;...Laradock の web サーバー上で同期するディレクトリパス&lt;/li&gt;
&lt;li&gt;&lt;code&gt;DATA_PATH_HOST&lt;/code&gt;...docker のストレージなどを保存するローカルのディレクトリパス&lt;/li&gt;
&lt;li&gt;&lt;code&gt;COMPOSE_PROJECT_NAME&lt;/code&gt;...コンテナの接頭辞（デフォルトの laradock だと、名前被りで上書きしやすいのでプロジェクトごとの命名に変更しておく）&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;# 例
APP_CODE_PATH_HOST=../(Laravel プロジェクトフォルダ)/
DATA_PATH_HOST=../.(Laradock フォルダ)/data

COMPOSE_PROJECT_NAME=laraveltutorial
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;setup.sh を編集&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;SCRIPT_DIR=$(cd $(dirname $0); pwd)

cd $SCRIPT_DIR

cp -f conf/.laradock-env ../(Laradockフォルダ)/.env
&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;MySQL 8 対応&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.sh/conf&lt;/code&gt;以下に mysql.cnf を作成（Laradock の mysql/my.cnf をコピー）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.sh/conf/mysql.cnf&lt;/code&gt;にmysql 8 の対応として以下を追記する&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;innodb_use_native_aio=0
default_authentication_plugin=mysql_native_password
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;a href=&quot;/posts/2019-05-15-docker-php&quot;&gt;DockerでPHP環境作成（+ MySQL + PHPMyAdmin）&lt;/a&gt;でも書いたとおり、MySQL は8から認証方式が変更になっているため、それを従来の認証方式に設定しなおすものです。
※追記 innodb_use_native_aio=0 については、他のバージョンでも必要の可能性あり。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;code&gt;.sh/setup.sh&lt;/code&gt;に以下を追加&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cp -f conf/mysql.cnf ../ (Laradockフォルダ)/mysql/my.cnf
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;setup.sh を実行して .env を作成 + mysql/my.cnf の更新&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sh .sh/setup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;docker の起動&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;イメージの作成と起動( laradock フォルダに移動して)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up --build -d nginx mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※起動するコンテナは使用したいものに応じて変化。
&lt;strong&gt;初回は作成に少し時間がかかるので気長に待ちましょう。&lt;/strong&gt;&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;起動イメージの確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;State がすべて Up になっていればOK。
（workspace と php-fpm も一緒に起動しているはず）&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;動作確認
&lt;a href=&quot;http://localhost/&quot;&gt;http://localhost&lt;/a&gt;にアクセス。
この段階では、nginx の404画面になればOK。&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Laravel プロジェクト&lt;/h2&gt;
&lt;h3&gt;作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;workspace コンテナに入る&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec workspace bash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;Laravel のインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;composer create-project --prefer-dist laravel/laravel ./
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;動作確認
&lt;a href=&quot;http://localhost/&quot;&gt;http://localhost&lt;/a&gt;にアクセス
以下の Laravel のトップページが表示されれば OK
&amp;lt;ImageWrapper className=&quot;w-[50%]&quot; src=&quot;screenshots/2019/laravel-laradock/laravel.png&quot; alt=&quot;Laravelトップ画面&quot; /&amp;gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;MySQL の接続設定&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;Laravel プロジェクトの&lt;code&gt;.env&lt;/code&gt;を修正
接頭辞が &lt;code&gt;DB_&lt;/code&gt; の部分を編集し、laradock フォルダ/.env に記述されている値と合わせる形になります。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 例
DB_CONNECTION=mysql
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=default
DB_USERNAME=default
DB_PASSWORD=secret
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;マイグレーションを実行して接続テスト
workspace コンテナの中で以下コマンドを実行。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;php artisan migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;デフォルトで存在するマイグレーションファイルが実行されます。ちゃんとテーブルが作成されていればOK。&lt;/p&gt;
&lt;h3&gt;Laravel の env ファイル準備&lt;/h3&gt;
&lt;p&gt;こちらもシェルで env ファイルの内容を共有できるようにしておきます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;.sh/conf&lt;/code&gt;に&lt;code&gt;.laravel-env&lt;/code&gt;を作成
(Laravel プロジェクトの&lt;code&gt;.env&lt;/code&gt;をコピー)&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;setup.sh に以下を追記する&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;cp -f conf/.**laravel**-env ../(Laravel プロジェクトフォルダ)/.env
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;他のメンバーが環境を作るときは&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;プロジェクトをクローン&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone (プロジェクトパス)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;サブモジュールの内容をクローン&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git submodule update -i
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※Laradock のディレクトリに移動し、&lt;code&gt;git submodule&lt;/code&gt;で状態が確認できる
&lt;code&gt;-c7289f7db3b96be585a879ceaf0f208102f8233f ./&lt;/code&gt; のように頭に&lt;code&gt;-&lt;/code&gt;がついている場合は、まだ中身を持ってこれてない状態。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;シェル実行で env ファイル作成 + mysql 設定ファイルコピー&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sh .sh/setup.sh
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;docker のイメージ作成と起動&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose up --build -d nginx mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※作成するコンテナは必要に応じて。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;起動イメージの確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose ps
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;State がすべて Up になっていれば OK。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;workspace コンテナに入る(2019/12/7追記)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;docker-compose exec workspace bash
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;パッケージのインストール(2019/12/7追記)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;composer install
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;テーブルの作成(2019/12/7追記)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;php artisan migrate
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;動作確認
&lt;a href=&quot;http://localhost/&quot;&gt;http://localhost&lt;/a&gt;にアクセス。
ちゃんとコードに応じた画面が表示されれば OK。&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;最初は MySQL 8対応で少し手間取りましたが、比較的楽に環境を作ることができました。
Laradock には豊富に Dockerfile が用意されているので、必要に応じてコンテナがすぐ作成できるのはいいですね。
機会があれば他のコンテナも試してみます。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://liginc.co.jp/364089&quot;&gt;PHPフレームワーク「Laradock」で、手軽にLaravel+Dockerな開発環境を構築する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/dev_satsuki/items/e2769925da33bfa77df5&quot;&gt;LaravelとLaradockを使ったチームでの開発環境を用意する vol.1 Laradock設定編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/dev_satsuki/items/5ce8b98100997f230775&quot;&gt;LaravelとLaradockを使ったチームでの開発環境を用意する vol.2 Laravelインストール編&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://ninolog.com/docker-build-php-from-laradock/&quot;&gt;DockerとLaradockでPHPフレームワークとCMSの開発環境を構築する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mgng.mugbum.info/1344&quot;&gt;MySQL のログに CA 関連と NO_ZERO_DATE 関連の Warning が出ていたので対応した&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>DockerでPHP環境作成（+ MySQL + PHPMyAdmin）</title><link>https://changeofpace.site/posts/2019-05-15-docker-php</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-05-15-docker-php</guid><description>DockerでPHP(フレームワーク無し)の環境を作る手順</description><pubDate>Wed, 15 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;今更ながら、Docker の勉強もしています。
というか、勉強しないといいかげんヤバい...。
先日、少々詰まりながらも、なんとか PHP の環境を作ってみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;自分の Docker 経験&lt;/h2&gt;
&lt;p&gt;Docker の知識があるのは当たり前くらいのご時世のなか、まだまだ勉強途中です。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://www.udemy.com/course/docker-k&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;先日、こちらの講座を受講して大まかな仕組みやコマンドは学びましたが、まだ自分で進んで環境作れるような状態ではありません。
参考書も買って勉強した方がいいのかな...。&lt;/p&gt;
&lt;p&gt;とはいえ、勉強もかねて今回環境を作ってみました。&lt;/p&gt;
&lt;p&gt;余談ですが、先日から WSL の Ubuntu の調子が悪く、Gitが使えない状態になってしまっています...。
調べながらもエラーが解消しなかったため、もう Docker で環境作ろ...となったのでした。&lt;/p&gt;
&lt;h2&gt;前提&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Docker 導入済み&lt;/li&gt;
&lt;li&gt;docker-compose コマンドが使用できる&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;PHP の Docker 環境&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/sitmk/items/f911be7ffa4f29293fd5&quot; /&amp;gt;
今回は、こちらの方の記事をベースに進めました。&lt;/p&gt;
&lt;p&gt;作成後のフォルダ構成は以下のようになります。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;(root)
├ mysql
│  ├ mysql_conf
│  │  └ custom.conf
│  ├ mysql_init
│  │  └ init.sql
│  └ Dockerfile
├ nginx
│  └ nginx.conf
├ php
│  ├ Dockerfile
│  └ php.ini
├ www
│  └ html
│      └ index.php
└ docker-compose.yml
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下、ファイルの簡単な解説です。&lt;/p&gt;
&lt;h3&gt;custom.conf&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;[mysqld]
default_authentication_plugin=mysql_native_password
character-set-server=utf8mb4

[client]
default-character-set=utf8mb4
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;MySQL の設定ファイルです。
デフォルトの認証方式と文字コードの設定を定義しています。&lt;/p&gt;
&lt;p&gt;認証方式については、MySQL 8.0.4以降から&lt;code&gt;caching_sha2_password&lt;/code&gt;がデフォルトとして変更になっています。
そのため、従来の方式ではアクセスできなくなってしまう（PHP の MySQL 接続ライブラリが対応していない模様）ので、従来の認証方式である&lt;code&gt;mysql_native_password&lt;/code&gt;に変更します。&lt;/p&gt;
&lt;p&gt;文字コードは文字化け対策です。&lt;/p&gt;
&lt;h3&gt;init.sql&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;CREATE DATABASE test;

USE test;

CREATE TABLE user (
    id int AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(255),
    email VARCHAR(255)
);

INSERT INTO user (name, email) VALUES
    (&apos;taro&apos;, &apos;taro@example.com&apos;),
    (&apos;jiro&apos;, &apos;jiro@example.com&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;データベースの初期化の SQL です。
あとで、PDO の接続テストもしたかったので、簡単な SQL を書いています。&lt;/p&gt;
&lt;h3&gt;Dockerfile(MySQL)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;FROM mysql:8.0

ADD ./mysql_conf/custom.cnf /etc/mysql/conf.d/.

CMD [&quot;mysqld&quot;]

CMD [&quot;client&quot;]
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;明示的に MySQL の設定ファイルを、Docker 環境内に ADD しています。&lt;/p&gt;
&lt;p&gt;こちらについては、ボリュームマウントでも対応可能らしいのですが...。
自分の場合ボリュームマウントするよりも先（認証方式を修正するより前）に MySQL の初期ユーザが作られてしまいました。
それによりアクセスできなくなる状態になってしまったので、ADD するようにしました。&lt;/p&gt;
&lt;h3&gt;nginx.conf&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;server {
    listen 80;
    server_name _;

    root  /var/www/html;
    index index.php index.html;

    access_log /var/log/nginx/access.log;
    error_log  /var/log/nginx/error.log;

    location / {
        try_files $uri $uri/ /index.php$is_args$args;
    }

    location ~ \.php$ {
        fastcgi_pass php:9000;
        fastcgi_index index.php;
        fastcgi_param SCRIPT_FILENAME  $document_root$fastcgi_script_name;
        include       fastcgi_params;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Nginx の設定ファイルです。&lt;/p&gt;
&lt;h3&gt;Dockerfile(PHP)&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;FROM php:7.2-fpm

COPY php.ini /usr/local/etc/php/

RUN apt-get update &amp;amp;&amp;amp; \
  # PHPのExtensionをインストール
  docker-php-ext-install pdo_mysql mysqli
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PDO の接続テストをしたかったので、追加でインストールしています。&lt;/p&gt;
&lt;h3&gt;php.ini&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;date.timezone = &quot;Asia/Tokyo&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHP の設定ファイルです。&lt;/p&gt;
&lt;h3&gt;index.php&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
phpinfo();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初の動作確認で、PHP のインフォメーションを表示するようにしておきます。&lt;/p&gt;
&lt;h3&gt;docker-compose.yml&lt;/h3&gt;
&lt;pre&gt;&lt;code&gt;version: &apos;3&apos;
services:
  nginx:
    image: nginx:latest
    ports:
      - 8080:80
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
      - ./www/html:/var/www/html
    depends_on:
      - php

  php:
    build: ./php
    volumes:
      - ./www/html:/var/www/html
    depends_on:
      - db

  db:
    build: ./mysql
    ports:
      - 13306:3306
    volumes:
      # DB data persistence
      - ./mysql/data:/var/lib/mysql
      # DB initialize data
      - ./mysql/mysql_init:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: secret
    command: --innodb-use-native-aio=0

  phpmyadmin:
    image: phpmyadmin/phpmyadmin:latest
    ports:
      - 8888:80
    environment:
      - PMA_ARBITRARY=1
      - PMA_HOST=db
      - PMA_USER=root
      - PMA_PASSWORD=secret
    depends_on:
      - db
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;# DB data persistence
- ./mysql/data:/var/lib/mysql
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでデータの永続化。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;# DB initialize data
- ./mysql/mysql_init:/docker-entrypoint-initdb.d
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;初期化 SQL のマウントです。
データベースの初期化時、&lt;code&gt;/docker-entrypoint-initdb.d&lt;/code&gt;以下にある SQL を実行してくれるようになっているそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;command: --innodb-use-native-aio=0
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラー対応でいれてます。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;environment:
  - PMA_ARBITRARY=1
  - PMA_HOST=db
  - PMA_USER=root
  - PMA_PASSWORD=secret
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;phpmyadmin のこちらは、環境変数で MySQL へのアクセス情報を記載しています。&lt;/p&gt;
&lt;p&gt;ちなみに yml 内のコメントが英語なのは、日本語で書くとなぜかエラーになってしまったからです（なんでや...）&lt;/p&gt;
&lt;h2&gt;動作確認&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;でコンテナを立ち上げます。&lt;/p&gt;
&lt;h3&gt;PHP + Nginx&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;localhost:8080&lt;/code&gt;で PHP のインフォメーション画面が表示されればOKです。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-php/php-info.png&quot; alt=&quot;PHPインフォメーション画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;PHPMyAdmin&lt;/h3&gt;
&lt;p&gt;&lt;code&gt;localhost:8888&lt;/code&gt;で次のような画面が表示されればOKです。
docker-compose.yml で記載した環境変数が正しければ、ログインしている状態になるはず。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-php/php-my-admin.png&quot; alt=&quot;PHPMyAdmin画面&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;PDO&lt;/h3&gt;
&lt;p&gt;PDO を使用して、データベースにアクセスできるかも確認していきます。&lt;/p&gt;
&lt;p&gt;index.php を修正します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
try {
    $dsn = &quot;mysql:host=db;dbname=test;&quot;;
    $db = new PDO($dsn, &apos;root&apos;, &apos;secret&apos;);
    $sql = &quot;SELECT * FROM user&quot;;
    $stmt = $db-&amp;gt;prepare($sql);
    $stmt-&amp;gt;execute();
    $result = $stmt-&amp;gt;fetchAll(PDO::FETCH_ASSOC);
    var_dump($result);
} catch (PDOException $e) {
    echo $e-&amp;gt;getMessage();
    exit;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;code&gt;localhost:8080&lt;/code&gt;にアクセスして、以下のようにデータベースから情報が取得できていればOKです。無事 DB にアクセスできています。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/docker-php/pdo-test.png&quot; alt=&quot;DBからのデータ表示画面&quot; /&amp;gt;&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;たくさんの記事を参考にさせていただきましたが、なんとか PHP 環境を作ることができました。
実は Laradock を使って、Laravel 環境を作ったりもしているのですが、それはまた別の記事で書く予定ですー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/sitmk/items/f911be7ffa4f29293fd5&quot;&gt;DockerによるPHP開発環境構築（PHP + MySQL + Nginx）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/fujitak/items/b56122e2ecd94022a7b6&quot;&gt;docker-composeでPHPとMySQLを連携させてみる&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/juhn/items/274e44ee80354a39d872&quot;&gt;docker-compose＋MySQL5.7(8.0も)+初期化+永続化&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.tes.co.jp/entry/2018/07/09/100555&quot;&gt;Docker 18.03でMySQL5.7コンテナ起動時に File ./ib_logfile101: &apos;aio write&apos; returned OS error 122. メッセージが表示されたときの対処法&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/saken649/items/00e752d89f2a6c5a82f6&quot;&gt;DockerのMySQLコンテナに外部からアクセスする方法まとめ改&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/uutarou10/items/94913e6e7536b713a574&quot;&gt;DockerでLAMP環境を作るときにハマったこと&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/furu8ma/items/50718efebee20fd24517&quot;&gt;phpMyAdmin on docker が便利すぎる&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GitLab CI でのみエラーになる問題</title><link>https://changeofpace.site/posts/2019-05-05-gitlab-ci-error</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-05-05-gitlab-ci-error</guid><description>GitLab CI で Invalid US-ASCII character &quot;\xEF&quot; が出たときの対処方法</description><pubDate>Sun, 05 May 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;当ブログは Netlify で配信していますが、GitLab CI も一応設定してあります。
Netlify の CI では問題ないのに、GitLab の CI でのみエラーになることがあったので、その対応を載せておきます。&lt;/p&gt;
&lt;h2&gt;エラー内容&lt;/h2&gt;
&lt;p&gt;push 時やマジリクの時に動く GitLab の CI の時のみ、なぜかエラーになる。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;image: ruby:2.3.6

cache:
  paths:
  - vendor/

before_script:
  - bundle install --path vendor/bundle

test:
  stage: test
  script:
    - bundle exec jekyll build -d test
  artifacts:
    paths:
    - test
  except:
    - master
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;CI のエラー箇所抜粋。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;bundle exec jekyll build -d test

Configuration file: /builds/{GitLabユーザID}/{リポジトリ名}/_config.yml
            Source: ./docs
       Destination: test
 Incremental build: disabled. Enable with --incremental
      Generating...
  Conversion error: Jekyll::Converters::Scss encountered an error while converting &apos;assets/css/main.scss&apos;:
                    Invalid US-ASCII character &quot;\xEF&quot; on line 1
jekyll 3.8.1 | Error:  Invalid US-ASCII character &quot;\xEF&quot; on line 1
ERROR: Job failed: exit code 1
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;最初の頃は問題なかったのに、途中から上記のようなエラーがしばらく出続けていて、なんだろうなと思いつつ、ブログ記事作成に大きな影響はなかったので後回しにしていました。&lt;/p&gt;
&lt;h2&gt;解決策&lt;/h2&gt;
&lt;p&gt;先輩が見てくださいまして、無事エラーが解消されました。どうもロケールの問題だったようです。&lt;/p&gt;
&lt;p&gt;.gitlab-ci.yml に以下を追加します。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;variables:
  LC_ALL: C.UTF-8
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;CIに関して、まだ知識が疎いので日々勉強ですね...。&lt;/p&gt;
</content:encoded></item><item><title>TILリポジトリで小さなアウトプット</title><link>https://changeofpace.site/posts/2019-03-26-til</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-03-26-til</guid><description>ちょっとしたアウトプットに向いているTILリポジトリのご紹介</description><pubDate>Tue, 26 Mar 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;こんにちは、2019年入って体調が絶不調のよしです。
AWS の資格の勉強をしなければ...と思いつつも、あまりはかどっていないなか、とある Qiita の記事を見かけました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &apos;@/components/OG.astro&apos;&lt;/p&gt;
&lt;h2&gt;TIL&lt;/h2&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/sitmk/items/239335b4ed0c3c797add&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;概要&lt;/h3&gt;
&lt;blockquote&gt;
&lt;p&gt;「Today I Learned」の略で、Github 上に TIL というリポジトリを作成してそこに今日覚えたことを書いていくというものです。&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;こちらの記事の中でも触れられていますが、敷居の低さは自分も感じました。
プログラミングの勉強はアウトプットしてこそという考えがありますが、自分結局大したアウトプットできてないなーと。
なにか作るとかいうと、より敷居が高く感じてしまって。&lt;/p&gt;
&lt;p&gt;あえて人に言えるのは、GAS で掃除当番通知 Bot を作って許可をもらって社内で動かしてるということくらいでしょうか。
自分で新しくサービスを立ち上げたりという経験は現時点ではありません。&lt;/p&gt;
&lt;p&gt;でも、これなら小さなアウトプットの積み重ねができるかなと思い自分もやり始めてみました。&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※2021年末くらいからは Notion で TIL 活動をすることにしたので、リポジトリの活動停止してます。&lt;/strong&gt;&lt;/p&gt;
&lt;h3&gt;何をアウトプットする？&lt;/h3&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/til/til-repository.png&quot; alt=&quot;TILリポジトリ画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/h-yoshikawa44/til-engineer&quot; /&amp;gt;
※2021/02/14追記...アカウント名、リポジトリ名を変えました。&lt;/p&gt;
&lt;p&gt;現状、また始めたばかりで、とりあえず基本的な構文を書いているような状態です。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;&amp;lt;?php
// 標準出力処理

// echo
// 一つ以上の文字列を出力する
// 関数でなく言語構造であるため、引数を括弧でくくる必要がない
$test = &apos;ABC&apos; . &quot;\n&quot;;
echo $test; // ABC
echo &apos;A&apos;, &apos;B&apos;, &apos;C&apos;, &apos;D&apos; . &quot;\n&quot;; // ABCD

// print
// 単一の引数のみ受け付ける出力
// 関数でなく言語構造であるため、引数を括弧でくくる必要がない
$testPrint = &apos;DEF&apos; . &quot;\n&quot;;
print $testPrint; // DEF
print &quot;alphabet $testPrint\n&quot;; // alphabet DEF

// printf
// フォーマット済みの文字列を出力する
$testPrintF = &apos;alphabet A:%s B:%s&apos;;
printf($testPrintF, &apos;a&apos;, &apos;b&apos;); // alphabet A:a B:b

// var_dump
// 指定した式に関して、その型や値を含む構造化された情報を返す
$testVar = [&apos;A&apos;, &apos;B&apos;, &apos;C&apos;, &apos;D&apos;];
var_dump($testVar);
// array(4) {
//     [0] =&amp;gt;
//     string(1) &quot;A&quot;
//     [1] =&amp;gt;
//     string(1) &quot;B&quot;
//     [2] =&amp;gt;
//     string(1) &quot;C&quot;
//     [3] =&amp;gt;
//     string(1) &quot;D&quot;
//   }

// print_r
// 指定した変数に関する情報をわかりやすく出力する
$testPrintR = [&apos;A&apos;, &apos;B&apos;, &apos;C&apos;, &apos;D&apos;];
print_r($testPrintR);
// Array
// (
//     [0] =&amp;gt; A
//     [1] =&amp;gt; B
//     [2] =&amp;gt; C
//     [3] =&amp;gt; D
// )
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;各言語で標準出力処理、分岐処理、繰り返し処理と書いていって、あとは配列操作とか。
仕事していてこういう書き方ができたとか発見したものを書いていこうかなと。&lt;/p&gt;
&lt;p&gt;もちろん言語に限らずで、Docker の Dockerfile や yml ファイルの例をコミットしておくとかもありかなと思ってます。
ちょっと振り返りにも使えるような内容がいいですね。&lt;/p&gt;
&lt;p&gt;これまで独学で勉強していた内容は、Google ドライブに同期してマークダウンファイルに書いていました。
バックアップがとれる点では便利なんですが、誰かと共有するという点ではちょっと気になったというか（ただ共有かければいいだけではあるんですけどね...）&lt;/p&gt;
&lt;p&gt;Git のリポジトリであれば、パブリックであれば URL 伝えるだけでいいので楽かなぁと。
自分としては、コードベースのアウトプットやメモを TIL リポジトリに残していくつもりです。&lt;/p&gt;
&lt;h2&gt;将来的に...&lt;/h2&gt;
&lt;p&gt;既存のサービスで SyntaxDB というものがありまして。
&amp;lt;OG url=&quot;https://syntaxdb.com&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/til/syntaxdb.png&quot; alt=&quot;SyntaxDB画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これで各言語の構文見たりできるんですが、こういった言語の構文比較もできるサービス作ってみたいなーとか漠然と思ってます。
タブで表示する言語切り替えて比較できるみたいな。気が向いたらいつかやるかも？&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;アウトプットがなかなかできてないという方は、TIL リポジトリで小さなアウトプットを始めてみてはどうでしょうか？
ではでは。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/sitmk/items/239335b4ed0c3c797add&quot;&gt;Githubのリポジトリ「TIL」を使って小さなアウトプットを習慣化する&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://syntaxdb.com/&quot;&gt;SyntaxDB&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>Netlifyでブログをデプロイ</title><link>https://changeofpace.site/posts/2019-02-10-netlify-deploy</link><guid isPermaLink="true">https://changeofpace.site/posts/2019-02-10-netlify-deploy</guid><description>Netlifyでブログをホスティングするまでの手順記録</description><pubDate>Sun, 10 Feb 2019 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;静的サイトを運用するにあたって「Netlify でデプロイすると楽よー」と教えてもらい、
細々と書いていた当ブログを、Netlify を使用して思い切って公開してみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;
import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;h2&gt;Netlify とは？&lt;/h2&gt;
&lt;p&gt;&amp;lt;ImageWrapper src=&quot;screenshots/2019/netlify-deploy/netlify.png&quot; alt=&quot;Netlify公式ページ&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;静的サイトのホスティングサービスです。
GitHub、GitLab、Bitbucket のリポジトリと連携させて、自動的にデプロイが行えるようになっています。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://www.netlify.com/&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;静的サイトジェネレータやフロントエンド構築ツールとも相性がいいそうで。
当ブログは&lt;code&gt;Jekyll&lt;/code&gt;で作成しているため、まさに相性がよさそうですね。&lt;/p&gt;
&lt;p&gt;ともあれ「どれくらい楽なのよ」ということで、リポジトリを連携させてデプロイするまでの流れを書いていきますー。&lt;/p&gt;
&lt;h2&gt;初回デプロイするまで&lt;/h2&gt;
&lt;p&gt;（※すでにアカウントがある場合、1、2はスキップ）&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;公式サイトにアクセスして&lt;code&gt;Get started for free&lt;/code&gt;を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netlify アカウントの作成
各種 Git のアカウントでも作成できます。
&amp;lt;ImageWrapper src=&quot;screenshots/2019/netlify-deploy/netlify-signup.png&quot; alt=&quot;Netlifyアカウント作成画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Netlify の自分のアカウントトップ画面から&lt;code&gt;New site from Git&lt;/code&gt;を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;以下のどの Git サービスと連携させるか選択&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;GitHub&lt;/li&gt;
&lt;li&gt;GitLab&lt;/li&gt;
&lt;li&gt;Bitbucket&lt;/li&gt;
&lt;/ul&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;code&gt;Authorize Netlify&lt;/code&gt;で連携を承認（初回のみ）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一覧から連携させるリポジトリを選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;デプロイの設定
&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2019/netlify-deploy/deploy-setting.png&quot; alt=&quot;Netlidyデプロイ設定画面&quot; /&amp;gt;
※↑ Jekyll で作成されたサイトの場合&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;Branch to deploy
どのブランチをデプロイ対象とするかを指定。&lt;/li&gt;
&lt;li&gt;Build command
ビルドを実行するコマンド。仕様ツールに応じたコマンドを指定。&lt;/li&gt;
&lt;li&gt;Publish directory
公開対象となるディレクトリを指定。
（例として Jekyll の場合は、&lt;code&gt;jekyll build&lt;/code&gt;でビルドして生成されたファイルは&lt;code&gt;_site&lt;/code&gt;配下に展開されるようになっています）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;8.&lt;code&gt;Deploy site&lt;/code&gt;を選択
ビルドおよび初回デプロイが実行されます。
なお、初回は依存関係をインストールするため少し時間がかかりますが、それ以降はキャッシュを用いるため、はやく終わるようになります。&lt;/p&gt;
&lt;p&gt;これが初回デプロイまでの手順になります。
はやいです。
自分がはじめてやってみた時は、5分もかからず、思わず「はやー」と言っていました(笑)。&lt;/p&gt;
&lt;p&gt;ビルド + 初回デプロイが終わると、自動的にドメインが割り当てられて公開されます。
（プロジェクトの Overview で公開サイト URL のリンクがあるので、そこからもいけます）&lt;/p&gt;
&lt;p&gt;独自ドメインへの変更も比較的簡単に行えます。もちろんカスタムドメインに変更も可能です。&lt;/p&gt;
&lt;p&gt;初回デプロイ以降は、デプロイ対象のブランチへ push するたびに、自動的にビルド+デプロイが実行されるようになります。楽ちんです。&lt;/p&gt;
&lt;h2&gt;その他の特徴&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;プルリク（マジリク）検知
デプロイ対象のブランチに対してリクエストを出すと、検知して自動的にプレビューが作成され、マージされた後の状態を事前に確認できます。
（※プレビューも公開 URL になるため、商用で使用する際は別途セキュリティを考慮する必要があることに注意）&lt;/li&gt;
&lt;li&gt;ロールバック
公開するサイトの状態を簡単に過去のデプロイの状態に戻すことができます。
（デプロイ一覧から任意のものを選択して、&lt;code&gt;Publish deploy&lt;/code&gt;を選択するだけ）&lt;/li&gt;
&lt;li&gt;Slack 連携
デプロイ成功時など、Slack に通知を送ることができます。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;この他にも機能はあるのですが、とても書ききれないのでこの辺で。&lt;/p&gt;
&lt;p&gt;Netlify は無料プランでも便利な機能が多く使用できるようです。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;今回はじめて Netlify を使用したのですが、比較的扱いやすい印象です。
他の紹介記事でも書かれていたのですが、なにより UI が優れているので英語でもわかりやすいですー。わかりやすいの大事。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://docs.netlify.com&quot; /&amp;gt;
Netlify docs を見れば、おおよそどんな機能があるかもわかります。
まだ、半分しか読めてないので読まねば...&lt;/p&gt;
&lt;p&gt;Netlify の機能を用いて、少しずつ当ブログを改修していきますー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.netlify.com/&quot;&gt;Netlify&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.netlify.com&quot;&gt;Netlify docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.qookie.jp/posts/info-netlify-static-web-deploy/&quot;&gt;NetlifyはGitHubなどからデプロイできる静的Webホスティングサービス&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://photo-tea.com/p/17/netlify-github-pages-hexo/&quot;&gt;Netlify移行記：GitHub Pagesから独自ドメイン＋Netlifyの引っ越しでやったこと&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GitLabでリポジトリ（ソースコード）を別アカウントのリポジトリに移行する</title><link>https://changeofpace.site/posts/2018-09-17-gitlab-repo-migration</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-09-17-gitlab-repo-migration</guid><description>GitLab内でリポジトリのソースコードを別アカウントのリポジトリに移行する手順</description><pubDate>Mon, 17 Sep 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;仕事用アカウントで個人的に作っていたプロジェクトを、個人用アカウントの方に移行したので、念のための備忘録。
（こんなの誰でもわかるやろって言われそうですが...）&lt;/p&gt;
&lt;p&gt;※2020/05/06追記
この記事での移行は、あくまで&lt;strong&gt;コード部分のみの移行&lt;/strong&gt;であることに注意です。&lt;/p&gt;
&lt;h2&gt;移行先リポジトリ&lt;/h2&gt;
&lt;h3&gt;作成&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;GitLab のメニューから&lt;code&gt;Projects&lt;/code&gt;→&lt;code&gt;Your Projects&lt;/code&gt;でプロジェクト一覧へ&lt;/li&gt;
&lt;li&gt;&lt;code&gt;New Project&lt;/code&gt;から新規プロジェクト作成&lt;/li&gt;
&lt;li&gt;任意の名前、任意の設定で&lt;code&gt;Create Project&lt;/code&gt;を選択&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;移行元リポジトリ&lt;/h2&gt;
&lt;h3&gt;ローカルのプロジェクトの設定変更&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;push 先の変更
&lt;code&gt;vi .git/config&lt;/code&gt;などで、リポジトリの URL を移行先リポジトリの URL に変更する。&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;# 例
[core]
        repositoryformatversion = 0
        filemode = false
        bare = false
        logallrefupdates = true
[remote &quot;origin&quot;]
        url = git@gitlab.com:XXXX/〇〇〇〇 ←このURLを変更
        fetch = +refs/heads/*:refs/remotes/origin/*
[branch &quot;master&quot;]
        remote = origin
        merge = refs/heads/master
[branch &quot;develop&quot;]
        remote = origin
        merge = refs/heads/develop
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;移行先リポジトリに push する
&lt;code&gt;git push origin (ブランチ名)&lt;/code&gt;で push して、移行先リポジトリに反映させる。&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;リポジトリの削除&lt;/h3&gt;
&lt;p&gt;無事に push が成功し、移行先リポジトリに反映されたら、不要になった移行元のリポジトリを削除する。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;移行元リポジトリのトップ画面のサイドメニューから&lt;code&gt;Setting&lt;/code&gt;→&lt;code&gt;General&lt;/code&gt;を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;一番下の&lt;code&gt;Advanced&lt;/code&gt;のなかの、&lt;code&gt;Remove project&lt;/code&gt;を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;表示される警告モーダルのなかで、そのプロジェクト名を入力し&lt;code&gt;confirm&lt;/code&gt;を選択&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;自分がこの移行をした際は、使用する GitLab のアカウントも変えたり SSH キーを設定したりもしたので、もう少し手順が増えましたが慣れてしまえばそう難しくはなかったです。
（はじめてやった時は時間かかってしまってましたが...苦笑）&lt;/p&gt;
&lt;p&gt;しょっちゅう、こういった移行をすることはないですが一応覚えておきたいものです。&lt;/p&gt;
</content:encoded></item><item><title>GASで掃除当番Botを作ってみる ③ランダムで掃除当番決め（複数グループ）</title><link>https://changeofpace.site/posts/2018-09-09-cleaning-duty-3</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-09-09-cleaning-duty-3</guid><description>グループごとに掃除当番を決めるBot作成チャレンジ</description><pubDate>Sun, 09 Sep 2018 14:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回までで、1つのグループ内でランダムに当番決め + 通知 ができました。
今回は、これを複数グループ対応にしていきます。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;&lt;/p&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;h3&gt;スプレッドシート側&lt;/h3&gt;
&lt;p&gt;こんな感じで5グループにしてみました。
人数も増やしています。
&amp;lt;ImageWrapper src=&quot;screenshots/2018/cleaning-duty/five-group-list.png&quot; alt=&quot;グループ表&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;実装&lt;/h2&gt;
&lt;p&gt;※2019/12/29 再代入しない変数の宣言を const に修正しました。
※2020/2/16
v8 ランタイムに対応したため、let やアロー関数などが新たに使用できるようになりました。
これに伴いリファクタリングを行っています。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ランタイムの変更は、GASエディタを開いたときに表示される案内（&lt;code&gt;Enable new Apps Script runtime powered by Chrome V8 for this project.&lt;/code&gt;）から変更。
もしくは、実行 → Chrome V8 を搭載した新しい Apps Script ランタイムを有効にする からできます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;// 掃除当番をランダムで決めて通知
function noticeCleaningDuty() {
  let msg;
  try { // ⑤
    // 連携するスプレッドシートのうち、グループ表のシート情報を取得
    const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&apos;グループ表&apos;); //①
    // 指定シートの全ての値を取得（二次元配列）
    const data = sheet.getDataRange().getValues();
    // 掃除当番のグループ
    const groupNames = [&apos;A&apos;, &apos;B&apos;, &apos;C&apos;, &apos;D&apos;, &apos;E&apos;]; //②
    // 掃除当番を決定し、通知メッセージを生成
    msg = decideCleaningDuty(data, groupNames); //③
  } catch (e) {
    msg = &apos;エラーが発生しました：&apos; + &apos;\nstack：\n&apos; + e.stack;
  }

  try { // ⑤
    // Slack 側 Incoming WebHook の URL
    const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty(&apos;WEBHOOK_URL&apos;);

    // Incoming WebHook に渡すパラメータ
    const jsonData =
        {
          &apos;text&apos;: msg
        };

    // パラメータを JSON に変換
    const payload = JSON.stringify(jsonData);

    // 送信オプション
    const options =
        {
          &apos;method&apos;: &apos;post&apos;,
          &apos;contentType&apos;: &apos;application/json&apos;,
          &apos;payload&apos;: payload
        };
    // 指定 URL、オプションでリクエスト
    UrlFetchApp.fetch(WEBHOOK_URL, options);
  } catch (e) {
    Logger.log(&apos;送信エラー：&apos; + &apos;\nstack：\n&apos; + e.stack);
  }
}

// 掃除当番を決定し、通知メッセージを作成して返す ③
function decideCleaningDuty(data, groupNames) {
  let noticeMsg = &apos;今週の掃除当番のお知らせ\n\n&apos;;
  for (let i = 0; i &amp;lt; groupNames.length; i++) {
    const groupNameRow = searchGroupNameRow(data, groupNames[i]);
    const lastColumn = getLastColumn(data, groupNameRow);
    const menbers = data[groupNameRow - 1].slice(2, lastColumn);
    const numbers = toDraw(menbers.length);
    noticeMsg += groupNames[i] + &apos;：&apos; + menbers[numbers[0]] + &apos;・&apos; + menbers[numbers[1]] + &apos;\n&apos;;
  }
  noticeMsg+= &apos;\nよろしくお願いします。&apos;
  return noticeMsg;
}

// 指定グループ名がある行番号を返す ④
function searchGroupNameRow(data, groupName) {
  for (let i = 0; i &amp;lt; data.length; i++) {
    if (data[i][0] === groupName) {
      return i + 1;
    }
  }
  throw new Error(groupName + &apos;グループのデータが見つかりませんでした。&apos;);
}

// 指定した行の最終使用列番号を返す
function getLastColumn(data, row) {
  for (let i = 2; i &amp;lt;= data[row-1].length; i++) {
    if (data[row-1][i] === undefined || data[row-1][i] === &apos;&apos;) {
      return i;
    }
  }
}

// 掃除当番の抽選結果を返す
function toDraw(length) {
  let numbers = [];
  numbers[0] = random(length);
  do {
    numbers[1] = random(length);
  } while(numbers[0] === numbers[1]);
  return numbers;
}

// 0～(length-1)の乱数を返す
function random(length) {
  //例 5人の場合 0~0.9999… * 5 の小数点切り捨てで、0~4になる
  return Math.floor(Math.random() * length);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;大まかな変更箇所&lt;/h3&gt;
&lt;h4&gt;① データを取得したいシートの指定でシート名で指定するように変更&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// before
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];

// after
const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheetByName(&apos;グループ表&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;に変更し、取得したいシートの名称を指定するように変更。&lt;/p&gt;
&lt;h4&gt;② グループ名称の配列の用意を追加&lt;/h4&gt;
&lt;p&gt;今回は複数グループ対応にするため、グループ名称の配列を用意。&lt;/p&gt;
&lt;h4&gt;③ 通知メッセージの作成部分をメソッドに変更&lt;/h4&gt;
&lt;p&gt;掃除当番を決定し、通知メッセージを作成する処理をメソッド化。&lt;/p&gt;
&lt;h4&gt;④ 指定した名称に応じた行番号を取得するメソッドを追加&lt;/h4&gt;
&lt;p&gt;グループ名に応じた行の番号を取得するメソッドを追加。&lt;/p&gt;
&lt;p&gt;上記の変更箇所はありますが、基本的には、前回まで書いていた処理を複数グループ分、for で回しているというだけです。&lt;/p&gt;
&lt;h4&gt;⑤ try〜catch ブロックを追加（2019/12/29追記）&lt;/h4&gt;
&lt;p&gt;コードやスプレッドシートのデータがおかしいなどして、エラーになった場合、エラーメッセージを Slack に投稿してくれるよう追記しました。
エラーメッセージがあることで、修正もしやすいです。&lt;/p&gt;
&lt;h3&gt;実行結果&lt;/h3&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2018/cleaning-duty/notification-2.png&quot; alt=&quot;Slack投稿結果&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;こんな感じになりましたー。
複数グループでの当番決めも、なんとかできました。
（前回と違い、メンバー名のところが敬称略形式になっていますが、そこは突っ込まないでください...）&lt;/p&gt;
&lt;p&gt;ちなみに bot の名称とアイコンが変わっていますが、これは&lt;code&gt;Incoming Webhook&lt;/code&gt;の設定から変更可能です。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2018/cleaning-duty/icon-name-setting.png&quot; alt=&quot;webhook設定画面&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;※ちなみに JSON ペイロードに渡すパラメータとして、以下のように指定して、設定を上書きもできるそうです。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;var jsonData =
  {
    &apos;text&apos;: msg,
    &apos;username&apos;: &apos;new-bot-name&apos;,
    &apos;icon_url&apos;: &apos;https://slack.com/img/icons/app-57.png&apos;
    //もしくは &apos;icon_emoji&apos;: &apos;:ghost:&apos;
  };
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こちらは今回試してはいないのですが、容易に変更できるのを考えると、こちらで設定する方がいいかも...？（人によっての好みでしょうか...）&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;あとやれたらいいかなと思った機能として、以下のようなものでしょうか。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;前回当番になった人を除外するようにする&lt;/li&gt;
&lt;li&gt;これまで当番になった回数の少ない人が優先的に選ばれるようにする&lt;/li&gt;
&lt;li&gt;当番を決めた後、それをスプレッドシートに書き込んで履歴にする&lt;/li&gt;
&lt;li&gt;設定した時間で通知するようにする（これはトリガー設定だけなので恐らく簡単かなと）&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;GAS の勉強として続きをやるのもいいのですが、ちょっと他の勉強もしたいので、一旦ここまでにしておきます。
勉強したいとか、ちょっとやってみたいとか、これも知っておいた方がいいだろうなとか、そういったことが山積みでとても追いつかないですが（涙）&lt;/p&gt;
&lt;p&gt;あまりきれいなコードではありませんが、
もし「GAS で bot 作るー」という方がいたとして、少しでも何かの参考にでもなれば幸いです。&lt;/p&gt;
</content:encoded></item><item><title>GASで掃除当番Botを作ってみる ②ランダムで掃除当番決め</title><link>https://changeofpace.site/posts/2018-09-09-cleaning-duty-2</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-09-09-cleaning-duty-2</guid><description>ランダムで掃除当番決めするBot作成チャレンジ</description><pubDate>Sun, 09 Sep 2018 12:00:00 GMT</pubDate><content:encoded>&lt;p&gt;前回、テスト投稿ができました。
次はスプレッドシートからメンバー情報を読み取り、そのメンバーの中からランダムで掃除当番を決めて通知するようにしていきますー。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;&lt;/p&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;h3&gt;スプレッドシート側&lt;/h3&gt;
&lt;p&gt;こんな感じで簡易的にメンバー表を作成してみました。
今後、複数グループで当番決めをしたいので、一旦 A グループとしています。
&amp;lt;ImageWrapper src=&quot;screenshots/2018/cleaning-duty/group-list.png&quot; alt=&quot;グループ表&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;実装&lt;/h2&gt;
&lt;h3&gt;GAS 側&lt;/h3&gt;
&lt;p&gt;一旦、こんな感じで実装。
それにしても、スプレッドシートの行や列番号は1から始まるのに対して、配列の添え字は0から始まるのでややこしいですねぇ(遠い目)
実装する上で、この点に関して少し混乱してしまいました。&lt;/p&gt;
&lt;p&gt;※2019/12/29 再代入しない変数の宣言を const に修正しました。
※2020/02/16
v8 ランタイムに対応したため、let やアロー関数などが新たに使用できるようになりました。
これに伴いリファクタリングを行っています。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-disable ja-technical-writing/sentence-length --&amp;gt; */}&lt;/p&gt;
&lt;p&gt;ランタイムの変更は、GAS エディタを開いたときに表示される案内（&lt;code&gt;Enable new Apps Script runtime powered by Chrome V8 for this project.&lt;/code&gt;）から変更。
もしくは実行→ Chrome V8 を搭載した新しい Apps Script ランタイムを有効にする からできます。&lt;/p&gt;
&lt;p&gt;{/* &amp;lt;!-- textlint-enable --&amp;gt; */}&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function noticeCleaningDuty() {
  // 連携するスプレッドシートの最初のシートを取得
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  // 指定シート、行の最終使用列番号を取得
  const lastColumn = getLastColumn(sheet, 4);
  // メンバー名称の取得（戻り値は二次元配列）
  const menbers = sheet.getSheetValues(4, 3, 1, lastColumn - 2);
  // 掃除当番抽選結果を取得
  const numbers = toDraw(menbers[0].length);

  const WEBHOOK_URL = // Webhook URL を記述

  // Incoming WebHook に渡すパラメータ
  const jsonData =
      {
        &apos;text&apos;: &apos;本日の掃除当番は &apos; + menbers[0][numbers[0]] + &apos;さん と &apos; + menbers[0][numbers[1]] + &apos;さん です&apos;
      };

  // パラメータを JSON に変換
  const payload = JSON.stringify(jsonData);

  // 送信オプション
  const options =
      {
        &apos;method&apos;: &apos;post&apos;,
        &apos;contentType&apos;: &apos;application/json&apos;,
        &apos;payload&apos;: payload
      };

  // 指定 URL、オプションでリクエスト
  UrlFetchApp.fetch(WEBHOOK_URL, options);
}

// 指定したシート・行の最終使用列を返す
function getLastColumn(sheet, row) {
  let lastColumn = 0;
  for (let i = sheet.getLastColumn(); i &amp;gt; 0; i--) {
    if(sheet.getRange(row, i).getValue() != &apos;&apos;) {
      break;
    }
  }
  lastColumn = i;
  return lastColumn;
}

// 掃除当番の抽選結果を返す
function toDraw(length) {
  let numbers = [];
  numbers[0] = random(length);
  do {
    numbers[1] = random(length);
  } while(numbers[0] === numbers[1]);
  return numbers;
}

// 0～(length-1)の乱数を返す
function random(length) {
  // 例 5人の場合 0~0.9999… * 5 の小数点切り捨てで、0~4になる
  return Math.floor(Math.random() * length);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;実行結果&lt;/h3&gt;
&lt;p&gt;5回実行してみました。
現時点では単純にランダム選定なので、連続して同じ人になっているところもあります。&lt;/p&gt;
&lt;p&gt;さすがに3回連続はキレられますね(笑)&lt;/p&gt;
&lt;p&gt;&amp;lt;ImageWrapper className=&quot;w-[70%]&quot; src=&quot;screenshots/2018/cleaning-duty/notification-1.png&quot; alt=&quot;Slack投稿結果&quot; /&amp;gt;&lt;/p&gt;
&lt;h2&gt;リファクタ&lt;/h2&gt;
&lt;p&gt;上記のコードでも一応動作はするのですが、なんか微妙...。&lt;/p&gt;
&lt;p&gt;また、調べながらやっていた中で見つけた記事に「API の呼び出し回数が多いと動作が遅くなる」という文言がありました。
今回の場合でいうと、スプレッドシートにアクセスして値を取得したり、値をセットしているときになります。この処理を for 文で回していたりすると、重たくなる原因だそうで。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;getLastColumn(sheet, row)&lt;/code&gt;の if 文のところでやってる&lt;code&gt;sheet.getRange(row, i).getValue()&lt;/code&gt;なんかは、まさしく該当しそうです...。&lt;/p&gt;
&lt;p&gt;ということでリファクタをすることに。&lt;/p&gt;
&lt;p&gt;※2019/12/29 再代入しない変数の宣言を const に修正しました。
※2020/02/16
v8 ランタイムに対応したため、let やアロー関数などが新たに使用できるようになりました。
これに伴いリファクタリングを行っています。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function noticeCleaningDuty() {
  // 連携するスプレッドシートの最初のシートを取得
  const sheet = SpreadsheetApp.getActiveSpreadsheet().getSheets()[0];
  // 指定シートのすべての値を取得（戻り値は二次元配列）
  const data = sheet.getDataRange().getValues();  //①
  // 指定シート、行の最終使用列番号を取得
  const lastColumn = getLastColumn(data, 4);
  // メンバー名称の取得
  const menbers = data[4-1].slice(2, lastColumn);  //②
  // 掃除当番抽選結果を取得
  const numbers = toDraw(menbers.length);

  // Webhook URL を記述
  const WEBHOOK_URL = PropertiesService.getScriptProperties().getProperty(&apos;WEBHOOK_URL&apos;);  //④

  // Incoming WebHook に渡すパラメータ
  const jsonData =
      {
        &apos;text&apos;: &apos;本日の掃除当番は &apos; + menbers[numbers[0]] + &apos;さん と &apos; + menbers[numbers[1]] + &apos;さん です&apos;
      };
  const payload = JSON.stringify(jsonData);

  // 送信オプション
  const options =
      {
        &apos;method&apos;: &apos;post&apos;,
        &apos;contentType&apos;: &apos;application/json&apos;,
        &apos;payload&apos;: payload
      };
  // 指定 URL、オプションでリクエスト
  UrlFetchApp.fetch(WEBHOOK_URL, options);
}

// 指定した行の最終使用列番号を返す
function getLastColumn(data, row) {  //③
  for (let i = 2; i &amp;lt;= data[row-1].length; i++) {
    if(data[row-1][i] === undefined || data[row-1][i] === &apos;&apos;) {
      return i;
    }
  }
}

// 掃除当番の抽選結果を返す
function toDraw(length) {
  let numbers = [];
  numbers[0] = random(length);
  do {
    numbers[1] = random(length);
  } while(numbers[0] === numbers[1]);
  return numbers;
}

// 0～(length-1)の乱数を返す
function random(length) {
  //例 0~0.9999… * 5 の小数点切り捨てで、0~4になる
  return Math.floor(Math.random() * length);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;大まかな変更箇所&lt;/h3&gt;
&lt;h4&gt;① 最初に行う、シートのデータ取得を全部まとめて取得に変更&lt;/h4&gt;
&lt;p&gt;シートのデータをその都度取りに行っていたところを、
最初にシートのデータをまとめて2次元配列で取得して、それをもとに処理をしていくようにしました。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;const data = sheet.getDataRange().getValues();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;がその処理です。&lt;/p&gt;
&lt;h4&gt;② ①の変更に伴い、取得したシート全体のデータから必要箇所を抽出するように変更&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;// before
const menbers = sheet.getSheetValues(4, 3, 1, lastColumn - 2);

// after
const menbers = data[4-1].slice(2, lastColumn);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;元々、&lt;code&gt;sheet.getSheetValues(行番号, 列番号, 行数, 列数)&lt;/code&gt;でシートから該当箇所のデータを取得していました。
そこを、①で最初にデータをまとめて取得しているため、それを使うようにしています。
&lt;code&gt;slice&lt;/code&gt;を使っているのは、メンバーの名前の部分のみ取得したいからです。&lt;/p&gt;
&lt;h4&gt;③ 最終列の判定方法を変更&lt;/h4&gt;
&lt;p&gt;元々、&lt;code&gt;sheet.getLastColumn()&lt;/code&gt;でシート全体での最終列を取得し、1列ずつ内側を参照し（シートにデータを取りに行き）、値がセットされていたら最終使用列とみなしていました。
それを逆に、（最初にまとめて取得したデータで）内側から1列ずつ外側を参照し、値がセットされていない、もしくは&lt;code&gt;undefined&lt;/code&gt;になったらそこを最終使用列とみなすとしました。&lt;/p&gt;
&lt;h4&gt;④ WebHookURL をスクリプトプロパティから取得するように変更（※2019/12/21追記）&lt;/h4&gt;
&lt;p&gt;WebHookURL をそのまま書いていましたが、こういったものはコードへべた書きせずに&lt;code&gt;プロパティストア&lt;/code&gt;で管理するとよいです。
GAS プロジェクトに紐づいてデータを持っておける領域で、プロパティ（キー）：値 の形式で環境変数のように扱うことができます。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;プロパティストア&lt;/code&gt;は複数の種類がありますが、今回は&lt;code&gt;スクリプトプロパティ&lt;/code&gt;を使用しています。&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;p&gt;登録、編集
GAS エディタの ファイル→プロジェクトのプロパティ→スクリプトのプロパティ から
&lt;strong&gt;※注意...そのGASプロジェクトのオーナー権限のアカウントである必要があります。&lt;/strong&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;使用&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;PropertiesService.getScriptProperties().getProperty(&apos;プロパティ名&apos;);
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;あとは細かなところを修正しました。&lt;/p&gt;
&lt;p&gt;とりあえず、1グループでランダム当番決め + 通知はできました。
次は、これを複数グループ対応にしていきます。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://tonari-it.com/gas-spreadsheet-find/&quot;&gt;Google Apps Scriptでスプレッドシート内を検索して行番号を返す関数（高速版）&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/ryan5500/items/e72eb205fbe006c2eb6f&quot;&gt;GASでSpreadsheetを操作する自分的ベストプラクティス&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://tonari-it.com/gas-property-store/&quot;&gt;【初心者向けGAS】プロパティストアの概要とスクリプトプロパティの編集方法&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>GASで掃除当番Botを作ってみる ①テスト投稿</title><link>https://changeofpace.site/posts/2018-09-09-cleaning-duty-1</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-09-09-cleaning-duty-1</guid><description>初めてのGASでBot作成チャレンジ</description><pubDate>Sun, 09 Sep 2018 10:00:00 GMT</pubDate><content:encoded>&lt;p&gt;職場で掃除当番の話が出ていたので、自動で掃除当番を決めて通知してくれる bot がいたらいいのかなぁと思い、ちょっと bot 作成に挑戦してみました。&lt;/p&gt;
&lt;p&gt;import ImageWrapper from &quot;@/components/ImageWrapper.astro&quot;&lt;/p&gt;
&lt;h2&gt;事前準備&lt;/h2&gt;
&lt;p&gt;まずは構成を構築していきます。
（以下、&lt;code&gt;Google Apps Script&lt;/code&gt;を&lt;code&gt;GAS&lt;/code&gt;と記述しています）&lt;/p&gt;
&lt;h3&gt;構成図&lt;/h3&gt;
&lt;p&gt;大まかな流れはこんな感じ。
（相変わらずスプレッドシートで図形描画はしづらいですねぇ...）
&amp;lt;ImageWrapper src=&quot;screenshots/2018/cleaning-duty/architecture.png&quot; alt=&quot;構成図&quot; /&amp;gt;&lt;/p&gt;
&lt;h3&gt;Slack 側&lt;/h3&gt;
&lt;p&gt;GAS から Slack に投稿するにあたって、
&lt;code&gt;Slack API&lt;/code&gt; or &lt;code&gt;Incoming-webhook&lt;/code&gt; が必要になりますが、今回は後者の方を使用しています。&lt;/p&gt;
&lt;h4&gt;ワークスペースとチャンネルを用意&lt;/h4&gt;
&lt;p&gt;Slack 側で bot の投稿をしたいワークスペースとチャンネルを事前に用意しておきます。&lt;/p&gt;
&lt;h4&gt;ワークスペースに Incoming-webhook を設定する&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;ワークスペースメニュー → その他管理項目 → App管理へ
（あとからキャプチャを撮ったので、キャプチャ上で投稿されている内容については後から出てきます...）
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2018/cleaning-duty/incoming-webhook-setting.png&quot; alt=&quot;Slackの設定からApp管理へ行くまで&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;(右上の Menu → )検索へ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Incoming webhook で検索し、Incoming webhook を選択し、設定画面へ
&amp;lt;ImageWrapper className=&quot;w-[60%]&quot; src=&quot;screenshots/2018/cleaning-duty/incoming-webhook-search.png&quot; alt=&quot;アプリ検索画面&quot; /&amp;gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;※すでに Incoming webhook を追加済みの場合&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;ワークスペースメニュー
→ その他管理項目
→ App管理
→ (セクションを変更する)
→ カスタムインテグレーション
→ Incoming webhook
を選択でも、同様の画面に行けます。&lt;/p&gt;
&lt;hr /&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;設定を追加 を選択&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;チャンネルへの投稿 で投稿したいチャンネルを選択 → 着信 Web フックインテグレーションの追加 を選択 で設定詳細画面へ&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;任意で設定を変更して、設定を保存する を選択
なお、この画面にある&lt;code&gt;Webhook URL&lt;/code&gt;は&lt;strong&gt;GAS 側で使用する&lt;/strong&gt;ので控えておいてください。
また、&lt;code&gt;セットアップの手順&lt;/code&gt;、&lt;code&gt;メッセージ添付ファイル&lt;/code&gt;の項目で、この webhook の使い方が書いてあるので、読んでおくといいですよー。&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;Google Drive 側&lt;/h3&gt;
&lt;h4&gt;スプレッドシート&lt;/h4&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;スプレッドシートを作成（この段階では何も記述しなくて OK）&lt;/p&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;スプレッドシートのメニュー → ツール → スクリプトエディタ を選択&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;Slack に投稿する&lt;/h2&gt;
&lt;h3&gt;GAS に投稿内容を記述&lt;/h3&gt;
&lt;p&gt;ここから実際に Slack へ投稿する内容を記述していきます。
GAS では、JavaScript で記述していきます。&lt;/p&gt;
&lt;p&gt;Webhook URL にデータを送信するには、以下の2つの方法があるようです。
（今回は前者のやり方になります）&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;POST リクエストで&lt;code&gt;payload&lt;/code&gt;パラメータとして JSON を送信する&lt;/li&gt;
&lt;li&gt;POST リクエストの本体として、JSON を送信する&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;ここでは一旦、ちゃんと投稿ができるか確認するためにテスト投稿を行います。&lt;/p&gt;
&lt;p&gt;※2019/12/29 変数宣言の var を const に修正しました。
※2020/02/16 細かな修正。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;function myFunction() {
  const WEBHOOK_URL = // 先ほど控えた WebhookURL を指定

  // Incoming WebHook に渡すパラメータ
  const jsonData =
      {
        &apos;text&apos;: &apos;test&apos;
      };

  // パラメータを JSON に変換
  const payload = JSON.stringify(jsonData);

  // 送信オプション
  const options =
      {
        &apos;method&apos;: &apos;post&apos;,
        &apos;contentType&apos;: &apos;application/json&apos;,
        &apos;payload&apos;: payload
      };

  // 指定 URL、オプションでリクエスト
  UrlFetchApp.fetch(WEBHOOK_URL, options);
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;GAS メニューから関数を選択し実行ボタンを押すことで、選択した関数を実行できます。
（※初回実行時には、実行の許可が求められるので許可してください）&lt;/p&gt;
&lt;h3&gt;実行結果&lt;/h3&gt;
&lt;p&gt;実行すると指定した Slack のチャンネルに以下のように投稿されます。
&amp;lt;ImageWrapper className=&quot;w-[80%]&quot; src=&quot;screenshots/2018/cleaning-duty/incoming-webHook-test.png&quot; alt=&quot;Slack投稿結果&quot; /&amp;gt;&lt;/p&gt;
&lt;p&gt;これで投稿ができるようになりました。
今度は、投稿内容を変えていきましょうー。&lt;/p&gt;
&lt;h2&gt;リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/chihiro/items/c7b11abc78f5d806c3a8&quot;&gt;【初心者向け】GASを使ってSlackへ自動通知&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>UbuntuにPHPインストールで格闘した話</title><link>https://changeofpace.site/posts/2018-06-05-php-install</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-06-05-php-install</guid><description>UbuntuにPHPをいれるにあたって、phpenv、brew、phpbrewと挑戦してエラーと格闘した記録</description><pubDate>Tue, 05 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;先日の anyenv 導入後、PHP をいれたかったので&lt;code&gt;phpenv&lt;/code&gt;も入れることにしたのですが...
なかなか長い戦いになりました(遠い目)&lt;/p&gt;
&lt;p&gt;import OG from &quot;@/components/OG.astro&quot;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;※注意点&lt;/strong&gt;
結論を先に言いますと、PHP のインストール自体はできたのですが、Ubuntu の apt コマンド使いました...。
なので、ここから先は&lt;code&gt;phpenv&lt;/code&gt;での導入の参考にはなりませんし、非常に長くなったのであしからず。&lt;/p&gt;
&lt;p&gt;自分が PHP をインストールするまでの戦いの履歴が以下のとおりです。&lt;/p&gt;
&lt;h2&gt;PHP をインストールする&lt;/h2&gt;
&lt;h3&gt;anyenv → phpenv でインストール&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;phpenv インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv install phpenv
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;ログインシェルで各種 profile 再読み込み&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;exec $SHELL -l
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストールできる PHP のバージョンの確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpenv install -l
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;指定したバージョンの PHP をインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpenv install (バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;この時、依存ライブラリも一緒にビルドをしてくれるのですが...以下のようなエラーが。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configure: WARNING: unrecognized options: --with-mcrypt
configure: WARNING: This bison version is not supported for regeneration of the Zend/PHP parsers (found: none, min: 204, excluded: ).
configure: WARNING: You will need re2c 0.13.4 or later if you want to regenerate PHP parsers.
configure: error: libxml2 not found. Please check your libxml2 installation.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;調べていくと...。
ビルドに必要な拡張ライブラリを入れておき、ビルドの際にそれらを使ってくれるようパスを指定したりしていけば、おおよそ解決する模様。
しかし、表示されたエラーに沿って対応していっても、なかなかビルドが通ってくれず。
&lt;code&gt;configure&lt;/code&gt;オプションでビルドの設定ができるのはわかったんですが、具体的なことはイマイチわからずで次の方法へ。&lt;/p&gt;
&lt;p&gt;phpenv をいれるところまではスムーズだったんですけどねー。&lt;/p&gt;
&lt;p&gt;※ちなみにビルドに成功した場合は、以下のコマンドでバージョンを選択できるようです。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;バージョン切り替え&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;システム全体で適用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;phpenv global (バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;一部プロジェクトで適用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;phpenv local (バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストールされているバージョン確認(選択されているバージョンに&lt;code&gt;*&lt;/code&gt;がついている)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpenv versions
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;brew でインストール&lt;/h3&gt;
&lt;p&gt;brew で各バージョンをインストールし、phpenv とリンクさせて繋ぐことで管理できないかなと考えてみました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;インストールできるバージョンの確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew search php@
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;表示される&lt;code&gt;php@5.6&lt;/code&gt; &lt;code&gt;php7.0&lt;/code&gt; &lt;code&gt;php@7.1&lt;/code&gt; &lt;code&gt;php@7.2&lt;/code&gt;が対応バージョンです。&lt;/p&gt;
&lt;p&gt;※2018/03/31まではPHPのフォーミュラとして&lt;code&gt;homebrew/php&lt;/code&gt;があり。
&lt;code&gt;php56&lt;/code&gt;と&lt;code&gt;php56-*&lt;/code&gt;(バージョンに応じた拡張) のような形式だったのですが、&lt;code&gt;homebrew/core&lt;/code&gt;に統合された関係で上記のような形式になった模様。(拡張は組み込み式に変更)
※2024/02/11にはこの情報を参照していた記事が見られなくなっていました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;指定したバージョンのPHPをインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;brew install php@(バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;依存パッケージも一緒にインストールしてくれますが...。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;ncurses, util-linux, apr, libbsd, expat, apr-util, brotli, gdbm, readline, sqlite, bzip2, python@2, libxml2, c-ares, libev, libevent, jansson, boost, jemalloc, nghttp2, pcre, httpd, aspell, curl, freetds, libpng, freetype, gettext, libffi, glib, icu4c, jpeg, libpq, libtool, libzip, mhash, mcrypt, unixodbc, libtiff, webp
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こんな感じでたくさん入ります。
こんなにあるんや...とか、なんか&lt;code&gt;python&lt;/code&gt;まで入ってるんやけどとか思いつつ。
順調にインストールが進んでいたのでいけるかと思いきや、最後の PHP のビルドでまたもやエラーに。
こんなにたくさん依存パッケージインストールしといて最後にコケるんかーい。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;configure: error: Cannot find sys/sdt.h which is required for DTrace support
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;エラーメッセージを頼りに調べていくと、どうやら&lt;code&gt;openssl&lt;/code&gt;に関するエラーの模様。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;Please do not report this issue to Homebrew/brew or Homebrew/core. which support macOS only.
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ちなみにエラーメッセージのあとにはこんなメッセージが。
「macOS しかサポートしてないから、不具合等の報告は送らないで」ということでしょうか。&lt;/p&gt;
&lt;p&gt;ダメもとで&lt;code&gt;openssl&lt;/code&gt;の新しいバージョンのものを入れてみたり、パスを確認してみたりするも、うまくいかず...。&lt;/p&gt;
&lt;p&gt;この時点で結構な時間を使っていたので、「PHP のビルドがうまくいかなくてキレそう」とつぶやいたところ、先輩から助け舟が。しかし、残念ながらそれもむなしい結果になってしまいました...。&lt;/p&gt;
&lt;h3&gt;phpbrew でインストール&lt;/h3&gt;
&lt;p&gt;今度は、以前業務で使ったことがある&lt;code&gt;phpbrew&lt;/code&gt;で挑戦してみることにしました。
&lt;code&gt;phpbrew&lt;/code&gt;は&lt;code&gt;phpenv&lt;/code&gt;と&lt;code&gt;php-build&lt;/code&gt;を1つにしたようなもので、こちらでもバージョン管理ができます。
業務で使っていたときはbrewでインストールしていましたが、前述のとおり&lt;code&gt;homebrew/php&lt;/code&gt;が使えなくなったため、curl コマンドでインストールすることに。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://github.com/phpbrew/phpbrew&quot; /&amp;gt;
※環境に応じた事前に必要なものが記載されているので、先に入れておくといいかも&lt;/p&gt;
&lt;p&gt;基本的に公式の案内に沿って進めました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;curl -L -O https://github.com/phpbrew/phpbrew/raw/master/phpbrew
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;権限変更&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;chmod +x phpbrew
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;格納場所を変更&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo mv phpbrew /usr/local/bin/phpbrew
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;bash script を初期化&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpbrew init
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;ここでまたもや問題が。
bash script を初期化しようとしたところ、&lt;code&gt;phpbrew&lt;/code&gt;コマンドが使えない状態でした。&lt;/p&gt;
&lt;p&gt;&lt;code&gt;php-cli&lt;/code&gt;をいれれば良いとのことで、brew でインストールしたところ&lt;code&gt;phpbrew&lt;/code&gt;コマンドが使えるようになりました。&lt;/p&gt;
&lt;p&gt;以下続きます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;以下を .bashrc に追記&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;source ~/.phpbrew/bashrc
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストールできるバージョン確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpbrew known
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;指定したバージョンの PHP をインストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpbrew install (バージョン) (バリアントオプション)
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※phpbrew では&lt;code&gt;バリアント&lt;/code&gt;という概念があります。
拡張ライブラリをより扱いやすくしたものといった感じでしょうか。&lt;/p&gt;
&lt;p&gt;はい、やってきましたビルドの時間。
いいかげんビルド成功してほしいという願いもむなしく...やっぱりコケました。
う～ん、PHP に嫌われてるのかなぁと思ってみたり。&lt;/p&gt;
&lt;p&gt;※ビルド成功時には以下のコマンドでバージョン選択できます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;バージョン切り替え&lt;/li&gt;
&lt;/ol&gt;
&lt;ul&gt;
&lt;li&gt;システム全体で適用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;phpbrew switch php-(バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;一部プロジェクトで適用&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code&gt;phpbrew use php-(バージョン)
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストールされているバージョン確認(選択されているバージョンに&lt;code&gt;*&lt;/code&gt;がついている)&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;phpbrew list
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;apt でインストール&lt;/h3&gt;
&lt;p&gt;これまでの戦いでヘロヘロになっていた自分は、バージョン管理をあきらめて&lt;code&gt;apt&lt;/code&gt;コマンドでインストールすることにしました...。&lt;/p&gt;
&lt;p&gt;&amp;lt;OG url=&quot;https://qiita.com/yamatmoo/items/b9e8035c55032de88084&quot; /&amp;gt;
ほぼこちらの方の記事のとおりに進めました。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;リポジトリ追加&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo add-apt-repository ppa:ondrej/php
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;apt のアップデート&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo apt update
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;sudo spt install php(バージョン) (拡張ライブラリ)

# 例（必要に応じてライブラリ追加）
sudo apt install php7.2 php7.2-common php7.2-cli php7.2-fpm php7.2-mysql php7.2-dev php7.2-mbstring php7.2-zip
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;バージョン確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;php -v
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;PHP 7.2.5-1+ubuntu16.04.1+deb.sury.org+1 (cli) (built: May  5 2018 04:59:13) ( NTS )
Copyright (c) 1997-2018 The PHP Group
Zend Engine v3.2.0, Copyright (c) 1998-2018 Zend Technologies
 with Zend OPcache v7.2.5-1+ubuntu16.04.1+deb.sury.org+1, Copyright (c) 1999-2018, by Zend Technologies
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;こんな感じで表示されたら OK です。やっと PHP 使えるようになりました...。
それも apt だと拍子抜けするくらいあっさり終わって、いままで使った時間なんやったんや...。&lt;/p&gt;
&lt;p&gt;バージョン情報を見てみると、もしや Ubuntu 用の PHP だったりするんでしょうか。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;いやはや、とても長い道のりでした（苦笑）
Ubuntu に慣れている方だったらこんなことしないんだろうなぁと思いつつ、まぁ無事に PHP 使えるようになって良かったです。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/phpbrew/phpbrew/blob/master/README.ja.md&quot;&gt;phpbrew - GitHub&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/yamatmoo/items/b9e8035c55032de88084&quot;&gt;UbuntuにPHP7の環境をつくる&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item><item><title>anyenvでバージョン管理</title><link>https://changeofpace.site/posts/2018-06-02-anyenv</link><guid isPermaLink="true">https://changeofpace.site/posts/2018-06-02-anyenv</guid><description>anyenvのインストール手順と使用方法</description><pubDate>Sat, 02 Jun 2018 00:00:00 GMT</pubDate><content:encoded>&lt;p&gt;個人でコード書いていくうえで、そんなに必要ないかも...?と思いつつも、一応、言語をバージョン管理できるようにしておこうと思いまして。&lt;/p&gt;
&lt;p&gt;当ブログは Jekyll という静的サイトジェネレータを使用して作成しています。
この Jekyll を使う上で Ruby が必要になるので、元々 Linuxbrew で rbenv を入れていました。
ふとしたきっかけで、rbenv をはじめとした env 系を一括管理できる&lt;code&gt;anyenv&lt;/code&gt;の存在を知りまして。せっかくなので&lt;code&gt;anyenv&lt;/code&gt;を使ってみることにしました。&lt;/p&gt;
&lt;hr /&gt;
&lt;p&gt;&lt;strong&gt;※2019/11/06追記&lt;/strong&gt;
この記事では&lt;code&gt;anyenv&lt;/code&gt;を&lt;code&gt;git clone&lt;/code&gt;で導入しています。
brewを使用して導入するやり方もあり、以下の記事ではLinuxbrewで導入しています。
&lt;a href=&quot;/posts/2019-09-26-wsl-develop&quot;&gt;WSLでWindowsの中にLinuxの開発環境を作ろう&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;anyenv&lt;/h2&gt;
&lt;p&gt;※env 系の管理を brew から&lt;code&gt;anyenv&lt;/code&gt;に移行する場合は、事前に brew で入れた分をアンインストール＋パスの削除をするか、参考記事のとおり、リンクを張る方法があります。&lt;/p&gt;
&lt;h3&gt;導入&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/riywo/anyenv ~/.anyenv
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;パスの設定
（ここでは&lt;code&gt;.bash_profile&lt;/code&gt;に記述していますが、使用している各種&lt;code&gt;profile&lt;/code&gt;のいずれかで問題ありません）&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;echo &apos;export PATH=&quot;$HOME/.anyenv/bin:$PATH&quot;&apos; &amp;gt;&amp;gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;bash 起動時に自動で init するよう設定&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;echo &apos;eval &quot;$(anyenv init -l)&quot;&apos; &amp;gt;&amp;gt; ~/.bash_profile
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;※これで bash 起動時に&lt;code&gt;anyenv&lt;/code&gt;からインストールした各種 env を init してくれるようになるため、すぐ各種コマンドが使えるようになります。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ログインシェルで各種&lt;code&gt;profile&lt;/code&gt;再読み込み&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;exec $SHELL -l
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;anyenv コマンド確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;以下のように表示されればOK。&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;anyenv
Usage: anyenv &amp;lt;command&amp;gt; [&amp;lt;args&amp;gt;]

Some useful anyenv commands are:
   commands            List all available anyenv commands
   local               Show the local application-specific Any version
   global              Show the global Any version
   install             Install a **env
   uninstall           Uninstall a specific **anv
   version             Show the current Any version and its origin
   versions            List all Any versions available to **env

See &apos;anyenv help &amp;lt;command&amp;gt;&apos; for information on a specific command.
For full documentation, see: https://github.com/riywo/anyenv#readme
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;anyenv-update インストール&lt;/h3&gt;
&lt;p&gt;各種 env とそのプラグインを一括でまとめて更新してくれるものだそうです。
それぞれ1つずつ更新するのは、確かに手間ですよね...。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;格納するディレクトリの作成&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;mkdir -p $(anyenv root)/plugins
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;git clone https://github.com/znz/anyenv-update.git $(anyenv root)/plugins/anyenv-update
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;これで&lt;code&gt;anyenv update&lt;/code&gt;コマンドが使えるようになります。&lt;/p&gt;
&lt;h3&gt;各種 env のインストール&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;インストールできる env の確認&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv install -l
&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code&gt;Available **envs:
  crenv
  denv
  erlenv
  exenv
  goenv
  hsenv
  jenv
  luaenv
  ndenv
  nenv
  nodenv
  phpenv
  plenv
  pyenv
  rbenv
  Renv
  sbtenv
  scalaenv
  swiftenv
&lt;/code&gt;&lt;/pre&gt;
&lt;ol&gt;
&lt;li&gt;インストール&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;anyenv install (各種env)

# 例
anyenv install rbenv
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;このコマンド1つで、ビルドに必要な各種&lt;code&gt;env-build&lt;/code&gt;(rbenvなら&lt;code&gt;ruby-build&lt;/code&gt;)も一緒にインストールしてくれます。&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;ログインシェルで&lt;code&gt;.bash_profile&lt;/code&gt;再読み込み&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code&gt;exec $SHELL -l
&lt;/code&gt;&lt;/pre&gt;
&lt;hr /&gt;
&lt;p&gt;自分はまず&lt;code&gt;anyenv&lt;/code&gt;で&lt;code&gt;rbenv&lt;/code&gt;をインストールしなおしたのですが、特別つまずくこともなくスムーズでした。
パスを汚さなくていいですし、楽でいいですねー。&lt;/p&gt;
&lt;h2&gt;参考リンクまとめ&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;http://d.hatena.ne.jp/riocampos+tech/20141019/install_anyenv&quot;&gt;anyenvをインストール＆Homebrewでインストール済のrbenvと互換を取る&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://qiita.com/zaburo/items/8ac16133c3823c6e6ad6&quot;&gt;anyenvを使う&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</content:encoded></item></channel></rss>