Проблема в том, что нет никакой связи между Loadable<T>
и определенными интерфейсами, которые гарантировали бы, что функция createLoadable()
устанавливает каждое свойство в правильное состояние перед возвратом элемента. Например, Loadable<string>
может иметь следующие значения:
var loadable = new Loadable<string>();
loadable.state = "Error";
lodable.item = "Result text.";
return loadable;
Выше не подходит ни один интерфейс, но это действительный Loadable
экземпляр.
Мой подход будет следующим:
Упрощенный интерфейс, только один должен быть универсальным:
interface ILoadableLoading {
state: "Loading";
id: number;
}
interface ILoadableLoaded<T> {
state: "Loaded";
id: number;
item: T;
}
interface ILoadableErrored {
state: "Error";
id: number;
error: string;
}
export type ILoadableDiscriminated<T> =
| ILoadableLoading
| ILoadableLoaded<T>
| ILoadableErrored;
type ILoadableState<T> = ILoadableDiscriminated<T>["state"];
Создайте отдельный класс для каждого интерфейса, чтобы гарантировать, что созданные объекты придерживаются определений интерфейса:
class LoadableLoading implements ILoadableLoading {
state: "Loading" = "Loading";
id: number = 0;
}
class LoadableLoaded<T> implements ILoadableLoaded<T> {
constructor(public item: T){}
state: "Loaded" = "Loaded";
id: number = 0;
}
class LoadableErrored implements ILoadableErrored {
constructor(public error: string){}
state: "Error" = "Error";
id: number = 0;
}
Тогда мы могли бы использовать функцию с перегрузкой, чтобы указать намерение:
function createLoadable<T>(someState: true, state: ILoadableState<T>, item?: T): ILoadableErrored;
function createLoadable<T>(someState: false, state: "Loading", item?: T): ILoadableLoading;
function createLoadable<T>(someState: false, state: "Loaded", item?: T): ILoadableLoaded<T>;
function createLoadable<T>(someState: boolean, state?: ILoadableState<T>, item?: T): ILoadableDiscriminated<T> {
if (someState) {
return new LoadableErrored("Some error");
}
if (state === "Loading") {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoading();
}
if (state === "Loaded" && item) {
// Would like to remove this cast, as it hsould figure it out from state;
return new LoadableLoaded(item);
}
throw new Error("Some Error");
}
Наконец, в зависимости от ваших входных параметров для функции createLoadable()
, тип будет возвращен, тип будет различаться автоматически:
const lodableError = createLoadable<string>(true, "Loading");
console.log(lodableError.error);
const lodableLoading = createLoadable<string>(false, "Loading");
console.log("Loading");
const loadableLoaded = createLoadable<string>(false, "Loaded", "MyResponse");
console.log(loadableLoaded.item)
Обратите внимание, что параметр перегружает намерение состояния для компилятора Typescript, но вы должны убедиться, что код в теле функции выполняет то, что вы объявили.