与可以扩展 TypeScript 的默认键值类型的接口
Interface with default key value types that can be extended TypeScript
在我的许多项目中,我都有一个反复重复的通用模式。
type RequestStatus =
| 'pending'
| 'requesting'
| 'successful'
| 'failed'
type AValue = string | undefined
type OtherValue = number
interface State {
requestStatus: RequestStatus;
aValue?: AValue;
otherValue?: OtherValue;
}
const [requestState, setRequestState] = useState<State>({
requestStatus: 'pending',
aValue: 'aValue',
otherValue: 11
});
const appendToState = (state: Partial<State>) =>
setRequestState(previousState => ({ ...previousState, ...state }));
在这种情况下,我正在获取值并根据 requestStatus
和值更改 UI。
这在我的项目中一遍又一遍地重复,为了减少重复,我正在考虑将所有这些都包装在一个钩子中。我的主要问题是处理类型。
这是我的尝试之一。
export const useExtendedRequestStatus = <T>(state: T) => {
const [requestState, setRequestState] = useState<{ requestStatus: RequestStatus } & T>({
requestStatus: 'pending',
...state,
});
const appendToState = (state: Partial<T>) =>
setRequestState(previousState => ({ ...previousState, ...state }));
return { requestState, setRequestState, appendToState };
};
我会这样实现
const {
requestStatus,
appendToState
} = useExtendedRequestStatus<{ requestStatus: RequestStatus; aValue: AValue }>({
aValue: aValue,
requestStatus: 'successful',
});
它有效但不完美。在某些情况下,如果我对钩子进行静态类型检查,我必须重新定义钩子中已经定义的 RequestStatus
类型。我想知道有没有办法,RequestStatus
在手动类型检查时仍然会挂钩而不必重新定义它。
我愿意接受任何想法。
您确实在 useExtendedRequestStatus
自定义挂钩中使用了 T
泛型,它描述了您希望与其一起使用的状态的可能成员。
但是由于您指定 state
参数,当您调用自定义挂钩时,直接属于 T
类型,如果您希望它包含初始 requestStatus
,则你还被迫在你的显式具体类型中提到这个 requestStatus
成员(当然你可以依赖自动类型推断,但我想你有手动类型检查的理由)。
您可以轻松地将此预定义成员“嵌入”到您的自定义挂钩中,并将其用于您的参数(appendToState
也是如此),其方式与您已经对内部 useState<{ requestStatus: RequestStatus } & T>({...})
:简单地指定你的 state
参数也是 T & { requestStatus: RequestStatus }
类型(或者甚至更好 T & Partial<{ requestStatus: RequestStatus }>
以防 requestStatus
最初可以省略,正如你的初始建议自定义挂钩中的默认值)。这样,T
不再需要包含 requestStatus
成员。 appendToState
.
相同
interface IRequestStatus {
requestStatus: RequestStatus;
}
export const useExtendedRequestStatus = <T>(
state: T & Partial<IRequestStatus> // Initial state can contain requestStatus, even if not mentioned in concrete T
) => {
const [requestState, setRequestState] = useState<IRequestStatus & T>({
requestStatus: 'pending',
...state,
});
const appendToState = (state: Partial<T & IRequestStatus>) =>
setRequestState((previousState) => ({ ...previousState, ...state }));
return { requestState, setRequestState, appendToState };
};
现在在你的 React 功能组件中,你可以像这样使用它:
const { requestState, appendToState } = useExtendedRequestStatus<{
//requestStatus: RequestStatus; // Can now be omitted
aValue: AValue;
}>({
aValue: aValue,
requestStatus: 'successful', // Okay
});
requestState.requestStatus; // Okay
appendToState({
requestStatus: 'successful', // Okay
});
演示:https://stackblitz.com/edit/react-ts-eyyzhg?file=Hello.tsx
在我的许多项目中,我都有一个反复重复的通用模式。
type RequestStatus =
| 'pending'
| 'requesting'
| 'successful'
| 'failed'
type AValue = string | undefined
type OtherValue = number
interface State {
requestStatus: RequestStatus;
aValue?: AValue;
otherValue?: OtherValue;
}
const [requestState, setRequestState] = useState<State>({
requestStatus: 'pending',
aValue: 'aValue',
otherValue: 11
});
const appendToState = (state: Partial<State>) =>
setRequestState(previousState => ({ ...previousState, ...state }));
在这种情况下,我正在获取值并根据 requestStatus
和值更改 UI。
这在我的项目中一遍又一遍地重复,为了减少重复,我正在考虑将所有这些都包装在一个钩子中。我的主要问题是处理类型。
这是我的尝试之一。
export const useExtendedRequestStatus = <T>(state: T) => {
const [requestState, setRequestState] = useState<{ requestStatus: RequestStatus } & T>({
requestStatus: 'pending',
...state,
});
const appendToState = (state: Partial<T>) =>
setRequestState(previousState => ({ ...previousState, ...state }));
return { requestState, setRequestState, appendToState };
};
我会这样实现
const {
requestStatus,
appendToState
} = useExtendedRequestStatus<{ requestStatus: RequestStatus; aValue: AValue }>({
aValue: aValue,
requestStatus: 'successful',
});
它有效但不完美。在某些情况下,如果我对钩子进行静态类型检查,我必须重新定义钩子中已经定义的 RequestStatus
类型。我想知道有没有办法,RequestStatus
在手动类型检查时仍然会挂钩而不必重新定义它。
我愿意接受任何想法。
您确实在 useExtendedRequestStatus
自定义挂钩中使用了 T
泛型,它描述了您希望与其一起使用的状态的可能成员。
但是由于您指定 state
参数,当您调用自定义挂钩时,直接属于 T
类型,如果您希望它包含初始 requestStatus
,则你还被迫在你的显式具体类型中提到这个 requestStatus
成员(当然你可以依赖自动类型推断,但我想你有手动类型检查的理由)。
您可以轻松地将此预定义成员“嵌入”到您的自定义挂钩中,并将其用于您的参数(appendToState
也是如此),其方式与您已经对内部 useState<{ requestStatus: RequestStatus } & T>({...})
:简单地指定你的 state
参数也是 T & { requestStatus: RequestStatus }
类型(或者甚至更好 T & Partial<{ requestStatus: RequestStatus }>
以防 requestStatus
最初可以省略,正如你的初始建议自定义挂钩中的默认值)。这样,T
不再需要包含 requestStatus
成员。 appendToState
.
interface IRequestStatus {
requestStatus: RequestStatus;
}
export const useExtendedRequestStatus = <T>(
state: T & Partial<IRequestStatus> // Initial state can contain requestStatus, even if not mentioned in concrete T
) => {
const [requestState, setRequestState] = useState<IRequestStatus & T>({
requestStatus: 'pending',
...state,
});
const appendToState = (state: Partial<T & IRequestStatus>) =>
setRequestState((previousState) => ({ ...previousState, ...state }));
return { requestState, setRequestState, appendToState };
};
现在在你的 React 功能组件中,你可以像这样使用它:
const { requestState, appendToState } = useExtendedRequestStatus<{
//requestStatus: RequestStatus; // Can now be omitted
aValue: AValue;
}>({
aValue: aValue,
requestStatus: 'successful', // Okay
});
requestState.requestStatus; // Okay
appendToState({
requestStatus: 'successful', // Okay
});
演示:https://stackblitz.com/edit/react-ts-eyyzhg?file=Hello.tsx