Typescript 类型的类型脚本递归子集
在Typescript中是否可以创建类似于此的类型子集Typescript 类型的类型脚本递归子集,typescript,Typescript,在Typescript中是否可以创建类似于此的类型子集 type Schema = { user: { name: string; age: number; profile: { isCool?: boolean; }; }; }; const wantedSubset = { user: { name: true, profile: { isCool: true } } }; type Expe
type Schema = {
user: {
name: string;
age: number;
profile: {
isCool?: boolean;
};
};
};
const wantedSubset = {
user: {
name: true,
profile: {
isCool: true
}
}
};
type ExpectedType = {
user: {
name: string;
profile: {
isCool?: boolean;
};
};
};
const result: ExpectedType = desiredCode(wantedSubset);
如果不清楚,我希望使用desiredCode
函数,给定的对象wantedSubset
将返回除递归使用声明函数pick(obj:T,…key:K[]):pick以外的任何内容代码>
我想在Schema
上递归地应用这个Pick
,但我不知道如何递归地“引用”到Schema
类型
我试过这样的方法:
function gql<Q, S, QK extends keyof Q, SK extends keyof S, K extends SK & QK>(
query: Q | true,
schema: S
) {
if (query === true) return (query as unknown) as S;
const keys = Object.keys(query) as K[];
const result = {} as Pick<S, K>;
keys.forEach(k => {
result[k] = gql(query[k], schema[k]);
});
return result;
}
const result = gql(wantedSubset, wantedSubset as unknown as Schema)
declare function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
const result = gql(wantedSubset, null! as Schema); // looks good
函数gql(
问题:Q |正确,
模式:S
) {
if(query==true)返回(查询为未知)为S;
const keys=Object.keys(查询)作为K[];
const result={}作为Pick;
keys.forEach(k=>{
结果[k]=gql(查询[k],模式[k]);
});
返回结果;
}
const result=gql(wantedSubset,与模式一样未知的wantedSubset)
但是它没有按照我希望的方式工作。它只是返回Pick的结果,而不是递归地应用它
基本上,问题在于如何动态构建对象,以便typescript能够推断返回值
因此我认为您希望wantedSubset
符合一种类型,其中每个属性要么是true
,要么本身就是该类型的对象。我们需要做一些准备工作,让TypeScript推断出这样一种类型,其中值true
被视为typetrue
,而不是boolean
(至少在TS3.4出来并给出我们之前):
因此,如果T
是Schema
,那么RecursivePicker
将为您提供:
type RPSchema = RecursivePicker<Schema>
// type RPSchema = {
// user?: {
// age?: true;
// name?: true;
// profile?: {
// isCool?: true;
// }
// }
// }
它基本上遍历了T
的属性,并检查了KO
的相应属性。如果属性为true
,则返回T
未更改的属性。如果属性本身是一个属性包,它将向下递归。如果属性不在那里,它将返回never
。整个过程都是Pick
ed,这样只有T
和KO
中出现的键才会出现在最终输出中(这是为了确保所有相关属性都是同态的,这意味着可选属性仍然是可选的)
让我们验证它是否有效:
type ExpectedType = RecursivePick<Schema, typeof wantedSubset>;
type ExpectedType=RecursivePick;
您可以浏览一下,但让编译器验证一下:
type ExpectedTypeManual = {
user: {
name: string;
profile: {
isCool?: boolean;
};
};
};
type MutuallyExtends<T extends U, U extends V, V=T> = true
// if no error in the next line, then ExpectedType and ExpectedTypeManual are
// structurally the same
type NoErrorHere = MutuallyExtends<ExpectedType, ExpectedTypeManual>
类型ExpectedTypeManual={
用户:{
名称:字符串;
简介:{
isCool?:布尔值;
};
};
};
类型MutuallyExtends=true
//如果下一行中没有错误,则显示ExpectedType和ExpectedTypeManual
//结构相同
类型NoErrorHere=MutuallyExtends
这样一切都会好起来。您的函数类型如下:
function gql<Q, S, QK extends keyof Q, SK extends keyof S, K extends SK & QK>(
query: Q | true,
schema: S
) {
if (query === true) return (query as unknown) as S;
const keys = Object.keys(query) as K[];
const result = {} as Pick<S, K>;
keys.forEach(k => {
result[k] = gql(query[k], schema[k]);
});
return result;
}
const result = gql(wantedSubset, wantedSubset as unknown as Schema)
declare function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
const result = gql(wantedSubset, null! as Schema); // looks good
声明函数gql(
问题:Q |正确,
模式:S
):递归拾取;
const result=gql(wantedSubset,null!作为模式);//看起来不错
要使函数的实现在没有错误的情况下编译,可能需要一个条件类型,因为众所周知,条件类型很难推断:
function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
function gql(query: any, schema: object): object {
return {}; // something better here
}
函数gql(
问题:Q |正确,
模式:S
):递归拾取;
函数gql(查询:任意,模式:对象):对象{
return{};//这里有更好的
}
好吧,希望有帮助;祝你好运 你真的希望ExpectedType['user']['profile']['isCool']
是true |未定义的
而不是boolean |未定义的
?@jcalz感谢你发现了这个错误-我想boolean |未定义的
-修复在asWantedSubset
中是否可能检测到它不是子集?(模式中不存在某些属性)type ExactRecursivePicker={[K in keyof RP]:K extensed keyof t?t[K]extensed object?ExactRecursivePicker:true:never};常量asWantedSubset=(wantedSubset:RP)=>wantedSubset代码>在gql()
中会被捕获,但如果您想更早捕获它,可以执行类似于上述的操作。最后一个问题是,当我将配置文件
设置为可选(配置文件?:
)时,当我键入result.user.profile.isCool时,信息将丢失。typescript说这很好,但应该提到的是,配置文件可能尚未定义。你知道如何修正这个问题吗?也许你可以通过将RecursivePicker
改为type RecursivePicker={[K in keyof T]?:T[K]扩展对象|未定义?RecursivePicker:true}
,来修正它,但我不是100%确定。
function gql<Q extends RecursivePicker<S>, S>(
query: Q | true,
schema: S
): RecursivePick<S, Q>;
function gql(query: any, schema: object): object {
return {}; // something better here
}