为什么我的Typescript“数字”字段是字符串?

时间:2019-08-02 09:39:58

标签: typescript

我有一个像这样的Typescript类:

    export class Contract {
        constructor (
           public id: string,
           public name: string,
           public spend: number
        ) {}
    }

这是由服务使用如下中间类加载的:

    export class ContractService {
        // the stuff you would expect

        public loadContracts() {
            this.httpService.get(this.contractEndpoint).subscribe((result) => this.createContracts(result));
        }

        private createContracts(contracts: Array<Contract>) {
            for ( let contract of contracts ) {
                console.log("Contract spend is "+contract.spend+": "+( typeof contract.spend));
            }
        }
    }

当我运行它时,在我的控制台中看到:

 Contract spend is 10000: string 
 Contract spend is 1222: string
 Contract spend is 20001: string

但是,如果我尝试使用parseInt(contract.spend),则Typescript编译器会拒绝,因为它知道contract.spend是一个数字,因此在编译时会知道该值是什么。

我假设正在发生的事情是我的rest服务中的JSON返回了spend字段作为引号,但是它似乎以一种无提示地失败的方式颠覆了Typescript的核心优势之一。我该怎么做才能确保我的数字字段中包含数字或错误输入的代码失败?

3 个答案:

答案 0 :(得分:1)

TypeScript在编译时有效,但可以编译为JavaScript。因此,在运行时,您会得到很好的旧JS。

这意味着:如果TypeScript不了解的服务为contract.spend提供了字符串值,则TypeScript无法知道它。

在这种情况下,如果您想利用静态类型,请让TypeScript知道HTTP调用的响应主体具有哪种类型。然后将响应从响应正文类型主动转换为您期望的类型。

例如:

type HttpContractResponse = {
    spend: string
}[];

export class ContractService {
    // the stuff you would expect

    constructor(private httpService: HttpService,
                private contractEndpoint = 'http://endpoint.com') {}

    public loadContracts() {
        this.httpService.get<HttpContractResponse>(this.contractEndpoint)
            .subscribe((result) => this.createContracts(result));
    }

    private createContracts(rawContracts: HttpContractResponse) {
        const contracts: Contract[] = rawContracts.map(rc => {return {
            ...rc,
            spend: +rc.spend
        }});
        for (let contract of contracts) {
            console.log('Contract spend is ' + contract.spend + ': ' + (typeof contract.spend));
        }
    }
}

答案 1 :(得分:1)

我建议您查看typescript-json-validator,它将提供类型安全验证,以确保某些未知的json与您的Typescript接口相匹配(您应该为JSON数据定义接口而不是类,它不能包含方法,因此一个接口就足够了来描述它)。

文件app/interfaces/contract.ts

export interface IContract {
    id: string;
    name: string;
    spend: number;
}

运行此命令(安装模块后),它将产生一个新文件contract.validator.js

yarn typescript-json-validator --collection --aliasRefs --noExtraProps --strictNullChecks --coerceTypes app/interfaces/contract.ts

这是一个使用示例:

import {validate} from "./interfaces/contract.validator";

const validateContract = validate('IContract');

const data: unknown = JSON.parse('{ "id": "42", "name": "Joe", "spend": "10000"}');
const contract = validateContract(data);
console.log(`Contract spend is ${contract.spend}: ${typeof contract.spend}` );

输出为:

Contract spend is 10000: number

此处contract的类型为IContract,所有类型都将匹配。您不必告诉打字稿const contract: IContract,因为它可以正确推断出打字稿的类型,但是您可以根据需要这样做。

如果JSON不包含正确的字段,或者它们不具有预期的类型,则它将引发错误。命令中的--coerceTypes选项允许进行一些转换,例如字符串到数字。您还可以在界面的注释中包括其他约束,例如正则表达式模式,请参见文档。如果将多个接口放在一个文件中,则--collection选项可确保它们均可用,只需创建一个单独的验证器,并传递每个接口的名称即可。

有一些烦人的限制,因此请在界面中使用简单的字符串和数字。例如不要使用Date作为类型,因为它将验证字段是一个包含ISO格式日期的字符串,但是它不会强制该类型,因此您仍然以字符串结尾。但是,您可以使用注释说出它是具有正确日期或日期时间格式的字符串,然后从经过验证的界面构造具有正确字段的类。

答案 2 :(得分:0)

TypeScript类型在编译过程中被删除-因为JavaScript(将TypeScript转换为它)没有静态类型的概念。因此,没有办法在运行时“开箱即用”期间确保该值具有正确的类型。