React の基礎 〜主作用から見る React の役割〜
この記事は社内で実施した勉強会の一部を再編したものです。
はじめに
React使ってますか?
関わっているプロダクトで毎日書いているという人もいれば、普段フロントエンドを書かないので既存のコードをちょちょいと修正して終わりという人もいるかと思います。普段あまり意識していないかもしれませんが、Reactでは主作用と副作用をきっちり分けて考えることが非常に重要です。
この記事ではReactの基礎について書きますが、その主作用に焦点を置いて 「Reactとは一体何なのか」 から解説します。
主な対象読者
- Reactの基礎を学びたい初学者
- 基礎を復習したい中級者
- 「俺たちは雰囲気でReactをやっている」という人
記事のゴール
- Reactの主作用が分かる
- Reactにおける「コンポーネント」とは何なのかが分かる
- ReactでWebページを構築するために必要な仕組みが分かる
おことわり
- Reactそのものの説明にフォーカスするため、前提知識は書きません
- HTMLやJavaScriptの基礎知識がある前提です
- 不安であればMDNやサバイバルTypeScriptなどで学習してください
- インストール・セットアップ方法は解説しません
- 公式ドキュメントを参照してください
- HTMLやJavaScriptの基礎知識がある前提です
- 分かりやすさを重視して、正確な説明をしていなかったり、そもそも説明をしていないこともあります
- 特にHooksについてはこの記事では一切触れません
Reactって一体何者なの?
そもそもReactが何をするためのものなのかを把握するところから始めます。
一言で言えば、 「ユーザインターフェース(UI)を表示するためのJavaScriptライブラリ」 です。Webの画面上にUIを表示するためにはHTMLを用いるので、ReactはHTMLを作るものと言えます。
コンポーネント
HTMLでは、UIを構成するテキスト、リンク、ボタンといった小さい部品を組み合わせてUIを構築します。ReactやVue.jsをはじめとした近年使用されているライブラリでは、HTML・CSS・JavaScriptの部品をコンポーネントという単位にまとめます。
コンポーネントはアプリのための再利用可能なUI要素、つまり部品として扱えるため、HTMLを使うときと同じようにこれを組み合わせてUIを構築できます。こういった考え方はコンポーネント指向と呼ばれることがあります。
Reactの基礎となる仕組み
ここからは、Reactでどのようにコンポーネントを作成するのかを見ていきます。
まずは画面上にHello Worldと表示するだけの簡単なHTMLを書いてみます。
<html lang="ja">
<head>
<title>タイトル</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
続いて、JavaScriptの関数でこのHTMLを生成するようなコードを書いてみます。
const generateHtml = () => (
<html lang="ja">
<head>
<title>タイトル</title>
</head>
<body>
<h1>Hello World</h1>
</body>
</html>
)
generateHtml
関数を実行すればHTMLが生成されます。
HTMLを返すだけの関数を使えば、HTMLをJavaScriptで生成できるというのが、Reactのベースとなる考え方です。この関数のことを、Reactではコンポーネントと呼びます。
もちろん、関数から別の関数を呼ぶこともできます。
// このコードは動作しません。あくまでイメージです
const generateH1 = () => <h1>Hello World</h1>
const generateHtml = () => (
<html lang="ja">
<head>
<title>タイトル</title>
</head>
<body>
generateH1() // 別のコンポーネント(関数)の戻り値を使用している
</body>
</html>
)
つまり関数を分割するのと同様に、HTMLを生成する関数を分割してパーツに分け、それを組み合わせて利用できるようになります。
ここまでのまとめ
Reactは、HTMLを返す関数(コンポーネント)を作り、それを組み合わせてUIを構築するライブラリである。
コンポーネントの書き方
ここからは、実際にReactを書いていくために必要なことを解説します。
JSX
先ほどは「HTMLを返す関数」と説明しましたが、普通はJavaScriptの中に直接HTMLを書くことはできません(逆であれば<script>
にJavaScriptが書けます)。ですが、文書の構造はHTMLのようにマークアップ言語で書くほうが効率的です。
ReactではJavaScript内でマークアップを書くためにJSXを使います。
const Title = () => <h1>Hello World</h1>
これはHTMLがほぼそのまま書けるので、上のコードは実際に動作します。違いとしては、属性がcamelCaseになる・classの代わりにclassNameと書く、タグは必ず閉じる(自己終了タグでも可)などがあります。詳しくは公式サイトを参照してください。
さらにJSXでは、 他のコンポーネントをまるでHTML要素かのように呼び出せます。 Reactは名前の先頭が大文字の関数をコンポーネントと認識し、HTMLタグのように<>
で囲うだけで呼び出せます。
const Title = () => <h1>Hello World</h1>
const Content = () => (
<main>
<Title />
</main>
)
JSXの中では、{}
で囲うことでJavaScriptが書けます。null、undefined、falseといった一部の値を除き、評価値がJSXとして出力されます。
const Title = () => {
// 変数の値を出力する
const text = 'Hello World'
return <h1>{text}</h1>
}
const Content = () => (
<main>
{/* コメントを書くときにも使う */}
<Title />
</main>
)
引数(Props)
固定の値だけではなく、可変の値も表示できます。コンポーネントはJSXを返す関数なので引数を受け取れます。コンポーネントを便利に使うために、少し特殊な書き方をします。
const Title = ({ text }) => <h1>{text}</h1>
const Content = () => (
<main>
{/* 文字列を渡しているので「text="Hello World"」と書いてもいい */}
<Title text={'Hello World'} />
</main>
)
コンポーネントに渡す値はHTMLの属性のように記述し、引数として受け取るときは一つのオブジェクトにまとめられます。
ReactではこれをPropsと呼び、JSXの属性として書いたものが渡されます。型を引き継ぐので、numberやbooleanを渡したいときは{}
を使用します。
children
特殊なPropsとしてchildrenがあります。ここにはコンポーネントの子要素として渡されたものが入ってきます。
ただのPropsを使うかchildrenを使うかの判断は場合によって異なり、特にchildrenは1つしか受け取れないことに注意が必要です。一般的には「その要素の子どもとして扱いたい値」はchildrenで渡すことが多いです。
// children には 'Hello World' が入る
const Title = ({ children }) => <h1>{children}</h1>
const Content = () => (
<main>
<Title>Hello World</Title>
</main>
)
繰り返し
受け取った配列の長さ分だけテーブルの行を表示する、ということもできます。{}
の中にはJavaScriptが書けて評価された値が表示されるので、map
でJSXを返す方法が主に用いられます。
以下のコードでは、Propsで受け取ったユーザーの一覧をリストとして表示します。Reactの制約として、繰り返される要素には表示するデータに対して一意のkey
Propsを指定する必要があります。
const Content = ({ users }) => (
<main>
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
</main>
)
注意点として、基本的にJSXの中でfor文やwhile文は使用できません。 これらは文であり、値を返さないためです。
条件分岐
繰り返しができるなら、当然条件分岐もできます。基本的には条件演算子(三項演算子)や論理演算子が使用されます。for文と同じ理由で、if文やswitch文は使えません。
コンポーネントはJSXを返す関数であればいいので、早期リターンもできます。
const Content = ({ users }) => {
// usersが渡されなかったら、エラーを表示する
if (users === undefined) {
return <p>エラーが発生しました。</p>
}
return (
<main>
{/* ユーザーが0人のときは、「ユーザーがいません。」を表示する */}
{users.length === 0 ? (
<p>ユーザーがいません。</p>
) : (
<ul>
{users.map((user) => (
<li key={user.id}>{user.name}</li>
))}
</ul>
)}
</main>
)
}
イベントハンドラー
Webアプリケーションでは、画面にデータを表示するだけでなく、ユーザーの入力を受け取って処理を行う必要があります。
通常のHTMLであれば、onclick
(c
は小文字)やaddEventListener
を使って、script
要素の中に定義したイベントハンドラー関数を呼び出します。
<script>
const handleClick = () => {
console.log('click!')
}
</script>
<button type="button" onclick="handleClick">Click</button>
一方Reactでは onClick
Propsを使います(C
は大文字)。
※HTMLではonclick
を避けたほうがいいですが、Reactでは書いても問題ありません。
イベントハンドラー関数もReactコンポーネントの中に書けるのがポイントです。clickの部分はinputやchangeなど、受け取りたいイベント名に変えられます。
const Button = () => {
const handleClick = () => {
console.log('click!')
}
return <button type="button" onClick={handleClick}>Click</button>
}
「handleClick
という関数定義が書いてあるscript
要素とbutton
要素」が記述されたHTML(上で例示したHTML)が生成されるイメージです。
Reactでの考え方
どのようにコンポーネントを書くかは理解できたかと思います。ここからは、ReactをReactらしく、より良いコードを書くための考え方について触れていきます。
コンポーネント呼び出しを木構造として捉える
ReactはHTMLと同様、パーツを組み合わせてUIを構築します。
そのため、パーツであるコンポーネントは何段階もネストされていき、木構造となります。 ただの関数の呼び出しではなく、木構造として捉えて頭の中に描けると、コンポーネントがどのように組み合わさっているのかが分かりやすくなります。
引用:https://ja.react.dev/learn/understanding-your-ui-as-a-tree
レンダリング(レンダー)
コンポーネントを実行してJSXを生成することを、React用語で「レンダリング(レンダー)」と言います。(公式サイトには「レンダー」と書いてありますが、日本では「レンダリング」と呼ぶことが多い印象です)
Reactは、UIを表示したいときにコンポーネント関数を実行してJSXを生成します。初回のレンダリング以外にも、Propsが変わったときにも表示内容を変える必要があるので再レンダリングされます。ただの関数呼び出しなので、実行されたコンポーネントから呼び出されている別のコンポーネントも再レンダリング(=関数が再実行)されます。
以下のような処理が行われているイメージをすると分かりやすいかと思います。
// 表示対象のコンポーネント
const Content = ({ users }) => { ... }
// 初回レンダリング
rootElement.innerHTML = Content({ users })
// ReactがPropsの変更を検知した際に実行される
onChangeProps(({ users }) => {
rootElement.innerHTML = Content({ users })
}
純粋な関数として書く
コンポーネントとはあくまでもJSXを返すただの関数です。つまり、普通の関数と同じようなプラクティスが適用できます。
JavaScriptでは関数型プログラミングの概念が取り入れられることが多く、Reactもそれに則って純粋関数で書きます。 つまり、参照透過性を担保し、副作用を持たない関数として実装します。
// NG 例
let userName = ''
const Render = () => {
// 出力の副作用
console.log('test')
// 冪等ではない
const num = Math.floor(Math.random() * 10)
if (num % 2 === 0) {
return <p>偶数は表示できません</p>
}
fetchUserName() // APIアクセス
.then((res) => userName = res.userName) // コンポーネント外の変数への書き込み
const handleClick = () => {
console.log('click!')
}
return (
<div>
<h1>Hello World.</h1>
<span>{num}番</span>
<span>{userName}</span>
<button type="button" onClick={handleClick}>CLICK</button>
</div>
)
}
主作用である「Propsを受け取ってJSXを生成する」という処理にフォーカスし、それ以外の処理(副作用)はコンポーネント内に書かないようにしましょう。
これにより、コードベースが大きくなるにつれて起きがちな、不可解なバグや予測不可能な挙動を回避できます。さらに、Reactがレンダリングを効率化してパフォーマンスの向上が見込めます。
サンプルコードについて補足
前述した通り、イベントハンドラーはJSXの中にscript要素として出力されるイメージです。レンダリング時にイベントハンドラーは実行されないため、コンポーネントの副作用とはみなされません。
まとめ
- HTMLのような構文を持つJSXを使用する
- JSXを返す純粋な関数である 「コンポーネント」 を組み合わせてUI(HTML)を作り上げる
- JSXの中でJSを書くには
{}
を使い、変数の出力・条件分岐・繰り返しを記述する - 動的な値は引数(Props)として受け取る
- イベントハンドラーはコンポーネント内に関数を定義してJSXに埋め込める
- Reactは関数を実行(レンダリング)してHTMLを生成する
- HTMLと同様に木構造で捉えるとイメージしやすい
ステップアップ
今回紹介した機能だけでは、動きのあるリッチなアプリケーションは作成できません。
コンポーネントでデータを保持したり、APIで外部にアクセスしたりするには、Hooksという仕組みを利用する必要があります。React公式でHooksについて説明されているページがあるので、こちらを読んでいただくと良いかと思います(後日Hooksの解説記事を投稿するかもしれません)。
Discussion