内容
- Reactの公式サイトを見て勉強したときのメモ
おさほう
- 基本はJSX
- react はclass指定する時に className=”class名”とかく
- 属性には{}で渡す
return (
<img
className="avatar"
src={user.imageUrl}
/>
);
- 条件分岐はJSX外にもかける
let content;
if (isLoggedIn) {
content = <AdminPanel />;
} else {
content = <LoginForm />;
}
return (
<div>
{content}
</div>
);
- JSX内でもかける
<div>
{isLoggedIn ? (
<AdminPanel />
) : (
<LoginForm />
)}
</div>
- 繰り返し
- mapでオブジェクトを回してhtmlのパーツをあらかじめ変数の中に作っておく
- それをJSX内で展開
const products = [
{ title: 'Cabbage', isFruit: false, id: 1 },
{ title: 'Garlic', isFruit: false, id: 2 },
{ title: 'Apple', isFruit: true, id: 3 },
];
export default function ShoppingList() {
const listItems = products.map(product =>
<li
key={product.id}
style={{
color: product.isFruit ? 'magenta' : 'darkgreen'
}}
>
{product.title}
</li>
);
return (
<ul>{listItems}</ul>
);
}
- イベントはそのまま
function MyButton() {
function handleClick() {
alert('You clicked me!');
}
return (
<button onClick={handleClick}>
Click me
</button>
);
}
フック
- use???とかのこと
Reactの流儀
1. UIをコンポーネントの階層に分割する
- 一番わかりやすいのは単一責任の原則にあてはめる1コンポーネント1つの役割
- JSONで送られてくるデータ1つに1つのコンポーネントになるように分割するのも手
{ category: "Fruits", price: "$1", stocked: true, name: "Apple" },
-
FilterableProductTable
SearchBar
-
ProductTable
ProductCategoryRow
ProductRow
2. React で静的なバージョンを作成する
- データモデルからUIをレンダーするだけのバージョンを作成
- 動かない状態から動くようにする方が多くの場合は楽
- コンポーネントからpropsでデータを渡すことに努める
- 単純な例ではトップダウン
FilterableProductTable
から作る方がらく - 大規模な例だとボトムアップ
ProductRow
から始めた方がらく - useXXXXは使わない これはインタラクティブ性を追加するためのもの
3. UIの状態を最小限に表現する
- 動作を加えるにはユーザーがデータモデルに変更を加えるのにする必要がある
- stateと呼ぶ変化するデータの最小セット
- state構造を考える上で大切なのはDRY(Don’t Repeat Yourself; 繰り返しを避ける)
- 必要最小限の表現を見つけ出しそれらを計算して必要なものを作る
- ショッピングカートを例に商品の数は計算して保存するのではなく都度長さをだす
- 時間が経っても変わらないものですか? そうであれば、state ではありません
- 親から props 経由で渡されるものですか? そうであれば、state ではありません
- コンポーネント内にある既存の state や props に基づいて計算可能なデータですか? そうであれば、それは絶対に state ではありません!
- 元となる商品のリスト —- propsで渡されるからstateではない
- ユーザが入力した検索文字列 —- 時間が経つとかわるのでstate
- チェックボックスの値 —- 時間が経つと変わるのでstate
- フィルタ済みの商品のリスト —- 1,2,3から計算できるのでstateではない
4. stateを保持するべき場所を特定する
- stateを保持する責任場所を考える
手順
- そのstateによってレンダーさえるものを全てのコンポーネントを洗い出す
- 階層内でそれら全ての上位に位置する最も近い共通コンポーネントを探す
- 決める
- 多くの場合は2番で探した共通コンポーネント
- さらにその上も可能
- 見つからない場合はstateを保持するためのコンポーネントを作る
5. 逆方向のデータフローを考える
- set関数をつかいましょう
インタラクティビティの追加
state内のオブジェクトの更新
- React のすべての state はイミュータブルとして扱う
- x = 4とかはだめ
- オブジェクトを書き換えるのではなく、代わりに新たなバージョンのオブジェクトを作成して、その新しいバージョンを新しい値として state をセットすることで再レンダーをトリガする。
- Reactが純粋性を重視するのは、ある入力に対する出力が一緒であることが保証されるのであれば出力内容をキャッシュしておくことができるから
state はスナップショットである
- stateはスナップショット、state変数は更新されず再レンダーのトリガーが発生
- イベントハンドラ実行
- setXXXがstate新しい値にセットし再レンダーをよやく
- 新しいstateを使ってコンポーネントを再レンダー
- レンダーするとは関数コンポーネントを呼び出すということ
- これは全てのステートはレンダー前のstateを参照するので+1しか増えない
→レンダーをしてstateが更新されるため
<button onClick={() => {
setNumber(number + 1);
setNumber(number + 1);
setNumber(number + 1);
}}>+3</button>
一連の state の更新をキューに入れる
- +3したい時は関数をキューに入れてあげる (更新用関数)
<button onClick={() => {
setNumber(n => n + 1);
setNumber(n => n + 1);
setNumber(n => n + 1);
}}>+3</button>
state 内の配列の更新
// 配列の後ろ
setArtists([
{ id: nextId++, name: name },
...artists // Put old items at the end
]);
// 配列のまえについか
setArtists( // Replace the state
[ // with a new array
...artists, // that contains all the old items
{ id: nextId++, name: name } // and one new item at the end
]
);
レンダーとコミット
- 画面に表示される前にレンダーされる必要がある
- レンダーのトリガー (注文を受け付け)
- 初回コミット
- その後のコンポーネントのstate変化の再レンダー
- コンポーネントへのレンダー (料理)
- レンダーがトリガされた関数コンポーネント
- 子コンポーネントがあるばあいは再起的に
- DOMへのコミット (配膳)
state を使って入力に反応する
-
コンポーネントの様々な視覚状態を特定する
-
それらの状態変更を引き起こすトリガを決定する
- 人間からの入力やコンピュータによるもの(レスポンスの到着など)
状態遷移図を書くのがおすすめ
- 人間からの入力やコンピュータによるもの(レスポンスの到着など)
-
useState
を使用してメモリ上に state を表現する- 絶対に必要なものから作る、全部作ってから削除するどちらか
-
必要不可欠でない state 変数をすべて削除する
- stateがユーザーに見せたい有効なUIを表現しない状況を防ぐため
const [answer, setAnswer] = useState(''); const [error, setError] = useState(null); const [isEmpty, setIsEmpty] = useState(true); const [isTyping, setIsTyping] = useState(false); const [isSubmitting, setIsSubmitting] = useState(false); const [isSuccess, setIsSuccess] = useState(false); const [isError, setIsError] = useState(false); ↓ const [answer, setAnswer] = useState(''); const [error, setError] = useState(null); const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
- このstateで矛盾が起きないか?:2 つのブール値の組み合わせは 4 通りありますが、有効な状態に対応するのは 3 つだけで。このような「ありえない」state を削除するためには、これらをまとめて、
typing
、submitting
、またはsuccess
の 3 つの値のうちどれかでなければならないstatus
という 1 つの state にすればよい -
同じ情報が別の state 変数から入手できないか?:
isEmpty
を削除して、代わりにanswer.length === 0
でチェックすることができる -
別の state 変数の逆を取って同じ情報を得られないか?:
isError
は不要。なぜなら代わりにerror !== null
をチェックできるから
state 構造の原則
- 関連するstateのグループか:2つ以上同時に更新する場合はそれらを単一のstateにまとめる
- 座標とかまさにそう
- ありえない状態が起きないように:isSendingとisSentのstateを作るとどちらもtrueになる可能性がある(ミスなどで起こるので)
- 計算できるものはstateにしない
- 重複はしないように
- 深くならないように:オブジェクトとか
再レンダリング
useState
memo
- propsによる再レンダリング
- 親コンポーネントの変更の影響を受けない
- 今後肥大化が予想されるコンポーネントにはmemo
- 複数の要素から成り立っている
useCallback
- 処理が変わらない場合set関数を同じものを使いまわすことができる
- test = () ⇒ {} はレンダリングされるたびに新しいものに置き換わる
- 親のレンダリングの時に新しい関数にが再生成される
- 再生成された関数が子コンポーネントにpropsされることでこの再レンダリングがでる
- これは子のレンダリングになるのでmemoが効かない
- 対処:
const XXX = useCallback(() ⇒ {}, [監視するset関数])
useMemo
- 何か複雑な処理結果の変数を固定かしたいときに使う
- レンダリングのたびに値が計算されるのはむだ
- 複雑な処理内で使っている変数に変化があったときにだけ処理結果を更新とかできる
- []は最初だけ
useSyncExternalStore
- UI側から外部ストアに状態を取得(subscribe)をするためのもの
- 状態管理ライブラリ(Redux)、DDDのようにするために使う
- DDDを例にすると、アプリケーション層で集約をあつかってそこに変更加えてもUIのstateを直接変えることはできない
- UI層でイベント→アプリケーションで操作→アプリケーション側から今の状態を返す
// App.tsx
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
<button onClick={() => todosStore.addTodo()}>Add todo</button>
- subscribe:listnerを登録する関数
getSnapshot:名前はなんでも良いsubscribeによって登録される関数 - これはセット
import { useSyncExternalStore } from "react";
let nextId = 0;
let todos = [{ id: 9, text: "Todo #1" }];
let listeners: Array<() => void> = [];
export const todosStore = {
addTodo() {
todos = [...todos, { id: nextId++, text: "Todo #" + nextId }];
emitChange();
},
subscribe(listener: () => void): () => void {
listeners = [...listeners, listener];
return () => {
listeners = listeners.filter((l: () => void) => l !== listener);
};
},
getSnapshot() {
return todos;
},
};
function emitChange() {
listeners.forEach((listener) => listener());
}
動き
- addTodoみたいなUI側で操作のトリガーとなる関数を作る
- トリガーの中でlistnerを呼ぶ関数を呼ぶ > emitChange
- emitChangeのなかでlistnerがよばれる
- 今回の場合listnerはgetSnapshotとしてsubscribeしたのでgetSnapshotが呼ばれる
- UI側で新しい状態が更新される
そのた
- 基本的に1コンポーネント1リスナー
css当て方
インライン
- style={styleの変数}
css Modules
- moduleファイルを作ってそこにまとめる
import classes from './CssModules.module.scss';
export const CssModules = () => {
return (
<div className={classes.container}>
<p></p>
<button>aaa</button>
</div>
);
};
Styled JSX
- jsxの形でcssを埋め込む
- sassの気泡がノーマルで使えないので何か入れる必要あり
<style jsx="true">
{
`
.container {
border: solid 2px #000000;
border-radius: 50px;
padding: 8px;
margin: 8px;
display: flex;
justify-content: space-around;
align-items: center;
}
`
}
</style>
styled components
- styled-componentsをinstall
- 自作のコンポーネント化styleなのかわからなくなる
- とか接頭語つけるといかも
- sassと同じ記法ができる
import styled from "styled-components";
<Container>
<p> aaaa </p>
<Container>
const Container = styled.div`
css
`;
- ベースとなるコンポーネントCSSを作ってそれにうわ付けできる
import { BaseComponent } from "./BaseComponent";
const SConponent = styled(BaseComponent)`
css
`;
ルーティング
基本
import { useState, useCallback } from 'react';
import { BrowserRouter, Link, Routes, Route } from 'react-router';
import './App.css';
import { Page2 } from './Page2';
import { Page1 } from './Page1';
import { Home } from './Home';
function App() {
return (
<>
<BrowserRouter>
<div>
<Link to="/">Home</Link>
<br />
<Link to="/page1">page1</Link>
<br />
<Link to="/page2">page2</Link>
</div>
<Routes>
<Route path={`/`} element={<Home />} />
<Route path={`/page1`} element={<Page1 />} />
<Route path={`/page2`} element={<Page2 />} />
</Routes>
</BrowserRouter>
</>
);
}
export default App;
状態管理
useState
- count状況など状態の管理を行う
- 単一のコンポーネントで完結する場合は単一のコンポーネントで書けばいい
- 複数コンポーネントでStateを共有したい場合はStateを親に持たせる
- useStateで生成された関数をpropsで渡す場合は型が
React.Dispatch<React.SetStateAction<number>>
になる