Reactハンズオンラーニング読んだメモ
2章 React学習に必要なJavaScriptの知識
デストラクチャリング
オブジェクトを変数に代入したり引数として受け取る際に、必要なプロパティのみ取捨選択すること
こんな長い名前だったんだ
const sandwich = {
bread: "dutch crunch",
meat: "tuna",
cheese: "swiss",
toppings: ["lettuce", "tomato", "mustard"]
};
const { bread, meat } = sandwich;
console.log(bread, meat); // dutch crunch tuna”
配列にも使えるらしい
filterかけた後の先頭要素だけ欲しいとかあるから、そういう時に使えそう
const [firstAnimal] = ["Horse", "Mouse", "Cat"]
console.log(firstAnimal) // Horce
スプレッド構文
...param
のやつ。これはよく聞く
イミュータブルにすること意識すると、結構使える印象
const peaks = ["Test1", "Test2", "Test3"]
const revercePeaks = [...peaks].reverse()
3章 Javascriptにおける関数型プログラミング
第一級オブジェクト
あるプログラミング言語において、あるオブジェクトを変数に格納できるだけでなく、関数の引数として渡したり、戻り値として受け取ることができる場合、そのオブジェクトを第一級オブジェクトとして呼ぶ
知らなかった
Javascriptは「関数」は第一級オブジェクト
本書では、Javascriptを関数型プログラミング言語と呼んでいる
宣言型プログラミング
「何をするのか」{what)を記述することでアプリケーションを構築するスタイル
Javascriptはこうあるべき
関数型プログラミングの基本概念
- イミュータブル
- 純粋関数
- データの変換
- 高階関数
- 再帰
イミュータブル
変異しない、変更を加えることが不可な状態
NG
let color_lawn = {
title: "lawn",
color: "#00FF00",
rating: 0
}
function rateColor(color, rating) {
color.rating = rating
return color
}
console.log(rateColor(color_rawn, 5).rating) // 5
console.log(color_lawn.rating) // 5 元のオブジェクトにも影響が出ている
OK
let color_lawn = {
title: "lawn",
color: "#00FF00",
rating: 0
}
const rateColor = (color, rating) => ({
...color,
rating
})
console.log(rateColor(color_lawn, 5).rating) // 5
console.log(color_lawn.rating) // 0 元のオブジェクトに変更がない(破壊されていない)
配列も同様
pushよく使っちゃうけど、関数型プログラミングの思考的にはNGらしい
NG
let list = [{ title: "Rad Red" }, { title: "Lawn" }, { title: "Party Pink" }]
const addColot = function(title, colors) {
colors.puch({ title: title })
return colors
}
console.log(addColor("Green").length) // 4
console.log(list.length) // 4 元の変数が更新されてしまっている
OK
let list = [{ title: "Rad Red" }, { title: "Lawn" }, { title: "Party Pink" }]
const addColor = (title, list) => [...list, { title }]
console.log(addColor("Green", list).length) // 4
console.log(list.length) // 3 元の変数が変更されていない
純粋関数
純粋関数 : 引数の値のみを参照して、それをもとに計算し、値を返す関数
(少なくとも一つの引数を取り、値もしくは他の関数を戻り値として返す
NG
引数、戻り値も返却せず、関数外で宣言された変数に変更を加えているため副作用が生じている
const frededick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
}
function selfEducate () {
frededick.canRead: true,
frededick.canWrite: true,
}
selfEducate()
console.log(frededick)
// {
// name: "Frederick Douglass",
// canRead: true,
// canWrite: true
// }
引数がイミュータブルではない
const frededick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
}
const selfEducate = person => {
person.canRead = true
person.canWrite = true
return person
}
// 引数が直接変更されている
console.log(selfEducate(frededick))
// {
// name: "Frederick Douglass",
// canRead: true,
// canWrite: true
// }
console.log(frededick)
// {
// name: "Frederick Douglass",
// canRead: true,
// canWrite: true
// }
ok
const frededick = {
name: "Frederick Douglass",
canRead: false,
canWrite: false
}
const selfEducate = person => ({
...person,
canRead: true,
canWrite: true
})
console.log(selfEducate(frededick))
// {
// name: "Frederick Douglass",
// canRead: true,
// canWrite: true
// }
console.log(frededick)
// {
// name: "Frederick Douglass",
// canRead: false,
// canWrite: false
// }
Javascriptで関数を書く際に意識すること
- 関数は少なくとも一つの引数を受け取らなければいけない (さらに関数は引数以外の値を参照してはいけない、グローバル変数の値によって振る舞いが変わる場合は純粋関数ではない)
- 関数は値もしくは他の関数を戻り値として返却しなければならない
- 関数は引数や関数外で定義された変数に直接変更を加えてはならない
純粋関数のメリット : テストが簡単
データの変換
データのイミュータブルである必要があり、データのコピーを作成して、コピーに変更を加えてどれを別の関数に渡しながら、最終的に変換されたデータを得ることができる
join
: Arrayを結合する
filter
: データの抽出
map
: Arrayの各要素に対して処理を行い、元の配列と同サイズの配列を作成
reduce, reduceRight
: 配列を単一の値に変換する
データの削除にはpop
やslice
を使用するのではなく、filterを使用するようにする
const cutSchools = (cut, list) => list.filter((school) => school !== cut);
console.log(cutSchools("Washington & Liberty", schools));
// Yorktown, Wakefield
console.log(schools.join("/n"));
// Yorktown
// Washington & Liberty
// Wakefield
他参考
高階関数
他の関数を引数に受け取るか、戻り値として関数を返す、またはそれら両方を満たす
カリー化
その場で処理を実行せずに、関数として返却して、任意のタイミングで処理を実行することができる
const userLogs = (userName) => (message) =>
console.log(`${userName} -> ${message}`);
const log = userLogs("john");
getFakeMembers(20)
.then((members) => log(`success: ${members.length} members`))
.catch((error) => log(`error: ${error.message}`));
4章 Reactの基本
ブラウザでReactを動作させるためのライブラリ
React: ビューを構築するためのライブラリ (ビュー : UI)
ReactDOM: Reactで構築されたビューをブラウザで描画する
React要素を作成するコード
React.createElement("h1", { id: "recipe-0" }, "Baked Salmon")
第一引数 : 作成したい要素のタイプ
第二引数 : 要素のプロパティ
第さん引数 : 子要素
上記コードは以下の要素に変換する
<h1 id="recipe-0">Baked Salmon</h1>
ブラウザに描画する
const dish = React.createElement("h1", { id: "recipe-0" }, "Baked Salmon")
ReactDOM.render(dish, document.getElementById("root"))
コンポーネント : 異なるデータでも同じ構成のデータで表示できるDOM
コンポーネントの設計 : そのコンポーネントがスケールするかどうかを必ず考慮する
Reactのコンポーネントの歴史
1: createClass
2: classコンポーネント
1は廃止済み
2は将来的に廃止される予定
そのため、これから作成するコンポーネントについては関数型コンポーネントとする
5章 ReactとJSX
JSX
= Javascript + XML
Javascriptのコードの中に、XMLのようなタグベースの構文を記述するための言語拡張
Babelを活用して、JSXで書かれたJavascriptをブラウザが解釈できるコードにコンパイルする
Reactでは2つ以上の要素をコンポーネントの戻り値として返すことはできない
=> 理由: Reactのコンポーネントは単一の要素ツリーを構築するように設計されているため
以下ChatGPT
Reactではコンポーネントが複数の要素を戻り値として直接返すことができない理由は、ReactのコンポーネントアーキテクチャとJSXの仕様に基づいています。Reactのコンポーネントは、単一の要素ツリーを構築するように設計されています。これは、コンポーネントが一つの要素を返す必要があるという制約につながります。その主な理由は以下の通りです。
1. **DOMツリーの整合性**: Reactは仮想DOMを使用して実際のDOMとの差分を計算し、最小限の更新を行います。コンポーネントが複数の要素を直接返すと、この仮想DOMツリーの構築と更新のプロセスが複雑になり、パフォーマンスの低下やバグの原因になり得ます。
2. **JSXの仕様**: JSXはReactでUIを宣言的に記述するための構文ですが、JavaScriptの関数と同様に、一つの式として評価される必要があります。複数の要素を含むJSXを返す場合、それらをグループ化して一つの式として扱う必要があります。
3. **コンポーネントの責務と再利用性**: Reactのコンポーネントは、独立して再利用可能なUIの単位として設計されています。コンポーネントが複数の要素を直接返すと、それらの要素の関係が不明瞭になり、コンポーネントの再利用性と保守性が低下します。
この制約に対処するために、Reactでは以下のいくつかの方法が提供されています。
- **フラグメント**: React Fragmentを使用すると、複数の要素をグループ化して返すことができますが、追加のDOM要素を挿入することなく、これらを一つの要素として扱うことができます。
- **配列**: 複数の要素を配列として返し、それぞれの要素に一意の`key`プロパティを割り当てることもできます。
これらの方法を使用することで、Reactのコンポーネントが複数の要素を返すときの制約を回避しつつ、整理されたDOMツリーを維持し、アプリケーションのパフォーマンスと保守性を向上させることができます。
<></>は
<React.Flagment></React.Flagment>
の省略形のこと(初めて知った)
webpack : モジュールバンドラ。異なる種類のファイル(js, cssなど)を単一のファイル(bundle: バンドル)に結合するためのツール
6章 ステート管理
制御されていないコンポーネント(uncontrolled component)
-> COMを介してデータにアクセスするコンポーネント
(uesRefを使ってinputの要素を取得する or valueを変更するなど)
import React, { useRef, type FormEvent } from "react";
// uncontrolled component
export default function AddColorForm({
onNewColor,
}: { onNewColor: (title: string, color: string) => void }) {
const txtTitle = useRef<HTMLInputElement | null>(null);
const hexColor = useRef<HTMLInputElement | null>(null);
const submit = (e: FormEvent<HTMLFormElement>) => {
e.preventDefault();
const title = txtTitle.current?.value ?? "";
const color = hexColor.current?.value ?? "";
onNewColor(title, color);
if (txtTitle.current) {
txtTitle.current.value = "";
}
if (hexColor.current) {
hexColor.current.value = "";
}
};
return (
<form onSubmit={submit}>
<input ref={txtTitle} type="text" placeholder="color title..." required />
<input ref={hexColor} type="color" required />
<button type="button">ADD</button>
</form>
);
}
制御されたコンポーネント
=> 全てReactによって管理されている
import { useState } from "react";
export default function AddColorForm({
onNewColor,
}: { onNewColor: (title: string, color: string) => void }) {
const [title, setTitle] = useState<string>("");
const [color, setColor] = useState<string>("#000000");
const submit = (e: React.FormEvent<HTMLFormElement>) => {
e.preventDefault();
onNewColor(title, color);
setTitle("");
setColor("");
};
return (
<form onSubmit={submit}>
<input
value={title}
onChange={(event) => setTitle(event.target.value)}
placeholder="color title..."
required
/>
<input
value={color}
onChange={(event) => setColor(event.target.value)}
required
/>
<button type="button">ADD</button>
</form>
);
}
uuid、簡単に生成できるんだ
フロントで使うことは少なそうだけど、nodejsとかサーバーサイド側では使えそう
import { v4 } from "uuid";
const uuid = v4()
こういうinputのカスタムhooksは結構使えそう
React hooks formでも使えないかな
import { useState } from "react";
export const useInput = (
initialValue: string,
): [
{ value: string; onChange: (e: React.ChangeEvent<HTMLInputElement>) => void },
() => void,
] => {
const [value, setValue] = useState(initialValue);
return [
{
value,
onChange: (e: React.ChangeEvent<HTMLInputElement>) =>
setValue(e.target.value),
},
() => setValue(initialValue),
];
};
この本でもPropsのバケツリレー問題について触れている
これの解消方法として、Contextを上げている
コンテキストプロバイダー : データを格納する場所
コンテキストコンシューマー : データを取り出す場所
これはglobalにstateを管理できるので、共通コンポーネント配下に入り込まないようにする必要がある
こんな感じのArrayのstateを更新するメソッドの書き方は参考になるかも
const rateColor = (id: string, rating: number) => {
setColors(
colors.map((color) => (color.id === id ? { ...color, rating } : color)),
);
};
7章 フック
コンポーネントの描画以外の処置については「副作用」となる
副作用となる処理については、useEffectに記載する
useEffectは画面をレンダリングしてから、実行される
これは使えそう
useEffectのコールバック関数に戻り値を設定することも可能
戻り値が別の関数だった場合、コンポーネントがツリーからアンマウントされた時に、その関数が実行される
Javascriptの配列の比較は内容ではなく、同一インスタンスかどうかで判定する
const a = [1, 2, 3]
const b = [1, 2, 3]
console.log(a === b) // false
const c = d = [1, 2, 3]
console.log(c === d) // true
メモ化
=> 情報工学の一般的な用語(知らなかった)。パフォーマンス改善のために計算結果をキャッシュすること
useMemo, useCallback
コンポーネントが再レンダリングされるたびに、レンダリングや副作用の処理が実行されることを防ぐ
配列であっても、useStateにセットされている場合については同一インスタンスとして見做している
useLayoutEffect:
1: コンポーネントの描画関数がcall
2: useLayoutEffectの副作用関数がcall
3: ブラウザのpaint関数で、処理が実行
4: useEffectの副作用関数がcall
useLayoutEffectの使い道:
描画前にブラウザのサイズから、コンポーネントのwidth, heightを計算する
マウスのポインターを追跡する
useReducer
ステート更新のロジックを抽象化する
import { useEffect, useReducer } from "react";
function Checkbox() {
// useStateからuseReducerに変更
// const [checked, setChecked] = useState(false);
const [checked, setChecked] = useReducer((checked) => !checked, false);
useEffect(() => {
alert(`checked: ${checked.toString()}`);
});
return (
<>
<input
type={"checkbox"}
value={checked.toString()}
onChange={setChecked}
/>
{checked ? "checked" : "not checked"}
</>
);
}
useReducerはobjectの一部を書き換える時とかにかなり使えそう
第二引数をPartialで宣言することがTypescriptで書くときのポイントになりそう
import { useReducer } from "react";
type UserType = {
id: string;
firstName: string;
lastName: string;
city: string;
state: string;
email: string;
admin: boolean;
};
const firstUser: UserType = {
id: "0391-3233-3201",
firstName: "Bill",
lastName: "Wilson",
city: "Missoula",
state: "Montana",
email: "bwilson@mtnwilsons.com",
admin: false,
};
export const User = () => {
const [user, setUser] = useReducer(
(user: UserType, newDetails: Partial<UserType>) => ({
...user,
...newDetails,
}),
firstUser,
);
return (
<div>
<h1>
{user.firstName} {user.lastName}
</h1>
<p>
{user.city}, {user.state}
</p>
<p>{user.email}</p>
<p>{user.admin ? "Admin" : "Not Admin"}</p>
<button
onClick={() =>
setUser({
admin: true,
})
}
type="button"
>
Make Admin
</button>
</div>
);
};