是否可以创建一个递归强制值的类型



考虑以下数据结构:

const routes = {
root: '/',
foo: '/foo',         //ok
bar: {
root:'/bar',
foo: '/bar/foo',   // ok 
bar: '/bar/:barId',// ok
baz: '/baz'        // should ko
},
} as const;

是否可以编写一个类型来强制执行从父&本地根属性?

如果我正确理解你的问题,我认为你可以通过条件映射类型和模板文本来实现这一点:

interface Root {
root: string;
}
type Routes<T extends Root> = {
[P in keyof T]: P extends "root"
? T[P]
:  P extends string
? T[P] extends Root
? T[P]["root"] extends `${T["root"]}${P}`
? Routes<T[P]>
: never
: `${T["root"]}${string}`
: never;
};

以下是Routes的注释版本,以帮助解释其工作原理:

// For any object that has a "root" key that is a `string`
type Routes<T extends Root> = {
// For each key in the object
[P in keyof T]:
// If the key is the "root" key
P extends "root"
// then its type is whatever it originally was (which is `string`)
? T[P]
// otherwise if the key is a string other than "root"
: P extends string
// and if the key's value is an object that also has a "root" key that's a `string`
? T[P] extends Root 
// and if the value of that object's "root" key begins with the value of the current object's "root" key + the name of the key we're currently examining
? T[P]["root"] extends `${T["root"]}${P}`
// then the sub-object is itself a `Routes`
? Routes<T[P]>
// otherwise it is an object in a different form that we don't allow
: never
// and if it wasn't an object, then it must be a `string` that begins with the value of the current object's "root" key
: `${T["root"]}${string}`
// otherwise the key we were examining couldn't be coerced into a string (e.g. a symbol)
: never;
};

证明其有效性的示例:

// This is a valid `Routes`.
const one = {
root: "/",
foo: "/foo",
bar: "/bar",
} as const;
// This is not a valid `Routes` because `two.bar.baz` doesn't begin with "/bar".
const two = {
root: "/",
foo: "/foo",
bar: {
root: "/bar",
foo: "/bar/foo",
bar: "/bar/:barId",
baz: "/baz",
},
} as const;
// This is not a valid `Routes` because `three.bar.root` is not "/bar".
const three = {
root: "/",
foo: "/foo",
bar: {
root: "qux",
foo: "qux/foo",
bar: "qux/:barId",
baz: "qux",
},
} as const;
const oneRoutes: Routes<typeof one> = one;
const twoRoutes: Routes<typeof two> = two;
const threeRoutes: Routes<typeof three> = three;

在本例中,oneRoutes的赋值将被编译器接受,但twoRoutesthreeRoutes的赋值将产生错误。请参阅下面链接的TS Playground以读取完整错误。

TS游乐场

最新更新