返回首页

01-基础类型与接口

分类:04-TypeScript类型系统
发布于:
阅读时间:56 分钟

基础类型与接口

📋 学习目标

  • 掌握TypeScript的基本数据类型
  • 理解接口的定义和使用方法
  • 学会类型别名和类型断言的应用
  • 掌握联合类型和交叉类型的使用

🎯 环境配置

1. 安装和配置TypeScript

# 全局安装TypeScript
npm install typescript -g

# 项目安装
npm install typescript --save-dev

# 安装类型声明文件
npm install @types/node --save-dev

# 安装ts-node用于直接运行TS文件
npm install ts-node -g

2. 基本编译命令

# 编译TS文件
tsc filename.ts

# 编译并自动运行
ts-node filename.ts

# 监听模式自动编译
tsc --watch filename.ts

🔧 基础数据类型

1. 原始类型

// Boolean类型
let isDone: boolean = false;
let isVisible: boolean = true;

// Number类型
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let float: number = 3.14;

// String类型
let color: string = "blue";
let templateString: string = `This is a template string with ${color}`;

// 注意:Boolean构造函数创建的是对象,不是布尔值
// ❌ 错误
let createdByBoolean: boolean = new Boolean(1); // Type 'Boolean' is not assignable to type 'boolean'

// ✅ 正确
let createdByBoolean: boolean = Boolean(1); // true
let isTrue: boolean = true;

2. 特殊类型

// Undefined类型
let u: undefined = undefined;

// Null类型
let n: null = null;

// Void类型 - 表示没有返回值
function warnUser(): void {
    console.log("This is a warning message");
}

