import React, { useState, Dispatch, SetStateAction } from "react";
// ユーティリティ関数と型定義
export type State<T> = [T, Dispatch<SetStateAction<T>>];
export type StateObj<T> = { get: T; set: Dispatch<SetStateAction<T>> };
export type StateArgs<T> = State<T> | StateObj<T>;
export const State = (() => {
return {
from: <T,>(args: StateArgs<T>): State<T> => {
if (Array.isArray(args)) return args;
return [args.get, args.set];
},
};
})();
const CounterComponent: React.FC = () => {
const countState = useState<number>(0);
const [countObj, setCountObj] = useState<number>(10);
// ユーティリティ関数で状態を取得
const [count, setCount] = State.from(countState); // タプル形式
const [countFromObj, setCountFromObj] = State.from({
get: countObj,
set: setCountObj,
}); // オブジェクト形式を useState に変換
return (
<div>
<div>タプル形式のカウンター</div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
<div>オブジェクト形式のカウンター</div>
<p>Count from Object: {countFromObj}</p>
<button onClick={() => setCountFromObj(countFromObj + 1)}>
Increment Count (Object)
</button>
</div>
);
};
export default CounterComponent;
export const State = (() => {...}
の部分について StateArgs<T>
でタプル形式・オブジェクト形式のどちらの場合にも、戻り値の形式をタプルで統一するために使う。// src\context\counterContext.ts
import { createContext, Dispatch, SetStateAction, useContext } from "react";
type CounterContextProps = {
count: number;
setCount: (
Dispatch<SetStateAction<number>>
);
countFromObj:number;
setCountFromObj: (
Dispatch<SetStateAction<number>>
);
};
export const CounterContext = createContext<CounterContextProps>({
count: 0,
setCount: () => {},
countFromObj:10,
setCountFromObj: () => {},
});
export const useCounterContext = () => useContext(CounterContext);
type SetStateAction<S> = S | ((prevState: S) => S);
SetStateAction
は、値の更新をするtype Dispatch<A> = (value: A) => void;
Dispatch
は関数の更新をするSetStateAction
では値や型のみの更新で状態を更新する機能は有していない。Dispatch
でsetStateAction
の関数の状態を更新する。// src\provider\counterProvider.tsx
import { Dispatch, ReactNode, SetStateAction, useState } from "react";
import { CounterContext } from "../context/counterContext";
// ユーティリティ関数と型定義
export type State<T> = [T, Dispatch<SetStateAction<T>>];
export type StateObj<T> = { get: T; set: Dispatch<SetStateAction<T>> };
export type StateArgs<T> = State<T> | StateObj<T>;
export const State = (() => {
return {
from: <T,>(args: StateArgs<T>): State<T> => {
if (Array.isArray(args)) return args;
return [args.get, args.set];
},
};
})();
export const CounterProvider = ({ children }: { children: ReactNode }) => {
const countState = useState<number>(0);
const [countObj, setCountObj] = useState<number>(10);
// ユーティリティ関数で状態を取得
const [count, setCount] = State.from(countState); // タプル形式
const [countFromObj, setCountFromObj] = State.from({
get: countObj,
set: setCountObj,
}); // オブジェクト形式を useState に変換
return (
<CounterContext.Provider value={{
count: count,
setCount,
countFromObj:countFromObj,
setCountFromObj,
}}
>
{children}
</CounterContext.Provider>
);
};
// src\components\Counter.tsx
import React from "react";
import { useCounterContext } from "../context/counterContext";
const Counter: React.FC = () => {
const { count, setCount, countFromObj, setCountFromObj } = useCounterContext();
return (
<div>
{/* タプル形式の状態管理 */}
<div>タプル形式のカウンター</div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>Increment Count</button>
{/* オブジェクト形式の状態管理 */}
<div>オブジェクト形式のカウンター</div>
<p>Count from Object: {countFromObj}</p>
<button onClick={() => setCountFromObj(countFromObj + 1)}>
Increment Count (Object)
</button>
</div>
);
};
export default Counter;
// src\App.tsx
import './App.css'
import { CounterProvider } from './provider/counterProvider'
import Counter from './components/Counter'
function App() {
return (
<>
<CounterProvider>
<Counter />
</CounterProvider>
</>
)
}
export default App
src\provider\counterProvider.tsx
で使っているユーティリティ関数State
を編集する。// src\types\State.ts
import { Dispatch, SetStateAction } from "react";
export type State<T> = [T, Dispatch<SetStateAction<T>>];
export type StateObj<T> = { get: T; set: Dispatch<SetStateAction<T>> };
export type StateArgs<T>
= State<T>
| StateObj<T>;
export type OptionalState<T>
= State<T>
| [undefined, undefined];
export type OptionalStateArgs<T>
= OptionalState<T>
| StateObj<T>
| undefined;
export const State = (() => {
return {
optionalFromArgs: <T>(
optional: OptionalStateArgs<T>,
): OptionalState<T> => {
if (Array.isArray(optional)) return optional;
if (optional == null) return [undefined, undefined];
return [optional.get, optional.set];
},
from: <T>(
args: StateArgs<T>,
): State<T> => {
if (Array.isArray(args)) return args;
return [args.get, args.set];
},
};
})();
OptionalState<T>
: undefined
を許容する型を追加。
これで部分的にnullが入っているデータにも対応可能。Provider
を修正// src\provider\counterProvider.tsx
export const CounterProvider = ({ children }: { children: ReactNode }) => {
const countState = useState<number>(0);
const [countObj, setCountObj] = useState<number>(10);
// ユーティリティ関数で状態を取得
const [count, setCount] = State.from(countState); // タプル形式
const [countFromObj, setCountFromObj] = State.optionalFromArgs({
get: countObj,
set: setCountObj,
}); // オブジェクト形式を useState に変換
return (
<CounterContext.Provider value={{
count: count,
setCount,
countFromObj:countFromObj || 0, // undefined の場合は 0 を設定
setCountFromObj: setCountFromObj || (() => {}), // undefined の場合は空の関数を設定
}}
>
{children}
</CounterContext.Provider>
);
};