现在,TS 在类型、对象、接口和 JSON 反序列化方面很时髦。
让我们快速回顾一下记忆通道(对我来说)。
我们有一些对象,因为,你知道,OOP。
export class MyObject {
public someProperty: string;
public AddSomething(str: string): void {
this.someProperty += str;
}
}
多么令人愉快的小物件。
当我们从 API 得到响应时,似乎一切都很好;
[
{"someProperty": "hello"},
{"someProperty": "world"}
]
但是当我们用我们的对象做事并想利用提供的超级有用的方法时......
this.data.forEach((x: MyObject) => {
x.addSomething('!');
});
...它告诉我们对象上没有这样的方法addSomething!
唔?
所以,事情是这样的:类型是一种幻觉。
TS/JS 只是将 json 解析为一个基本对象,一个东西的字典。例如,与 C# 不同的是,反序列化某些东西实际上会创建该对象的实例,例如:
var result = JsonConvert.DeserialiseObject<IEnumerable<MyObject>>(str);
result 列表中的每一项都是一个具体的 MyObject 对象。在引擎盖下,它做了它的整个反射事情和诸如此类的事情,并且在物理上做了一个new MyObject()
在 TS 中,它只是一个字典/对象,不是一个具体的MyObject对象,所以它只是:
{someProperty: 'hello'}
并不是:
{someProperty: 'hello', addSomething: (str: string) => {...}}
所以,最终这就是你问的问题。如何推出自己的自动 C# 类 JSON 解析水化机制。
我们选择对我们的对象做一件相当奇怪的事情,那就是给他们时髦的构造函数:
export class MyObject {
public someProperty: string;
constructor(obj?: any) {
if (!obj) return;
this.someProperty = obj.someProperty;
}
}
这样我们就可以做到:
const concreteObjects: MyObject[] = [];
this.data.forEach((x: MyObject /*ButNotARealMyObject*/) => {
concreteObjects.push(new MyObject(x));
});
现在我们已经从无生命的属性包转换为我们可以使用的实际对象类型。万岁!
另一种方法是做一些更智能和可重用的事情。 this 之类的东西看起来很有前途......
看起来是一个有效使用“反射”的帮手——在 JS/TS 中只是在做Object.keys(someObj)——用一些我不知道存在的时髦语法/机制来遍历对象具有的所有属性,因为我还没有以前关心创建给定类型的实例。
仍然有点手动,因为一旦您收到回复,您需要输入const data = HydrationHelper.hydrate(MyObject, jsonData); 或其他任何内容。
每当您需要手动重复做某事时,这意味着您可以自动完成!
让自己成为 HttpClient 的包装器(假设您正在发出某种 API 请求来获取您的数据)——坦率地说,无论如何您都应该包装它,这样做有很多好处。
因此,您的服务不会直接使用HttpClient,而是使用您的新HttpService,这样您就可以在将响应返回给调用服务之前更改响应(补充您的响应)。
加上一小撮盐,就会产生这样的东西:
export class HttpService {
constructor(private readonly http: HttpClient) { }
public get<T>(url: string, queryParams: object): Observable<T> {
url = parseParams(url, queryParams);
this.http.get(url)
.pipe(map( /*map might not be the right function, I don't have my IDE up to make sure and at this point I want to go to bed*/
(x) => {
return HydrationHelper.hydrate(T, x);
}
)
);
}
}
像那样的东西。我不知道,会不会活着褪色。
我将最后一件事发送垃圾邮件,因为在链接失效的情况下有效地从任何链接复制相关内容是标准的,所以这里是 hydrate 函数:
function hydrate<T>(constr: { new(...args: any[]): T }, data: string, strictMode: boolean = true, ...args: any[]): T {
const obj = JSON.parse(data);
const instance = new constr(...args);
for (let key in obj) {
if (!strictMode || instance.hasOwnProperty(key)) {
instance[key] = obj[key];
}
}
return instance;
}
我对 HydrationHelper 的模糊引用将把它从 js 函数变成更打字的东西。我不知道为什么我选择静态类而不是可注入服务,这是明智的,但无论如何,它说明了这一点。
export class HydrationHelper {
public static hydrate<T>(constr: { new(...args: any[]): T }, data: string, strictMode: boolean = true, ...args: any[]): T {
const obj = JSON.parse(data);
const instance = new constr(...args);
for (let key in obj) {
if (!strictMode || instance.hasOwnProperty(key)) {
instance[key] = obj[key];
}
}
return instance;
}
}
哦,实际上,最后一件事——我的第一句话提到了接口,我没有做过。
我们不再为我们的基本 dto 打扰类。接口在 TS 的世界中发挥作用,因为无论如何您都无法获得对象类型的物理实例。使用 helpers/services/whatever 或者,是的,做所有这些额外的工作以获得正确的类型,以便使用各种函数来操作您的数据。
没有内部函数,这个接口和类一样好,你不会遇到任何反序列化混淆:
export interface MyObject {
someProperty: string;
}
编辑:稍后进行一些挖掘,看起来 TS 确实具有与 C# 相同的能力,能够从字符串中获取类型——只要字符串是确切的类型名称。
const typeName = 'number';
type myType = typeof typeName;
const x: myType = 5;
因此,鉴于此,稍微改变我的第一个垃圾邮件,我认为你应该能够做类似的事情:
type responseType = typeof response.typeName;
HydrationHelper.hydrate(responseType, response);
试试看?