跳至主要內容

基础类型

樱桃茶大约 19 分钟

基础类型

基础类型是 TypeScript 中最基本的数据类型,它们为你在编码时提供了明确的类型标注。理解这些基础类型对于学习 TypeScript 非常重要。下面是一些基础类型的例子和简单的说明:

  • 布尔值 (boolean):

    • 表示逻辑上的真或假。
    • 例子: let isDone: boolean = false;
  • 数字 (number):

    • TypeScript 里所有数字都是浮点数或整数。
    • 例子: let decimal: number = 6;
  • 字符串 (string):

    • 用于表示文本数据。
    • 例子: let color: string = "blue";
  • 数组 (Array<Type>Type[]):

    • 表示元素类型相同的集合。
    • 例子:
      • let list: number[] = [1, 2, 3];
      • 或者使用泛型形式: let list: Array<number> = [1, 2, 3];
  • 元组 (tuple):

    • 允许表示一个已知元素数量和类型的数组,各元素的类型不必相同。
    • 例子: let x: [string, number] = ["hello", 10];
  • 枚举 (enum):

    • 一个帮助你处理一组数值的方式,可以给这组数值更友好的名字。
    • 例子:
      enum Color {Red, Green, Blue}
      let c: Color = Color.Green;
      
  • 任意类型 (any):

    • 对于那些我们不想给出一个具体类型的变量,可以使用any
    • 例子: let notSure: any = 4;
  • 空值 (void):

    • 通常用于没有返回值的函数。
    • 例子: function warnUser(): void { console.log("This is a warning message"); }
  • Null 和 Undefined:

    • TypeScript 里,nullundefined有它们各自的类型分别叫做nullundefined
    • 尽管不太常用,但很重要。默认情况下nullundefined是所有类型的子类型。
    • 例子:
      • let u: undefined = undefined;
      • let n: null = null;
  • Never:

    • 表示那些永不存在的值的类型。例如,never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回类型。
    • 例子:
      function error(message: string): never {
        throw new Error(message);
      }
      
  • 对象 (object):

    • 非原始类型,除number, string, boolean, symbol, null, 或undefined之外的类型。
    • 例子: declare function create(o: object | null): void;

通过掌握这些基础类型,你将能够更好地利用 TypeScript 的类型系统来写出更安全、更清晰的代码。

布尔值

基础类型 > 布尔值

  • TypeScript 中的布尔值是最基本的数据类型之一,它用来表示逻辑上的“真”或“假”。

  • 在 TypeScript 中,你可以使用boolean类型来声明一个布尔类型的变量。这个变量只能接受两个值:truefalse

  • 声明布尔值的时候通常会用到letconst关键字。例如:

    let isDone: boolean = false;
    
  • 如果你已经学习过 JavaScript,那么你应该知道在 JS 中布尔类型也非常重要,经常用于控制流语句(如 if 条件语句)以决定代码是否执行。

  • 在 TypeScript 中,使用布尔值可以帮助你编写更清晰易懂的代码,因为类型注解可以让其他开发者知道某个变量是用来表示逻辑状态的。

  • 下面是一些使用布尔值的实用例子:

    // 判断数字是否大于0
    let isPositive: boolean = 1 > 0;
    console.log(isPositive); // 输出 true
    
    // 检查用户是否登录
    let isLoggedIn: boolean = true; // 假设用户已登录
    if (isLoggedIn) {
      console.log("用户已登录");
    } else {
      console.log("用户未登录");
    }
    
    // 使用布尔值控制循环
    let isActive: boolean = true;
    while (isActive) {
      // 执行一些操作
      // ...
    
      // 最终可能根据某些条件停止循环
      if (/* 条件满足 */) {
        isActive = false;
      }
    }
    
  • 学习 TypeScript 时,了解如何正确地使用基础类型比如布尔值是非常重要的。它们构成了程序逻辑的基石,并且是后续学习更复杂概念的基础。

