强类型代码简介
让我们来学习强类型代码及其对应用程序开发的影响。
什么是强类型代码?
强类型代码是一种编程技术,它明确地定义变量及其数据类型。换句话说,变量的值必须与其指定的数据类型一致。让我们通过两个示例来对比强类型和弱类型代码:
sum.js
1function sum(num1, num2) { 2 return num1 + num2; 3}
sum.ts
1function sum(num1: number, num2: number): number { 2 return num1 + num2; 3}
在上述示例中,我们展示了相同函数 sum() 的两种实现:一种是弱类型的 JavaScript,另一种是强类型的 TypeScript。两者唯一的区别在于 TypeScript 示例中的类型声明。函数参数 num1 和 num2 被赋予了 number 类型,函数 sum() 的返回类型也明确指定为 number。
为什么强烈推荐使用强类型?
目前我们在 Angular 应用中尚未使用强类型代码,而是将所有地方的数据类型都设为 any。让我们以 products.service.ts 文件中的 getProducts() 函数为例,来理解使用 any 类型带来的问题。
src/app/products.service.ts
1import { Injectable } from '@angular/core'; 2import { HttpClient, HttpHeaders } from '@angular/common/http'; 3import { catchError, Observable, throwError } from 'rxjs'; 4 5@Injectable({ 6 providedIn: 'root', 7}) 8export class ProductsService { 9 private BASE_URL: string = `api`; 10 private PRODUCT_URL: string = 'api/products'; 11 private SHOPPING_CART_URL: string = 'api/shoppingCart'; 12 13 constructor(private httpClient: HttpClient) {} 14 15 public getProducts(): Observable<any[]> { 16 return this.httpClient 17 .get<any[]>(this.PRODUCT_URL) 18 .pipe(catchError(this.errorHandler)); 19 } 20 21 public getProduct(productId: number): Observable<any> { 22 return this.httpClient 23 .get<any>(`${this.PRODUCT_URL}/${productId}`) 24 .pipe(catchError(this.errorHandler)); 25 } 26 27 public getShoppingCart(): Observable<any[]> { 28 return this.httpClient 29 .get<any[]>(this.SHOPPING_CART_URL) 30 .pipe(catchError(this.errorHandler)); 31 } 32 33 public addToShoppingCart(product: any) { 34 const headers = { 35 headers: new HttpHeaders({ 'Content-Type': 'application/json' }), 36 }; 37 38 return this.httpClient 39 .post<any>(`${this.BASE_URL}/shoppingCart`, product, headers) 40 .pipe(catchError(this.errorHandler)); 41 } 42 43 private errorHandler(err: any) { 44 let errMsg: string; 45 46 if (err.error instanceof ErrorEvent) { 47 errMsg = `前端错误: ${err.error.message}`; 48 } else { 49 errMsg = `后端错误码: ${err.status}: ${err.body.error}`; 50 } 51 52 return throwError(errMsg); 53 } 54}
我们已将 getProducts() 的返回数据类型指定为 Observable<any[]>。这意味着返回的数组可以包含任意类型的数据。由于我们的代码未进行强类型定义,无法判断数据是否符合预期结构。
为了说明这一点,我们手动修改后端的产品数据。在 getProducts() 函数的第 17 行,我们注释掉实际代码,改为返回一个字符串数组。在开发过程中,由于返回类型是 Observable<any[]>,IDE 不会提示任何错误。
src/app/products.service.ts
1import { Injectable } from '@angular/core'; 2import { HttpClient, HttpHeaders } from '@angular/common/http'; 3import { catchError, Observable, throwError } from 'rxjs'; 4 5@Injectable({ 6 providedIn: 'root', 7}) 8export class ProductsService { 9 private BASE_URL: string = `api`; 10 private PRODUCT_URL: string = 'api/products'; 11 private SHOPPING_CART_URL: string = 'api/shoppingCart'; 12 13 constructor(private httpClient: HttpClient) {} 14 15 public getProducts(): Observable<any[]> { 16 // 错误的数据 17 return of(['Product 1', 'Product 2']); 18 19 // return this.httpClient 20 // .get<any[]>(this.PRODUCT_URL) 21 // .pipe(catchError(this.errorHandler)); 22 } 23 24 public getProduct(productId: number): Observable<any> { 25 return this.httpClient 26 .get<any>(`${this.PRODUCT_URL}/${productId}`) 27 .pipe(catchError(this.errorHandler)); 28 } 29 30 public getShoppingCart(): Observable<any[]> { 31 return this.httpClient 32 .get<any[]>(this.SHOPPING_CART_URL) 33 .pipe(catchError(this.errorHandler)); 34 } 35 36 public addToShoppingCart(product: any) { 37 const headers = { 38 headers: new HttpHeaders({ 'Content-Type': 'application/json' }), 39 }; 40 41 return this.httpClient 42 .post<any>(`${this.BASE_URL}/shoppingCart`, product, headers) 43 .pipe(catchError(this.errorHandler)); 44 } 45 46 private errorHandler(err: any) { 47 let errMsg: string; 48 49 if (err.error instanceof ErrorEvent) { 50 errMsg = `前端错误: ${err.error.message}`; 51 } else { 52 errMsg = `后端错误码: ${err.status}: ${err.body.error}`; 53 } 54 55 return throwError(errMsg); 56 } 57}
由于在 products.selectors.ts 文件中将 getProductsSelector 的类型声明为 any,我们的 IDE 无法识别任何错误。同样的情况也出现在 products.component.ts 文件中。products$ 变量期望的是产品数组,但由于其类型被设为 any,我们无法发现潜在的错误。
src/app/products/state/products.selectors.ts
1import { createFeatureSelector } from "@ngrx/store"; 2 3export const getProductsSelector = createFeatureSelector<any>('products')
src/app/products/products.component.ts
1import { Component, OnInit } from '@angular/core'; 2import { ProductsService } from '../products.service'; 3import { Store } from '@ngrx/store'; 4import { getProductsAction } from './state/products.actions'; 5import { getProductsSelector } from './state/products.selectors'; 6@Component({ 7 selector: 'app-products', 8 templateUrl: './products.component.html', 9 styleUrls: ['./products.component.scss'], 10}) 11export class ProductsComponent implements OnInit { 12 products$: any = this.store.select<any>(getProductsSelector); 13 14 constructor(private store: Store, private productsService: ProductsService) {} 15 16 ngOnInit() { 17 this.getProducts(); 18 } 19 20 getProducts() { 21 this.productsService 22 .getProducts() 23 .subscribe((products: any) => (this.store.dispatch(getProductsAction({ products })))); 24 } 25}
点击下方“运行”按钮开始编译代码。如果我们关注终端输出,会发现代码成功编译且没有报错。然而,当我们浏览应用时,会发现首页并未显示任何产品信息。如果正确地使用强类型,我们可以在开发阶段就发现这类错误,从而确保应用在生产环境中正常运行。
我们将在此章节后续部分实现强类型,并深入学习相关内容。