Hooksの役割:
関数コンポーネントに状態管理とライフサイクルメソッドを導入するために使用され、高階コンポーネントやレンダープロップを置き換えて抽象化と再利用性を実現します。
useCallbackとuseMemoの違い:
useMemoとuseCallbackが受け取る引数は同じで、最初の引数はコールバック、2番目の引数は依存するデータです。
共通の作用: 依存データが変化したときのみ再計算され、キャッシュの役割を果たします。
useCallbackとuseMemoは、関数の参照や値をキャッシュできますが、使用の観点から見ると、useCallbackは関数の参照をキャッシュし、useMemoは計算データの値をキャッシュします。
両者の違い:
useMemoは計算結果として返される値で、主に計算結果の値をキャッシュするために使用されます。適用シーンとしては、計算が必要な状態などがあります。
useCallbackは計算結果が関数で、主に関数をキャッシュするために使用されます。適用シーンとしては、キャッシュが必要な関数です。関数型コンポーネントでは、任意のステートが変化するたびに全体が再描画されるため、一部の関数は再描画する必要がなく、その場合にはキャッシュして性能を向上させ、リソースの浪費を減らすべきです。
注意:乱用しないこと。性能の浪費を引き起こす可能性があります。Reactではレンダリングを減らすことで性能が向上するため、これはあくまでキャッシュによって重複描画を減らす場合や計算結果をキャッシュする場合にのみ使用すべきです。
コード理解:
// memoizedCallbackの参照は変わらず、useCallbackの最初の引数の関数はキャッシュされ、レンダリング性能の最適化を達成します。
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
// memoizedValueの値は変わらず、useMemo関数の最初の引数の関数は実行されず、計算量を節約する目的を達成します。
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Hooksのクロージャ:
useEffect、useMemo、useCallbackはすべてクロージャを持っています。コンポーネントがレンダリングされるたびに、それらは現在のコンポーネント関数のコンテキスト内の状態(state、props)をキャッチします。そのため、これら3つのhooksの実行は常に現在の状態を反映しており、前回の状態をキャッチすることはできません。
useEffectとuseLayoutEffectの違い:
useEffect内の操作はDOMを処理する必要があり、ページのスタイルを変更する場合にはこれを使用する必要があります。そうしないと、フラッシュ問題が発生する可能性があります。useLayoutEffect内のコールバック関数はDOMの更新が完了した後にすぐに実行されますが、ブラウザが描画を行う前に完了します。これにより、ブラウザの描画をブロックします。
useEffectは非同期で実行され、useLayoutEffectは同期で実行されます。
両者の実行メカニズム:
useLayoutEffectはクラスコンポーネントのcomponentDidMountとcomponentDidUpdateが同時に実行されるようなものです。
useEffectは今回の更新が完了した後、つまり最初の方法が実行された後にタスクスケジューリングを開始し、次回のタスクスケジューリングでuseEffectを実行します。
useRefについて: 関数コンポーネントは毎回レンダリングされるたびに実行され、関数内部のローカル変数は一般的に再作成されます。useRefを利用することで、前回のレンダリングの変数にアクセスでき、クラスコンポーネントのインスタンス変数のような効果を得ることができます。
使用法:
1.毎回レンダリングされてもuseRefの返り値は変わりません。
2.ref.currentが変化してもre-renderを引き起こしません。
3.ref.currentが変化する場合はSide Effectとして扱うべきです(次回のレンダリングに影響を与えるため)、そのためrenderフェーズでcurrent属性を更新するべきではありません。
4.render内でref.currentの値を更新することはできません。非同期レンダリングでは、renderフェーズが複数回実行される可能性があります。
5.render内でref.currentの値を更新することは可能ですが、毎回のrenderが予期しない効果を引き起こさないことを保証する必要があります。そのため、renderフェーズでref.currentを更新できますが、あまり推奨されず、問題を引き起こしやすいです。結局、useRefは遅延初期化の特別な例です。
6.ref.currentは他のhooks(useMemo、useCallback、useEffect)の依存項目として使用できません。
7.refは他のhooks(useMemo、useCallback、useEffect)の依存項目として使用できます。
コード理解:
// クロージャを利用してisMountedをキャッシュし、最初に状態をtrueに変更し、その後effect関数を実行します。
export const useUpdateEffect = (effect: EffectCallback, deps: DependencyList | undefined) => {
const isMounted = useRef(false);
// react-refresh用
useEffect(() => {
return () => {
isMounted.current = false;
};
}, []);
useEffect(() => {
if (!isMounted.current) {
isMounted.current = true;
} else {
return effect();
}
}, deps);
}
制御コンポーネントと非制御コンポーネント:
制御コンポーネント:状態(state)とUI表示は相互に関連しており、ユーザーの入力が状態に影響を与え、状態の変化がUIの表示にも影響を与えます。制御コンポーネントは、Reactを通じてフォーム要素の状態を管理します。
非制御コンポーネント:状態はUI表示とは関連しておらず、ユーザーの入力が状態に影響を与えず、状態を通じてUIを更新することもできません。非制御コンポーネントは、DOM APIを使用してフォーム要素の状態を管理します。
ログインロジック:
トークンがあるかどうかを判断し、あれば該当する画面に遷移し、なければログイン画面に遷移します。実装方法:トークンを判断するコンポーネントを書き、他のコンポーネントを包みます。例:
判断コンポーネントのコード:
import React from 'react'
import { Navigate, useLocation } from "react-router-dom";
interface Props {
children ?: any
}
export default function Auth({children}: Props) {
const userToken = sessionStorage.getItem("user-token")
const location = useLocation()
if(location.pathname === "/login") {
if(userToken) {
return <Navigate to="/" state={{from: location}} replace/> // replace属性がtrueに設定されている場合、ナビゲーションは新しい履歴エントリを追加するのではなく、現在の履歴エントリを置き換えます
} else {
return <Navigate to="/login" state={{from: location}} replace/>
}
}
}
ルーティングコンポーネント:
import { createHashRouter } from "react-router-dom";
import type { RouteObject } from "react-router-dom";
import React, { lazy, Suspense } from "react";
import Auth from "./Auth";
import LayoutView from "./views/Layout";
// lazy: アプリケーションの初期読み込み時間を短縮するのに役立ちます。lazy関数を使用する際は、通常Suspenseコンポーネントと組み合わせて使用します。Suspenseコンポーネントは、動的に読み込まれるコンポーネントの読み込みインジケーターを表示したり、コンポーネントが読み込まれる前にユーザーに何かを表示するための他のインタラクションを提供します。一般的にはappコンポーネントに書かれます。例:
}>
const LoginView = lazy(() => import("./views/Login"))
const HomeView = lazy(() => import("./views/Home"))
const AboutView = lazy(() => import("./views/About"))
const routes: RouteObject[] = [{
path: "/login",
element:
},{
path: "/",
element: ,
children: [{
index: true,
element:
}, {
path: "/about",
element:
}]
}]
ログインコンポーネント:
import React, { FC, useState } from "react";
import "../Login.css"
import { Button, Form, Input, Space, Row, Col } from 'antd';
import { postLogin } from "../api";
import { User, LoginRes } from "../types";
import { useNavigate } from "react-router-dom";
const loginForm: User = {
username: "",
password: "",
city: 0
}
const LoginView: FC = () => {
const nav = useNavigate()
const toHome = () => {
nav("/")
}
const [loginFormState, setLoginFormState] = useState(loginForm)
const [form] = Form.useForm()
// ログイン情報を送信
const onSubmit = () => {
const formFieldsValue = form.getFieldsValue()
// ログインAPIを呼び出す
postLogin(formFieldsValue).then(res => {
const { data } = res
if(data) {
sessionStorage.setItem("user-token", data.userToken)
setLoginFormState(formFieldsValue)
localStorage.setItem("user", JSON.stringify(formFieldsValue))
toHome()
}
})
}
return (
<Row className='wrapper' align={"middle"}>
<Col xs={{span:22, offset: 1}} sm={{ span: 16, offset: 4 }} md={{ span: 12, offset: 6 }} lg={{ span: 8, offset: 8 }} xl={{ span: 6, offset: 9 }}>
<Form form={form} initialValues={loginFormState} onFinish={onSubmit}>
<Form.Item label='ユーザー名' name="username" rules={[{required: true}]}>
<Input placeholder="ユーザー名を入力してください"/>
</Form.Item>
<Form.Item label='パスワード' name="password" rules={[{required: true}]}>
<Input.Password autoComplete="false" placeholder="パスワードを入力してください"/>
</Form.Item>
<Form.Item wrapperCol={{ offset: 5 }}>
<Space>
<Button type='primary' htmlType={"submit"}>ログイン</Button>
</Space>
</Form.Item>
</Form>
</Col>
</Row>
)
}
export default LoginView;
mockを使用してログインインターフェースを模擬する:
import axios from "axios";
import MockAdapter from "axios-mock-adapter"
import { fakerJA } from "@faker-js/faker";
import cityList from "./mock";
axios.defaults.baseURL = "/api"
axios.defaults.timeout = 5000
const mock = new MockAdapter(axios)
mock.onPost("/api/login").reply(200, {
data: {
userToken: fakerJA.string.uuid()
},
status: 200,
statusText: "success",
headers: [],
config: {}
})
mock.onGet("/api/citys").reply(200, {
data: cityList,
statusText: "success",
headers: [],
config: {}
})
const http = axios.create()
http.interceptors.request.use(
config => config,
error => Promise.reject(erro)
)
http.interceptors.request.use(
reponse => reponse.data,
error => Promise.reject(erro)
)
export default http