Typescript:如果所有道具都是可选的,有没有一种不需要对象的方法?

时间:2019-06-09 21:31:03

标签: typescript

我正在研究一些库(某种API代理),该库将在其他内部项目中使用。我想定义一些方法以便于使用,但是我很难确定类型断言:

首先,我为所有终结点方法提供了一个工厂:

export type Params<TQueryParams> = {
  customConfig?: object;
  fullResponse?: boolean;
} & (TQueryParams extends undefined ? { queryParams?: undefined } : { queryParams: TQueryParams })

export const getFactory = <
  TQueryParams extends object | undefined = undefined
>({ path }: any) => async (
  { queryParams, customConfig, fullResponse = false }: Params<TQueryParams>
): Promise<any> => {
  // some irrelevant factory code
  return {};
};

我已经使用了一些魔术,所以取决于TQueryParams,是否需要它,但这还不够。

现在,当我想定义所有端点并使用工厂时:

// Case 1 - no interface
export const getFile = getFactory({
  path: '/api/file'
});

// I'd like for this one to not throw error
getFile();
// These will not throw error - as intended
getFile({});
getFile({
  fullResponse: true
});
// This will throw error - as intended
getFile({
  queryParams: {}
});
// Case 2 - interface with all optional props
export interface ImageQueryParams {
  width?: number;
  height?: number;
}

export const getImage = getFactory<ImageQueryParams>({
  path: '/api/image'
});

// All of them should be ok, but only last will not throw error
getImage();
getImage({});
getImage({
  fullResponse: true
});
getImage({
  queryParams: {}
});
// Case 3 - interface with mandatory props
export interface DataQueryParams {
  id: number;
  sort?: string;
}

export const getData = getFactory<DataQueryParams>({
  path: '/api/data'
});

// All will throw error - as intended
getData();
getData({});
getData({
  fullResponse: true
});
getData({
  queryParams: {}
});
// This one will not throw error - as intended
getData({
  queryParams: {
    id: 2131241
  }
});

只有第3种情况可以正常工作。

如果没有接口或它包含所有可选道具,我将特别想实现一个解决方案,那么我将什么也不能传递(getImage();)并且没问题。

TS Playground link

1 个答案:

答案 0 :(得分:2)

我对这些类型定义并不感到高兴(我希望有一些简化和通用的东西),但它们似乎适用于您的用例:

type CanBeOmitted<T, Y = T, N = never> =
  {} extends T ? Y : // T is weak (all props are optional), or
  undefined extends T ? Y : // T can be undefined
  N;

类型别名CanBeOmitted<T, Y, N>检查T是全选对象类型(也称为"weak type")还是兼容undefined的类型。如果是这样,则返回Y;如果不是,则返回N(默认为never)。我们可以多次使用它来建立您的Params类型。

这里是:

export type Params<TQueryParams> =
  CanBeOmitted<TQueryParams, [undefined?]> |
  [
    {
      customConfig?: object,
      fullResponse?: boolean
    } & (
      { queryParams: TQueryParams } |
      CanBeOmitted<TQueryParams, { queryParams?: never }>
    )
  ];

我做过的另一件事是使它成为包含参数的元组类型。如果可以完全省略该参数,则返回optional tuple

这里是getFactory

export const getFactory = <
  TQueryParams extends object | undefined = undefined
>({ path }: any) => async (
  ...args: Params<TQueryParams>
): Promise<any> => {
    // get rid of conditional types
    const arg = args[0] as {
      customConfig?: object,
      fullResponse?: boolean,
      queryParams?: TQueryParams
    } | undefined;
    const queryParams = arg ? arg.queryParams : undefined;
    const customConfig = arg ? arg.customConfig : undefined;
    const fullResponse = (arg ? arg.fullResponse : undefined) || false;
    // some irrelevant factory code
    return {};
  };

请注意,Params<TQueryParams>被用作rest tuple而不是单个参数。当该元组是可选的时,它允许使用零参数调用getFactory()的返回值。还要注意,我们不能再假设该参数存在,因此我们必须更改如何将queryParamscustomConfigfullResponse作为实现内的变量。

好的,让我们看看它是如何工作的:

// Case 1 - no interface
export const getFile = getFactory({
  path: '/api/file'
});

getFile(); // okay
getFile({}); // okay
getFile({
  fullResponse: true
}); // okay
getFile({
  queryParams: {}
}); // error, {} is not undefined

// Case 2 - interface with all optional props
export interface ImageQueryParams {
  width?: number;
  height?: number;
}

export const getImage = getFactory<ImageQueryParams>({
  path: '/api/image'
});

getImage(); // okay
getImage({}); // okay
getImage({
  fullResponse: true
}); // okay
getImage({
  queryParams: {}
}); // okay

export interface DataQueryParams {
  id: number;
  sort?: string;
}

// Case 3 - interface with mandatory props
export const getData = getFactory<DataQueryParams>({
  path: '/api/data'
});

getData(); // error, expected 1 arg
getData({}); // error, queryParams missing
getData({
  fullResponse: true
}); // error, queryParams missing
getData({
  queryParams: {}
}); // error, id missing
getData({
  queryParams: {
    id: 2131241
  }
}); // okay

认为就是您要的东西。好的,希望对您有所帮助。祝你好运!

Link to code