4. TypeScript基础(下):函数定义、箭头函数、联合类型、泛型入门、模块导入导出
好,咱们接着往下聊。上一节我们把TypeScript的基础类型和接口过了一遍,这一节要讲的东西,说白了就是你在写ArkUI代码时每天都会碰到的——函数怎么定义、类型怎么灵活组合、还有怎么把代码拆成模块。
我个人觉得,这一节的内容比上一节更贴近实战。你想想看,一个鸿蒙应用少说几十个文件,函数成百上千,如果类型写不好,后面维护起来真的很痛苦。我曾经在一个项目里见过一个函数,参数类型写了any,结果调用的时候传了个字符串进去,运行时直接崩了……嗯,从那以后我对类型定义就特别较真。
4.1 函数定义:从基础到重载
TypeScript里的函数,和JavaScript最大的区别就是——参数和返回值都得有类型。别嫌麻烦,这其实是帮你省事。
// 基础函数定义
function greet(name: string): string {
return `你好,${name}!`;
}
// 可选参数
function buildUrl(base: string, path?: string): string {
return path ? `${base}/${path}` : base;
}
// 默认参数
function createButton(text: string, width: number = 100): string {
return `按钮: ${text}, 宽度: ${width}`;
}
// 剩余参数
function sum(...numbers: number[]): number {
return numbers.reduce((a, b) => a + b, 0);
}
函数重载这个特性,说实话在ArkUI开发中用得不算特别频繁,但一旦遇到就很香。比如你要写一个处理事件的方法,参数可能是点击事件也可能是滑动事件,这时候重载就派上用场了。
// 函数重载
function handleEvent(event: ClickEvent): void;
function handleEvent(event: SwipeEvent): void;
function handleEvent(event: any): void {
if (event.type === 'click') {
console.log('处理点击');
} else {
console.log('处理滑动');
}
}
4.2 箭头函数:简洁与this的陷阱
箭头函数在ArkUI里太常见了,尤其是写事件回调的时候。它的好处有两个:一是语法简洁,二是不绑定自己的this。
// 普通函数 vs 箭头函数
const numbers = [1, 2, 3];
// 普通函数
const doubled1 = numbers.map(function(n) {
return n * 2;
});
// 箭头函数
const doubled2 = numbers.map(n => n * 2);
为什么会说this是个陷阱?我刚开始写TypeScript时也踩过这个坑。在ArkUI的组件方法里,如果你用普通函数定义回调,this指向可能会丢失。箭头函数就没有这个问题,因为它会捕获定义时的this。
class MyComponent {
private count: number = 0;
// 这样写没问题
onClick = () => {
this.count++;
}
// 这样写可能会出问题
onSwipe() {
// 这里的this可能不是MyComponent的实例
this.count++; // 运行时可能报错
}
}
4.3 联合类型:让类型更灵活
联合类型说白了就是「或」的关系。一个变量可以是类型A,也可以是类型B。这在处理多种可能的数据时特别有用。
// 基础联合类型
let id: string | number;
id = 'abc123'; // 正确
id = 456; // 正确
// id = true; // 错误
// 联合类型与函数
function getLength(value: string | string[]): number {
if (typeof value === 'string') {
return value.length;
}
return value.length;
}
这里有个关键点——类型收窄。当你用typeof或者instanceof判断之后,TypeScript会自动缩小类型的范围。我个人习惯在写联合类型参数时,第一时间做类型判断,这样后面的代码就安全了。
// 类型收窄
function formatInput(input: string | number): string {
// 先收窄类型
if (typeof input === 'string') {
return input.toUpperCase();
}
// 到这里TypeScript知道input一定是number
return input.toFixed(2);
}
4.4 泛型入门:写一次,用多种类型
泛型这个概念,很多初学者会觉得抽象。我换个说法——你写一个函数,不想把参数类型写死,但又不想用any丢掉类型检查,这时候泛型就来了。
// 基础泛型函数
function firstElement<T>(arr: T[]): T | undefined {
return arr[0];
}
// 使用
const num = firstElement([1, 2, 3]); // num类型是 number
const str = firstElement(['a', 'b']); // str类型是 string
在ArkUI开发中,泛型最常用的场景是封装列表组件。比如你要写一个通用的列表,不管数据是用户信息还是商品信息,都能复用。
// 泛型接口
interface ListProps<T> {
data: T[];
renderItem: (item: T, index: number) => void;
}
// 泛型约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const user = { name: '张三', age: 25 };
const userName = getProperty(user, 'name'); // 类型是 string
4.5 模块导入导出:组织代码的艺术
模块化是大型项目的基石。TypeScript沿用了ES Module的语法,说白了就是export和import。
// 文件: types.ts
export interface User {
id: number;
name: string;
}
export const DEFAULT_PAGE_SIZE = 20;
// 默认导出
export default function createUser(name: string): User {
return { id: Date.now(), name };
}
// 文件: app.ts
import createUser, { User, DEFAULT_PAGE_SIZE } from './types';
const newUser = createUser('李四');
console.log(newUser.name); // 李四
这里有个细节——默认导出和命名导出的区别。我个人习惯一个文件只做一个默认导出,其他辅助类型和常量用命名导出。这样别人看你的代码时,一眼就知道这个文件主要提供什么功能。
// 重命名导入
import { User as AppUser } from './types';
// 整体导入
import * as Types from './types';
const user: Types.User = { id: 1, name: '王五' };
4.6 实战小技巧:组合使用
学完这些知识点,咱们看看怎么组合起来用。下面是一个ArkUI里常见的场景——封装一个通用的数据请求Hook。
// api.ts
export interface ApiResponse<T> {
code: number;
data: T;
message: string;
}
export async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
const response = await fetch(url);
return response.json();
}
// 使用
import { fetchData } from './api';
interface Product {
id: number;
name: string;
price: number;
}
async function loadProducts() {
const result = await fetchData<Product[]>('/api/products');
if (result.code === 200) {
console.log(result.data.length); // 类型安全
}
}
你看,泛型保证了返回数据的类型,联合类型可以处理不同的响应状态,模块化让代码结构清晰。这些知识点串起来,就是你在实际项目中每天都会用到的技能。
好了,这一节的内容就到这里。下一节我们会讲枚举和类型别名,这两个在定义常量状态时特别有用。记得多动手写代码,光看是学不会的。