数字

数字(number)是 TypeScript 中的一种基本数据类型,用于表示整数和浮点数值。这与 JavaScript 中的 number 类型相同,因为 TypeScript 最终会被编译成 JavaScript。

  • 在 TypeScript 中,所有的数字都是浮点数,没有特定的整数类型。
  • 数字类型可以使用十进制、十六进制、二进制或八进制字面量来表示。
  • 为了给变量指定数字类型,可以在声明变量时使用冒号(:)后跟 number 关键字。

下面是一些数字类型的例子:

let decimal: number = 6; // 十进制
let hex: number = 0xf00d; // 十六进制,以 0x 开头
let binary: number = 0b1010; // 二进制,以 0b 开头
let octal: number = 0o744; // 八进制,以 0o 开头

使用数字类型可以执行各种数学运算,并且 TypeScript 会提供类型检查,确保你不会将数字与其他不兼容的类型(如字符串)混淆。例如:

// 正确的数学运算
let sum: number = decimal + hex + binary + octal;

// 错误示例:不能将字符串分配给数字类型变量
// TypeScript 编译器将抛出错误
let invalidNumber: number = "123"; // Error: Type 'string' is not assignable to type 'number'.

在学习 TypeScript 时,理解和使用基础类型是非常重要的,它们是构建类型安全代码的基石。数字类型作为最基础的之一,有助于你在处理各种数值计算时写出更健壮的代码。

