简体   繁体   English

TypeScript:在PascalCase和camelCase之间切换时的奇怪行为

[英]TypeScript: Strange behaviour when switching between PascalCase and camelCase

I am refactoring a C# desktop application to an Angular/TypeScript web application. 我将C#桌面应用程序重构为Angular / TypeScript Web应用程序。

All class properties inside the C# application use PascalCase, so I thought, it would be a good idea to just keep that. C#应用程序中的所有类属性都使用PascalCase,所以我认为,仅保留它是一个好主意。

Here are 2 examples of basically the same TypeScript class. 这是两个基本上相同的TypeScript类的示例。 Number 1 uses PascalCase, number 2 uses camelCase: 1号使用PascalCase,2号使用camelCase:

//PascalCase
export class Info 
{
    public ManagedType:string;

    public ApiTemplate:string;
}

//camelCase
export class Info 
{
    public managedType:string;

    public apiTemplate:string;
}

Here is the strange behaviour: 这是奇怪的行为:

  1. I load JSON data from the webserver and create an array of the above Info class. 我从Web服务器加载JSON数据,并创建上述Info类的数组。 It doesn't seem to matter, if the TypeScript class uses PascalCase or camelCase. TypeScript类使用PascalCase还是camelCase似乎无关紧要。 So far, so good. 到现在为止还挺好。

     this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>(); 
  2. When I log my array to the console, I can see, that the output uses camelCase for the properties, no matter if the Info class uses PascalCase or camelCase. 当我将数组记录到控制台时,我可以看到,无论Info类使用PascalCase还是camelCase,输出都将camelCase用于属性。 A little strange, but so far so good. 有点奇怪,但到目前为止还算不错。

  3. Now here it became strange to me : When I use PascalCase and filter the array, to get one specific instance of the Info class, the result is always undefined/null. 现在,这对我来说变得很奇怪 :当我使用PascalCase并对数组进行过滤以获取Info类的一个特定实例时,结果始终是undefined / null。

  4. When I use camelCase and filter the array, to get one specific instance of the Info class, the result is found and correct. 当我使用camelCase并对数组进行过滤时,要获取Info类的一个特定实例,则会找到正确的结果。

     //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]; 

My questions are: 我的问题是:

Why is that so? 为什么会这样? Is that a TypeScript issue or is this an Angular issue? 这是TypeScript问题还是Angular问题?

Is there an unwritten convention that I have to follow? 是否有我必须遵循的不成文约定?

Why doesn't the TypeScript compiler throw an error or a warning, that using PascalCase may not work properly? 为什么TypeScript编译器没有抛出错误或警告,说明使用PascalCase可能无法正常工作?

Why is that so? 为什么会这样? Is that a TypeScript issue or is this an Angular issue? 这是TypeScript问题还是Angular问题?

Neither. 都不是。 The cause of the issue is that the json data that comes from your webserver is not in the exactly same structure/format that you have defined the class Info in typescript. 问题的原因是,来自Web服务器的json数据的结构/格式与您在打字稿中定义的Info类完全不同。

Is there an unwritten convention that I have to follow? 是否有我必须遵循的不成文约定?

Well, yes there is. 好吧,是的。 You should manually test and make sure that you actually get the correct data structures before casting them to specific classes. 您应该手动测试并确保在将它们强制转换为特定类之前确实获得了正确的数据结构。 To clarify, you should take the json (body of the HTTP response), parse it as JSON to a generic object, and then test it if it actually has exactly all the properties (with same name and types) as the class (Info) that you are about to cast them to. 为了明确起见,您应该获取json(HTTP响应的主体),将其作为JSON解析为一个通用对象,然后对其进行测试,以了解它实际上是否具有与类(Info)完全相同的所有属性(具有相同的名称和类型)。您将要投放到的对象。 And then do it. 然后做。

UPDATE: actually there is a cool way to determine if an object is a particular type and let typescript know about this providing a strong assurnace/type guarding. 更新:实际上,有一种很酷的方法来确定对象是否为特定类型,并让打字稿知道这一点,从而提供了强有力的保证/类型保护。 Typescript has this feature called User-defined Typeguard functions , where you define a function that returns true or false if an object is tested to be of a particular type. Typescript具有称为“ 用户定义的Typeguard函数”的此功能 ,您可以在其中定义一个函数,如果测试对象具有特定类型,则该函数返回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? 为什么TypeScript编译器没有抛出错误或警告,说明使用PascalCase可能无法正常工作?

Because you are using implicit cast when you are using the this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>(); 因为当您使用this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();时,您正在使用隐式this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>(); you telling typescript that 'hey I know that in runtime, the server will send a json string that will be parsed and will be absolutely exactly compatible with the Info[] (an array of Info Objects). 您告诉打字稿说:“嘿,我知道在运行时,服务器将发送一个json字符串,该字符串将被解析并且与Info[] (Info Objects的数组)绝对完全兼容。 But actually in runtime this doesn't happen because there is a small difference in the case sensitivity of the property names. 但是实际上在运行时不会发生这种情况,因为属性名称的大小写敏感性之间存在很小的差异。 Typescript will not error here because you implicitly told it that you know what you are doing. Typescript在这里不会出错,因为您隐式告诉它您知道自己在做什么。

So to elabarate: 所以要详细说明:

It is obvious that you are converting at runtime a JSON object that is not completely compatible with the Info class definition that you implicitly cast it to. 显然,您正在运行时转换一个与隐式将其强制转换为的Info类定义不完全兼容的JSON对象。 The json data actually has the property names with camelCase, but you have defined the Info class with PascalName. json数据实际上具有带有camelCase的属性名称,但是您已经使用PascalName定义了Info类。 Take a look at this example: 看一下这个例子:

//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); 

The last line of the above example does the same exact 'blind' casting/converting that this does: 上面示例的最后一行执行的操作完全相同,完全是“盲”转换/转换:

this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();

Essentially you are telling typescript that the response will be an Array of Info classes defined exactly as the class definition, but in reality in the actual json data they are not, the differ therefore the JSON.parse() will return an object that has the property names exactly as they are in the json string, in camelCase instead of PascalCase that you let typescript to assume. 从本质上讲,您是在告诉打字稿,该响应将是完全按照类定义定义的Info类数组,但是实际上,在实际的json数据中,它们不是,因此不同,因此JSON.parse()将返回具有以下内容的对象:属性名称与camelCase中的json字符串中的名称完全相同,而不是让Typescript假定的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}`);
});

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM