こんにちは。ひらやま(@rhirayamaaan)です。
先日とあるツイートを見かけ、つい反応してしまいました。
これはReactコンポーネントを作る時に最低限必要なTypeScriptの知識をまとめた記事を書く気運高まってますか…!!? https://t.co/rb9iwfNqDG
— ひらやま (@rhirayamaaan) 2023年3月1日
3, 4年くらい前(2023年4月現在)は、フロントエンドを好むエンジニアやプログラムを書くマークアップエンジニアが目を輝かせながら React に飛びついていたように私は感じていました。しかし最近では React を使ったサービスが運用される機会も増え、デザイン修正を行うためにデザイナーが React のコードを触るケースも徐々に増えてきている印象があります。
ここ最近ではデザインエンジニアや UX エンジニアという形で表記ゆれを許しながら新たな職能が生まれつつありますが、React / Vue などの登場により、デザイナーとエンジニアの職能の責務がコード上で交わる機会が、以前に比べてかなり多くなったと感じています。
おそらくデザイナーの方の中には、世にある jQuery のコードを使いながらなんとかインタラクティブな UI を作っていたという方も多くいらっしゃると思いますが、その方がいきなり React に触れるだけでなく、TypeScript のコードの海に飛び込むのはかなりのハードルだろうと想像します。
弊社はデザイナー全員がコードを書くことを大切にしているため React のコードを書いていくことにも果敢に挑戦しており、実際にそのコードは日常的にリリースされています。ただ、日常的に書いていても難しいと思うことも多いようで、デザイナーが UI に近いフロントエンド領域に対して日々関心を高めているのをひしひしと感じます。
なので、今回はかんたんな例を挙げながら、弊社デザイナーだけに閉じず、疑問に感じやすそうな点をピックアップしながら説明していこうと思います。
React のコードを見ることで知ることになった記法の背景や、記述していく中で疑問となりそうなことにも言及しつつ、最終的には、混沌とした React + TypeScript のコードを今より解像度が高く読めるようになること目指していきたいと思います。
なるべく網羅的に記述しようと心がけたため記事の分量は多めです。 しかし、気になったときに気になったところを読んでもらえるよう、なるべく一つのセクションで完結するように書いています。なので、お気軽に読んでいただければと思っています。 もしよろしければ、辞書のようにお手元に置いていただいて、いざというときにサッと見られるようにすると、さらにみなさんのお役に立てるのではないかと思っています。
(この記事ではイメージが付きやすくなるように、仕様通り正しく説明しきれていない箇所があります。そのあたりはご了承いただけるとうれしいです。)
- そもそも JSX ってなに?
- const ってなに?
- () => {} ってなに?
- .map() ってなに?
- Component ってなに?
- import / export ってなに?
- useState / useEffectってなに?
- 自分たちでも hooks は作れるの?
- なんで TypeScript を使うの?
- TypeScript で Component を作るには?
- Component に値を渡すには?
- 型が長くなりすぎてしまう場合は?
- さらに厳格な型を作りたい場合は?
- ... ってなに?
- 子要素を受け付けるようにするには?
- ? ってなに?
- <T> ってなに?
- CSS の書き方は?
- さいごに
そもそも JSX ってなに?
まずは、React を書いているとほぼ確実に出会うこちらの話から始めてみたいと思います。
JSX とは JavaScript を拡張したものです。
JavaScript を使って HTML の要素(正しくは DOM)を生成したい場合は document.createElement('div')
(jQuery の場合は $('<div />')
)と書いていましたが、これを JavaScript 上でも HTML のように <div />
と書けるように拡張したのが JSX です。
現在、JSX はブラウザでは読み取れないので JavaScript に変換する必要があります。それを変換する代表的なものが Babel と呼ばれるトランスパイラです。
別の言語の中で HTML を書いていくやり方は以前からあり、代表的なものだと PHP があります。しかし PHP と JSX ではベースとなる考え方が異なります。PHP の場合は HTML の中に要素の出し分けやループ処理などを書いていく記法がメジャーだと思いますが、JSX の場合は JavaScript のコードの中にマークアップを書いていく感覚になります。
<!-- PHPの例 --> <?php $items = ['apple', 'orange', 'banana']; ?> <ul> <?php foreach($items as $item): ?> <li><?php echo $item ?></li> <?php endforeach; ?> </ul>
// JSXの例 const items = ['apple', 'orange', 'banana'] const FruitsList = () => { return ( <ul> {items.map(item => ( <li>{item}</li> ))} </ul> ) }
上記の通り、JavaScript のコードの中に <ul>
や <li>
が書けるようになっています。この HTML のタグを書けるように JavaScript を拡張してサポートしているのが JSX です。
参考:JSX の導入 – React 参考:JSX を深く理解する – React
const
ってなに?
これまでの例で当たり前のように const
と出てきましたが、これはvar
の代替品です。var
で定義した変数は何度でもその中の値を上書きができましたが、const
は上書きできません。こうすることで、変数の中身がいつの間にか変わってしまうという現象を防ぐことができます。そして、React の場合は基本的に const
を利用して実装します。どうしても変数の上書きをしたい場合は let
を使いますが、こちらが出てくることもあまりないため、使いたくなったら別の実装法を考えるほうが良いかもしれません。どちらにせよ var
を使うのはアンチパターンと考えて良いです。
() => {}
ってなに?
これは Arrow Function と呼ばれる、JavaScript に備わっている新しい関数の書き方です。役割としては従来の function () {}
とほとんど同じですが、function
よりも機能が少なくシンプルです。例えば jQuery で記述していた $('div').on('click', function() { $(this).addClass('foo') })
という形で click されたものだけを取得できましたが、この this
という記述が Arrow Function では使えません。
現在では、基本的に React を書いている場合 this
を利用するケースは少ないです。そのため関数を必要とするときは Arrow Function を使うことが多いです。
ただ、React の公式チュートリアルでは React Component の定義で function
を利用しています。Arrow Function の場合は関数に名前をつけることができないためデバッグがしづらかったり、パフォーマンスのチューニングがしづらいという側面があるようです。これはプロジェクトのコードフォーマットの基準に依存するため、そのプロジェクトの書き方に習うのが良いでしょう。
また、Arrow Function を書く場合、 return
を書くケースと書かないケースがあります。これは、 return
する前に特に処理を必要としない場合は return
を省略できるというルールがあるためです。
function double1 (num) { return 2 * num; } const double2 = function (num) { return 2 * num; } const double3 = (num) => { return 2 * num; } const double4 = (num) => ( 2 * num; ) const double5 = (num) => 2 * num;
これらの関数はすべて同じ処理結果となります。
また、Arrow Function は、引数が一つしかない場合は ()
を省略できるというルールもあります。
const double1 = (num) => 2 * num; const double2 = num => 2 * num;
上記も同じ処理結果となります。
ただ、上記の書き方はプロジェクトによって禁止している記法もあるため、この点に関しても、そのプロジェクトの書き方に習いましょう。
.map()
ってなに?
上述の例にも入っていましたが、React を書いているとmap
関数が頻繁に出てきます。これは jQuery 全盛期には出てこなかった関数だと思います。
map
は、現在の JavaScript では標準で用意されている関数です。ざっくり説明すると、この関数は配列に備わっていて、ループ処理をするための関数です。配列の一つひとつの要素を取り出してそれを意図する形に加工し、新しい配列を生成するための関数です。
そのため、この関数は React で使う特別な関数というわけではありません。配列を操作するために用意された関数には push
shift
concat
join
などなど挙げるとたくさんありますが、さらに JavaScript が進化する過程で便利な関数が増えていきました。map
はその新しく生まれた便利な関数の一つです。
追加された便利な関数の一つに forEach
がありますが、関数名が違うので当然役割も異なります。配列から要素を一つひとつ取り出すところまでは map
と同じですが、その後の新しい配列の生成するところまでは実施しません。なので、もし今回実現したいことを forEach
で実現する場合は、以下の例のように実行前に空の配列を作っておき、forEach
の内部で値を加工した後、その新しい配列に push
してからその配列を返す必要があります。
const items = ['apple', 'orange', 'banana'] const FruitsList = () => { return ( <ul> {(() => { const newItems: ReactElement[] = []; items.forEach((item) => { newItems.push(<li>{item}</li>); }); return newItems; })()} </ul> ) }
map
の場合よりも少し記述量が増えたと思います。 map
の場合は新しい配列を作って返してくれるので、React を書く場合にはシンプルに書くことができます。
なので、React だから map
を使っているというわけではなく、map
を使ったほうが React の場合はシンプルに書けるからよく出てきているというだけです。forEach
を使ったほうが記述しやすい場合には forEach
で記述して問題ありません。
Component ってなに?
1ページ分の HTML を書くことをイメージしてみると、そのファイルのコードはかなり長くなりそうな予感がします。しかし、その HTML の中でも、Button や TextField というような小さな UI はページ内で何度も出てきたり、何なら他のページでも登場する UI だったりするので、コードが冗長になりがちです。
UI として再利用性が高いものだったり、巨大すぎてコードの見通しが悪くなりそうな状態だったりするときに、切り出して管理することができます。大小限らず、一つに切り出されていればそれは Component となります。
React で書いている場合、Component を作る場合は React の機能の上で生成されます。つまり Component の機能は JSX の機能の対象ではありません。JSX の機能の上に、さらに React の Component の機能を使えるようにしているという解釈をすると良いのかもしれません。Vue や Angular などでも React と同じように Component を作ることができますが、これらの上で作られた Component は React Component とは別ものです。
const items = ['apple', 'orange', 'banana'] const FruitsList = () => { return ( <ul> {items.map(item => ( <li>{item}</li> ))} </ul> ) }
React の場合、Component の作り方は関数の作り方と大差ありません。例えば、中学生のときに習った数学の一次関数の f(x) = 2x + 1
を JavaScript で表すなら const f = (x) => { return 2 * x + 1 }
となりますが、Component の関数の場合は数字を return するのではなく、JSX や React Component を return する形になります。
(このような関数を使った Component のことを Functional Component と呼び、現在では React の主流な書き方です。しかし、昔はこの書き方がなく Class Component という書き方が主流でした。今でも Class Component で記述できますが、特別な理由がない場合は Functional Component で実装します。)
上記のように Component を定義しておいた場合、 Component の利用方法は以下の通りです。
const App = () => { return ( <div> <FruitsList /> </div> ) }
Component として定義して利用する体験は、まるで自分が新しい HTML タグを追加したかのような書き心地になります。
また、React Component の名前を決める場合は、必ず大文字から始める必要があります。これは <div>
などと差別化するためで、小文字から始めると期待通りに動かないため注意が必要です。
import / export ってなに?
従来の HTML/JS の形では、分割したコードを各 JS ファイルに保存して、それを script タグで順番に読み込む形を取っていました。
ファイルが分けられていても、特に策を打たずに JS を書いている(処理を即時関数で囲っていない)場合は、すでに script タグで読み込まれたコードであればアクセスすることができてしまい、バグを生みやすい状態でした。(例えば A.js で変数 x を定義していたけれど、B.js でも変数 x に代入してしまい、A.js は意図しない値によってエラーが発生するということが容易に発生する状況でした。)
それを解消するために、ファイルの中で定義したものはファイル内でしか使えないようにし、もし外でも使えるようにしたい場合は、export
と明示して意図的に外に出せるようになりました。
逆に export してあるものを使いたい場合は import
と記述し、変数や関数を読み込むことができます。
// components/modules/FruitsList.jsx // items はこのファイル内でしか使えない const items = ['apple', 'orange', 'banana'] // export しているので他のファイルで import することができる export const FruitsList = () => { return ( <ul> {items.map(item => ( <li>{item}</li> ))} </ul> ) }
// components/pages/Top.jsx // import 文はファイルの場所を指定する import { FruitsList } from '../modules/FruitsList' export const Top = () => { return ( <div> <FruitsList /> </div> ) }
記述した JavaScript や JSX は最終的に一つの JavaScript ファイルにまとめ上げ、そのファイルを script タグで読み込ませてブラウザ上でプログラムが動くようになります。JSX を Babel で変換しながら import / export の依存関係を解決しつつ、一つのファイルへのまとめ上げの作業を Webpack や Vite と呼ばれるツールが担ってくれています。
参考:import - JavaScript | MDN
参考:export - JavaScript | MDN
useState
/ useEffect
ってなに?
React を書いていると use
という接頭辞のついた関数をよく目にすると思います。これらは hooks と呼ばれるもので、タイトルにある 3 つの hooks は React が提供しているものです。(最近は Vue でも hooks が登場しはじめていますが Vue と React の hooks に互換性はありません。)
hooks はこれ以外にも、useRef
useMemo
useCallback
useContext
などがありますが、特に目にする hooks はタイトルにある 2 つの hooks なのではないでしょうか。
hooks は、React のライフサイクルというものに大きく影響する関数です。そもそもライフサイクルというのは React Component が利用されるとき(Mount)や、更新されるとき(Update)、利用を終了したとき(Unmount)の一連のプロセスのことを指します。このプロセスに乗っているとき、React Component は表示されているべきなのか、表示されているのであれば更新させるべきなのかを React が監視下にある状態になります。このライフサイクルプロセスに「引っかけて」処理を走らせる関数なので、hooks と呼びます。
また、なぜ use
という接頭辞がつくのかというと、それぞれ use の後ろに書かれたものを使うからだと解釈しています。関数名の前に I
をつけると I use State
となります。state という機能を使うという意味が込められていると捉えると、覚えやすいかもしれません。
useState
useState
はそのまま関数名の通り、state というものを使うためのものです。ただなぜ state というものが必要なのかはイメージが湧きづらいかもしれません。
JavaScript に限らず、プログラミングをやったことがある人であれば、値を保存するには変数を使えば良いと気付けるはずです。しかし、state として扱う場合はただ変数に入れるだけではうまくいきません。
React Component を作る場合、関数を作ればよいという話をしてきました。そしてそこに変数を作成して state を作ろうと試みます。
const items = ['apple', 'orange', 'banana'] export const FruitsList = () => { let isDisplayed = true const onClick = () => { isDisplayed = !isDisplayed } return isDiplayed ? ( <div> <ul> {items.map(item => ( <li>{item}</li> ))} </ul> <button type="button" onClick={onClick}>toggle</button> </div> ) : null }
let
を使えば値は上書きできるので使ってみました。しかし残念ながらこれでは期待通りに動きません。なぜかというと、関数が実行される度に変数は都度再生成され、中身も常に false がセットされるようになるからです。
もう少し身近な例として、純粋な JavaScript のコードを見てみましょう。
document.querySelector('button').addEventListener('click', function(event) { var timeStamp = event.timeStamp console.log(timeStamp) }
button
をクリックした時間を都度表示するように設定するための処理です。
この例でいうと、console.log
が吐き出す値は、クリックしたときの時間だというイメージは湧きやすいと思います。
timeStamp
の中身は関数が実行される度にセットされ、その値を吐き出し、処理が終わるということです。
React Component の場合も同じです。関数が実行され、Component を返して処理が終わります。その関数の中でどんなに変数を定義しても、その変数は関数が実行される度に変数が定義されて中身がセットされます。。
また React Component の場合は、ブラウザ上の見た目が変わっていなくても、関数は何度も実行されていることが多いです。 関数を実行した結果、見た目(正確には DOM)を変更する必要がなければブラウザの再描画をしないようにコントロールしてくれています。ただ、再描画の有無を検討するには React Component の関数を実行してみなければわからないことなので、気づかないうちに何度も実行されているのです。
なので、思っている以上に変数はどんどん初期化されてしまうのです。ではそれを回避しようとして以下のようにする案も生まれると思います。
const items = ['apple', 'orange', 'banana'] let isDisplayed = true const onClick = () => { isDisplayed = !isDisplayed } export const FruitsList = () => { return isDiplayed ? ( <div> <ul> {items.map(item => ( <li>{item}</li> ))} </ul> <button type="button" onClick={onClick}>toggle</button> </div> ) : null }
一見良さそうですが、これも悲しいことに意図通りに動作しません。この記述では isDisplayed
という変数が React のライフサイクルに乗っていないのでこの変数の中身が変更したかどうかをキャッチできません。そのため、再描画が必要かどうかをジャッジするために関数をもう一度走らせるべきかも判断できず、期待通りの動きになりません。
また、もし仮に isDisplayed
の変更をキャッチできていたとしても、FruitsList
を 2 つ使おうとしたら isDisplayed
が共通なので、表示状態が連動してしまって違和感が生まれると思います。(共通の state を作成したい場合は useContext
の利用を検討するのが良いですが、useContext
について長くなるのでこの記事では触れません。)
なので useState
は、値を保持するためにも、React のライフサイクルに乗せるためにも、Component を使うごとにそれぞれ独立した形で値が管理されるためにも必要なものなのです。
さて、前置きが長くなりましたが、useState
を使った実際のコードは以下の通りになります。
import { useState } from 'react' const items = ['apple', 'orange', 'banana'] export const FruitsList = () => { const [isDisplayed, setIsDisplayed] = useState(true) const onClick = () => { setIsDisplayed((prev) => { return !isDisplayed }) } return isDisplayed ? ( <div> <ul> {items.map(item => ( <li>{item}</li> ))} </ul> <button type="button" onClick={onClick}>toggle</button> </div> ) : null }
useState
は、保存された値を格納する変数と、値を更新するための関数の2つを配列形式で返します。
(const [a, b] = useState()
という記述は、分割代入という展開しながら代入する方式です。オブジェクトを例に取った紹介ではありますが、詳細を後述しているのでご覧ください。オブジェクトも配列も大差はありません。)
値を更新するための関数を setIsDisplayed
という名にしています。この関数は、関数を渡してあげることで、第一引数(prev
という名にしています)から前回の値を取得することができます。今回の場合は関数を渡さずに setIsDisplayed(!isDisplayed)
という形で値を直接渡すことも可能ですが、他の hooks 等を使いながらこのやり方で実装するとうまくいかないケースもあるので、確実に前回の state の値を取りたい場合は今回挙げた例で実装すると良いです。
useEffect
useEffect
は React Component の副作用(effect)が発生した際に任意の処理を実施するための hooks です。副作用は React Component のライフサイクルプロセスに沿って発生します。
例えば、上記で挙げた useState
の例の state が更新される度になにか処理を走らせたいときに有効です。
import { useState, useEffect } from 'react' const items = ['apple', 'orange', 'banana'] export const FruitsList = () => { const [isDisplayed, setIsDisplayed] = useState(true) const onClick = () => { setIsDisplayed((prev) => { return !isDisplayed }) } useEffect(() => { console.log('isDisplayed is updated!') }, [isDisplayed]) return ... }
useEffect
の第一引数に実施したい処理を入れた関数を入れ、第二引数には更新をキャッチしたい変数を配列にして入れます。
今回は isDisplayed
が更新されたかどうかを確認し、更新された場合に console.log
が実行されます。(後に解説する props が変更されたかどうかもこの useEffect
で監視し、処理を実行することもできます。)
また、他の利用用途もあります。React Component が利用された初回のみ処理を実行したいときにも使えます。
useEffect(() => { window.addEventListener('resize', () => { // do something... }) }, [])
上記のように window
に対してイベントを付与したい場合は、重複して処理を実行させないように確実に一度だけイベントを付与したいです。useEffect
を利用せず Component 内にベタに記述すると、上述した useState
のところでも説明したとおり Component の関数自体は何度も実行されるため、イベントの付与の量が大変なことになります。また、useEffect
に引数を設定しない場合も、Component の更新時に何度も実行されてしまいます。
意図的に一度だけ実行したい場合は、空配列を指定してあげることで解決できるのでぜひ活用してみてください。
また、上記の例のときに、 FruitsList
が一度画面から消え、再度表示される場合、上記のままだと問題が起きます。
一度しかイベントを付与したくないのに、 FruitsList
が二度利用されれば、もう一度イベントが付与されてしまいます。
そのため、FruitsList
の利用が終了した場合はイベントを削除したくなります。その場合は以下のように記述します。
const resizeHandler = () => { // do something... } useEffect(() => { window.addEventListener('resize', resizeHandler) return () => { window.removeEventListener('resize', resizeHandler) } }, [])
useEffect
に渡している関数の中で、さらに新しい関数を返すようにしています。この返している関数の処理は、Component の利用が終了したとき(Unmount 時)に実行されるようになっています。なので、画面上から消えたときなどに removeEventListener
を実行してイベントを削除するようにしています。
(removeEventListener
を実施するときの第二引数の関数は、addEventListener
を実施したときと全く同じ関数でないといけないため、resizeHandler
というのを作成しています。)
また、余談にはなりますが、useEffect は、初回時も更新時も DOM がレンダリングされた後に実行されること保証しているので、Component 内の DOM を取得して操作したいときにも利用されます(例えば DOM の高さを取りたいときなど)。
その場合は、useRef
を利用して DOM を取得できるようにし、useEffect
内で ref 経由で DOM にアクセスすると意図通りに挙動します。ただ、useRef
の説明まですると長くなりすぎるため、今回は割愛します。
参考:フック API リファレンス – React
自分たちでも hooks は作れるの?
実をいうと hooks の実態はただの関数なので誰でも作ることができます。hooks となる条件は、接頭辞に use
をつけるだけです。このような独自の hooks のことを Custom Hooks と呼びます。
では、なぜただの関数なのに hooks の場合はわざわざ use とつけなければならないかというと、その関数が React のライフサイクルに影響する関数かどうかをジャッジする必要があるからです。
そもそも、use 接頭辞がつく hooks をただの関数の中で利用しようとすると「React Component か hooks の中でのみ利用してください」というような内容のエラーが発生します。
React Component ではない関数の中で hooks 使われたとしても、その hooks は React の管理外になり、React の管理内で動くことを想定している hooks 群は当然動きません。なので、それを未然に防ぐためにエラーを丁寧に出してくれています。
use
接頭辞がついたものは、最終的には React Component の中で利用されるようにこのエラーによって制約しているわけです。
では、なぜ hooks の中での hooks の呼び出しは許可されているのか。そもそも React Component の中でしか使えないようにすればよいはずですが、それもそれで不都合が起きます。
import { useState } from 'react' const items = ['apple', 'orange', 'banana'] export const FruitsList = () => { const [isDisplayed, setIsDisplayed] = useState(true) const onClick = () => { setIsDisplayed((prev) => { return !isDisplayed }) } const resizeHandler = () => { // do something... } useEffect(() => { window.addEventListener('resize', resizeHandler) return () => { window.removeEventListener('resize', resizeHandler) } }, []) return isDisplayed ? ( <ul> {items.map(item => ( <li> <button type="button" onClick={onClick}>{item}</button> </li> ))} </ul> ) : null }
useState
で提示した例と useEffect
で提示した例を結合してみました。こうして見てみると、 return までのステップ数が多くなっています。そうなるとコードの見通しも悪くなり、コード同士の密結合化も起きやすくなってきます。
そこで、その中間の処理を別の関数にして処理を疎結合にし、見通しも良くしたくなるわけです。そのときに Custom Hooks の恩恵を受けることになります。
import { useState } from 'react' const useDisplayHandling = () => { const [isDisplayed, setIsDisplayed] = useState(true); const onClick = () => { setIsDisplayed((prev) => { return !isDisplayed }) } return { isDisplayed, onClick, } } const useResizeHandling = () => { const resizeHandler = () => { // do something... } useEffect(() => { window.addEventListener('resize', resizeHandler) return () => { window.removeEventListener('resize', resizeHandler) } }, []) } export const FruitsList = () => { const { isDisplayed, onClick } = useDisplayHandling(); useResizeHandling(); return isDisplayed ? ( <ul> {items.map(item => ( <li> <button type="button" onClick={onClick}>{item}</button> </li> ))} </ul> ) : null }
useDisplayHandling
という Custom Hook を作って利用してみました。これによって Component がかなりシンプルな記述になりました。
useEffect
が Component 直下の記述ではなくなりましたが、これも問題なく動作します。
現在は一つのファイルとして書いていますが、もちろんそれぞれのファイルに分割して書くことも可能なので、より見通しがよくなります。
また、これはかなりエンジニアリング寄りの話になりますが、useDisplayHandling
はただの関数なので、Jest と react-testing-library を使うことで、テストもしやすくなっています。React Component のテストをしようとすると DOM を探索して表示有無を見なければなりませんが、hooks であれば return
された値をテストすれば良くなるので、実装コストも心理的コストもかなり下がります。
なんで TypeScript を使うの?
JavaScript の場合、例えば、一つの引数を持つ何かしらの関数を作ったとして、その関数を使うときに引数にはどんな値を入れたとしてもプログラム自体は動かせていました。
これだけ聞くと制約がなくて書き心地が良さそうで、むしろ何が悪いのかイメージが湧きづらいですが、実はデメリットの方が大きいです。例を踏まえて説明していきます。
const format = (num) => { return num.toFixed(2) }
このような記述があったときに、このプログラムは一見問題がなさそうですが、これはバグを引き起こしやすいプログラムになっています。
例えばformat([1, 2, 3])
と入れられてしまったら、この関数はエラーを吐きます。
toFixed
という関数は数値にしか使えない関数なのですが、上記では配列を代入されてしまっていて、配列には用意されていない状況にも関わらず toFixed
を実行してしまうためエラーとなります。
つまり、format
関数には以下のように処理を足さないといけなくなります。
const format = (num) => { if (typeof num !== 'number') { throw Error('`num` is not number.') } return num.toFixed(2) }
今回は単調な処理なのでこれでも良いですが、引数がたくさんあったり、引数自体が配列やオブジェクトだったりするとこの分岐がどんどん複雑になっていき、実装が大変になります。
また、関数を作った人がここまで配慮しているとも限らないため、利用者側はこの関数の中身の処理を見に行って、引数に渡していい値をチェックしなければなりません。
関数の作成者、利用者の両者に「気をつけてね」と注意喚起をしたところで、人の注意力なんてものはたかが知れているのですぐにバグを起こします。
これを解決してくれるのが TypeScirpt です。導入すると以下のように書けます。
const format = (num: number) => { return num.toFixed(2) } // エラーにならない format(1) // エラーになる format('文字') format([1, 2]) format()
num という引数のあとに : number
がつきました。これは引数に数値しか入れてはいけないという指定になります。(上記の指定だと、引数に何も入れないのもエラーになります)
TypeScirpt は上記のように型を付けることができ、変数や引数に引き渡される値の期待の型を設定できます。TypeScript も JSX と同様にブラウザは読めないので JS に変換する必要があるのですが、その JS の変換時にエラーが発生し、変換自体が止まるようになっています。
設定をしっかり入れていれば、変換前にもチェックすることもできます。TypeScript の設定か ESLint と呼ばれる JavaScript / TypeScript のバグを検知するツールがプロジェクトに入っていて、VSCode 等のエディタの設定が正しく行われていればエディタ上でリアルタイムにエラーを検知できます。
また、型を意識しないでコーディングをしていると any
という文字を目にすることがあると思います。これも型ではあるのですが、この型は「型を指定していないこと」を意味します。any
のままでプログラムを書くことは大変危険なので、基本的にはプロジェクト内で any
を許容しないように設定されているはずです。よくわからないけどエラーが出るという状況は、この any
によるエラーが原因の一つだろうと思います。
参考:TypeScript: Documentation - Everyday Types
しかし、その any
がしっかり解決されていれば、ブラウザ上でプログラムを動かさないとエラーに気づけなかった状態から、JS への変換時や、エディタ上でのエラー検知で気づくことができるようになります。
また、型でエラーが発生するのでわざわざ typeof num !== 'number'
というコードも不要になります。型情報は JS には変換されないので、冗長な記述を減らしつつリリースすることも可能になります。
TypeScript で Component を作るには?
Component 作成は JSX がベースとなりますが、TS を使う場合は JSX 上で TypeScript が使える TSX を使います。拡張子は .tsx
です。前に例で示した FruitsList Component を TSX ベースに書きなおしてみます。
import { FC } from 'react' const items = ['apple', 'orange', 'banana'] export const FruitsList: FC = () => { return ( <ul> {items.map(item => ( <li>{item}</li> ))} </ul> ) }
主に追加されたものは FC
という記述です。これは、React が提供してくれている Component の型で、Functional Compoent の略です。(VFC
というのもありますが、こちらは React のバージョンアップの変遷上生まれたものなので、基本は FC
を使う形で問題ありません。)
FruitsList
という変数に、React の Functional Component として作った関数だけを受け付けられるようにしています。
例えば const FruitsList: FC = '文字'
と書いたり const FruitsList: FC = () => { return 0 }
はエラーになります。FruitsList
という箱は Functional Component として正しく定義された関数しか受け付けられない箱になっているためです。ただの文字はもちろんだめですが、後者の0
という数を返している関数も代入できません。数ではなくて <div />
という HTML 要素みたいなもの(正確には ReactElement
か null
)を返す関数でなければなりません。
FC がどういうものかを我々が定義するのはとても大変です。なので React 側が定義して export
してくれていて、それを import
して利用しているという状況になります。TypeScript では型も import
/ export
を使って利用と提供ができます。
Component に値を渡すには?
今までは、定義した Component をただ使うだけでしたが、実際には HTML で <input type="checkbox" disabled>
と属性値を指定できるように、自分が定義した Component にも値を指定できるようにしたいはずです。
HTML の場合では type="checkbox"
を attribute と読んでいましたが、 Component の場合は prop と呼びます。まずは props を受け取れるようにします。
import { FC } from 'react' export const FruitsList: FC = (props) => { return ( <ul> {props.items.map(item => ( <li>{item}</li> ))} </ul> ) }
追加したのは、 props
という引数です。 Component の props
は関数の引数で引き受けることができます。
なので、上記のように定義しておくと、以下のように Component を使うことが可能です。
import { FruitsList } from '../modules/FruitsList' export const Top = () => { return ( <div> <FruitsList items={['apple', 'orange', 'banana']} /> </div> ) }
上記の例は JSX でしたら問題ないのですが、今は TSX で書いているのでエラーになる可能性が高いです。
まず、FruitsList
を見てみると props
に対して型がありません。なので props
は any
型になっています。props.items
は any
の中の items
を指定していることになるので items
も当然どんな型なのかがわかりません。また、props
に流れてきたものがたまたま items
を持つオブジェクトなら良いのですが、props
が string
や number
、または undefined
や null
などの場合はその値の中に items
というものは存在しないので、こちらもエラーになります。なので、これを以下のように書き換えてあげる必要があります。
export const FruitsList = (props: { items: string[] }) => { return ( <ul> {props.items.map(item => ( <li>{item}</li> ))} </ul> ) }
※混乱を呼ぶので一時的に FC
の記述を外しています。後に復活させます。
型が少し複雑ですが、やっていることは format()
の例で引数に number
の型を付与した作業と同じです。props
に { items: string[] }
という型を付与しています。
React の場合、引数の props
の型はオブジェクトにするという取り決めがあります。その props
オブジェクトの中に items
という領域を作ると、 Component を利用するときに <FruitsList items={...}>
という形で items を指定できるようになります。
また、今回の例の場合、フルーツの名前の一覧を表示させたいので、items
には「フルーツ名が入った配列」を代入したいと思っています。
配列の場合はいくつか指定の仕方がありますが、配列の中身の型を決め、さらに型の後ろに []
と書くと配列の型を定義できます。数字の配列([0, 1, 2])
だったら number[]
、配列の中にオブジェクトを入れたい([{ text: 'a' }, { text: 'b' }])
場合は{ text: string }[]
となります。
今回はフルーツ名を受け付けたいので中身は string だと都合が良く、それを配列として受け付けたいので string[]
と指定しています。
型が長くなりすぎてしまう場合は?
上記の状態なら、 props
が一つしか無いのでそこまで気になりませんが、 props
が増えてくると引数の中身の記述量が増えてきてしまって引数のところに記述が集中してしまうので、もう少し記述をスッキリさせたいです。
その場合は変数を定義するみたいに、まずは型だけを定義してあげて、その定義した型を適用する方法があります。
type Props = { items: string[] } export const FruitsList = (props: Props) => { return ( <ul> {props.items.map(item => ( <li>{item}</li> ))} </ul> ) }
型を定義するときは const
ではなく type
を使います。そもそも型は JS には変換されないので、この記述は Babel 等で変換しても出力結果には現れません。
また、似たようなものに interface
というものがありますがここでは触れません。React で書くときは基本的に type
を使えば困ることはないと思うので、気になる方は調べてみてください。(オブジェクト指向等で Class を書くときにはよく使いますが、React は Functional ベースなので登場頻度が少ないです。)
さらに厳格な型を作りたい場合は?
もともと、 FruitsList
が props
で受け付ける前に、内部的に持っていた items
は ['apple', 'orange', 'banana']
でした。ただ現在では型が string
になっているので、配列の中に 'hello'
や 'あのイーハトーヴォのすきとおった風…'
という文字を入れても良い状態になってしまっています。なので、せっかくなら 'apple'
'orange'
'banana'
だけが入った配列を受け付けられるようにし、この3つの文字列以外の文字が入った配列が props
に流れてきたらエラーにしたいと思います。型を定義する方法を活用しつつさらにコードを変えてみます。
type FruitsType = 'apple' | 'orange' | 'banana' type Props = { items: FruitsType[] } export const FruitsList = (props: Props) => { return ( <ul> {props.items.map(item => ( <li>{item}</li> ))} </ul> ) }
今回定義した型は FruitsType
というものです。文字列を定義しているように見えますが、type から始まる文なので、これは型を定義しています。
TypeScript にはリテラル型と呼ばれるものがあります。TypeScript ではプリミティブな値(オブジェクトや配列のような関数を持たない値のこと)をそのまま型にできるという特徴があります。
('apple'.split()
などと書けますが、これは JavaScript が内部的にオブジェクトに変換しているとのことです。詳しくはこちら。)
プリミティブな値とは、文字列や数値、真偽値(boolean)などが挙げられ、これらをそのまま型にすることができるということです。
文字列以外だと、type BinaryDigitType = 0 | 1
とすればその型がついた変数は 0 と 1 しか入れられなくなります。
つまり、FruitsType
は 'apple'
'orange'
'banana'
の文字列しか受け付けないことを表現できます。また、|
を使うことで or を表現でき、この型のことを Union 型と言います。この型が設定されている場合 'hello'
や 'あのイーハトーヴォのすきとおった風…'
、さらには ''
も条件にマッチしないのでエラーとなります。
さらに、FruitsType
の後ろに []
を入れて items
の型を定義しています。これはその文字しか入れられない配列を定義している状態になります。['banana', 'banana', 'apple']
はエラーが出ませんが、['orange', 'hello']
はエラーになります。
厳格な型を作る手段はたくさんあり、リテラル型はそのうちの一つでしかありません。型についてはぜひいろいろ調べてみてください。
...
ってなに?
React を書いていると ...
という記述をよく目にすると思います。こちらは JavaScript の「スプレッド構文」という記法で、息を詰まらせていたり、絶句しているわけではありません。
配列、オブジェクト、文字列等に対して利用できる記述の仕方で、その要素を展開することができます。
例えば、const arr = [1, 2, 3]
という配列があり、この要素一つひとつを console.log
の引数として渡したい場合は do(arr[0], arr[1], arr[2])
と書いていましたが、スプレッド構文を用いると console.log(...arr)
と書けます。
また、この記法は値を代入するときにも使えます。例えば、const person = { name: 'taro', age: 20, phone: 09012345678 }
というオブジェクトから name
だけ取り出して、他2つは別のオブジェクトとしたい場合、 const name = person.name
と const rest = { age: person.age, phone: person.phone}
と書かなければなりませんでしたが、const { name, ...rest } = person
と記述できます。この記法を分割代入と言います。また、...rest
の部分は残余プロパティと呼び、残りの部分を一つの引数に入れることができます。
(前に紹介した const [isDisplayed, setIsDisplayed] = useState()
という書き方は、 useState
が配列を返していて、その配列の書く要素を分割代入している状態です。配列の場合は []
を使います。)
さらにこの分割代入を応用して、引数を受け取るときにも分割して受け取ることができます。
先ほど、FruitsList Component に props
という引数を入れましたが、その内部で props.items
と、いちいち props
と記述するのは手間です。
なので、この記法を使って以下のように書き換えることができ、記述をシンプルにすることができます。
export const FruitsList = ({ items }: Props) => { return ( <ul> {items.map(item => ( <li>{item}</li> ))} </ul> ) }
参考:スプレッド構文 - JavaScript | MDN
参考:分割代入 - JavaScript | MDN
子要素を受け付けるようにするには?
HTML を記述する視点で考えると、やはり要素を入れ子したい気持ちが出てきます。それは以下のように記述すると実現できます。
import { ReactNode } from 'react'; type FruitsType = 'apple' | 'orange' | 'banana' type Props = { items: FruitsType[] children: ReactNode } export const FruitsList = ({ items, children }: Props) => { return ( <> <ul> {items.map(item => ( <li>{item}</li> ))} </ul> <div>{children}</div> </> ) }
※ <></>
というのを追加してますが、これは React Fragment というものです。React は並列で2つ以上の要素を return することができないので、Fragment というもので囲って一つの要素を return するようにしています。div 等で囲っても良いのですが、HTML として考えた時に要素を生成したくない場合はこの Fragment が役に立ちます。
React において、children
というのは特殊な意味を持ちます。props
として値を受け付けた場合、利用者側は <FruitsList items={[...]} />
というように、HTML の属性値のように渡していました。しかし children
と指定した場合に限り、子要素として入れ子で値を渡せるようになります。
import { FruitsList } from '../modules/FruitsList' export const Top = () => { return ( <div> <FruitsList items={['apple', 'orange', 'banana']}> <button type="button">もっと見る</button> </FruitsList> </div> ) }
また、 FruitsList
側の children
の型の定義を ReactNode
としています。 children
は例のように <button>
を入れることもあるかもしれませんが、文字のみを入れることもあれば、React Component
を入れることもあり、さらにはコメントアウト({/* something… */}
という記法)や数値や boolean、条件分岐によって undefined
や null
などを入れることもあります。この条件を毎回定義していたら大変なので、React はこういうときのために ReactNode
という型を用意してくれています。ただし、逆に文字しか受け付けたくない場合は children: string
とすることも可能ですので、 Component に合わせてカスタマイズしてください。
?
ってなに?
少し前の JavaScript ではほとんど登場しなかった ?
が、現在の JS や TS になると頻繁に登場します。
とはいいつつ、実は従来の JavaScript でも ?
は使われています。それは、三項演算子という if と似たような働きをする記法で登場していました。const result = typeof data !== 'undefined' ? 'success' : 'failed'
というような使い方で、?
より手前に書かれた条件が true
の場合は、: 手前の 'success'
が、false なら 'failed'
が result
に代入されるという式です。
しかし、最新の JavaScript 及び TypeScript ではこれ以外の使い方もあります。
オプショナルチェーン
props?.items
という JavaScript の記述の仕方を説明します。
従来だと、props.items
と書いていて、もし仮に props
が undefined
や null
になってしまっていたらエラーになっていました。なぜエラーが起こるかというと、undefined
の場合は undefined.items
となってしまって、undefined
には items
というものは当然存在しないのでエラーになっていました。これは、非同期通信が終わる前に処理が走ってしまって、undefined
に対して値を参照してしまうということがしばしば起こります。なので今までは、undefined or null
かどうかを if 文や三項演算子で調べる必要がありました。
しかしこのオプショナルチェーンを使うと、undefined
や null
に対して値を参照しようとしてもエラーを起こさないように調整してくれます。例えば、props
は undefined
になる可能性があるから、undefined
ではないときは items
を参照するという指定を、props?.items
と書くだけで解決します。
参考:オプショナルチェーン (?.) - JavaScript | MDN
??
演算子
時折、クエスチョンマークを2つ並べて使うことがあります。これは const result = data ?? 'default'
という形で使うことができます。これも JavaScript で使える記法です。
これは、data
が undefined
か null
だったときは、??
以降の 'default'
が result
に代入されます。逆に、data
が undefined
null
のどちらでもなければ data
の値が result
に入ります。
これは、||
ととても良く似ています。data || 'default'
の場合は、data
が falsy(false
0
''
undefined
null
など)の場合に 'default'
が選ばれます。ただ、??
の場合は undefined
と null
の場合に限られるため、空文字や 0
を false
と扱いたくないときなどにとても重宝する記法です。
参考:Null 合体演算子 (??) - JavaScript | MDN
オブジェクトのオプションプロパティ
この項目は型についての話なので TypeScript にだけ関わります。
例えば、props
で items
を受け取っていたとして、必ずしも items
のデータを流さなくても良いという作りにしたいときも出てくるはずです。
FruitsList
で考えたときに、items
が undefined
(配列を流さなかった)場合は「フルーツはありません」と表示したいとします。
その場合は以下のように記述することで実現できます。
import { ReactNode } from 'react'; type FruitsType = 'apple' | 'orange' | 'banana' type Props = { items?: FruitsType[] } export const FruitsList = ({ items }: Props) => { return items ? ( <ul> {items.map((item) => ( <li>{item}</li> ))} </ul> ) : ( <p>フルーツはありません</p> ); }
※上記の場合は、ul
と p
のどちらかが return
されるため要素が一つだけのままとなるため、Fragment は不要となります。
type
の items
のところに ?
を足しています。こうすることで、items
は string[] | undefined
という型になり、上記のように三項演算子を使わずに、ただ items.map
と記述するとエラーになります。(オプショナルチェーンで説明したとおり、undefined.map
となってしまうため)
今回は三項演算子で undefined
ではないときのみ ul
の処理に入るため、items.map
と記述してもエラーにはなりません。
三項演算子を使わなくても良い場合は、オプショナルチェーンと ??
演算子を組み合わせて以下のようにもできます。
export const FruitsList = ({ items }: Props) => { return ( <ul> {items?.map((item) => ( <li>{item}</li> )) ?? ( <li>フルーツはありません</li> )} </ul> ) }
ただ、HTML の構造上違和感はあるため、三項演算子で ul
と p
を出し分けたほうがきれいだとは思うので、シーンに合わせて使い分けるのが良いと思います。
<T>
ってなに?
TypeScript を書いていると、時折 Type<T>
みたいな記述の仕方に出会うことがあります。この記述の仕方を深く理解するには難しいので、自分で型を作るときにはこのことを意識しなくても良いのですが、型を利用するときには使うことがあるので触れていきます。
この記述の仕方は Generics と呼ばれていて、かんたんに言うと型の引数にあたります。
例えば、type Props<T> = { item: T }
と書いておいてProps<string>
と書くと { item: string }
tとなります。
これだけ聞くとかんたんに見えますが、型には「推論」というものがあり、Generics は推論を考慮することでより効果を発揮するので、もし詳しく知りたい方は調べてみてください。
さて、なぜこの話を持ち出したかというと「Component に値を渡すには?」の例で、FC
の記述を一度消したことに起因します。
今までは ({ items, children }: Props) => { … }
と記述していましたが、React Component を作成する場合、特殊なケースが無い限りは Component の引数に型を直接付与しません。
付与しなかったら、今までさんざん避けろと言っていた any 型を許容するのかという話になるかもしれませんが、もちろんそれも違います。引数に型をつけるのではなく FC<Props>
という書き方をすることで解決します。
import { FC, ReactNode } from 'react'; type FruitsType = 'apple' | 'orange' | 'banana' type Props = { items: FruitsType[] children: ReactNode } export const FruitsList: FC<Props> = ({ items, children }) => { return ( <> <ul> {items.map(item => ( <li>{item}</li> ))} </ul> <div>{children}</div> </> ) }
※今まで消していた理由は、直接引数に指定するやり方と FC
での指定がバッティングしてエラーになっていたため、Generics を知らなくても話を進められるように FC
を使っていませんでした。
そもそも、FC
というのはなんだったかというと、React Component を定義するための関数の型を取り決めてくれているものでした。なので、一時的に FC
を消していたということは、React Component としての定義を正しく行えていない可能性が高かったということです。「TSX ってなに?」にも記載しましたが、例えば FC を設定した上で return
の値を数値にしていたらエラーになっていましたが、FC を消していた現在ではそれはエラーにならず、変換後の JavaScript を実行したときに初めてエラーに気づく状態になっていたということです。(実際には、Component の利用時に型でエラーになっている可能性が高いので、変換前に気付けているとは思います。)
また、そもそも FC
が React Component の型なので、やはり関数の引数(つまり Props)の型までしっかりと面倒をもらいたいです。なので、その引数の型の定義までしてもらうために、 FC
には Generics を受け付ける口が存在しているのです。
引数の型は、作成する Component によって異なるため FC の一部として定義することはできません。なので、Generics で型を受け付けており、その受け付けた型を引数の型として定義されるようにできています。
実際にはもう少し考慮されていますが、イメージとしては type FC<P> = (props: P) => ReactElement
というような型が FC に定義されているため、直接引数に型を設定しなくても FC
が引数に型を付与してくれます。
CSS の書き方は?
従来であれば CSS の書き方で悩むことはありませんでしたが、React 上で CSS を記述するにはいくつか方法があり、その記法は各プロダクトの採用した技術によって決まります。
いくつかの方法を紹介するので、自分のプロダクトがどれにあたるのかを確認してみてください。
(ちなみに、Vue.js の場合の多くはテンプレート構文という書き方を採用していて、Vue ファイルの中に Template (HTML構造)、Script、Style のすべてを記述する方法のため大きな揺れはなさそうです)
CSS in JS
JavaScript (JSX) / TypeScript (TSX) のファイルの中に、CSS を書く方法です。
CSS in JS を使うためにはパッケージが必要で、そのパッケージに入っている関数を使って CSS を記述します。
CSS に関しては JSX のように JS の拡張版が存在しないので、特に策もなく JSX / TSX にベタ書きで CSS を書いても、その打ち込んだ文字は変数等として認識されて、ただただエラーが出るばかりです。なので、テンプレートリテラルという JavaScript の文字列を定義する方法と組み合わせて CSS を書いていきます。
パッケージが用意している関数は、最終的に HTML / CSS に変換するときに class 名を吐き出してくれます。このクラス名はユニークな文字列を付与してくれるので、従来の CSS を書くときに他の箇所にもスタイルがあたってしまう心配をしなくて済みます。
import { css } from '@emotion/css'; const items = ['apple', 'orange', 'banana'] const rootStyle = css` diplay: flex; `; const itemStyle = css` diplay: flex; `; export const FruitsList = () => { return ( <ul css={style}> {items.map(item => ( <li css={itemStyle}>{item}</li> ))} </ul> ) }
上記は emotion というパッケージを使って CSS を適用している例です。(その他の代表的なパッケージでは styled-components というものがあります。)
css`...`
という書き方に癖がありますが、こちらがテンプレートリテラルを活用した記述の仕方になります。タグ付きテンプレートと呼ばれるもので、詳しくはこちらを参照してください。
テンプレートリテラル (テンプレート文字列) - JavaScript | MDN
VSCode を使っている場合は vscode-styled-components を利用することでテンプレートリテラルで定義した文字列を CSS のカラーリングで表示してくれます。(styled-components と書かれていますが、emotion でも問題なく動作します)
また、<ul css={}>
という記述は emotion 独特の記法です。一般的な JSX の書き方ではありませんのでご注意ください。
CSS Modules
ファイル名を styles.module.css
という形で module
というのを含めるのが特徴です。
CSS Modules は拡張子が CSS なので、書き方は CSS と何も変わりません。一番デザイナーに優しい書き方だろうと思います。また、Sass を使いたい場合は、Sass を扱える設定を施した上でスタイルを記述することも可能です。その場合は拡張子を .scss
にすると良いです。
一つの CSS ファイルは、一つの JSX ファイルに対応させる書き方が主流です。そうすることで、CSS も冗長な状態から抜け出せます。 また、もう一つの利点として class 名を適当につけても、CSS in JS と同様で class 名をユニークな文字列に変換してくれます。変換を促すために以下のように記述します。
import styles from './styles.modules.css' const items = ['apple', 'orange', 'banana'] export const FruitsList = () => { return ( <ul className={styles.fruitsList}> {items.map(item => ( <li className={styles.fruitsList__item}>{item}</li> ))} </ul> ) }
従来の HTML / CSS の記述にかなり近いと思います。
ただ、この方法の利用率は、感覚値ではありますが減少傾向にある印象です。一つの理由としては、この方法は CSS を JavaScript に import
するため、Webpack 等のビルドツールで CSS の読み込みを解決するためのパッケージ(css-loader)を利用する必要があるのですが、そのパッケージの開発が止まっているという噂があり、利用が控えられつつあるようです。
Web を探ると色々な記事が出てきて現状がどういう取り扱いなのかはわかりませんが、トレードオフを考えながら採用すれば、悪い技術ではないということは間違いないはずです。
Tailwind CSS
これに関しては、私はあまり詳しくありません。ただざっくりと概要だけ触れておきます。
Tailwind では一つひとつの style に対して class 名が付与されており、要素に class 名をたくさん指定して styling を実現していくやり方です。
参考:Tailwind CSS - Rapidly build modern websites without ever leaving your HTML.
この方法を用いると、そもそも CSS を style 属性のように直に指定ができる上に、style属性で懸念される詳細度の強さも class 名の強さまで落とせるという利点があります。なので style の上書きがしやすい上に、CSS の class 名を考える必要もなく、安全に CSS を記述できます。
しかし、class 名にたくさんの指定が入ってしまうのと、従来の CSS の書き方からかなり離れることになるので、デメリットもそれなりに多そうです。
さいごに
一つひとつの疑問が、JavaScript / TypeScript / React のそれぞれどこから発生したものなのかをなるべく明示しながら記事を書いてみました。
なるべく網羅的に書いたつもりではありますが、まだまだ「この記述はなんなんだ!」という疑問は解消しきれていないとも思っています。
他の疑問が集まれば、また記事化することも検討しようと思うので、コメントお待ちしております。
また、この記事はデザイナー向けに書きましたが、React / TypeScript を書き始めた人にとっても参考になる記事になっているのではないかと考えてます。
もしこの記事を通して、弊社に興味を持っていただけたなら、デザイナー・エンジニア問わずぜひお会いできたらと思っていますので、気軽にお問い合わせください。