与可以扩展 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