Typescript 可选泛型属性

4
我有这样一种类型,其中我的value属性是“可选的”(如果T不是undefined)。
type AsyncState<T = undefined> = {
    value?: T;
    loading: boolean;
    error: { reason: string } | null;
}

现在我需要以一种方式创建新对象,该对象取决于 AsyncState 参数 - 如果 T 不是 undefined,则添加 value 属性,否则不添加。 (这只是更复杂的逻辑的虚拟示例,但由于类型问题,这应该足够了)
function asyncGet<T>(initialState: AsyncState<T>) {
    return typeof initialState.value !== 'undefined' 
        ? (s: AsyncState<T>) => ({ ...initialState })
        : (s: AsyncState) => ({ loading: initialState.loading, error: initialState.error });
}


const first: AsyncState<string> = {
    loading: true,
    error: null,
    value: ""
}

const second: AsyncState<string> = {
    loading: true,
    error: null,
    value: ""
}

const creator = asyncGet(first);

/* 
Argument of type 'AsyncState<string>' is not assignable to parameter of type 'AsyncState<string> & AsyncState<undefined>'.
  Type 'AsyncState<string>' is not assignable to type 'AsyncState<undefined>'.
    Type 'string' is not assignable to type 'undefined'.
*/
creator(second);

这里是typescript playground

1
为什么你使用条件类型而不是 type AsyncState<T> = {loading: boolean; error: {reason: string} | null; value?: T | null}?两者的差异似乎很小,而且处理起来应该更容易。 - jcalz
@jcalz 我有类似的设置,但是在我的redux store中,我总是有那个值:null,尽管我甚至不需要它(基本上我只需要跟踪特定属性的错误和加载,例如登录操作)。 - zhuber
1
如果value是可选的,例如{value?: T | null},那么你不需要value: null,对吧?它会被省略掉。我可能会认为你需要使用相对笨重的条件类型,但我想在这里看到一个[mcve],并且有实际的用例。就目前而言,我不知道该如何继续(combineReducers()是什么?这个问题没有标记为redux,如果你需要redux专业知识,那么它可能应该是...如果它不是关于redux的话,就提供一些不依赖于它的示例用例)。祝好运! - jcalz
@jcalz 请查看已编辑的解决方案(使用value?: T),以及没有任何redux引用的解决方案。 - zhuber
1个回答

1
你可以通过将需要推断T的实际类型的返回函数变成泛型来解决这个问题。
function asyncGet<T>(initialState: AsyncState<T>) {
  return typeof initialState.value !== "undefined"
    ? (s: AsyncState<T>) => ({ ...initialState })
    : <U>(s: AsyncState<U>) => ({
        loading: initialState.loading,
        error: initialState.error
      });
}

话虽如此,如果你尝试使用以下方式调用它来覆盖 TypeScript 推断,这将会让你陷入麻烦:asyncGet<string>({ loading: true, error: null })

更好的解决方案是使用条件类型来指定函数有条件地使用调用返回函数的推断值。

function asyncGet<T>(initialState: AsyncState<T>): 
  <U>(s: AsyncState<[T] extends [undefined] ? U : T>) => 
    AsyncState<[T] extends [undefined] ? U : T> {

  return typeof initialState.value !== "undefined"
    ? (s) => ({ ...initialState })
    : (s) => ({
        loading: initialState.loading,
        error: initialState.error
      });
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接