У меня есть объект данных, который мне нужно передать в одно из двух мест, в зависимости от того, какой член объединения в данный момент содержится в объекте. В обоих местах требуются все данные в объекте, поэтому воссоздание объекта суженным типом кажется немного глупым. Очевидно, что это работает.
В качестве альтернативы я попытался сделать интерфейс объекта универсальным для объединения, чтобы интерфейс мог представлять объект во всех трех местах, думая, что автоматическое сужение типа может применяться к параметру типа TestData
.
interface UserData {
kind: 'user',
user: string,
}
interface ServerData {
kind: 'server',
url: string,
}
type DataTypes = UserData | ServerData
interface TestData<D extends DataTypes> {
data: D,
id: string,
}
Теперь верхний класс может использовать TestData<DataTypes>
, а дети могут использовать TestData<UserData>
или TestData<ServerData>
. Это работает нормально, пока вы не попытаетесь передать объект одному из дочерних элементов. Компилятор правильно сузит свойство TestData
data
, но это не сузит тип фактического объекта, который по-прежнему имеет тип TestData<DataTypes>
. Вот пример.
function basicNarrow(test: TestData<DataTypes>) {
if (test.data.kind === 'user') {
// Correctly narrowed to `UserData`
test.data.user
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<UserData> = test
} else {
// Correctly narrowed to `ServerData`
test.data.url
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<ServerData> = test
}
}
На этом этапе я мог либо использовать утверждение типа, либо (снова) создать новый объект для передачи правильного типа, но после некоторого рытья я обнаружил этот ответ на аналогичный вопрос, который дает мне
type NarrowKind<T, N> = T extends { kind: N } ? T : never;
function predicateNarrow(test: TestData<DataTypes>) {
const predicate = <K extends DataTypes['kind']>(narrow: TestData<DataTypes>, kind: K): narrow is TestData<NarrowKind<DataTypes, K>> => (
narrow.data.kind === kind
)
if (predicate(test, 'user')) {
// Correctly narrowed to `UserData`
test.data.user
// Success! Generic type narrowed to `TestData<UserData>
const typed: TestData<UserData> = test
} else {
// Error: Not narrowed
test.data.url
// Error: Generic type not narrowed, still `TestData<DataTypes>
const typed: TestData<ServerData> = test
}
}
Это делает то, что я искал внутри блока if
, но компилятор не сузится до альтернативного случая в блоке else
без другой явной проверки, как если бы data
был просто локальной переменной.
Вот пример того, что я бы хотел, чтобы суженные типы в конечном итоге были идеально
function idealNarrow(test: TestData<DataTypes>) {
function isKind(/*???*/) { /*???*/ }
if (isKind(test, 'user')) {
const user: UserData = test.data
const typed: TestData<UserData> = test
} else {
const server: ServerData = test.data
const typed: TestData<ServerData> = test
}
}
Любое из решений можно использовать без проблем, но predicateNarrow(...)
так близок к тому, что я искал, есть ли способ каким-то образом объединить эти два поведения, чтобы автоматически сузить весь общий тип TestData<D>
в else блокировать?