在typescript中构建可重新序列化的 Package 器类

hpxqektj  于 2021-09-23  发布在  Java
关注(0)|答案(1)|浏览(363)

我正在开发一个用于复杂api模型的实用程序库。我们从wire接收json解析对象,并对其结构进行某种保证,如下所示:

// Known structure of the message:
interface InputBox {
  top: number;
  height: number;
  left: number;
  width: number;
}
interface InputObj {
  box: InputBox
}

// User code (outside our scope) does something like:
const inputObj: InputObj = JSON.parse(
  '{ "box": { "top": 0, "height": 10, "left": 1, "width": 2, "color": "red" } }'
);

我的目标是创建对象的一些视图,其中:
添加一些实用程序函数和属性
字符串化为与输入相同/兼容的json
当被视为可变时,行为一致
如果可能,保留任何意外属性
例如,用户代码可能如下所示:

// In-place or new obj constructor would both be fine:
const easyObj = myCoolLibrary(inputObj);

easyObj.box.top = 5;
console.log(easyObj.box.getBottom());  // '15'

JSON.stringify(easyObj);
// -> { "box": { "top": 5, "height": 10, "left": 1, "width": 2, "color": "red" } }
// (Note box.color still present, although missing from the input interface)

在阅读了一些关于选项的内容后,似乎:
使用object.setprototypeof直接覆盖输入中的原型可能有一个很好的就地解决方案,但这是最近的一个解决方案,不确定polyfilling是否适用于较旧的浏览器。
创建新对象(使用类/原型)并跨object.assign克隆属性可能会起作用,因为已知输入对象是简单/可复制的 Object Sie上仍然需要polyfill,但可能更标准一点?
问题是,即使这些方法是可行的,我也看不出它们是如何以一种让typescript满意的方式实现的?
例如:

class MyCoolBox implements InputBox {
  constructor(box: InputBox) {
    Object.assign(this, box);
  }

  getBottom() {
    return this.top + this.height;
  }
}
// > Class 'MyCoolBox' incorrectly implements interface 'InputBox'.
// (and doesn't recognise presence of .top, .height)

Object.setPrototypeOf(inputObj.box, MyCoolBox);
inputObj.box.getBottom();
// > Property 'getBottom' does not exist on type 'InputBox'
// Doesn't recognise the change of interface.

我是否错过了typescript可以理解的合理方法?这似乎是一个合理的要求,只是装饰一个json解析对象(已知接口)与一些方法!

qhhrdooz

qhhrdooz1#

首先,你似乎对如何做有误解 implements 作品属性是可识别的,只是您告诉编译器您的类实现了这些属性,但从未实际实现它们。是的,你做这个 Object.assign 在构造函数中,它在运行时实现了所需的结果,但编译器不知道这一点。
因为您显然不想详细说明 InputBox 在类上,解决方案是使用这样一个事实,即类具有绑定到它们的同名接口。因此,您可以使 MyCoolBox 接口是要扩展的接口的子类型(即 InputBox ):

interface MyCoolBox extends InputBox { }

class MyCoolBox {
  constructor(box: InputBox) {
    Object.assign(this, box);
  }

  getBottom() {
    return this.top + this.height;
  }
}

其次,你期望 setPrototypeOf 其行为类似于某种类型的guard,但其定义如下:

ObjectConstructor.setPrototypeOf(o: any, proto: object | null): any

也就是说,通过更改原型,编译器对原型形状的了解不会发生任何变化 box 对象:对于它来说,它仍然是一个 InputBox . 还有一个问题是 class javascript中的es主要是函数+基于原型的继承的语法糖。
设置 box 原型 MyCoolBox 当您尝试调用 getBottom 方法,因为它不是静态的,并且您仅以这种方式设置类的静态端。您实际上想要的是设置 MyCoolBox -这将设置示例属性:

const myCoolLibrary = <T extends InputObj>(input: T) => {
  Object.setPrototypeOf(input.box, MyCoolBox.prototype); //note the prototype
  return input as T & { box: MyCoolBox };
};

const enchanced = myCoolLibrary(inputObj);
const bottom = enchanced.box.getBottom(); //OK
console.log(bottom); //10

最后,根据上面的示例,您需要告诉编译器输出类型是增强类型。您可以通过一个简单的类型Assert来实现这一点( as T & { box: MyCoolBox } )如上所述,或者让编译器为您推断增强类型:

{
  const myCoolLibrary = (input: InputObj) => {
    return {
      ...input,
      box: new MyCoolBox( input.box )
    }
  };

  const enchanced = myCoolLibrary(inputObj);
  const bottom = enchanced.box.getBottom(); //OK
  console.log(bottom); //10
}

操场

相关问题