Reactでいいねボタンを作ろう
このチュートリアルでは、TypeScriptとReactの両方を用いて、SNSでよく見かける「いいねボタン」のUIを実装します。
本チュートリアルは、TypeScriptとReactによるコーディングの体験をすることを主眼に置いています。そのため、TSとReactの理論的な説明は省きます。「TypeScriptとReactでUIを開発するにはどのような流れになるのか」を感じ取って頂くことを目的としています。
Reactの専門書と比べて、本書の解説は詳しさや正確さは劣ります。それでも、初めてReactに触れる方でも読み進められるよう、Reactについて随時ワンポイント解説をしていくので、安心してお読みください。
このチュートリアルで作成するいいねボタンの最終的な成果物はデモサイトで確認できます。チュートリアルを開始する前に事前に触ってみることで、各ステップでどんな実装をしているかのイメージが掴みやすくなります。また、完成形のソースコードはGitHubで確認することができます。
Reactとは?
ReactはFacebook社が開発した、ウェブアプリケーションのUIを作るためのパッケージです。JavaScriptやTypeScriptだけでもインタラクティブなUIは実装できます。しかし、UIが複雑になるとReactなしではコードの記述量が増大したり、可読性が悪くなったりと難易度が上がります。なんといっても、UIが今どのような状態なのかを管理するのは、プログラマが把握しきれない複雑さになることがあります。Reactを使うと、複雑なUIやインタラクションを短く簡潔に読みやすく書けるようになり、状態の管理も分かりやすくなります。
Reactの3大特徴
Reactはどのような特徴を持ったパッケージなのでしょうか?ここではReactの特徴を3つに分けて説明します。Reactについて多少の予備知識を得たい方は、このセクションをお読みください。今すぐコードを書きたいという方は、ここは読み飛ばしても問題ありません。
特徴その1: 仮想DOM
Reactは仮想DOM(virtual DOM)という考えを採用しています。仮想DOMを理解するには、仮想ではない普通のDOMが何かを知る必要があります。DOM(document object model)とは、HTMLをJavaScriptから参照・操作する仕組みです。これのおかげで、HTMLを文字列操作ではなく、オブジェクトとして処理できます。DOMはHTMLを操作するためのAPIのようなものです。
プログラマがDOMを操作すると、間接的にHTMLが書き換えられ、その結果が画面に描画されます。多くの動的なUIはDOM操作で成り立っています。
js
// <input id="email">の文字色を赤色にするDOM操作の例constemailInput =document .getElementById ("email");emailInput .style .color = "red";
js
// <input id="email">の文字色を赤色にするDOM操作の例constemailInput =document .getElementById ("email");emailInput .style .color = "red";
DOMは必ずしもプログラマにとって使いやすいAPIではありません。上の例のようなスタイルを少し変更するくらいなら実用的です。しかし、複雑なUIを作ろうとすると途端に難しくなります。注意深く実装しないと、表示や状態の変更し忘れといったバグを生みやすくなります。操作の方法が悪くパフォーマンス面で問題が出たりします。
仮想DOMはリアルDOMのプロキシのようなものです。リアルDOMと比べて、状態管理上のバグを起こしにくい設計になっています。加えて、パフォーマンス面では描画処理の最適化もします。プログラマにとっては、リアルDOMを扱うときのような慎重さが不要になります。画面表示を変えたければ、仮想DOMを操作します。仮想DOMに起こった変更はリアルDOMに伝わり、画面に現れてきます。仮想DOMは、複雑なUIを苦労せずに実装するための仕組みと言えます。
特徴その2: 宣言的UI
Reactの2つ目の特徴はUIを宣言的に書ける点です。Reactを使わずにUIを実装すると、命令的なコードになります。命令的なコードでは、何かを表示したい場合でもどのように表示するかのhowの部分を細かく書く必要があります。
次の簡単なHTMLのリストを表示するために、命令的なコードと宣言的なコードで書き方がどう違うかを見ていきましょう。
html
<ul><li>リンゴ</li><li>オレンジ</li><li>ぶどう</li></ul>
html
<ul><li>リンゴ</li><li>オレンジ</li><li>ぶどう</li></ul>
まず、命令的なコードでは、次のようになります。
js
constlist =document .createElement ("ul");constapple =document .createElement ("li");apple .innerText = "リンゴ";list .append (apple );constorange =document .createElement ("li");orange .innerText = "オレンジ";list .append (orange );constgrape =document .createElement ("li");grape .innerText = "ぶどう";list .append (grape );
js
constlist =document .createElement ("ul");constapple =document .createElement ("li");apple .innerText = "リンゴ";list .append (apple );constorange =document .createElement ("li");orange .innerText = "オレンジ";list .append (orange );constgrape =document .createElement ("li");grape .innerText = "ぶどう";list .append (grape );
この処理を日本語に書き下すと、次のようになります。
ul
要素を作り、変数list
に代入するli
要素を作り、変数apple
に代入するapple
のテキストは「リンゴ」にするlist
にapple
を追加するli
要素を作り、変数orange
に代入するorange
のテキストは「オレンジ」にするlist
にorange
を追加する- ...
3つの果物のリストのような簡単なUIでも、どのように作ったらいいかを細かく記述しなければなりません。これを見るだけでも、UIを命令的に書くのは大変で、保守していくことも考えると望ましい書き方には思えないのではないでしょうか。
今度は宣言的な書き方を見てみましょう。次はReactでの書き方です。
js
functionFruits () {return (<ul ><li >リンゴ</li ><li >オレンジ</li ><li >ぶどう</li ></ul >);}
js
functionFruits () {return (<ul ><li >リンゴ</li ><li >オレンジ</li ><li >ぶどう</li ></ul >);}
見てのとおり、どのように表示するかの部分はなく、「このような表示になってほしい」という目標だけが書かれています。
宣言的UIでは、実装の細部やアルゴリズムを気にしなくてよいです。「どんなUIにしたいか」の一点に集中してコードを書けるようになります。
特徴その3: コンポーネントベース
Reactの3つ目の特徴は、コンポーネントベースです。コンポーネントというのはUIの部品のことです。たとえば、小さいもので言えばボタンや入力欄、より大きめの部品だとフォーム、さらに大きい部品ではページもコンポーネントです。
Reactには、小さいコンポーネントを組み合わせ、大きなアプリケーションを成すという思想があります。ここがReactがコンポーネントベースと言われるゆえんです。
コンポーネントベースのメリットは、同じコンポーネントを再利用できる点です。たとえば、ボタンコンポーネントを1つ作っておけば、それをアプリケーションの至るところで使い回せます。プログラマは同じコードを何度も書かなくて済み、開発効率が良くなります。
加えて、オープンソースのコンポーネントも数多く公開されています。プログラマは自分でゼロからコンポーネントを作らなくても、公開されているコンポーネントを利用することもできます。カレンダーUIのような自力で作ると面倒なコンポーネントも種類豊富に公開されているので、開発者はオープンソースのコンポーネントを使うとより手軽にアプリケーションが作れます。
このチュートリアルに必要なもの
このチュートリアルをやるに当たって、必要なツールがあります。それらはここにリストアップしておくのであらかじめ用意しておいてください。
- Node.js (このチュートリアルではv18.15.0で動作確認しています)
- NPM
- Yarn v1系 (このチュートリアルはv1.22.19で動作確認しています)
- VS CodeやWebStormなどのエディター
Yarnのインストール
チュートリアルではパッケージ管理ツールとして Yarn を利用しているので、最初にインストールをしておきます。
すでにインストール済みの方はここのステップはスキップして大丈夫です。
sh
npm install -g yarn
sh
npm install -g yarn
プロジェクトを作る
まず、yarn create
コマンドでReactプロジェクトのひながたを生成します。
sh
yarn create react-app like-button --template typescript
sh
yarn create react-app like-button --template typescript
1分ほどするとひながたの生成が完了します。like-buttonディレクトリが生成されるので、次のコマンドを実行してそのディレクトリに移動すると、ひながたが生成されているのが分かります。
sh
cd ./like-button
sh
cd ./like-button
生成後のディレクトリ構成text
.├── .gitignore├── README.md├── package.json├── public│ ├── favicon.ico│ ├── index.html│ ├── logo192.png│ ├── logo512.png│ ├── manifest.json│ └── robots.txt├── src│ ├── App.css│ ├── App.test.tsx│ ├── App.tsx│ ├── index.css│ ├── index.tsx│ ├── logo.svg│ ├── react-app-env.d.ts│ ├── reportWebVitals.ts│ └── setupTests.ts├── tsconfig.json└── yarn.lock
生成後のディレクトリ構成text
.├── .gitignore├── README.md├── package.json├── public│ ├── favicon.ico│ ├── index.html│ ├── logo192.png│ ├── logo512.png│ ├── manifest.json│ └── robots.txt├── src│ ├── App.css│ ├── App.test.tsx│ ├── App.tsx│ ├── index.css│ ├── index.tsx│ ├── logo.svg│ ├── react-app-env.d.ts│ ├── reportWebVitals.ts│ └── setupTests.ts├── tsconfig.json└── yarn.lock
create-react-appではReactのインストールも自動で行われます。インストールされたReactのバージョンを確認するには次のコマンドを用います。
sh
yarn list react
sh
yarn list react
yarn list reactの実行結果text
yarn list v1.22.19└─ react@18.2.0
yarn list reactの実行結果text
yarn list v1.22.19└─ react@18.2.0
このディレクトリにてyarn start
を実行すると、Reactのローカル開発サーバーが起動します。
sh
yarn start
sh
yarn start
開発サーバーが起動すると自動的にブラウザが開かれ、ひながたアプリの様子が確認できます。ブラウザが起動しない場合は、ターミナルに表示されているURLをブラウザで開いてください。
Reactのローカル開発サーバーを停止する場合は、Ctrl + Cキーを押してください。
CtrlキーとCキーを同時に押すと、コマンドを中断することができます。
ここからは実際にコードを書いて行きますので、生成したlike-buttonプロジェクトをお好みのエディターで開いてください。
ひながた初期状態の上のページはsrc/App.tsxの内容が描画されています。ためしに、src/App.tsxを変更してみましょう。App.tsxの<header>
要素の中身を消して、「TypeScriptはいいぞ」に書き換えてみましょう。
App.tsxtsx
importReact from "react";import "./App.css";functionApp () {return (<div className ="App"><header className ="App-header">TypeScriptはいいぞ</header ></div >);}export defaultApp ;
App.tsxtsx
importReact from "react";import "./App.css";functionApp () {return (<div className ="App"><header className ="App-header">TypeScriptはいいぞ</header ></div >);}export defaultApp ;
App.tsxを見てこのような疑問を持ったのではないでしょうか。このHTMLに見える部分はJSXと言われるものです。JSXはJavaScriptを拡張した言語で、JavaScriptの中にXMLを直接書けるようにしたものです。XMLとHTMLは厳密には異なりますが、ここでは同じものと考えてください。
UIを実装しようとするとHTMLと密接に関わるコードを書くことになりますが、JavaScriptの構文だけでHTMLを表現しようとすると、可読性が低くなりがちです。ReactではJSXを採用することで可読性の問題を解決しました。JSXは、HTMLをほぼありのままに書けるので、可読性の高いコードになります。
TypeScriptとJSXは本来無関係の言語ですが、開発者の利便性のために、TypeScriptでもJSXが書けるようになっています。
JSXを書いたJavaScriptファイルは拡張子を.jsxにします。同様にTypeScriptファイルは.tsxにします。
📄️ JSX
JSX(JavaScript XML)は、コンポーネント指向のJavaScriptライブラリやフレームワーク(特にReact)で一般的に採用されている、JavaScriptの拡張構文です。JSXを用いると、JavaScriptのコード内にHTMLタグのような構文が埋め込み可能となり、より直感的かつ読みやすい形でUIのコードを表現することができます。それによって、開発者のコーディング体験や開発、デバッグの効率が上がります。
書き換えたらファイルを保存し、ブラウザで確認してみてください。ブラウザに書いた文言が表示されていればOKです。
ボタンを作る場所を用意する
ここからは、いいねボタンを実際に作っていきます。まずは、いいねボタンを実装する場所を作ります。
まず、先ほど「TypeScriptはいいぞ」と書いたところを<LikeButton />
に変えます。次に、LikeButton
関数を作ります。次のコードのようになるようにしてください。
App.tsxtsx
importReact from "react";import "./App.css";functionApp () {return (<div className ="App"><header className ="App-header"><LikeButton /></header ></div >);}functionLikeButton () {return <span >いいねボタン予定地</span >;}export defaultApp ;
App.tsxtsx
importReact from "react";import "./App.css";functionApp () {return (<div className ="App"><header className ="App-header"><LikeButton /></header ></div >);}functionLikeButton () {return <span >いいねボタン予定地</span >;}export defaultApp ;
このLikeButton
関数が、これからいいねボタンを作っていく場所になります。
ReactのJSXでは、HTMLタグのdiv
やheader
が使えるだけでなく、自分で定義した関数もタグとして使うことができます。上で定義したLikeButton
関数はその一例です。JSXを戻り値として返す関数だけがタグとして使えます。上の例では、span
タグが戻り値になっているのがわかると思います。
JSXを戻り値にする関数をReact用語で「関数コンポーネント」と言います。Reactを使ったフロントエンドアプリケーション開発では、関数コンポーネントをうまく使うことがポイントになります。画面の部品をコンポーネントにしておくと、再利用できたり変更が一箇所で済んだりと、開発や保守がしやすくなります。
先ほども書いたように、JSXはJavaScriptの拡張構文であり、厳密にはHTMLと異なるものです。そのため、JSXにはHTMLとは異なる書き方や制約があります。
<LikeButton />
のようにスラッシュをタグに含める書き方も、JSXならではの書き方です。これはセルフクロージング要素(self-closing element)と呼ばれます。自己閉じ要素、自己完結型要素と呼ばれることもあります。<LikeButton></LikeButton>
のように子要素などを持たない場合に、<LikeButton />
のように末尾に/
をつけることで、短く表現できる書き方です。
JSXとHTMLのその他の違いについては、Reactの公式ドキュメントを参照してください。
ボタンのビジュアルを作り込む
いいねボタンの実装場所が確保できたので、ここではボタンのタグを変更したり、CSSを書いたりして、ボタンの見た目を作っていきます。今回作るボタンは次の図のようなシンプルなものです。
まずは、LikeButton
関数のspan
タグのテキストを♥ {count}
にします。このcount
は変数なので、その変数も一緒に定義します。
App.tsxtsx
functionLikeButton () {constcount = 999;return <span >♥ {count }</span >;}
App.tsxtsx
functionLikeButton () {constcount = 999;return <span >♥ {count }</span >;}
count
変数は固定値になっていますが、あとでクリックしたときに増減するように変えるので今はこれで構いません。JSX内では{}
で囲まれた部分には、JavaScriptの変数や式が書けます。上の例は変数名だけですが、{count + 1}
のような式も有効です。
次に、CSSのクラスを割り当てるために、span
タグにclassName
属性を追加します。
App.tsxtsx
functionLikeButton () {constcount = 999;return <span className ="likeButton">♥ {count }</span >;}
App.tsxtsx
functionLikeButton () {constcount = 999;return <span className ="likeButton">♥ {count }</span >;}
HTMLではCSSクラスを指定するのにclass
属性を用いるので、ここでclassName
属性にしていることに驚いたのではないでしょうか。これは初期のReactがDOMプロパティに直接値をセットしていた名残りです。DOMでは、HTMLのclass
属性がclassName
プロパティになります。現在は、ReactがDOMプロパティを直接セットすることがなくなったので、className
属性に縛られる技術的理由はないのですが、React開発陣はclass
属性への乗り換えは慎重のようです。これまで作られたコンポーネントが動かなくなるかも知れないからです。また、両方サポートする気もないようです。class
とclassName
のどちらもOKとなると混乱を招くからです。
続いて、likeButton
クラスのCSSを書いていきます。Reactではスタイルシートを実装するのにいくつか方法がありますが、ここではApp.cssにCSSを書く方法にします。次のCSSをApp.cssの最後に追加してください。
App.csscss
.likeButton {background-color: rgb(231, 76, 60);color: white;padding: 0.8rem;border-radius: 0.4rem;cursor: pointer;}
App.csscss
.likeButton {background-color: rgb(231, 76, 60);color: white;padding: 0.8rem;border-radius: 0.4rem;cursor: pointer;}
App.cssに上の内容を書いたら、ブラウザで確認してみましょう。スタイルが効いていれば、次の図のような表示になっているはずです。
App.cssはApp.tsxでimport
しているので特に何もしなくてもLikeButton
コンポーネントのスタイルに反映されます。もし、スタイルが反映されていないようなら、App.tsxにApp.cssをimport
するコードがあるか確認してください。
App.tsxtsx
importReact from "react";import "./App.css"; // この行があるか確認するfunctionApp () {// ...}
App.tsxtsx
importReact from "react";import "./App.css"; // この行があるか確認するfunctionApp () {// ...}
ここまでで、ボタンのビジュアルの作り込みは一旦完了です。
ボタンに機能をつける
このままでは、ボタンを押しても何も起きません。ここからは、ボタンを押したときに999がひとつ増えて1,000にカウントアップされる機能を作っていきます。
現状のボタンはcount
変数を表示していますが、この変数は固定値になっています。この値が変動できるように、ReactのuseState
関数を使い、カウント数の状態をReactに管理させるようにします。
App.tsxtsx
importReact , {useState } from "react";// ^^^^^^^^^^^^^^ここも追加/* ... */functionLikeButton () {const [count ,setCount ] =useState (999); // このように書き換えるreturn <span className ="likeButton">♥ {count }</span >;}
App.tsxtsx
importReact , {useState } from "react";// ^^^^^^^^^^^^^^ここも追加/* ... */functionLikeButton () {const [count ,setCount ] =useState (999); // このように書き換えるreturn <span className ="likeButton">♥ {count }</span >;}
このuseState
は関数コンポーネントに状態を持たせるためのReactの機能です。useState
の戻り値をcount
とsetCount
の2つの変数に代入しています。count
には999
のような値が代入され、setCount
にはcount
の値を変更する関数が代入されます。
次に、span
要素をクリックしたときに、count
の値を増加するhandleClick
関数を実装します。この関数では、現在のcount
の値に1を足した値をsetCount
関数に渡すようにします。そして、span
要素のonClick
属性にhandleClick
関数を渡します。
App.tsxtsx
functionLikeButton () {const [count ,setCount ] =useState (999);consthandleClick = () => {setCount (count + 1);};return (<span className ="likeButton"onClick ={handleClick }>♥ {count }</span >);}
App.tsxtsx
functionLikeButton () {const [count ,setCount ] =useState (999);consthandleClick = () => {setCount (count + 1);};return (<span className ="likeButton"onClick ={handleClick }>♥ {count }</span >);}
これで、ボタンをクリックしたらいいねの数が増えるようになります。
以上でTypeScriptで作るReactいいねボタンは完成です。