TypeScript: странное поведение при переключении между PascalCase и camelCase - PullRequest
0 голосов
/ 04 апреля 2019

Я выполняю рефакторинг настольного приложения C # в веб-приложение Angular / TypeScript.

Все свойства класса в приложении C # используют PascalCase, поэтому я подумал, что было бы неплохо просто сохранить это.

Вот 2 примера одного и того же класса 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. Я загружаю данные JSON с веб-сервера и создаю массив извыше инфо класса.Кажется, это не имеет значения, если класс TypeScript использует PascalCase или camelCase.Пока все хорошо.

    this.Infos = await this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();
    
  2. Когда я записываю свой массив в консоль, я вижу, что выходные данные используют camelCase для свойств, независимо от того, использует ли класс InfoPascalCase или CamelCase.Немного странно, но пока все хорошо.

  3. Теперь мне стало странно : когда я использую PascalCase и фильтрую массив, чтобы получить один конкретный экземпляркласса Info, результат всегда равен undefined / null.

  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 Ответ

1 голос
/ 04 апреля 2019

Почему это так?Это проблема TypeScript или проблема Angular?

Ни то, ни другое.Причиной проблемы является то, что данные json, которые поступают с вашего веб-сервера, не имеют ту же структуру / формат, в котором вы определили класс Info в машинописи.

Существует ли неписанное соглашение, которое ядолжны следовать?

Ну да, есть.Вы должны вручную проверить и убедиться, что вы действительно получили правильные структуры данных, прежде чем приводить их к определенным классам.Чтобы уточнить, вы должны взять json (тело HTTP-ответа), проанализировать его как JSON в универсальном объекте, а затем проверить, действительно ли он имеет все свойства (с тем же именем и типами), что и у класса (Info)что вы собираетесь бросить их.А затем сделайте это.

ОБНОВЛЕНИЕ: на самом деле, есть классный способ определить, является ли объект определенным типом, и сообщить машинописи об этом, обеспечивая надежную защиту типа / типа.В 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
}

Почему компилятор TypeScript не выдает ошибку или предупреждение о том, что использование PascalCase может работать некорректно?

Поскольку вы используете неявное приведение при использовании this.HttpClient.get<Info[]>(this.Url).toPromise<Info[]>();, который вы указали для машинописного текста, что«эй, я знаю, что во время выполнения сервер отправит строку json, которая будет проанализирована и будет абсолютно точно совместима с Info[] (массивом информационных объектов).Но на самом деле во время выполнения этого не происходит, потому что есть небольшая разница в чувствительности к регистру имен свойств.Typescript здесь не будет ошибкой, потому что вы неявно сказали ему, что знаете, что делаете.

Итак, чтобы сказать:

Очевидно, что во время выполнения вы конвертируете объект JSON, который не является полностьюсовместим с определением класса Info, к которому вы неявно приводите его.Данные json на самом деле имеют имена свойств с помощью camelCase, но вы определили класс Info с помощью PascalName.Взгляните на этот пример:

//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[]>();

По сути, вы говорите машинописному тексту, что ответом будет класс Array of Info, определенный точно как определение класса, но в действительности в реальных данных json они не различаются, поэтому JSON.parse () вернет объект, которыйимеет имена свойств в точности такие, как они есть в строке json, в camelCase вместо 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}`);
});

...