Я мог бы частично решить проблему, добавив больше дженериков. Чтобы код компилировался без реакции, замените импорт следующими объявлениями:
function useState<S>(initialState: S | (() => S)): [S, Dispatch<SetStateAction<S>>];
type Dispatch<A> = (value: A) => void;
type SetStateAction<S> = S | ((prevState: S) => S);
Полный код:
import { useState, Dispatch, SetStateAction } from 'react';
interface BaseAgg<S extends boolean|string|number> {
value: S
setter: (value: S) => void
}
interface BooleanAgg extends BaseAgg<boolean> {
falseHandler: (event?: any) => void,
trueHandler: (event?: any) => void,
toggleHandler: (event?: any) => void,
}
interface NormalAgg<S extends string|number> extends BaseAgg<S> {
makeSetter: (value: S) => ((event?: any) => void)
changeHandler: (e: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => void
}
function booleanAgg(value: boolean, setter: (value: boolean) => void, makeSetter: (value: boolean) => ((event?: any) => void)) : BooleanAgg {
return {
value,
setter,
falseHandler: makeSetter(false),
trueHandler: makeSetter(true),
toggleHandler: makeSetter(!value),
};
}
function stringAgg<S extends string>(value: S, setter: (value: S) => void, makeSetter: (value: S) => ((event?: any) => void)) : NormalAgg<S> {
return {
value,
setter,
makeSetter,
changeHandler: (e: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => {
e.stopPropagation();
setter(e.currentTarget.value as S);
}
};
}
function numberAgg<S extends number>(value: S, setter: (value: S) => void, makeSetter: (value: S) => ((event?: any) => void)) : NormalAgg<S> {
return {
value,
setter,
makeSetter,
changeHandler: (e: React.ChangeEvent<HTMLInputElement|HTMLTextAreaElement>) => {
e.stopPropagation();
setter(+e.currentTarget.value as S);
}
};
}
/**
* Works like useState and adds a third element containing boolean-specific handlers directly useable in buttons and checkboxes.
*/
export function useStateEx<S extends boolean>(initialState: S) : [S, Dispatch<SetStateAction<S>>, BooleanAgg];
/**
* Works like useState and adds a third element containing string-specific handlers directly useable in text fields.
*/
export function useStateEx<S extends string>(initialState: S) : [S, Dispatch<SetStateAction<S>>, NormalAgg<S>];
/**
* Works like useState and adds a third element containing number-specific handlers directly useable in text fields.
*/
export function useStateEx<S extends number>(initialState: S) : [S, Dispatch<SetStateAction<S>>, NormalAgg<S>];
/**
* Prohibits use with any type not handled in the above overloads.
*/
export function useStateEx(initialState: any) : never;
export function useStateEx<S extends boolean|string|number>(initialState: S) : unknown {
const [value, setter] = useState(initialState);
function makeSetter(value: S) {
return function(e: any) {
if (typeof e?.stopPropagation === 'function') e.stopPropagation();
setter(value);
};
}
switch (typeof value) {
case 'boolean': return [value, setter, booleanAgg(value, setter as any, makeSetter as any)];
case 'string': return [value, setter, stringAgg(value, setter, makeSetter)];
case 'number': return [value, setter, numberAgg(value, setter, makeSetter)];
}
}
Я не добавил обобщений для логического случая, поскольку я не хочу иметь дело с S extends boolean
- тип, подобный false
с его единственным значением, нельзя использовать в качестве состояния. Поэтому аргумент generi c, который я хотел бы использовать, находится в псевдокоде S=boolean || S extends number|string
, который AFAIK не может быть выражен.