字符串

  • 字符串在 TypeScript 中表示文本数据。和 JavaScript 一样,可以使用单引号('), 双引号("), 或者模版字符串(```)来定义一个字符串。

    • 单引号或双引号用于普通的字符串数据。例如:

      let name: string = "John Doe";
      let greeting: string = "Hello, World!";
      
    • 模板字符串提供了一种方便的方式来构造包含变量的字符串。它们被反引号包围,并允许你通过${expression}的形式嵌入表达式。例如:

      let user: string = "Jane";
      let age: number = 28;
      let sentence: string = `Hello, my name is ${user} and I am ${age} years old.`;
      
  • 在 TypeScript 中, 对字符串类型的操作和在 JavaScript 中大致相同,因为 TypeScript 是 JavaScript 的超集。这意味着所有在 JavaScript 中有效的字符串方法,在 TypeScript 中也同样有效。

    • 例如, 使用.length属性来获取字符串的长度, 或者.toUpperCase()方法将字符串转换为大写:
      let hello: string = "hello world";
      console.log(hello.length); // 输出: 11
      console.log(hello.toUpperCase()); // 输出: HELLO WORLD
      
  • 当你从 JavaScript 迁移到 TypeScript 时,关于字符串的知识几乎可以无缝转移。只需记得声明变量时指定类型为string即可。这有助于在编译阶段捕获潜在的错误,如将非字符串值赋给本应为字符串的变量。

  • 最后,理解 TypeScript 中字符串的处理对于构建类型安全的程序至关重要。比如,当函数期望接收一个字符串参数时,正确地指定类型可以避免运行时错误。例如:

    function greet(name: string) {
      return `Hello, ${name}!`;
    }
    
    console.log(greet("Alice")); // 正确调用
    // console.log(greet(42)); // 类型错误,编译时将提示错误
    

数组

在 TypeScript 中,数组可以用两种方式定义:使用元素类型后跟[],或者使用泛型数组类型。这两种方式实际上是等价的,但不同的开发者可能会根据个人喜好或项目规范选择不同的表示方法。

  • 使用元素类型后跟[]

    这种方式更直观,尤其是对于那些刚从 JavaScript 转向 TypeScript 的开发者来说。比如,如果你想要一个由数字组成的数组,你可以这样声明:

    let numbers: number[] = [1, 2, 3, 4, 5];
    

    如果你想要一个字符串数组,可以这样声明:

    let fruits: string[] = ["apple", "orange", "banana"];
    
  • 使用泛型数组类型

    泛型提供了一种方式来确保数组中元素的一致性,同时保持灵活性。这种声明方式使用Array<元素类型>的形式。例如:

    let numbers: Array<number> = [1, 2, 3, 4, 5];
    

    和字符串数组的声明:

    let fruits: Array<string> = ["apple", "orange", "banana"];
    

无论哪种声明方式,TypeScript 都能够帮助你确保数组中的元素类型是一致的。试图将错误类型的值添加到数组中时,TypeScript 会在编译阶段报错,从而避免运行时错误。例如,以下尝试将字符串加入到一个数字数组中将会引发编译错误:

let numbers: number[] = [1, 2, 3];
numbers.push("4"); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.

通过了解和使用 TypeScript 的数组类型,你可以有效地减少运行时错误,提高代码的可维护性和稳健性。

元组 Tuple

  • 元组(Tuple)是 TypeScript 中的一种特殊类型,它允许你明确地表达一个数组里元素数量固定且各元素类型不必相同的概念。这与普通的数组区别在于,数组的元素通常是同一种类型,而元组的每个位置的元素类型可以不同。

  • 用法示例:

    • 定义一个元组,表示一个有两个元素的元组,第一个是string类型,第二个是number类型: let myTuple: [string, number] = ['hello', 10];
  • 元组在使用时要注意:

    • 当赋值或访问一个已知索引的元素时,会得到正确的类型:
      • myTuple[0].substring(1); // 正确, .substring()是string上的方法
      • myTuple[1].toFixed(2); // 正确, .toFixed()是number上的方法
    • 如果尝试访问一个越界的元素,将会使用联合类型替代:
      • myTuple[2] = 'world'; // 错误, 'world'不是类型string | number``
      • 这个例子中,尽管 TypeScript 3.0 之后允许元组在声明后添加新的元素,但这些元素类型必须是每个类型的联合类型(在本例中是string | number),并且仅当使用.push()方法时有效;直接通过索引赋值则会出错。
    • TypeScript 对元组的长度检查非常严格,不允许添加超过声明时长度的元素,也不允许少于声明的长度。
  • 元组非常适合用于那些需要管理小型、固定数量的相关数据项的场景,例如函数返回多个值、CSV 文件行解析等情况,提供了一种严格控制数组元素类型和数量的方式。

枚举

枚举(Enums)是 TypeScript 中一种特殊的类型,它允许你为一组数值定义友好的名字。在刚接触 TypeScript 或从 JavaScript 过渡来时,理解枚举的概念和使用场景对于平滑学习 TypeScript 非常重要。以下是枚举类型的几个关键点和例子:

  • 基本枚举: TypeScript 支持数字和字符串的枚举。

    • 数字枚举:

      enum Direction {
        Up = 1,
        Down,
        Left,
        Right,
      }
      

      在数字枚举中,首项默认从 0 开始自增,但你可以指定起始数字。在上面的例子中,Up 被赋值为 1,后续的 DownLeftRight 分别自增为 234

    • 字符串枚举:

      enum Direction {
        Up = "UP",
        Down = "DOWN",
        Left = "LEFT",
        Right = "RIGHT",
      }
      

      字符串枚举中的每一个成员都必须用字符串字面量或另一个字符串枚举成员进行初始化。

  • 异构枚举 (Heterogeneous enums): 尽管不推荐,但是 TypeScript 允许枚举包含字符串和数字成员。

    enum BooleanLikeHeterogeneousEnum {
      No = 0,
      Yes = "YES",
    }
    

    这种混合类型的枚举的用处不大,可能会引入混淆,因此在实际开发中很少使用。

  • 枚举成员的属性: 枚举成员可以是只读属性,分为两类:常量成员和计算所得成员。

    • 成员没有初始化器并且之前的成员是常量,则该成员也是常数。
    • 如果成员的值是计算得出的,则它是计算得出的成员。
  • 反向映射: 数字枚举具有反向映射的特性,即你既可以通过枚举成员的名字访问其值,也可以通过值访问到名字。

    enum Enum {
      A,
    }
    let a = Enum.A;
    let nameOfA = Enum[a]; // "A"
    

    这里 Enum.A 访问的是值 0,而 Enum[0] 反过来又能获取到成员名 "A"

  • const 枚举: 使用 const 声明的枚举在编译时会被删除,并且不能包含计算成员。

    const enum Directions {
      Up,
      Down,
      Left,
      Right,
    }
    let directions = [
      Directions.Up,
      Directions.Down,
      Directions.Left,
      Directions.Right,
    ];
    

    const 枚举的优点是,在编译后的代码中能够减少额外的代码开销,直接使用枚举成员的值替换了枚举名。

枚举是 TypeScript 中非常强大的特性,允许开发者以类型安全的方式处理一组相关的常量值。理解并正确地使用枚举,可以帮助你写出更清晰、更易维护的代码。

Any

  • Any类型是 TypeScript 中的一种基础类型,它可以让变量接受任何类型的值。当你尚不确定一个变量应该使用什么类型时,或者由于某些原因你需要跳过类型检查,就可以使用Any类型。

    • 示例 1:不确定变量类型

      let notSure: any = 4;
      notSure = "maybe a string instead";
      notSure = false; // okay, definitely a boolean
      

      在这个例子中,变量notSure被声明为any类型,这意味着你可以给它赋任何类型的值,比如数字、字符串或布尔值。

    • 示例 2:混合不同类型的数组

      let mixed: any[] = [1, "hello", true];
      mixed.push(500); // Valid
      

      这里mixed是一个类型为any[]的数组,即一个可以包含任意类型元素的数组。你可以向其中添加数字、字符串或其它类型的数据,没有类型上的限制。

  • 使用Any类型可能会失去一部分 TypeScript 提供的类型安全保障,在编译阶段无法检查到某些潜在的错误。所以,尽管Any类型非常灵活,但通常建议只在不得不用的情况下才使用它。

    • 示例 3:避免使用any类型
      let avoidAny: any = 10;
      avoidAny.ifItDoesNotExist(); // TypeScript不会报错,但在运行时这将是一个错误
      
      正因为avoidAnyany类型,TypeScript 不会提前警告.ifItDoesNotExist()方法不存在,这会使得潜在的错误难以在编写代码时被发现。

总之,虽然Any类型给予了最大的灵活性,但尽量限制它的使用可以帮助你更好地利用 TypeScript 强大的类型系统,从而编写出更健壮、更容易维护的代码。

Void

  • Void 类型: 在 TypeScript 中,void 类型用于表示没有任何类型。通常,在函数中使用它来指示该函数不返回任何值。

  • 在 JavaScript 中的类比: 考虑到你已经了解 JavaScript,我们可以将 TypeScript 中的 void 与 JavaScript 中的 undefined 做一个比较。当一个 JavaScript 函数没有返回值时,其实它默认返回 undefined。在 TypeScript 中,我们显式地使用 void 来表达这个意图。

  • 函数举例:

    function greet(): void {
      console.log("Hello, World!");
    }
    

    这个函数没有返回值,它输出一条信息到控制台。这里使用 : void 明确说明 greet 函数不应该有返回值。

  • 变量使用: 变量几乎不会声明为 void 类型,因为你只能为它们赋予 undefinednull(当 --strictNullChecks 不启用时)。

  • 类型推断: 如果你声明一个函数没有返回值,TypeScript 编译器可以自动推断出这个函数的返回类型是 void。例如:

    function warnUser() {
      console.log("This is a warning message");
    }
    

    尽管没有明确标注 warnUser 函数的返回类型为 void,TypeScript 会自动理解这一点。

  • 为什么要使用 void: 使用 void 类型可以提高代码的清晰性和可维护性,因为它向开发者传达了函数的预期行为,即不期望有返回值。这样就避免了其他人或未来的你错误地使用该函数可能产生的返回值。

  • 注意事项: 尽管 nullundefined 可以赋给 void 类型,但是不应该将 void 当作仅仅能存放 nullundefined 的容器。它更多的是在函数上下文中使用来表示没有返回值的概念。

Null 和 Undefined

  • nullundefined 在 TypeScript 中各自有其类型,分别叫做 nullundefined. 它们的作用基本和在 JavaScript 中是一样的。

  • null 是一个表示"没有值"的特殊值,在 TypeScript 中,你可以将它赋给任何类型,标识为该类型的“空”或“不存在”的状态。

    let empty: null = null;
    
  • undefined 代表变量已被声明但尚未被初始化。类似于 nullundefined 也可以赋给其他类型的变量。

    let notInitialized: undefined = undefined;
    
  • TypeScript 的 --strictNullChecks 标志可以让 nullundefined 只能赋值给 any 和它们各自的类型(这意味着你不能将 nullundefined 赋给 number 类型的变量,除非你显式地标注该变量的类型为 number | nullnumber | undefined)。

    let age: number | null = null; // 这样是允许的
    age = 26; // 同样允许
    
    let name: string | undefined;
    name = "Alice"; // 允许
    name = undefined; // 同样允许
    
  • 当你不启用 --strictNullChecks 时,nullundefined 可以赋给任何类型,这可能会导致运行时错误。打开这个选项可以使得你的代码更安全。

  • 在处理可选参数和属性时,TypeScript 会自动把它们加上 | undefined 类型。

    function greet(name?: string) {
      // name 的类型是 'string | undefined'
      console.log(`Hello, ${name}`);
    }
    
    interface User {
      name: string;
      age?: number; // age 的类型是 'number | undefined'
    }
    
  • 使用联合类型可以让你明确地表达一个值可以是 nullundefined,或者是一些其他类型。

    let distance: number | null = null; // distance 可以是 number 类型或 null
    distance = 10; // 有效的赋值
    
  • 理解 nullundefined 对于处理外部数据来源(例如 API 请求)时异常重要,因为它们经常用于表示“值缺失”或“值未定义”。

记住,最佳实践是尽可能使用 --strictNullChecks 配置项,并且通过联合类型来明确地处理 nullundefined,这样可以提高代码的健壮性并减少潜在的运行时错误。

Never

  • never 类型表示的是那些永远不存在的值的类型。换句话说,never 类型用于那些总会抛出异常或根本就不会有返回值的函数表达式或箭头函数的返回类型。

  • 在 TypeScript 中,当你定义一个不可能有返回值的函数时,你会使用 never 类型。这种情况通常发生在函数内部抛出了错误,导致函数无法正常结束,或者函数陷入了一个无限循环。

  • never 类型也用于从未真实存在的类型检查场景,例如,当你使用类型守卫缩小类型范围到一个不可能的类型时。

实用的例子:

// 这个函数无法正常结束,因为它抛出了一个错误
function throwError(message: string): never {
  throw new Error(message);
}

// 这个函数有一个死循环,也就是说它永远不会结束
function infiniteLoop(): never {
  while (true) {}
}

// 使用 never 类型进行类型保护
function controlFlowAnalysisWithNever(value: string | number) {
  if (typeof value === "string") {
    // 在这里,value 被视为 string 类型
  } else if (typeof value === "number") {
    // 在这里,value 被视为 number 类型
  } else {
    // value 在这里必须为 never 类型,否则我们有可能遗漏了某些情况
    const check: never = value;
    console.log(check); // 这一行代码实际上永远不会执行
  }
}

在这些例子中,我们看到 never 类型被用在不同的语境下,但核心思想是一样的:never 类型代表了那些永远不可能发生的情形。通过在你的函数或类型守卫中使用 never 类型,可以帮助 TypeScript 更好地理解你的代码,并且确保你处理了所有可能的情况。

Object

  • TypeScript 中的 Object 类型是 JavaScript 中最基础的类型之一。当你声明一个变量为 Object 类型时,你可以将任何 JavaScript 对象赋值给这个变量。

  • 在 TypeScript 中,Object 类型实际上并不常用,因为它允许太多的自由度,没有提供关于该对象应有的具体结构或者形状的信息。

  • TypeScript 更倾向于使用接口(Interfaces)或类型别名(Type Aliases)来描述一个对象的形状,这样可以更精确地控制对象中应该包含哪些属性和方法。

例子:

let obj: Object;

obj = { name: "Alice" }; // OK
obj = [1, 2, 3]; // OK
obj = () => console.log("Hello!"); // OK

// 下面是使用接口定义一个对象类型的例子
interface Person {
  name: string;
  age: number;
}

let person: Person;

person = { name: "Alice", age: 30 }; // OK
person = { name: "Bob" }; // Error: Property 'age' is missing
  • 使用 Object 类型意味着 TypeScript 编译器无法准确知道对象内部的结构。例如,尝试调用 obj 上可能不存在的属性或方法时,编译器将不会报错,但在运行时可能会出现问题,因为实际上没有进行严格的类型检查。

  • 如果你知道一个对象的确切结构,建议始终使用接口或类型别名来代替 Object 类型,以便获得更好的类型安全性和代码自动补全功能。

类型断言

类型断言(Type Assertions)是 TypeScript 中的一种语法,允许你告诉编译器某个值具有更具体的类型。这在你比 TypeScript 更了解某个值的类型时特别有用。

  • 类型断言不会改变程序在运行时的值,它只是在编译阶段起作用。
  • TypeScript 提供了两种语法来进行类型断言:
    • 使用尖括号<type>
    • 使用as关键字。

例子:

let someValue: any = "this is a string";

// 尖括号语法
let strLength: number = (<string>someValue).length;

// as语法
let strLengthAs: number = (someValue as string).length;

在以上示例中:

  • someValue被声明为any类型,这意味着它可以是任何类型。
  • 我们知道实际上someValue是一个字符串,所以我们使用类型断言来告诉 TypeScriptsomeValue应该被当作string处理。
  • 然后我们就可以获取字符串的长度属性.length,并将其存储在一个类型为number的变量中。

注意:

  • 当你在 TypeScript 中使用 JSX 时,只能使用as语法断言。
  • 类型断言并不是类型转换,它不会真的影响到变量的值,只是影响 TypeScript 类型检查的方式。

关于 let

在 TypeScript(以及现代 JavaScript)中,let关键字用于声明一个块作用域的变量。使用let与传统的var关键字相比,有几个关键差异需要了解:

  • 块级作用域:与var不同,let声明的变量仅在声明它们的代码块内有效。
  • 无变量提升:使用let声明的变量不会被提升到它们所在代码块的顶部。
  • 重复声明错误:在同一个作用域内,不能用let重复声明同一个变量。

以下是一些实用的例子:

  • 块级作用域示例:

    function letExample() {
      let a = 30;
      if (true) {
        let a = 50; // 这里的'a'只在这个if语句的块中有效
        console.log(a); // 输出: 50
      }
      console.log(a); // 输出: 30,因为外部的'a'并没有被内部的'a'覆盖
    }
    
  • 无变量提升示例:

    function hoistingExample() {
      console.log(a); // 这里会报错,因为'a'还没有声明
      let a = 10;
    }
    
  • 重复声明错误示例:

    let b = 20;
    let b = 30; // 这里会编译错误,因为变量'b'已经被声明过了
    

理解let如何工作是学习 TypeScript 和现代 JavaScript 的一个基础知识点,可以帮助避免常见的作用域相关问题。