// Never类型 - 表示永不存在的值的类型
function error(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

// Any类型 - 任意类型(尽量避免使用)
let notSure: any = 4;
notSure = "maybe a string";
notSure = false;

// Unknown类型 - 类型安全的any
let value: unknown = "hello world";

// ❌ 错误:unknown类型不能直接操作
// let upperCaseValue: string = value.toUpperCase();

// ✅ 正确:需要类型检查
if (typeof value === "string") {
    let upperCaseValue: string = value.toUpperCase();
}

3. 对象类型

// Array类型
let list1: number[] = [1, 2, 3];
let list2: Array<number> = [1, 2, 3];
let list3: string[] = ["a", "b", "c"];
let list4: any[] = [1, "two", true];

// 元组类型 - 固定长度和类型的数组
let tuple1: [string, number] = ["hello", 10];
tuple1[0] = "world";
// tuple1[0] = 10; // ❌ 错误:Type 'number' is not assignable to type 'string'

// 可选元组元素
let tuple2: [number, string?, boolean?] = [1];
let tuple3: [number, string?, boolean?] = [1, "hello"];
let tuple4: [number, string?, boolean?] = [1, "hello", true];

// 只读元组
let readonlyTuple: readonly [number, string] = [1, "hello"];
// readonlyTuple[0] = 2; // ❌ 错误:Cannot assign to '0' because it is a read-only property

// Object类型
let obj: object = {prop: 0};
let obj2: { name: string; age: number } = {
    name: "张三",
    age: 25
};

// Function类型
let myAdd: (x: number, y: number) => number = function(x: number, y: number): number {
    return x + y;
};

// 函数声明
function add(x: number, y: number): number {
    return x + y;
}

// 函数表达式
const subtract = function(x: number, y: number): number {
    return x - y;
};

// 箭头函数
const multiply = (x: number, y: number): number => x * y;

🎯 接口(Interface)

1. 基本接口定义

// 基本接口
interface Person {
    name: string;
    age: number;
}

// 使用接口
let user: Person = {
    name: "张三",
    age: 25
};

// ❌ 错误:缺少必需属性
// let user2: Person = {
//     name: "李四"
// };

// ❌ 错误:属性类型不匹配
// let user3: Person = {
//     name: "王五",
//     age: "25" // Type 'string' is not assignable to type 'number'
// };

2. 可选属性

interface User {
    id: number;
    name: string;
    age?: number;  // 可选属性
    email?: string; // 可选属性
}

let user1: User = {
    id: 1,
    name: "张三"
};

let user2: User = {
    id: 2,
    name: "李四",
    age: 30,
    email: "lisi@example.com"
};

3. 只读属性

interface Config {
    readonly id: number;
    readonly apiUrl: string;
    timeout: number;
}

let config: Config = {
    id: 1,
    apiUrl: "https://api.example.com",
    timeout: 5000
};

// ❌ 错误:Cannot assign to 'id' because it is a read-only property
// config.id = 2;

// ✅ 正确:可以修改非只读属性
config.timeout = 3000;

4. 任意属性

interface FlexiblePerson {
    name: string;
    age?: number;
    [propName: string]: any; // 允许任意属性
}

let person: FlexiblePerson = {
    name: "张三",
    gender: "male", // 额外属性
    city: "北京",   // 额外属性
    hobby: ["coding", "reading"]
};

// ⚠️ 注意:当有任意属性时,其他属性类型必须兼容
interface Problematic {
    name: string;
    [propName: string]: number; // ❌ 错误:string类型不能赋值给number
}

// ✅ 正确:使用联合类型
interface Fixed {
    name: string;
    [propName: string]: string | number;
}

5. 函数类型接口

interface SearchFunc {
    (source: string, subString: string): boolean;
}

let mySearch: SearchFunc = function(source: string, subString: string): boolean {
    return source.search(subString) > -1;
};

// 简化的函数类型
let mySearch2: (source: string, subString: string) => boolean = function(source, subString) {
    return source.search(subString) > -1;
};

6. 接口继承

// 单继承
interface Animal {
    name: string;
}

interface Dog extends Animal {
    breed: string;
}

let myDog: Dog = {
    name: "旺财",
    breed: "金毛"
};

// 多继承
interface Shape {
    color: string;
}

interface Square {
    sideLength: number;
}

interface ColoredSquare extends Shape, Square {
    borderColor: string;
}

let mySquare: ColoredSquare = {
    color: "red",
    sideLength: 10,
    borderColor: "black"
};

7. 混合类型接口

interface Counter {
    (start: number): string; // 函数
    interval: number;        // 属性
    reset(): void;          // 方法
}

function getCounter(): Counter {
    let counter = (function(start: number) {
        return start.toString();
    }) as Counter;

    counter.interval = 123;
    counter.reset = function() {
        console.log("Reset called");
    };

    return counter;
}

let c = getCounter();
console.log(c(10));
c.reset();
c.interval = 5.0;

🔧 类型别名(Type Alias)

1. 基本类型别名

// 简单类型别名
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
    if (typeof n === "string") {
        return n;
    } else {
        return n();
    }
}

// 对象类型别名
type Person = {
    name: string;
    age: number;
};

// 函数类型别名
type AddFunction = (a: number, b: number) => number;
const add: AddFunction = (a, b) => a + b;

2. 泛型类型别名

type Container<T> = { value: T };
type Tree<T> = {
    value: T;
    left?: Tree<T>;
    right?: Tree<T>;
};

// 使用
let numberContainer: Container<number> = { value: 42 };
let stringTree: Tree<string> = {
    value: "root",
    left: { value: "left" },
    right: { value: "right" }
};

3. 联合类型与交叉类型

// 联合类型(Union Types)
type Status = 'pending' | 'success' | 'error';
type ID = string | number;

function printId(id: ID) {
    console.log(`ID: ${id}`);
}

printId(101); // ID: 101
printId("ABC123"); // ID: ABC123

// 交叉类型(Intersection Types)
interface BusinessPartner {
    name: string;
    credit: number;
}

interface Identity {
    id: number;
    email: string;
}

type Employee = BusinessPartner & Identity;

// 使用
let emp: Employee = {
    name: "John Doe",
    credit: 7000,
    id: 100,
    email: "john.doe@example.com"
};

🎯 类型断言(Type Assertion)

1. 基本类型断言

// 尖括号语法(JSX中不可用)
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;

// as语法(推荐)
let someValue2: any = "this is a string";
let strLength2: number = (someValue2 as string).length;

2. 联合类型断言

interface Cat {
    type: 'cat';
    purr(): void;
}

interface Dog {
    type: 'dog';
    bark(): void;
}

type Animal = Cat | Dog;

