缩小接口的通用联合 属性 就好像它是打字稿中的局部变量一样
Narrowing an interface's generic union property as though it was a local variable in typescript
我有一个 object 数据需要根据 object 当前包含的联合成员传递到两个位置之一。这两个地方都需要 object 中的所有数据,因此用缩小的类型重新创建 object 似乎有点傻。显然它有效。
作为替代方案,我尝试使 object 的接口在联合上通用,这样接口就可以在所有三个地方表示 object,认为自动类型缩小可能适用于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,
}
现在排名靠前的class可以使用TestData<DataTypes>
,排名靠前的children可以使用TestData<UserData>
或TestData<ServerData>
。在您尝试将 object 向下传递给 children 之一之前,这一切正常。编译器将正确缩小 TestData
的 data
属性,但这不会缩小实际 object 的类型,它仍然具有 [=16= 的类型].这是一个例子。
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
}
}
此时我可以使用类型断言或(再次)创建一个新的 object 来传递正确的类型,但经过一些挖掘我发现 这给了我
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
块中所做的事情,但是如果 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(...)
所以 接近我正在寻找的东西,有没有办法将这两种行为结合起来以某种方式在 else 块中自动缩小整个通用 TestData<D>
类型?
这里的问题是 TestData
本身不是 discriminated union type,只有 data
属性 中的 D
是。换句话说,TS 可以通过 kind
判别式缩小 data
,而不是外部 TestData
类型。
predicate
只能检查 TestData
是否包含特定类型 UserData
或 ServerData
,但它无法推断其他可能的联合部分与控制流中的if/else
块。可能的解决方案:
1) 缩小DataTypes
并重新组合TestData
(code)
function basicNarrow({ id, data }: TestData<DataTypes>) {
if (data.kind === 'user') {
data // UserData
const typed: TestData<UserData> = { id, data }
} else {
data // ServerData
const typed: TestData<ServerData> = { id, data }
}
}
2) 使 TestData
本身成为受歧视的联盟 (code)
type DataTypes = UserData | ServerData
type TestData<D extends DataTypes> = D & { id: string }
function basicNarrow(test: TestData<DataTypes>) {
if (test.kind === 'user') {
test // UserData & { id: string; }
const typed: TestData<UserData> = test
} else {
test // ServerData & { id: string; }
const typed: TestData<ServerData> = test
}
}
我有一个 object 数据需要根据 object 当前包含的联合成员传递到两个位置之一。这两个地方都需要 object 中的所有数据,因此用缩小的类型重新创建 object 似乎有点傻。显然它有效。
作为替代方案,我尝试使 object 的接口在联合上通用,这样接口就可以在所有三个地方表示 object,认为自动类型缩小可能适用于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,
}
现在排名靠前的class可以使用TestData<DataTypes>
,排名靠前的children可以使用TestData<UserData>
或TestData<ServerData>
。在您尝试将 object 向下传递给 children 之一之前,这一切正常。编译器将正确缩小 TestData
的 data
属性,但这不会缩小实际 object 的类型,它仍然具有 [=16= 的类型].这是一个例子。
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
}
}
此时我可以使用类型断言或(再次)创建一个新的 object 来传递正确的类型,但经过一些挖掘我发现
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
块中所做的事情,但是如果 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(...)
所以 接近我正在寻找的东西,有没有办法将这两种行为结合起来以某种方式在 else 块中自动缩小整个通用 TestData<D>
类型?
这里的问题是 TestData
本身不是 discriminated union type,只有 data
属性 中的 D
是。换句话说,TS 可以通过 kind
判别式缩小 data
,而不是外部 TestData
类型。
predicate
只能检查 TestData
是否包含特定类型 UserData
或 ServerData
,但它无法推断其他可能的联合部分与控制流中的if/else
块。可能的解决方案:
1) 缩小DataTypes
并重新组合TestData
(code)
function basicNarrow({ id, data }: TestData<DataTypes>) {
if (data.kind === 'user') {
data // UserData
const typed: TestData<UserData> = { id, data }
} else {
data // ServerData
const typed: TestData<ServerData> = { id, data }
}
}
2) 使 TestData
本身成为受歧视的联盟 (code)
type DataTypes = UserData | ServerData
type TestData<D extends DataTypes> = D & { id: string }
function basicNarrow(test: TestData<DataTypes>) {
if (test.kind === 'user') {
test // UserData & { id: string; }
const typed: TestData<UserData> = test
} else {
test // ServerData & { id: string; }
const typed: TestData<ServerData> = test
}
}