Я повторю свои комментарии и продолжу дальше:
Дискриминационные союзы действительно работают так, как вы ожидаете, с конкретными типами, а не с дженериками. Кроме того, внутри реализации getApiAsyncStatus()
тип T
является неразрешенным универсальным параметром, и компилятор не делает много работы, пытаясь проверить, можно ли присвоить значение условному типу, зависящему от такого неразрешенного универсального. Лучше всего здесь просто использовать утверждение типа или что-то эквивалентное, например overload подпись. Преимущество этого условного типа для абонентов, а не для разработчиков.
Если вы используете TypeScript 3.5 с более умной проверкой типа объединения , вы можете исправить приведенный выше код следующим образом:
export const getApiAsyncStatus = <T extends ApiAsyncStatus["_type"]>(
registry: Registry,
id: string,
type: T,
) => {
let status = registry[id];
if (status !== undefined && status._type !== type) {
// annotate as error state
const errorStatus: Extract<ApiAsyncStatus, { state: "error" }> = {
_type: type as ApiAsyncStatus["_type"], // widen to concrete union
error: new Error(`Expected _type ${type}, but received ${status._type}`),
id,
state: "error"
};
status = errorStatus; // this assignment is okay
}
// still need this assertion
return status as ExtractAsyncStatusByType<T, ApiAsyncStatus> | undefined;
};
Расширение type
с T
до ApiAsyncStatus["_type"]
изменяет его с универсального типа (для которого отсутствуют дедуктивные навыки компилятора) на конкретный союз (что лучше). Более разумная проверка объединения в TS3.5 необходима для того, чтобы компилятор понимал, что значение типа {_type: A | B, error: Error, state: "error"}
присваивается переменной типа {_type: A, error: Error, state: "error"} | {_type: B, error: Error, state: "error"}
. Для TS3.4 и ниже, компилятор вообще не будет выполнять такой анализ . Так что даже вышеупомянутое будет все еще ошибкой в тех более ранних версиях.
Чтобы поддержать тех, кого вы могли бы также сделать своими утверждениями шире и менее безопасными:
export const getApiAsyncStatus = <T extends ApiAsyncStatus["_type"]>(
registry: Registry,
id: string,
type: T,
) => {
let status = registry[id];
if (status !== undefined && status._type !== type) {
status = {
_type: type,
error: new Error(`Expected _type ${type}, but received ${status._type}`),
id,
state: "error"
} as Extract<ApiAsyncStatus, { state: "error", _type: T }>; // assert as error type
}
// still need this assertion
return status as ExtractAsyncStatusByType<T, ApiAsyncStatus> | undefined;
};
Ссылка на код
Так что любой из них должен работать в зависимости от используемой вами версии TypeScript. Я склонен думать об этой проблеме как об общем классе проблем, связанных с коррелированными типами , где все будет работать нормально, если вы сможете убедить компилятор проверить тип блока, кратного множественному коду времена для каждого возможного сужения некоторой переменной типа объединения. В вашем случае для каждого возможного значения T
("LOGIN"
и "SEARCH"
здесь) ваш код должен проверяться нормально. Но, глядя на союзы или общие расширения союзов «все сразу», компилятор думает, что некоторые запрещенные ситуации возможны, и отказывается. Боюсь, нет лучшего ответа на этот вопрос ... мой совет - просто утвердить свой выход и идти дальше.
Хорошо, надеюсь, это поможет; удачи!