【2日目】React初学者が1週間でどこまで出来るか挑戦(ひたすらドキュメント編)
はじめに
React学習1週間チャレンジ2日目、日曜日をReactに捧げました。ひたすらドキュメントを読み進めてました。
前回の振り返り:
-
【1日目】React初学者が1週間でどこまで出来るか挑戦
- LaravelのBreezeパッケージの導入でReactを使えるようにした
- Reactのコンポーネントとは何か基礎を学んだ
教材:
- React公式ドキュメント: https://react.dev/learn
今日の学び:
- コンポーネントのImportとExport
- JSXのマークアップ
- JSXの構文ルール
- コンポーネントにpropsを渡す
- 条件付きレンダリング
- リストをレンダリングするときの注意
- コンポーネントをpureに保つ
- イベントハンドリングの基礎
- Stateの基礎の基礎
チャプター「Describing the UI」が完了しました!
Importing and Exporting Components
ドキュメント参考箇所:
root component fileとは
ドキュメントを参考に、App.js
というファイルにUser
関数を定義して、それをUserList
関数内で呼び出してみた。このApp.js
をレンダリングする際、このファイルはroot component fileとして扱われる。(Reactの公式ドキュメントには、ブラウザ上でコードを編集して結果を確認できるサンドボックスが提供されているためそれを利用している。)
// App.js
function User() {
return <p>Name: Ryosuke</p>
}
export default function UserList() {
return (
<section>
<h1>User</h1>
<User />
<User />
<User />
</section>
);
}
componentのexportとimport
App.js
のように一つのファイル内でcomponentを定義し使用する場合、再利用性を高めるためにファイルを分けたくなる。その場合の手順は:
- componentを入れたいJSファイルを新規作成
- function componentをexportする
- componentを使いたいファイルでimportする
まず、JavaScriptのexportについて知識がなかったので、MDNで基礎を確認した。
exportには2種類ある。各モジュールの中で持てるexportは、
- named export : 複数
- default export : 1つのみ
// named export
export { myFunction2, myVariable2 };
// default export
export default myFunction;
export default function () { /* … */ }
前回のBreezeパッケージで生成されたresources/js
下の.jsx
ファイルを一通り確認したところ、全てdefault exportが使われていた。
Reactドキュメントに戻り、サンプルコードを参考にApp.js
に定義した関数をUserList.js
に切り出してimportする形に変更。
// UserList.js
function User() {
return <p>Name: Ryosuke</p>;
}
export default function UserList() {
return (
<section>
<h1>User</h1>
<User />
<User />
<User />
</section>
);
}
// App.js
import UserList from "./UserList";
export default function App()
{
return (
<UserList />
);
}
同一ファイルから複数のcomponentをexportとimport
UserList
ではなくUser
だけを表示したい場合はどうしたらいいか?という観点でコードを書き換えていく。
先に学んだnamed exportを使う。User
をexportとする。
// UserList.js
export function User() {
return <p>Name: Ryosuke</p>;
}
export default function UserList() {
// 省略
}
User
を{}
で囲ってimportする。そして<User />
をreturnする。
// App.js
import { User } from "./UserList";
export default function App()
{
return <User />
}
使用上のTipsも書かれていた。defaultとnamedで混乱を招かないように以下のようなスタイルをとるのがよさそう。
- defaultかnamedかどちらか一つに統一する
- 単一ファイル内ではdefaultとnamedを混在させない
Writing Markup with JSX
ドキュメント参考箇所:
ポイント:
- JSXはJavaScriptの拡張構文で、JavaScriptファイル内でHTML風にマークアップできる
- JSXは拡張構文で、ReactはJavaScriptのライブラリである
- JSXはHTMLに似ているが、より厳格なルールがある
The Rules of JSX
ルート要素は一つだけ
先のUser
のp
タグを増やしてみた。同階層に複数のタグがあるとエラーになるため、親要素に<div>
を使って単一のルート要素にした。
export function User() {
return (
<div>
<p>Name: Ryosuke</p>
<p>Learn: React</p>
<p>Like: Coffee</p>
</div>
);
}
JSX構文に対応するために<div>
を使ったが、本当はHTMLの構造に<div>
タグを増やしたくない、という時はFragmentを使う。
Fragmentは、<> ... </>
のように空タグで要素を囲う。
タグは閉じる
HTMLでは閉じタグがなくても記述できるタグであっても、
<!-- HTML -->
<input type="text" value="test">
<ul>
<li>List1
<li>List2
</ul>
JSXでは必ずタグを閉じること
// JSX
<input type="text" value="test" />
<ul>
<li>List1</li>
<li>List2</li>
</ul>
ほとんどcamelCase
- JSX で記述された属性がJavaScript オブジェクトのキーになる
- 属性はcamelCaseにする
-
class
などの予約語は使えない(class
属性は代わりにclassName
にする)
JavaScript in JSX with Curly Braces
ドキュメント参考箇所:
文字列を渡す: const
で変数を定義して、{ }
内で使用できる。
export function User() {
const name = "Ryosuke";
return (
<input type="text" value={name} />
);
}
{ }
内では、JavaScriptのメソッドを書くこともできる。例えば、
const today = new Date();
function formatDate(date) {
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
return `${year}-${month}-${day}`;
}
return <h1>Today is {formatDate(today)}</h1>; // Today is 2023-10-08
JavaScriptのオブジェクトとしての使い方
export function User() {
const UserInfo = {
name: "Ryosuke",
like: "coffee",
};
return (
<>
<p>Name: {UserInfo.name}</p>
<p>Like: {UserInfo.like}</p>
</>
);
}
Passing Props to a Component
ドキュメント参考箇所:
ポイント:
- props = 小道具
- 親componentはpropsを使って子componentに情報を渡す
- propsはオブジェクト、配列、関数などあらゆるJavaScriptを渡せる
componentとpropsを学んで、昨日見たInertia::render
メソッドの引数の意味が分かってきた。
// Inertia\Inertia::render
public static function render($component, $props = []) { }
propsの渡し方
- 子component(
User
)にpropsを渡す(ここではuserInfo
オブジェクト) - 子component(
User
)内でpropsを読み込む
function User({userInfo}) {
return (
<>
<p>Name: {userInfo.name}</p>
<p>Like: {userInfo.like}</p>
</>
);
}
export default function UserList() {
return (
<section>
<h1>UserList</h1>
<User
userInfo={{name: 'Ryosuke', like: 'coffee'}}
/>
</section>
);
}
ネストする方法
// App.js
import User from './User.js';
function Card({ children }) {
return (
<div className="card">
{children}
</div>
);
}
export default function UserList() {
return (
<Card>
<User
userInfo={{
name: 'Ryosuke', like: 'coffee'
}}
/>
</Card>
);
}
// User.js
export default function User({ userInfo }) {
return (
<>
<p>Name: {userInfo.name}</p>
<p>Like: {userInfo.like}</p>
</>
);
}
以下のパートは後から出てくるstateに関わるようで軽く読むだけにした。
条件付きレンダリング
ドキュメント参照箇所:
JavaScriptの構文と同じようにif
や&&
、? :
演算子が使える。
以下はTodoリストの例。isDone
の値で条件分岐させている。ただしDRYのため次で書き換える。
// App.js
function Task({ task, isDone }) {
if (isDone) {
return <li className="task">{task} ✅</li>;
}
return <li className="task">{task}</li>;
}
export default function ToDoList() {
return (
<section>
<h1>Today's Tasks</h1>
<ul>
<Task
isDone={false}
task="Study React"
/>
<Task
isDone={false}
task="Write an article"
/>
<Task
isDone={true}
task="Buy some coffee"
/>
</ul>
</section>
);
}
三項演算子(? :
)によってDRYを解消。
function Task({ task, isDone }) {
return (
<li className="task">
{ isDone ? task + ' ✅' : task }
</li>
);
}
条件式の中でさらにHTMLタグを使う例。JSX内のJavaScript式は{}
で、複数行のJSXは()
で囲む。
function Task({ task, isDone }) {
return (
<li className="task">
{ isDone ? (<del>{task + ' ✅'}</del>) : task }
</li>
);
}
論理積(&&
)演算子を使う例。左から全てtrue
で通過した場合は最後のオペランドが返される。false
があった時点で評価されず終了。文法はMDNを参照: Logical AND (&&) - JavaScript | MDN
function Task({ task, isDone }) {
return (
<li className="task">
{task} { isDone && '✅' }
</li>
);
}
Rendering Lists
ドキュメント参考箇所:
例えば「従業員 : 部署」というリストを表示したいとする。
<ul>
<li>青木: 営業部</li>
<li>井上: 商品部</li>
<li>上田: 総務部</li>
<li>江原: 人事部</li>
<li>小田: 営業部</li>
</ul>
JavaScriptのmap
を使って
const employees = [
'青木 : 営業部',
'井上 : 商品部',
'上田 : 総務部',
'江原 : 人事部',
'小田 : 営業部',
];
export default function List() {
const listItems = employees.map(employee =>
<li>{employee}</li>
);
return <ul>{listItems}</ul>;
}
map
メソッドによってlistItems
は以下のような配列になるはず。それを<ul>
タグの中でレンダリングさせている。
// listItemsの中身
[
<li>青木 : 営業部</li>,
<li>井上 : 商品部</li>,
<li>上田 : 総務部</li>,
<li>江原 : 人事部</li>,
<li>小田 : 営業部</li>
]
表示はされるがコンソールに次のようなエラーが出る。
Warning: Each child in a list should have a unique "key" prop.
これは、配列の各要素にkey
が必要だから(ユニークキー)
<li key={employee.id}>...</li>
次のように各リストにユニークキーを持たせるように書き換える。
const employees = [
{ id: 1, name: '青木', department: '営業部' },
{ id: 2, name: '井上', department: '商品部' },
{ id: 3, name: '上田', department: '総務部' },
{ id: 4, name: '江原', department: '人事部' },
{ id: 5, name: '小田', department: '営業部' }
];
export default function List() {
const listItems = employees.map(employee =>
<li key={employee.id}>
{employee.name} : {employee.department}
</li>
);
return <ul>{listItems}</ul>;
}
filter
メソッドを使って「営業部」だけに絞り込んだ例を書いてみる。
const employees = [
{ id: 1, name: '青木', department: '営業部' },
{ id: 2, name: '井上', department: '商品部' },
{ id: 3, name: '上田', department: '総務部' },
{ id: 4, name: '江原', department: '人事部' },
{ id: 5, name: '小田', department: '営業部' }
];
export default function List() {
const sales = employees.filter(employee =>
employee.department === '営業部'
);
const listItems = sales.map(employee =>
<li key={employee.id}>
{employee.name} : {employee.department}
</li>
);
return <ul>{listItems}</ul>;
}
Keeping Components Pure
ドキュメント参考箇所:
Describing the UIという最初のチャプターの最後のパート
ポイント:
- componentは常に同じ結果を返すべき
- componentを呼び出す度に結果が変わるような実装をすべきでない
例えば、以下のコードはコンポーネント外部で宣言した変数を内部で使っているため、コンポーネントを呼び出す度に異なるJSXを生成してしまう。
// 悪い例
let itemId = 0;
function Item() {
itemId++;
return <div>Item #{itemId}</div>;
}
export default function List() {
return (
<>
<Item />
<Item />
<Item />
</>
);
}
// レンダリング結果は以下ようになる
// Item #2
// Item #4
// Item #6
解決方法として次のようにpropを使うこと。
function Item({ itemId }) {
return <div>Item #{itemId}</div>;
}
export default function List() {
return (
<>
<Item itemId={1} />
<Item itemId={2} />
<Item itemId={3} />
</>
);
}
また、コンポーネントの内部またはpropsを通してデータを渡すと、予期せぬバグや副作用を避けるこができる。
function Item({ itemId }) {
return <li>Item #{itemId}</li>;
}
export default function List() {
let items = [];
for (let i = 1; i <= 5; i++) {
items.push(<Item key={i} itemId={i} />);
}
return items;
}
Responding to Events
チャプター: LEARN REACT > ADDING INTERACTIVITY
ドキュメント参照箇所:
Event handler functionsは通常、
- コンポーネントの中に定義する
-
handle
から始まるイベントの名を命名する- 例えば、
onClick={handleClick}
,onMouseEnter={handleMouseEnter}
など
- 例えば、
注意点:
- 正)
<button onClick={handleClick}>
は、ボタンクリック時に関数が呼び出される - 誤)
<button onClick={handleClick()}>
は、レンダリング時に即時呼び出される
Event propagation(イベントの伝播):
- イベントは子から親要素に向かって伝播していく
- 伝播を止めるにはイベントオブジェクト(
e
)に対して、e.stopPropagation()
を呼ぶ - formの送信イベントを止めるには、
e.preventDefault()
State: A Component's Memory
ドキュメント参照箇所: State: A Component's Memory – React
ポイント:
- ローカル変数はレンダリング間で保持されない
- ローカル変数が更新されても新しいデータで再度レンダリングされるわけではない
解決するには:
-
useState
を使う -
import { useState } from 'react';
でインポートする
Hookとは:
-
useState
のように、use
から始まる関数をHookと呼ぶ
連載記事
- 【1日目】React初学者が1週間でどこまで出来るか挑戦
- 【2日目】React初学者が1週間でどこまで出来るか挑戦(ひたすらドキュメント編)★今回