実装Memo(主にReact)

React で配列を更新するときは mutation なしで新しい配列を返す方法が望ましい

reverse() や sort() は元の配列を変更してしまうため、まず配列を複製してから変更を加える。
const [list, setList] = useState(initialList);
function handleClick() {
{/* spread 構文で元の配列を複製する */}
const nextList = [...list];
{/* 複製した配列に対してreverse() を実行する */}
nextList.reverse();
setList(nextList);
}
{/* 🙅♀️ */}
list.reverse();
setList(list);

JavaScriptで重複排除を自分で実装してはいけない(Setを使う)
const array1 = [1, 5, 3, 1, 5, 3];
const array2 = Array.from(new Set(array1));
console.log(array2); // [ 1, 5, 3 ]
🙅♀️ filter と indexOf を使う

popstate
event
browser back button の押下時に処理を実行できる
useEffect(() => {
const handlePopstate = () => {
// 実行したい処理
};
window.addEventListener("popstate", handlePopstate);
return () => {
window.removeEventListener("popstate", handlePopstate);
}
}, [])

ResizeObserver
- Web ページ上の要素のサイズの変化を監視できる
- ビューポートのサイズ、レイアウト、またはその他の要因の変更により要素のサイズが変化するたびに監視する
- 変化に応じてコールバック関数を実行できる
// callback => 監視対象の要素のサイズが変化するたびに呼び出される関数
const observer = new ResizeObserver(callback);
observer.observe(targetElement);

useEffect clean up
In development, your Effect will call addEventListener(), then immediately removeEventListener(), and then addEventListener() again with the same handler.
addEventListener と removeEventListener の関数の参照が異なると適切に処理されない。
useEffect(() => {
function handleScroll(e) {
console.log(window.scrollX, window.scrollY);
}
window.addEventListener('scroll', handleScroll);
return () => window.removeEventListener('scroll', handleScroll);
}, []);

stateの更新が非同期
const errors = validate(inputValues);
setErrors(errors);
if (errors.length > 0) return;
🙅♀️
setErrors(validate(inputValues));
if (errors.length > 0) return;
ここで setErrors を呼んでも、次の行での error はまだ更新されていない。
そのため error の値は「前のレンダーの値」のままになっていて、意図通りの判定ができない。

useActionState
a Hook that allows you to update state based on the result of a form action
フォームアクションの結果に基づいて状態を更新できるフック
フォーム送信時に「処理結果」と「処理中状態(pending)」をまとめて管理できる。
const [state, formAction, isPending] = useActionState(fn, initialState, permalink?);

form
非同期関数を action に直接渡すと自動的に呼ばれる
<form action={search}>
<input name="query" />
<button type="submit">Search</button>
</form>

search 遅延検索
ユーザーの入力が速すぎてキー入力のたびにリクエストを送信
=> throttle や debounce などのスローダウンを行う(遅延検索)
ユーザーが入力を止めてから1秒後にAPI呼び出しを行うことでサーバーの負荷を軽減
loadash ライブラリ debounce の例
npm i --save lodash
import { debounce } from "lodash";
...
const handleOnChange = useCallback((e) => { console.log(e.target.value) }, []);
const debouncedHandleOnChange = debounce(handleOnChange, 100);
<input onChange={(e) => debouncedHandleOnChange(e)} />
useCallback
ここでの コンポーネントが再レンダリングされるたびに
-
handleOnChange
が新しく作られる -
debounce(handleOnChange, 100)
も新しく作られる - これまでの debounce のタイマーがリセットされる
debounce のカウントが正しく機能しなくなる可能性があるため、 useCallback
でwrapすることにより同じ関数を再利用できるようになる。

useEffect の依存配列が boolean で特定の値の場合のみ処理をしたい場合
useEffect(() => {
if (isEdit === false) {
something();
}
}, [isEdit]);
true -> false
の場合にのみ再レンダリングされれば良い
false -> false
true -> true
のときにもレンダリングされてしまう
const prevIsEdit = useRef<boolean>(isEdit);
useEffect(() => {
if (prevIsEdit.current === true && isEdit === false) {
something();
}
prevIsEdit.current = isEdit;
}, [isEdit]);

debounce をコンポーネントの最上位で使用しない
問題点
- レンダリングの度にタイマーがリセットされる
- クリーンナップされない(直前のタイマーが再レンダリングされた際に残る)
- メモリーリーク
const debounceHandleOnChange = useMemo(() => {
return debounce(handleOnChange, 300);
}, [handleOnChange]);
// clean up debounce
useEffect(() => {
return () => {
debounceHandleOnChange.cancel();
};
}, [debounceHandleOnChange]);
<input onChange={(e) => debounceHandleOnChange(e.target.value)} />

custom file upload button
const fileInputRef = useRef(null);
const handleClick = event => {
fileInputRef.current.click();
};
const handleChange = (event) => {
const fileUploaded = event.target.files[0];
handleFile(fileUploaded);
};
return (
<>
<button onClick={handleClick}>
Upload a file
</button>
<input
type="file"
onChange={handleChange}
ref={fileInputRef}
style={{display: 'none'}} // Make the file input element invisible
/>
</>
);

file upload button with size limit
const handleOnChange = (event) => {
if (!event.target.files) return false;
const file = e.target.files[0];
// max 2MB
if (file.size > 2 * 1000 * 1024) {
// error handling
e.target.value = "";
return false;
}
// do something with file
};
<input
type="file"
accept="image/*"
onChange={handleOnChange}
/>

SSR と SSG
SSG(Static Site Generation) 静的
ビルド時にHTMLを生成(サーバーでHTMLに変換されてブラウザに届く)
npm run build実行時:
page.html を生成
→ Cloudflare Pagesにアップロード
→ ユーザーアクセス時: 既に完成したHTMLを即座に返す
例)ユーザープロフィール表示ページ, ブログ記事表示ページ
SSR(Server-Side Rendering) 動的
リクエスト時にHTMLを生成(ブラウザでJavaScriptが実行される)
ユーザーアクセス時:
リクエスト受信 → データベースからデータ取得 → HTMLを動的生成 → 返す
例)ユーザープロフィール編集ページ, ブログ記事編集ページ