function makeSound(animal: Animal) {
    if ((animal as Cat).type === 'cat') {
        (animal as Cat).purr();
    } else {
        (animal as Dog).bark();
    }
}

3. 非空断言

// 可能是null或undefined的值
let value: string | null = "hello";

// ❌ 错误:Object is possibly 'null'
// console.log(value.length);

// ✅ 使用非空断言
console.log(value!.length);

// 在函数中使用
function processValue(value: string | undefined) {
    // 非空断言告诉TypeScript value不是undefined
    const processedValue = value!.toUpperCase();
    return processedValue;
}

4. const断言

// 普通数组
let array1 = [1, 2, 3];
array1.push(4); // 可以修改

// const断言 - 创建只读字面量类型
let array2 = [1, 2, 3] as const;
// array2.push(4); // ❌ 错误:Property 'push' does not exist on type 'readonly [1, 2, 3]'

// 对象const断言
let obj1 = { x: 10, y: 20 };
obj1.x = 30; // 可以修改

let obj2 = { x: 10, y: 20 } as const;
// obj2.x = 30; // ❌ 错误:Cannot assign to 'x' because it is a read-only property

// 字面量类型推断
let direction = "north"; // 类型是string
let direction2 = "north" as const; // 类型是"north"

🚀 高级类型技巧

1. 条件类型

// 基本条件类型
type IsString<T> = T extends string ? true : false;

type Test1 = IsString<string>; // true
type Test2 = IsString<number>; // false

// 条件类型与映射
type NonNullable<T> = T extends null | undefined ? never : T;

type Test3 = NonNullable<string | null>; // string
type Test4 = NonNullable<number | undefined>; // number

2. 映射类型

// 基本映射类型
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type Partial<T> = {
    [P in keyof T]?: T[P];
};

// 使用
interface User {
    name: string;
    age: number;
    email: string;
}

type ReadonlyUser = Readonly<User>;
type PartialUser = Partial<User>;

// 自定义映射类型
type Stringify<T> = {
    [P in keyof T]: string;
};

type StringifiedUser = Stringify<User>;
// { name: string; age: string; email: string; }

3. 索引类型

// 索引访问类型
interface Person {
    name: string;
    age: number;
    address: string;
}

type PersonName = Person['name']; // string
type PersonKeys = keyof Person; // "name" | "age" | "address"

// 泛型索引类型
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

let person: Person = {
    name: "张三",
    age: 25,
    address: "北京"
};

let name = getProperty(person, "name"); // string
// let age = getProperty(person, "salary"); // ❌ 错误:Argument of type '"salary"' is not assignable to parameter of type '"name" | "age" | "address"'

⚠️ 常见陷阱

1. 类型推断陷阱

// ❌ 错误的类型推断
let a = []; // 推断为 any[]
a.push(1);
a.push("string");

// ✅ 明确指定类型
let b: number[] = [];
// 或者
let c = [] as number[];

2. 接口与类型别名的选择

// 接口:支持声明合并
interface User {
    name: string;
}

interface User {
    age: number;
}

// 合并后的接口:{ name: string; age: number; }

// 类型别名:不支持声明合并,但支持更灵活的表达
type ID = string | number;
type User2 = {
    [K in keyof User]: User[K];
} & { id: ID };

3. any类型的滥用

// ❌ 滥用any
function processData(data: any): any {
    return data.map((item: any) => item.id);
}

// ✅ 使用泛型
function processData<T extends { id: any }>(data: T[]): T[] {
    return data.filter(item => item.id);
}

📝 最佳实践

  1. 优先使用interface:定义对象类型时
  2. 合理使用type:需要联合类型、交叉类型或复杂类型表达式时
  3. 避免使用any:尽量使用unknown或具体类型
  4. 使用const断言:创建不可变的字面量类型
  5. 利用类型推断:让TypeScript自动推断类型,减少冗余的类型注解

🎯 小结

  • 掌握了TypeScript的基本数据类型
  • 理解了接口的定义和使用方法
  • 学会了类型别名和类型断言的应用
  • 掌握了联合类型和交叉类型的使用
  • 了解了高级类型技巧和最佳实践

下一步学习泛型编程