TypeScript:在 PascalCase 和 camelCase 之间切换时出现奇怪的行为
TypeScript: Strange behaviour when switching between PascalCase and camelCase
我正在将 C# 桌面应用程序重构为 Angular/TypeScript Web 应用程序。
C# 应用程序中的所有 class 属性都使用 PascalCase,因此我认为,最好保留它。
这里有 2 个基本相同的 TypeScript 示例 class。数字 1 使用 PascalCase,数字 2 使用驼峰式:
//PascalCase
export class Info
{
public ManagedType:string;
public ApiTemplate:string;
}
//camelCase
export class Info
{
public managedType:string;
public apiTemplate:string;
}
这是奇怪的行为:
我从网络服务器加载 JSON 数据并创建上述信息 class 的数组。这似乎并不重要,如果 TypeScript class 使用 PascalCase 或 camelCase。到目前为止,还不错。
this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
当我将我的数组记录到控制台时,我可以看到,输出使用驼峰式命名的属性,无论信息 class 使用 PascalCase 还是驼峰式命名。有点奇怪,但到目前为止还不错。
现在这里对我来说变得很奇怪:当我使用 PascalCase 并过滤数组时,获取信息的一个特定实例 class , 结果总是 undefined/null.
当我使用 camelCase 并过滤数组时,为了获取 Info class 的一个特定实例,结果被找到并正确。
//This doesn't work: Info is always undefinded, although the Array exists.
let Info = Infos.filter(i => i.ManagedType == "Something" && i.ApiTemplate == "Something else")[0];
//This works: Info is found
let Info = Infos.filter(i => i.managedType == "Something" && i.apiTemplate == "Something else")[0];
我的问题是:
为什么会这样?这是 TypeScript 问题还是 Angular 问题?
是否有必须遵守的不成文约定?
为什么 TypeScript 编译器不抛出错误或警告,即使用 PascalCase 可能无法正常工作?
Why is that so? Is that a TypeScript issue or is this an Angular issue?
都没有。问题的原因是来自您的网络服务器的 json 数据与您在打字稿中定义的 class 信息不完全相同 structure/format。
Is there an unwritten convention that I have to follow?
嗯,是的。在将它们转换为特定的 classes 之前,您应该手动测试并确保您确实获得了正确的数据结构。为澄清起见,您应该获取 json(HTTP 响应的主体),将其解析为通用对象的 JSON,然后测试它是否确实具有所有属性(具有相同的名称和types) 作为 class (信息),你将把它们投射到。然后去做。
更新:实际上有一种很酷的方法可以确定一个对象是否是
一个特定的类型并让打字稿知道这提供了强大的 assurnace/type 保护。 Typescript 具有称为 User-defined Typeguard functions 的功能,您可以在其中定义一个 return 为 true 或 false 的函数,如果
对象被测试为特定类型。
// user-defined type-guard function
function isInfo(obj: Object): obj is Info {
if ('ManagedType' in obj && 'ApiTemplate' in obj) {
return true;
} else {
// object does not have the required structure for Info class
return false;
}
}
// lets assume that jsonString is a string that comes from an
// http response body and contains json data. Parse it "blindly" to a generic object
let obj = JSON.parse(jsonString);
if (isInfo(obj)) {
obj.ApiTemplate; // typescript in this scope knows that obj is of type Info
} else {
// in this scope, typescript knows that obj is NOT of type Info
}
Why doesn't the TypeScript compiler throw an error or a warning, that using PascalCase may not work properly?
因为您在使用 this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
时使用了隐式转换,所以您告诉打字稿‘嘿,我知道在运行时,服务器将发送一个 json 字符串,该字符串将被解析并将与 Info[]
(信息对象数组)绝对完全兼容。但实际上在运行时这不会发生,因为 属性 名称的区分大小写存在细微差别。 Typescript 不会在这里出错,因为你隐含地告诉它你知道你在做什么。
所以详细说明:
很明显,您在运行时转换一个 JSON 对象,该对象与隐式转换为的 Info class 定义不完全兼容。 json 数据实际上具有 属性 驼峰式命名,但您已使用 PascalName 定义了信息 class。看看这个例子:
//PascalCase
class Info
{
public ManagedType:string;
public ApiTemplate:string;
}
let jsonString = `{
"managedType": "1234asdf",
"apiTemplate": "asdf1234"
}`;
// And HERE IS THE ISSUE. This does an implicit cast to Info object
// assuming that the JSON parsed object will strictly be the same as defined Info
// class. But that is not guaranteed. Typescript just assumes that you know
// what you are doing and what kind of data you will actually get in
// runtime.
let obj: Info = JSON.parse(jsonString);
上面示例的最后一行与此完全相同 'blind' casting/converting:
this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
本质上你是在告诉 typescript 响应将是一个信息数组 classes 定义与 class 定义完全相同,但实际上在实际的 json 数据中它们是不,因此 JSON.parse() 将 return 一个具有 属性 名称的对象与它们在 json 字符串中的名称完全相同,在驼峰式而不是 PascalCase 中让打字稿假设。
// typescript just assumes that the obj will have PascalCase properties
// and doesn't complain. but in reality this at runtime will not work,
// because the json properties in the json string are camelCase. Typescript
// can not know what data you will actually cast to this type in runtime.
// and cannot catch this error
console.log(`accessing Info.ManagedType property: ${obj.ManagedType}`);
// lets check at runtime all the actual properties names
// they will be in camelCase, just like in the jsonString.
Object.keys(obj).forEach(key => {
console.log(`found property name: ${key}`);
});
我正在将 C# 桌面应用程序重构为 Angular/TypeScript Web 应用程序。
C# 应用程序中的所有 class 属性都使用 PascalCase,因此我认为,最好保留它。
这里有 2 个基本相同的 TypeScript 示例 class。数字 1 使用 PascalCase,数字 2 使用驼峰式:
//PascalCase
export class Info
{
public ManagedType:string;
public ApiTemplate:string;
}
//camelCase
export class Info
{
public managedType:string;
public apiTemplate:string;
}
这是奇怪的行为:
我从网络服务器加载 JSON 数据并创建上述信息 class 的数组。这似乎并不重要,如果 TypeScript class 使用 PascalCase 或 camelCase。到目前为止,还不错。
this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
当我将我的数组记录到控制台时,我可以看到,输出使用驼峰式命名的属性,无论信息 class 使用 PascalCase 还是驼峰式命名。有点奇怪,但到目前为止还不错。
现在这里对我来说变得很奇怪:当我使用 PascalCase 并过滤数组时,获取信息的一个特定实例 class , 结果总是 undefined/null.
当我使用 camelCase 并过滤数组时,为了获取 Info class 的一个特定实例,结果被找到并正确。
//This doesn't work: Info is always undefinded, although the Array exists. let Info = Infos.filter(i => i.ManagedType == "Something" && i.ApiTemplate == "Something else")[0]; //This works: Info is found let Info = Infos.filter(i => i.managedType == "Something" && i.apiTemplate == "Something else")[0];
我的问题是:
为什么会这样?这是 TypeScript 问题还是 Angular 问题?
是否有必须遵守的不成文约定?
为什么 TypeScript 编译器不抛出错误或警告,即使用 PascalCase 可能无法正常工作?
Why is that so? Is that a TypeScript issue or is this an Angular issue?
都没有。问题的原因是来自您的网络服务器的 json 数据与您在打字稿中定义的 class 信息不完全相同 structure/format。
Is there an unwritten convention that I have to follow?
嗯,是的。在将它们转换为特定的 classes 之前,您应该手动测试并确保您确实获得了正确的数据结构。为澄清起见,您应该获取 json(HTTP 响应的主体),将其解析为通用对象的 JSON,然后测试它是否确实具有所有属性(具有相同的名称和types) 作为 class (信息),你将把它们投射到。然后去做。
更新:实际上有一种很酷的方法可以确定一个对象是否是 一个特定的类型并让打字稿知道这提供了强大的 assurnace/type 保护。 Typescript 具有称为 User-defined Typeguard functions 的功能,您可以在其中定义一个 return 为 true 或 false 的函数,如果 对象被测试为特定类型。
// user-defined type-guard function
function isInfo(obj: Object): obj is Info {
if ('ManagedType' in obj && 'ApiTemplate' in obj) {
return true;
} else {
// object does not have the required structure for Info class
return false;
}
}
// lets assume that jsonString is a string that comes from an
// http response body and contains json data. Parse it "blindly" to a generic object
let obj = JSON.parse(jsonString);
if (isInfo(obj)) {
obj.ApiTemplate; // typescript in this scope knows that obj is of type Info
} else {
// in this scope, typescript knows that obj is NOT of type Info
}
Why doesn't the TypeScript compiler throw an error or a warning, that using PascalCase may not work properly?
因为您在使用 this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
时使用了隐式转换,所以您告诉打字稿‘嘿,我知道在运行时,服务器将发送一个 json 字符串,该字符串将被解析并将与 Info[]
(信息对象数组)绝对完全兼容。但实际上在运行时这不会发生,因为 属性 名称的区分大小写存在细微差别。 Typescript 不会在这里出错,因为你隐含地告诉它你知道你在做什么。
所以详细说明:
很明显,您在运行时转换一个 JSON 对象,该对象与隐式转换为的 Info class 定义不完全兼容。 json 数据实际上具有 属性 驼峰式命名,但您已使用 PascalName 定义了信息 class。看看这个例子:
//PascalCase
class Info
{
public ManagedType:string;
public ApiTemplate:string;
}
let jsonString = `{
"managedType": "1234asdf",
"apiTemplate": "asdf1234"
}`;
// And HERE IS THE ISSUE. This does an implicit cast to Info object
// assuming that the JSON parsed object will strictly be the same as defined Info
// class. But that is not guaranteed. Typescript just assumes that you know
// what you are doing and what kind of data you will actually get in
// runtime.
let obj: Info = JSON.parse(jsonString);
上面示例的最后一行与此完全相同 'blind' casting/converting:
this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
本质上你是在告诉 typescript 响应将是一个信息数组 classes 定义与 class 定义完全相同,但实际上在实际的 json 数据中它们是不,因此 JSON.parse() 将 return 一个具有 属性 名称的对象与它们在 json 字符串中的名称完全相同,在驼峰式而不是 PascalCase 中让打字稿假设。
// typescript just assumes that the obj will have PascalCase properties
// and doesn't complain. but in reality this at runtime will not work,
// because the json properties in the json string are camelCase. Typescript
// can not know what data you will actually cast to this type in runtime.
// and cannot catch this error
console.log(`accessing Info.ManagedType property: ${obj.ManagedType}`);
// lets check at runtime all the actual properties names
// they will be in camelCase, just like in the jsonString.
Object.keys(obj).forEach(key => {
console.log(`found property name: ${key}`);
});