ホームページ >ウェブフロントエンド >jsチュートリアル >useCustomReducer フック: 多用途の状態管理ツール
React での状態管理は、特に複雑な状態構造やネストされた状態構造を扱う場合には注意が必要です。これを簡素化するために、useCustomReducer フックは useReducer の機能と柔軟な API を組み合わせて、クリーンで宣言的な方法で状態を更新します。このフックはプリミティブ、ネスト、配列の状態をサポートしているため、幅広いユースケースに適しています。
この記事では、React アプリケーションで状態を管理するための useCustomReducer フックとそのコア メソッドについて説明します。フックの定義、そのメソッド シグネチャ、さまざまなタイプの状態構造の詳細な使用例について説明します。最後には、useCustomReducer フックを使用して React コンポーネントの複雑な状態を処理する方法をしっかりと理解できるようになります。
useCustomReducer フックは、複雑な状態構造を管理するためのシンプルかつ柔軟な方法を提供するカスタム React フックです。 useReducer の利点と、状態値を更新するためのクリーンな API が組み合わされています。このフックは、プリミティブ値、オブジェクト、配列、ネストされたデータ構造など、さまざまなタイプの状態を処理するように設計されています。
useCustomReducer フックの概要は次のとおりです。
コアメソッド:
状態構造: - プリミティブ値 (数値、文字列、ブール値など) をサポートします。 - オブジェクトベースの状態構造 (フォーム データ、ユーザー プロファイルなど) を処理します。 - 配列ベースの状態構造 (リスト、コレクションなど) を管理します。
タイプセーフ: - 信頼性の高い開発とエラー防止のため、TypeScript を使用して完全に型付けされます。
シンプル API: - 状態値を更新、リセット、マージするための直感的なメソッドを提供します。 - 動的状態変更のための直接更新とコールバック関数をサポートします。
import { useReducer, useCallback, useMemo } from "react"; type Primitive = boolean | string | number | Date | null | undefined; type NestedObject = { [key: string]: Primitive | NestedObject | NestedArray }; type NestedArray = Array<Primitive | NestedObject>; type State = Primitive | NestedObject | NestedArray; type Action<T> = | { type: "SET"; payload: Partial<T> | ((prevState: T) => Partial<T>) } | { type: "RESET"; payload?: T } | { type: "MERGE"; payload: Partial<T> }; function useCustomReducer<T extends State>(initialState: T) { const reducer = useCallback( (state: T, action: Action<T>): T => { switch (action.type) { case "SET": const newPayload = typeof action.payload === "function" ? action.payload(state) : action.payload; if (newPayload instanceof Date) { return newPayload as T; } if ( typeof state === "object" && !Array.isArray(state) && state !== null ) { return { ...state, ...newPayload }; } return newPayload as T; case "RESET": return action.payload ?? initialState; case "MERGE": if ( typeof state === "object" && !Array.isArray(state) && state !== null ) { return { ...state, ...action.payload }; } return action.payload as T; default: throw new Error("Invalid action type"); } }, [initialState] ); const [state, dispatch] = useReducer(reducer, initialState); const set = useCallback( (payload: Partial<T> | ((prevState: T) => Partial<T>)) => dispatch({ type: "SET", payload }), [] ); const reset = useCallback( (payload?: T) => dispatch({ type: "RESET", payload }), [] ); const merge = useCallback( (payload: Partial<T>) => dispatch({ type: "MERGE", payload }), [] ); const memoizedState = useMemo(() => state, [state]); return [memoizedState, { set, reset, merge }] as const; } export default useCustomReducer;
useCustomReducer フックは、React の useReducer フックを使用して実装されます。これは、状態値を更新、リセット、またはマージするさまざまなタイプのアクションを処理するカスタム リデューサー関数を定義します。フックには、状態と対話するための 3 つのコア メソッド set、reset、merge が用意されています。 set メソッドは、新しい状態値を持つオブジェクト、または次の状態を計算するコールバック関数のいずれかを受け入れることができます。リセット メソッドは状態を初期値に戻しますが、マージ メソッドは部分的な更新を既存の状態にマージします。
これは、React コンポーネントで useCustomReducer フックを使用して単純なカウンター状態を管理する例です。
import useCustomReducer from "./use-custom-reducer"; import { faker } from "@faker-js/faker"; import { Button } from "@/components/ui/button"; export default function Use() { const [formValues, { set, reset, merge }] = useCustomReducer({ name: faker.person.firstName(), age: faker.number.int({ min: 18, max: 99 }), address: { street: faker.location.streetAddress(), city: faker.location.city(), state: faker.location.state(), zip: faker.location.zipCode(), }, hobbies: [faker.person.bio(), faker.person.bio(), faker.person.bio()], }); const [bool, { set: setBool }] = useCustomReducer<boolean>( faker.datatype.boolean() ); const [num, { set: setNum }] = useCustomReducer(faker.number.int()); const [str, { set: setStr }] = useCustomReducer<string>(faker.lorem.word()); const [date, { set: setDate }] = useCustomReducer(faker.date.recent()); const [nil, { set: setNil }] = useCustomReducer(null); const [undef, { set: setUndef }] = useCustomReducer(undefined); const [arr, { set: setArr }] = useCustomReducer([ faker.number.int(), faker.number.int(), faker.number.int(), ]); const [nestedArr, { set: setNestedArr }] = useCustomReducer([ faker.number.int(), faker.lorem.word(), { three: faker.number.float() }, ]); const [obj, { set: setObj }] = useCustomReducer({ a: faker.number.int(), b: faker.number.int(), c: faker.number.int(), }); const [nestedObj, { set: setNestedObj }] = useCustomReducer({ a: faker.number.int(), b: faker.lorem.word(), c: { three: faker.number.float() }, }); return ( <div className="p-4 space-y-6"> <h1 className="text-2xl font-bold">Use</h1> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Form Values</h2> <p className="text-gray-500">{JSON.stringify(formValues)}</p> <Button onClick={() => set({ name: faker.person.firstName() })}> Set Name </Button> <Button onClick={() => set((prevState) => ({ age: prevState.age - 1 }))} > Decrement Age </Button> <Button onClick={() => set((prevState) => ({ age: prevState.age + 1 }))} > Increment Age </Button> <Button onClick={() => set((prevState) => ({ address: { ...prevState.address, street: faker.location.streetAddress(), }, })) } > Set Street </Button> <Button onClick={() => reset()}>Reset</Button> <Button onClick={() => merge({ age: faker.number.int({ min: 18, max: 99 }) })} > Merge </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Boolean Value</h2> <p className="text-gray-500">{bool.toString()}</p> <Button onClick={() => setBool(faker.datatype.boolean())}> Set Bool </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Number Value</h2> <p className="text-gray-500">{num.toString()}</p> <Button onClick={() => setNum(faker.number.int())}>Set Num</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">String Value</h2> <p className="text-gray-500">{str}</p> <Button onClick={() => setStr(faker.lorem.word())}>Set Str</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Date Value</h2> <p className="text-gray-500">{JSON.stringify(date)}</p> <Button onClick={() => setDate(faker.date.recent())}>Set Date</Button> <Button onClick={() => setDate(new Date("2022-01-01"))}> Set Date to 2022 </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nil and Undefined</h2> <p className="text-gray-500">{String(nil)}</p> <Button onClick={() => setNil(null)}>Set Nil</Button> <p className="text-gray-500">{String(undef)}</p> <Button onClick={() => setUndef(undefined)}>Set Undef</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Array Value</h2> <p className="text-gray-500">{arr.toString()}</p> <Button onClick={() => setArr([faker.number.int(), faker.number.int(), faker.number.int()]) } > Set Arr </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nested Array</h2> <p className="text-gray-500">{JSON.stringify(nestedArr)}</p> <Button onClick={() => setNestedArr([ faker.number.int(), faker.lorem.word(), { three: faker.number.float() }, ]) } > Set Nested Arr </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Object Value</h2> <p className="text-gray-500">{JSON.stringify(obj)}</p> <Button onClick={() => setObj({ a: faker.number.int(), b: faker.number.int(), c: faker.number.int(), }) } > Set Obj </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nested Object</h2> <p className="text-gray-500">{JSON.stringify(nestedObj)}</p> <Button onClick={() => setNestedObj({ a: faker.number.int(), b: faker.lorem.word(), c: { three: faker.number.float() }, }) } > Set Nested Obj </Button> </div> </div> ); }
多様な状態構造をサポート: プリミティブ、オブジェクト、配列、およびネストされたデータ構造を処理します。
シンプルな API:
タイプセーフ: 信頼性の高い開発のために TypeScript を使用して完全に型付けされています。
useCustomReducer フックは、複雑な状態を管理するためのカスタム React フックです。これは、プリミティブ、ネスト、および配列ベースの状態構造を処理するための 3 つのコア メソッド set、reset、および merge を提供します。フックとそのメソッドの内訳は次のとおりです:
function useCustomReducer<T extends State>( initialState: T ): [ T, { set: (payload: Partial<T> | ((prevState: T) => Partial<T>)) => void; reset: (payload?: T) => void; merge: (payload: Partial<T>) => void; } ];
const [state, { set }] = useCustomReducer({ count: 0 }); set((prevState) => ({ count: prevState.count + 1 }));
reset(); // Resets to initial state. reset({ name: "John", age: 25 }); // Resets to a new state.
merge({ city: "New York" }); // Adds or updates the 'city' field.
useCustomReducer フックは多用途であり、さまざまなタイプの状態構造の管理に使用できます。以下に、さまざまなタイプの状態での使用法を示すいくつかの例を示します。
const initialState = 0; const [count, { set, reset }] = useCustomReducer(initialState);
使用法:
set((prevState) => prevState + 1);
reset();
set(10);
文字列:
const initialState = "Hello, World!"; const [message, { set, reset }] = useCustomReducer(initialState);
使用法:
import { useReducer, useCallback, useMemo } from "react"; type Primitive = boolean | string | number | Date | null | undefined; type NestedObject = { [key: string]: Primitive | NestedObject | NestedArray }; type NestedArray = Array<Primitive | NestedObject>; type State = Primitive | NestedObject | NestedArray; type Action<T> = | { type: "SET"; payload: Partial<T> | ((prevState: T) => Partial<T>) } | { type: "RESET"; payload?: T } | { type: "MERGE"; payload: Partial<T> }; function useCustomReducer<T extends State>(initialState: T) { const reducer = useCallback( (state: T, action: Action<T>): T => { switch (action.type) { case "SET": const newPayload = typeof action.payload === "function" ? action.payload(state) : action.payload; if (newPayload instanceof Date) { return newPayload as T; } if ( typeof state === "object" && !Array.isArray(state) && state !== null ) { return { ...state, ...newPayload }; } return newPayload as T; case "RESET": return action.payload ?? initialState; case "MERGE": if ( typeof state === "object" && !Array.isArray(state) && state !== null ) { return { ...state, ...action.payload }; } return action.payload as T; default: throw new Error("Invalid action type"); } }, [initialState] ); const [state, dispatch] = useReducer(reducer, initialState); const set = useCallback( (payload: Partial<T> | ((prevState: T) => Partial<T>)) => dispatch({ type: "SET", payload }), [] ); const reset = useCallback( (payload?: T) => dispatch({ type: "RESET", payload }), [] ); const merge = useCallback( (payload: Partial<T>) => dispatch({ type: "MERGE", payload }), [] ); const memoizedState = useMemo(() => state, [state]); return [memoizedState, { set, reset, merge }] as const; } export default useCustomReducer;
import useCustomReducer from "./use-custom-reducer"; import { faker } from "@faker-js/faker"; import { Button } from "@/components/ui/button"; export default function Use() { const [formValues, { set, reset, merge }] = useCustomReducer({ name: faker.person.firstName(), age: faker.number.int({ min: 18, max: 99 }), address: { street: faker.location.streetAddress(), city: faker.location.city(), state: faker.location.state(), zip: faker.location.zipCode(), }, hobbies: [faker.person.bio(), faker.person.bio(), faker.person.bio()], }); const [bool, { set: setBool }] = useCustomReducer<boolean>( faker.datatype.boolean() ); const [num, { set: setNum }] = useCustomReducer(faker.number.int()); const [str, { set: setStr }] = useCustomReducer<string>(faker.lorem.word()); const [date, { set: setDate }] = useCustomReducer(faker.date.recent()); const [nil, { set: setNil }] = useCustomReducer(null); const [undef, { set: setUndef }] = useCustomReducer(undefined); const [arr, { set: setArr }] = useCustomReducer([ faker.number.int(), faker.number.int(), faker.number.int(), ]); const [nestedArr, { set: setNestedArr }] = useCustomReducer([ faker.number.int(), faker.lorem.word(), { three: faker.number.float() }, ]); const [obj, { set: setObj }] = useCustomReducer({ a: faker.number.int(), b: faker.number.int(), c: faker.number.int(), }); const [nestedObj, { set: setNestedObj }] = useCustomReducer({ a: faker.number.int(), b: faker.lorem.word(), c: { three: faker.number.float() }, }); return ( <div className="p-4 space-y-6"> <h1 className="text-2xl font-bold">Use</h1> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Form Values</h2> <p className="text-gray-500">{JSON.stringify(formValues)}</p> <Button onClick={() => set({ name: faker.person.firstName() })}> Set Name </Button> <Button onClick={() => set((prevState) => ({ age: prevState.age - 1 }))} > Decrement Age </Button> <Button onClick={() => set((prevState) => ({ age: prevState.age + 1 }))} > Increment Age </Button> <Button onClick={() => set((prevState) => ({ address: { ...prevState.address, street: faker.location.streetAddress(), }, })) } > Set Street </Button> <Button onClick={() => reset()}>Reset</Button> <Button onClick={() => merge({ age: faker.number.int({ min: 18, max: 99 }) })} > Merge </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Boolean Value</h2> <p className="text-gray-500">{bool.toString()}</p> <Button onClick={() => setBool(faker.datatype.boolean())}> Set Bool </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Number Value</h2> <p className="text-gray-500">{num.toString()}</p> <Button onClick={() => setNum(faker.number.int())}>Set Num</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">String Value</h2> <p className="text-gray-500">{str}</p> <Button onClick={() => setStr(faker.lorem.word())}>Set Str</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Date Value</h2> <p className="text-gray-500">{JSON.stringify(date)}</p> <Button onClick={() => setDate(faker.date.recent())}>Set Date</Button> <Button onClick={() => setDate(new Date("2022-01-01"))}> Set Date to 2022 </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nil and Undefined</h2> <p className="text-gray-500">{String(nil)}</p> <Button onClick={() => setNil(null)}>Set Nil</Button> <p className="text-gray-500">{String(undef)}</p> <Button onClick={() => setUndef(undefined)}>Set Undef</Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Array Value</h2> <p className="text-gray-500">{arr.toString()}</p> <Button onClick={() => setArr([faker.number.int(), faker.number.int(), faker.number.int()]) } > Set Arr </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nested Array</h2> <p className="text-gray-500">{JSON.stringify(nestedArr)}</p> <Button onClick={() => setNestedArr([ faker.number.int(), faker.lorem.word(), { three: faker.number.float() }, ]) } > Set Nested Arr </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Object Value</h2> <p className="text-gray-500">{JSON.stringify(obj)}</p> <Button onClick={() => setObj({ a: faker.number.int(), b: faker.number.int(), c: faker.number.int(), }) } > Set Obj </Button> </div> <hr className="border-t border-gray-300" /> <div className="space-x-2 space-y-2"> <h2 className="text-lg font-semibold">Nested Object</h2> <p className="text-gray-500">{JSON.stringify(nestedObj)}</p> <Button onClick={() => setNestedObj({ a: faker.number.int(), b: faker.lorem.word(), c: { three: faker.number.float() }, }) } > Set Nested Obj </Button> </div> </div> ); }
ブール値:
function useCustomReducer<T extends State>( initialState: T ): [ T, { set: (payload: Partial<T> | ((prevState: T) => Partial<T>)) => void; reset: (payload?: T) => void; merge: (payload: Partial<T>) => void; } ];
使用法:
const [state, { set }] = useCustomReducer({ count: 0 }); set((prevState) => ({ count: prevState.count + 1 }));
reset(); // Resets to initial state. reset({ name: "John", age: 25 }); // Resets to a new state.
merge({ city: "New York" }); // Adds or updates the 'city' field.
日付:
const initialState = 0; const [count, { set, reset }] = useCustomReducer(initialState);
使用法:
set((prevState) => prevState + 1);
reset();
set(10);
NULL および未定義の状態:
const initialState = "Hello, World!"; const [message, { set, reset }] = useCustomReducer(initialState);
使用法:
set("Hello, React!");
reset();
const initialState = false; const [isToggled, { set, reset }] = useCustomReducer(initialState);
set((prevState) => !prevState);
使用法:
reset();
set(true);
const initialState = new Date(); const [date, { set, reset }] = useCustomReducer(initialState);
set(new Date("2022-01-01"));
reset();
set(new Date("2023-01-01"));
const initialState: string | null = null; const initialState: string | undefined = undefined; const [value, { set, reset }] = useCustomReducer(initialState); // Implicitly infer the type. const [value, { set, reset }] = useCustomReducer<string | undefined>( initialState ); // Explicitly define the type.
使用法:
set("New Value");
reset();
set("New Value");
const initialState = { name: "John Doe", age: 30, address: { street: "123 Main St", city: "Sample City", state: "CA", }, }; const [formData, { set, reset, merge }] = useCustomReducer(initialState);
set({ name: "Jane Doe" });
ネストされた配列の初期状態:
set((prevState) => ({ address: { ...prevState.address, city: "New City", }, }));
使用法:
set({ name: "Jane Doe" });
merge({ address: { city: "New York" } });
merge({ phone: "123-456-7890" });
reset();
const initialState = [1, 2, 3, 4, 5]; const [numbers, { set, reset, merge }] = useCustomReducer(initialState);
set((prevState) => [...prevState, 6]);
使用法:
set((prevState) => prevState.filter((item) => item !== 3));
reset();
set([10, 20, 30]);
merge([6, 7, 8]);
柔軟な状態管理:
単純な API:
クリーナーコード:
タイプセーフ:
動的更新:
useCustomReducer フックは、React アプリケーションの複雑な状態構造を管理するための強力なツールです。 useReducer の柔軟性と状態を更新するためのシンプルな API を組み合わせることで、このフックは状態管理を簡素化し、定型コードを削減します。プリミティブ値、ネストされたオブジェクト、または配列を扱うかどうかに関係なく、useCustomReducer フックは状態の変更を処理するクリーンで宣言的な方法を提供します。次のプロジェクトで試してみて、多用途な状態管理の利点を簡単に体験してください。
以上がuseCustomReducer フック: 多用途の状態管理ツールの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。