目的
- React(v18)の知識が必要なので確認
クイックスタートのメモ
ドキュメントをみて初めて知ったところや、
記載通りの内容では理解できず調べて理解したことをメモする。
(普段はサーバーサイドを担当しているので、フロントよりの疑問が多め。)
- Reactはすべてがコンポーネント
- Reactコンポーネントは大文字で始まる(HTMLは小文字)
- Reactコンポーネントはreturnで返すことができるのは1つの要素のみ
- そのため複数の要素がある場合、一つの親要素で囲む必要がある
// これだとエラー
return (
<h1>Hello</h1>
<p>World</p>
);
// これならOK
return (
<div>
<h1>Hello</h1>
<p>World</p>
</div>
);
// 空のフラグメントでもOK
// (DOMの構造をシンプルにあるいはスタイルやレイアウトに影響を与えたくない場合に使うと理解)
return (
<>
<h1>Hello</h1>
<p>World</p>
</>
);
- ReactはJSX(JavaScript XML)をサポートしている
- JavaScriptの中にHTMLのような構文を埋め込むことができる
- これによりUIコンポーネントを直感的に記述できる
- 最終的にはJavaScriptにコンパイルされる
- これによりUIコンポーネントを直感的に記述できる
- JavaScriptの中にHTMLのような構文を埋め込むことができる
- JSXはHTMLよりも厳密なので閉じタグには必ずスラッシュ
/
を使う必要がある
// これだとJSXではエラー
<br>
<img src="image.jpg">
// スラッシュつけて閉じること
<br />
<img src="image.jpg" />
- HTMLからJSXに大量変換したい場合にはコンバーターもある
- JavaScriptではオブジェクト指向における
class
の文脈で、class
が既に使われている(予約語)- そのためReactにおいてCSSを当てるための
class
を使いたい場合はclassName
を使う
- そのためReactにおいてCSSを当てるための
- ReactではCSSの指定方法について特段定めていない
- 通常のCSSを使ってもいいし名前の衝突を避けるためCSSモジュールを使ってもいい
- CSSを読み込む方法としてHTMLの
head
タグにlink
タグを追加して読み込んでもいいし、JavaScript/TypeScriptならばimport
で読み込んでもいい
HTML
<head>
<link rel="stylesheet" href="style.css">
</head>
JavaScript/TypeScript
import './style.css';
- React内で動的にデータを表示したり、属性を設定したい場合には中括弧
{}
を使う- Reactで動的なデータを扱う際には、状態
state
やプロパティprops
を変数として格納し、それを中括弧{}
を使って表示するということ- Reactの特性のため
- 状態管理やライフサイクルによりコンポーネントの状態やプロパティの中身が変化するのが前提のため
- JSXの特性の特性のため
- JSX自体はデータを取得する機能を持たないので、JavaScriptの変数や式を埋め込むことで機能する
- JavaScriptの特性のため
- JavaScriptは非同期処理をサポートしているがデータの取得まで時間がかかることも
- 初期レンダリング時にはデータが存在しないこともあるため状態を変数に格納する
- Reactの特性のため
- 中括弧
{}
に設定できるのはJavaScriptの式である- 式とは?
- 数値、文字列、変数、演算子、関数などを使い、評価されると結果として値を返すもの
- 式とは?
- Reactで動的なデータを扱う際には、状態
// 例文から抜粋
export default function Profile() {
return (
<>
<h1>{user.name}</h1>
<img
className="avatar"
src={user.imageUrl}
alt={'Photo of ' + user.name}
style={{
width: user.imageSize,
height: user.imageSize
}}
/>
</>
);
}
-
img
タグの中にあるsrc
・alt
・style
はプロパティ(属性)という- その中で
style
属性だけはCSSスタイルをJavaScriptオブジェクトとして定義するので二重の中括弧が必要- 外側の
{}
:style属性に渡す値がJavaScriptの式であることをReactに示す - 内側の
{}
:Reactがこの部分を評価し{ width: user.imageSize, height: user.imageSize }
という JavaScript オブジェクトを作成する
- 外側の
- その中で
- 条件を書くときは通常のJavaScriptコードを書くときと同じでOK
if文
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
条件演算子
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
論理構文
<div>
{isLoggedIn && <AdminPanel />}
</div>
- コンポーネントのリストをレンダリングする場合には、リスト内容を一意に識別できる文字列または数値を渡す必要がある
メソッド | 説明 | 元のデータ | 使用例 |
---|---|---|---|
map() |
各要素に関数を適用し、新しい配列を生成 | 壊さない | 配列の数値を2倍にする: [1, 2, 3].map(x => x * 2) → [2, 4, 6]
|
filter() |
条件を満たす要素だけを含む新しい配列を生成 | 壊さない | 偶数だけを抽出: [1, 2, 3, 4].filter(x => x % 2 === 0) → [2, 4]
|
reduce() |
各要素を累積的に処理し、単一の値を生成 | 壊さない | 配列の合計を計算: [1, 2, 3].reduce((acc, x) => acc + x, 0) → 6
|
forEach() |
各要素に関数を実行するが、新しい配列は返さない | 壊さない | 各要素をコンソールに出力: [1, 2, 3].forEach(x => console.log(x))
|
splice() |
指定した位置から要素を削除または追加する | 壊す | 2番目の要素を削除: [1, 2, 3].splice(1, 1) → [1, 3]
|
push() |
配列の末尾に要素を追加 | 壊す | 要素を追加: [1, 2].push(3) → [1, 2, 3]
|
pop() |
配列の末尾の要素を削除し、その要素を返す | 壊す | 最後の要素を削除: [1, 2, 3].pop() → 3 、残りは [1, 2]
|
shift() |
配列の先頭の要素を削除し、その要素を返す | 壊す | 最初の要素を削除: [1, 2, 3].shift() → 1 、残りは [2, 3]
|
unshift() |
配列の先頭に要素を追加 | 壊す | 要素を追加: [2, 3].unshift(1) → [1, 2, 3]
|
slice() |
配列の一部を抽出し、新しい配列を生成 | 壊さない | 1番目から2番目の要素を取得: [1, 2, 3].slice(1, 2) → [2]
|
- JavaScriptにおいて、関数名の後に括弧 () があると、その関数が呼び出される(実行される)という意味になる
- そのため関数を呼び出して使うような時には(例:コールバック、イベントハンドラー)呼び出し元で
()
をつけないようにする
- そのため関数を呼び出して使うような時には(例:コールバック、イベントハンドラー)呼び出し元で
// 正しい使い方
<button onClick={handleClick}>Click Me</button>
//ユーザーがボタンをクリックした時に呼び出される
// 間違った使い方
<button onClick={handleClick()}>Click Me</button>
// 即座にhandleClickが呼び出されてしまう
状態管理とライフサイクルとは?
- 状態管理
- 対象データはどれなのか把握
- 例:APIから取得するユーザー情報を定義
- その中身がどんな状態かを保持(=
useState
)- 例:「データを取得できている」「エラーで失敗している」という状態を持つ
- 対象データはどれなのか把握
- ライフサイクル
- 状態管理に「データの情報どうですか」と聞きに行く(=
useEffect
) - その結果によって以下のレンダリングを行う
- コンポーネントのマウント(表示)
- コンポーネントの更新
- コンポーネントのアンマウント(削除)
- 状態管理に「データの情報どうですか」と聞きに行く(=
それをコードで表現するとこんな感じ(Rails+React+Vite)
import React, { useEffect, useState } from 'react';
import axios from 'axios';
const UserProfile = () => {
// 状態管理: APIから取得するユーザー情報を定義
// useStateフックを使って、userという状態変数とその状態を更新するためのsetUser関数を定義
const [user, setUser] = useState(null); // 初期値はnull
// ライフサイクル: コンポーネントがマウントされたときにデータを取得
useEffect(() => {
// APIを叩いてユーザー情報を取得
axios.get('/api/users/1') // RailsのAPIエンドポイント
.then(response => {
// 取得したデータを状態に格納
setUser(response.data);
})
.catch(error => {
// エラーが発生した場合の処理(必要に応じて)
console.error("Error fetching user data:", error);
});
}, []); // 空の依存配列は、コンポーネントがマウントされたときにのみ実行される
// データがまだ取得できていない場合はローディングメッセージを表示
if (!user) return <div>Loading...</div>;
// 状態に格納したデータを表示
return (
<h1>{user.name}</h1>
);
};
export default UserProfile;
- React Server Components(RSC)だとAPIを介さずに直接データを取得・表示(コンポーネントの戻り値として返す)ことができる
- この場合だと、RSC側(サーバー側)でデータを取得と再レンダリングを自動管理してくれるので、クライアントサイドで状態管理やAPI呼び出しを意識しなくてよくなる✨
import React from 'react';
async function fetchUserData() {
const response = await fetch('https://api.example.com/users/1');
return response.json();
}
const UserProfile = async () => {
const user = await fetchUserData(); // データを直接取得
return (
<h1>{user.name}</h1> // 取得したデータをそのまま表示
);
};
export default UserProfile;
-
useState
には現在の状態とそれを更新するための関数を定義する- 名前の付け方は任意だが、慣習的に
[something, setSomething]
と定義する
- 名前の付け方は任意だが、慣習的に
-
use
で始まる特別な関数をフックHooks
という- クラスベースのアプローチを使わずに関数ベースのアプローチで副作用を扱えるところが特別
プログラミングにおける”副作用”は日常的に使っている”副作用”とは異なる
〜例〜
- 主作用:コンポーネントがその状態に基づいてどのように表示されるか。視覚的な出力(レンダリング)を指す。
- 副作用①:APIからデータをフェッチする行為。外部のデータソースから情報を取得するための手段であり、コンポーネントの内部状態を更新するための準備。
- 副作用②:フェッチした結果を元に状態を変更する行為。コンポーネントの外部に影響を与え、再レンダリングを引き起こす。
-
Hooks
を使うにはコンポーネント内部のトップレベルに定義する- つまり条件分岐やループの中ではなくてコンポーネントの関数の最上部で呼び出す必要がある
- 一つのコンポーネントで複数の
Hooks
を使ってOK
- 渡す情報を
props
という - なにをもって親子と判定するのか?
- コンポーネントの構造によって決まる
- ページをわけている場合:
import
している側が子 - 同ページの場合:
props
を受け取っている側が子
- ページをわけている場合:
- コンポーネントの構造によって決まる
// 子コンポーネント
export const ChildComponent = ({ title, description }) => {
return (
<div>
<h1>{title}</h1>
<p>{description}</p>
</div>
);
};
// 親コンポーネント
export const ParentComponent = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
const result = await response.json();
setData(result);
} catch (err) {
setError(err);
} finally {
setLoading(false);
}
};
fetchData();
}, []);
if (loading) return <div>Loading...</div>;
if (error) return <div>Error: {error.message}</div>;
return (
<div>
{/* データが取得できたら子コンポーネントに渡す 関数名と渡すデータを指定 */}
<ChildComponent title={data.title} description={data.description} />
</div>
);
};