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

在Typescript中是否可以创建类似于此的类型子集

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
被视为type
true
,而不是
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
}