获取通用对象成员的编译时类型 平面版本递归版本

我正在尝试创建一个函数,以简化可用于任意目的的对象上的set ter函数的设置。明显的用途包括类型防护,值和边界检查或触发事件。

这个想法是要获取一个形状已知的对象,并对其调用一个函数,该函数将返回一个具有相同可枚举形状的新对象,其中该对象的每个成员实际上都具有set和{{1} }“属性”。 get ter来自函数的第二个参数,而set ter仅返回“ protected”值。

此方法的大量实用程序来自可能的严格键入。当受保护的对象是在代码的不同部分中定义的,甚至可能超出您的控制范围时,此功能特别有用。如果对象形状发生变化,则键入错误将确保添加/删除新的get ter。

我很容易地创建了一个“扁平”版本。

平面版本

set

用法

function makeObjectSetter<T extends {}>(
  internal: T,setters: {
    [P in keyof T]: (next: T[P]) => void;
  }
) {
  const ret = {};

  for (const x in internal) {
    Object.defineProperty(ret,x,{
      set: setters[x],get: () => internal[x],enumerable: true,});
  }

  return ret as T;
}

当目标对象具有嵌套对象时,就会出现棘手的部分。虽然可以使用平面版本,但是编写更深的嵌套变得很麻烦。

因此,我正在尝试编写平面函数的递归版本。这将检测成员的类型是否是对象,并对其进行不同的处理。我相信我的函数签名类型正确,但是函数内部存在一个硬错误,我无法弄清。

我知道TypeScript类型在运行时不可用,但是我认为这是一个编译时问题。我相信,如果在上述行中得到正确的语法,它将可以正常工作。但是也许我错了?

递归版本

const myobject = {
  num: 42,str: 'initialValue',};

const protectedObject = makeObjectSetter(myobject,{
  num(x) {
    // Make sure positive
    myobject.num = Math.max(x,0);
  },str(s) {
    // Always double the input
    myobject.str = s + s;
  },});

console.log(myobject);
// { num: 42,str: 'initialValue' }

protectedObject.num = -1;
protectedObject.str = 'a';

console.log(myobject);
// { num: 0,str: 'aa' }

for (let x in protectedObject) console.log(x);
// num
// str

type nestedSetters<T extends {}> = { [P in keyof T]: T[P] extends {} ? nestedSetters<T[P]> : (next: T[P]) => void }; function makeObjectSetterRecursive<T extends {}>(internal: T,setters: nestedSetters<T>) { const ret = {}; for (const x in internal) { let prop: PropertyDescriptor; // Can't figure out this line type t = typeof internal[x]; // Pretty sure this test is the right runtime test for my purposes if (typeof internal[x] == 'object') { prop = { value: makeObjectSetterRecursive(internal[x],setters[x] as nestedSetters<t>),// Should be able to avoid this `as` cast,no? }; } else { prop = { set: setters[x] as (next: t) => void,no? get: () => internal[x],}; } prop.enumerable = true; Object.defineProperty(ret,prop); } return ret as T; // Extra extra bonus get rid of this `as` cast } 旁边,我也尝试过typeof internal[x]和其他猜测,但无济于事。

任何对此的想法将不胜感激。答案可能是我想要的是不可能的。

附带要求:我认为Pick<typeof internal,x>强制类型转换不需要带有正确的类型提示。

a704271485 回答:获取通用对象成员的编译时类型 平面版本递归版本

如前所述,TypeScript类型在运行时不可用,因此typeof internal[x]无法正常工作。您正在寻找的是T[Extract<keyof T,string>],它可以提取道具价值的类型。

关于强制转换问题,似乎存在条件类型范围缩小的问题。 https://github.com/microsoft/TypeScript/issues/30152 因此,必须在if else语句中映射正确类型(在这种情况下为typeof value === 'object')中依靠运行时逻辑。对于语义,我认为使用户定义的类型防护isNestedSetters并将值强制转换为联合类型NestedSetters<T[P]> | Setter<T[P]>很有用,因为这可使编译器正确缩小范围。

这是完整版本:

type Setter<T> = T extends boolean ? (next: boolean) => void : (next: T) => void

type SetterOrNested<T> = T extends object ? NestedSetters<T> : Setter<T>

type NestedSetters<T> = {
  [P in keyof T]: SetterOrNested<T[P]>
}

function isNestedSetters<T>(value: any): value is NestedSetters<T> {
  return typeof value === 'object';
}

function makeObjectSetterRecursive<T extends {}>(internal: T,setters: NestedSetters<T>) {
  const ret = <T>{};

  for (const x in internal) {
    let prop: PropertyDescriptor;

    type P = Extract<keyof T,string>

    const setterOrNested = setters[x] as NestedSetters<T[P]> | Setter<T[P]>

    if (isNestedSetters<T[P]>(setterOrNested)) {
      prop = {
        value: makeObjectSetterRecursive(internal[x],setterOrNested),};
    } else {
      prop = {
        set: setterOrNested,get: () => internal[x],};
    }

    prop.enumerable = true;

    Object.defineProperty(ret,x,prop);
  }

  return ret;
}

应输出以下内容:

const myObject = {
  num: 42,str: 'initialValue',others: {
    bool: true,nestedStr: ''
  }
};

const protectedObject = makeObjectSetterRecursive(myObject,{
  num(x) {
    // Make sure positive
    myObject.num = Math.max(x,0);
  },str(s) {
    // Always double the input
    myObject.str = s + s;
  },others: {
    bool(b) {
      // Toggle
      myObject.others.bool = !b
    },nestedStr(s) {
      // Add 3 dots
      myObject.others.nestedStr = s + '...'
    }
  }
});

console.log(myObject);
// { num: 42,others: { bool: true,nestedStr: '' } }

protectedObject.num = -1;
protectedObject.str = 'a';
console.log(myObject);
// { num: 0,str: 'aa',nestedStr: '' } }

protectedObject.others.bool = true;
protectedObject.others.nestedStr = 'abc';
console.log(myObject);
// { num: 0,others: { bool: false,nestedStr: 'abc...' } }

我必须说,我不太确定用例,但这是一个有趣的概念,所以我还是决定尝试一下。

本文链接:https://www.f2er.com/3129736.html

大家都在问