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

时间:2019-04-03 22:25:59

标签: arrays angular typescript camelcasing pascalcasing

我正在将C#桌面应用程序重构为Angular / TypeScript Web应用程序。

C#应用程序中的所有类属性都使用PascalCase,所以我认为,仅保留它是一个好主意。

这里有两个基本上相同的TypeScript类的示例。第1个使用PascalCase,第2个使用camelCase:

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

    public ApiTemplate:string;
}

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

    public apiTemplate:string;
}

这是奇怪的行为:

  1. 我从Web服务器加载JSON数据,并创建上述Info类的数组。 TypeScript类使用PascalCase还是camelCase似乎无关紧要。到目前为止,一切都很好。

    this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
    
  2. 当我将数组记录到控制台时,我可以看到,无论Info类使用PascalCase还是camelCase,输出都将camelCase用作属性。有点奇怪,但到目前为止还不错。

  3. 现在这对我来说很奇怪:当我使用PascalCase并对数组进行过滤时,要获取Info类的一个特定实例,结果始终是不确定的/为空。 p>

  4. 当我使用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];
    

我的问题是:

为什么会这样?是TypeScript问题还是Angular问题?

是否有我必须遵循的不成文约定?

为什么TypeScript编译器没有抛出错误或警告,说明使用PascalCase可能无法正常工作?

1 个答案:

答案 0 :(得分:1)

  

为什么会这样?是TypeScript问题还是Angular问题?

都不是。造成此问题的原因是,来自Web服务器的json数据与您在Typescript中定义Info类的结构/格式不完全相同。

  

是否有我必须遵循的不成文约定?

嗯,是的。您应该手动测试并确保在将它们强制转换为特定类之前确实获得了正确的数据结构。为了明确起见,您应该获取json(HTTP响应的主体),将其作为JSON解析为一个通用对象,然后对其进行测试,以了解它实际上是否具有与类(Info)完全相同的所有属性(具有相同的名称和类型)。您将要投放到的对象。然后做。

更新:实际上有一种很酷的方法来确定对象是否为 特定类型,并让打字稿知道这一点,以提供强大的保证/类型保护。 Typescript具有称为User-defined Typeguard functions的功能,您可以在其中定义一个返回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
}
  

为什么TypeScript编译器没有抛出错误或警告,说明使用PascalCase可能无法正常工作?

因为在使用this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();时您正在使用隐式强制类型转换,所以您告诉打字稿'嘿,我知道在运行时,服务器将发送一个json字符串,该字符串将被解析,并且与Info[](一组信息对象)。但是实际上在运行时不会发生这种情况,因为属性名称的大小写敏感性之间存在很小的差异。这里的Typescript不会出错,因为您暗中告诉它您知道自己在做什么。

所以要讲清楚:

很明显,您正在运行时转换一个与隐式转换为Info类定义不完全兼容的JSON对象。 json数据实际上具有带有camelCase的属性名称,但是您已经使用PascalName定义了Info类。看一下这个例子:

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

以上示例的最后一行执行的操作完全相同,完全是“盲”转换/转换:

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

基本上,您是在告诉打字稿,该响应将是完全按照类定义定义的Info类数组,但实际上,在实际的json数据中,它们不是,因此,JSON.parse()将返回一个对象具有与json字符串中的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}`);
});

相关问题