xChar

接口 (interface) 是什么?

:::commend{title="answer"}
接口用于定义对象应具有的属性、方法及其类型。也可以用来定义类、函数、数组等的结构
:::

// 1、 定义对象结构
interface Car {
  brand: string;
  speed: number;
  drive(): void;
}
let myCar: Car = {
  brand: "Toyota",
  speed: 120,
  drive() {
    console.log("Driving at speed: " + this.speed);
  }
};

// 2、指定接口只读和可选
interface Point {
  readonly x: number; // 只读
  readonly y: number; // 只读
  z?: number;  // 可选
}
let p1: Point = { x: 10, y: 20 };

type 和 interface 的区别?

:::commend{title="answer"}

  • interface 只能定义对象类型,而type可以定义任何类型(基本类型,联合类型,元组)。
  • interface 可以被合并(merge),如果多个同名接口会被自动合并为一个接口,而type不行。
  • interface 使用extends扩展其他接口;而type是通过交叉类型(&)来合并多个类型
    :::

:::assert{title="使用场景"}

  • interface 更适合用于描述对象的形状,尤其是当需要继承和声明合并时。
  • type 更灵活,除了对象类型,还可以用来定义联合类型、交叉类型、基础类型等。
    :::

unknown 和 any 有什么区别?

:::commend{title="answer"}

  • any: 表示任意类型,不进行类型检查。
  • unknown: 也可以表示任意类型,但相比 any,unknown 是类型安全的。因为在对 unknown 类型的值做任何操作时需要先确定它的类型。
    :::
let value: unknown;
value = 42;
// 需要先做类型检查,才能调用 `toFixed` 方法
if (typeof value === "number") {
  value.toFixed(); // 正确
}

value = "Hello";
// 需要先做类型检查,才能调用 `toUpperCase` 方法
if (typeof value === "string") {
  value.toUpperCase(); // 正确
}

:::assert{title="使用场景"}

  • any:适用于不关心类型安全的场景,或者需要快速编写代码时,但它会使得代码失去类型检查的优势。
  • unknown:适合在你不知道确切类型的情况下,但仍然希望保持类型安全性。在使用前可以强制开发者对类型进行校验
    :::

说一下never 类型的用法?

:::commend{title="answer"}

  • never 最常见的用法是表示一个函数永远不会有返回值。通常用于抛出错误或者导致程序无法继续执行的场景。
    :::
// 抛出异常
function throwError(message: string): never {
  throw new Error(message);
}

// 无限循环
function infiniteLoop(): never {
  while (true) {
    // 无限循环,永远不会返回
  }
}

// 穷尽检查
type Shape = 'circle' | 'square';

function getArea(shape: Shape): number {
  switch (shape) {
    case 'circle':
      return Math.PI * 1 ** 2;
    case 'square':
      return 1 * 1;
    default:
      const _exhaustiveCheck: never = shape; // 这里的 `shape` 永远不会是其他值
      throw new Error(`Unexpected shape: ${shape}`);
  }
}

never 与 void 的区别?

:::commend{title="answer"}

  • void: 表示没有返回值,但函数执行后仍然会正常结束,返回 undefined。
  • never: 表示永远不会返回值,函数永远不会结束(比如抛出错误或无限循环)。
    :::
function logMessage(message: string): void {
  console.log(message); // 该函数返回 `undefined`
}

function throwError(message: string): never {
  throw new Error(message); // 该函数永远不会返回
}

说一下联合类型、交叉类型、类型别名、类型保护

联合类型 (Union Types)

  • 联合类型 允许一个变量可以是多种类型中的一种。使用 |(竖线)符号来定义。
  • 场景: 用于表示某个值可以是多个不同类型中的一种,比如函数参数可以接受多种类型的数据。
let value: string | number;

function formatInput(input: string | number): string {
  if (typeof input === "string") {
    return input.toUpperCase();
  } else {
    return input.toFixed(2);
  }
}

交叉类型 (Intersection Types)

  • 交叉类型 将多个类型合并为一个类型,该类型同时拥有所有类型的特性。使用 &(和符号)定义。
  • 场景: 用于组合多个类型的属性,创建一个拥有所有属性的对象类型。
interface Person {
  name: string;
}
interface Employee {
  id: number;
}
let employee: Person & Employee = {
  name: "Alice",
  id: 123
};

function printEmployee(employee: Person & Employee) {
  console.log(`Name: ${employee.name}, ID: ${employee.id}`);
}

类型别名 (Type Aliases)

  • 类型别名 用来为一个类型创建一个新的名称(别名)。通过 type 关键字定义,适用于基本类型、联合类型、交叉类型等。
  • 场景: 用于简化复杂类型,提升代码的可读性。它可以为对象、函数、联合类型等复杂类型创建简单的别名。
type ID = string | number;
let userId: ID = "abc123";

type User = {
  name: string;
  age: number;
};
function greet(user: User) {
  console.log(`Hello, ${user.name}`);
}

类型保护 (Type Guards)

类型保护 是 ts 中用于判断某个值的实际类型,并根据该类型进行进一步处理的技术。它在使用联合类型时非常有用,确保在不同类型的分支中能正确地执行相应的操作。

  1. typeof 类型保护: 用于判断基本类型(string, number, boolean 等)
function printId(id: string | number) {
  if (typeof id === "string") {
    console.log(id.toUpperCase());
  } else {
    console.log(id.toFixed(2));
  }
}
  1. instanceof 类型保护: 用于判断对象是否是某个类的实例。
class Dog {
  bark() {
    console.log("Woof!");
  }
}
class Cat {
  meow() {
    console.log("Meow!");
  }
}
function makeSound(animal: Dog | Cat) {
  if (animal instanceof Dog) {
    animal.bark();
  } else {
    animal.meow();
  }
}
  1. is 关键字: 可以通过函数返回值 is 来创建自定义类型保护。
function isString(value: any): value is string {
  return typeof value === "string";
}

function printValue(value: string | number) {
  if (isString(value)) {
    console.log(value.toUpperCase());
  } else {
    console.log(value.toFixed(2));
  }
}
  1. in 关键字: 用于判断对象是否具有某个属性,特别在判断联合类型中的对象类型时。
interface Bird {
  fly(): void;
}
interface Fish {
  swim(): void;
}
function move(animal: Bird | Fish) {
  if ("fly" in animal) {
    animal.fly();
  } else {
    animal.swim();
  }
}

typeof 关键字的作用是什么?

:::commend{title="answer"}

  • 复用已有变量的类型: 如果你有一个已知类型的对象或变量,可以通过 typeof 查询它的类型,避免重复编写类型声明。
  • 函数返回类型: 可以用 typeof 来推断函数返回的类型。
  • 运行时:JavaScript 中的类型检查
    :::
// 1、复用已有变量的类型
let person = {
  name: "Alice",
  age: 25,
};
type PersonType = typeof person; // { name: string; age: number; }

// 2、函数返回类型
function getPerson() {
  return { name: "Alice", age: 25 };
}

type PersonType = typeof getPerson(); // { name: string; age: number; }

// 3、JavaScript 中的类型检查
function printValue(value: number | string) {
  if (typeof value === "string") {
    console.log("This is a string: ", value);
  } else {
    console.log("This is a number: ", value);
  }
}

keyof 关键字的作用是什么?

:::commend{title="answer"}

  • 获取对象类型的键: keyof 常用于从对象类型中提取所有键,并用于动态地操作类型。
  • 类型约束: keyof 可以用来约束函数参数,使得函数的参数必须是对象类型的某个键。
    :::
// 1、获取对象类型的键
type Person = {
  name: string;
  age: number;
  location: string;
};

type PersonKeys = keyof Person; // 'name' | 'age' | 'location'

// 2、类型约束(key必须是obj的对象键)
function getValue<T, K extends keyof T>(obj: T, key: K): T[K] {
  return obj[key];
}
const person = { name: "Alice", age: 25, location: "NYC" };
const name = getValue(person, "name");
const age = getValue(person, "age");

如何定义一个枚举?使用场景是?

:::commend{title="answer"}
枚举用于定义一组固定的、命名的常量,适合处理有限的选项集、状态、方向或权限。使用关键字 enum 定义
:::

enum ResultEnum {
  SUCCESS = 200,
  ERROR = 500,
  NOTLOGGED = 401,
  OVERDUE = 402,
  TIMEOUT = 5000,
}

enum LogLevel {
  Info,
  Warn,
  Error
}

如何联合枚举类型的 Key?

enum Direction {
  Up,
  Down,
  Left,
  Right
}

type DirectionType = keyof typeof Direction; // Up | Down | Left | Right

什么是泛型 (generics)?

:::commend{title="answer"}

  • 泛型 允许我们编写代码时不指定具体类型,使用类似占位符的方式,等到实际使用的时候再指定类型。使得泛型具有更高的灵活性
  • 泛型函数、接口、类 是 TypeScript 中实现泛型的主要方式。
    :::
// 简单泛型函数
function echo<T>(arg: T): T {
  return arg;
}
echo<number>(42); // 传入 number 类型

// 泛型接口
interface Box<T> {
  content: T;
}

// 默认泛型类型
function createArray<T = string>(length: number, value: T): T[] {
  return Array(length).fill(value);
}

如何对泛型类型进行约束?

规定泛型参数必须符合某些条件,比如必须具有某些属性或方法。

interface Lengthwise {
  length: number;
}
function logLength<T extends Lengthwise>(arg: T): T {
  console.log(arg.length);
  return arg;
}
logLength("Hello"); // 字符串有 length 属性,合法
// logLength(123); // 这里会报错,因为 number 类型没有 length 属性

as 作用?什么时候使用?

:::commend{title="answer"}

  • 在某些情况下,TypeScript 不能推断出值的确切类型,或者你知道某个值的类型比 TypeScript 推断得更加具体,此时可以使用 as 来进行类型断言。但需要自己保证类型断言的正确性。
  • 类型推断不准确时、当我们明确知道数据的类型时可以使用 as 类型断言
    :::
let someValue: any = "This is a string";

// 我们知道 someValue 是 string 类型
let strLength: number = (someValue as string).length;

let colors = ["red", "green", "blue"] as const;
// `colors` 的类型将是 readonly ["red", "green", "blue"]

元组是什么?和ts数组有何区别?

:::commend{title="answer"}

  • 元组是一种固定长度、各个元素可以是不同类型的数组。在定义元组时,必须指定每个元素的类型和顺序。在使用元组时,你必须按顺序和类型访问元素。而 TypeScript 数组的元素类型通常是相同的。
    :::
let tuple: [string, number];
tuple = ["Hello", 42]; // 合法
// tuple = [42, "Hello"]; // 非法,顺序错误

:::assert{title="使用场景"}

  • 函数返回值: 当函数需要返回多个不同类型的值时,可以使用元组。
  • 键值对: 元组可以用于表示键值对,像对象的属性和值
    :::
function getUserInfo(): [string, number] {
  return ["Alice", 25];
}

let entry: [string, number] = ["score", 100];
console.log(entry[0]); // 输出 "score"

// 可选元素的元组
type FlexibleTuple = [string, number?, boolean?];
let tuple1: FlexibleTuple = ["Hello"];

函数重载是什么?有何作用?

:::commend{title="answer"}

  • 是什么?: 函数重载使得同一个函数名可以有多个不同的参数列表(签名),这些签名可以根据参数的类型或数量不同而变化。使其能够处理不同的输入情况。
  • 作用: 增强灵活性、提高代码可读性、提供类型安全。适合根据不同参数/参数类型 获取对应类型及实现的情况
    :::
// 重载签名(没有实现)
function format(input: number): string;
function format(input: Date): string;

// 实现签名(包含逻辑)
function format(input: any): string {
  if (typeof input === "number") {
    return input.toFixed(2); // 格式化数字
  } else if (input instanceof Date) {
    return input.toISOString(); // 格式化日期
  }
}

// 不同数量、类型参数
function greet(name: string): string;
function greet(firstName: string, lastName: string): string;

function greet(firstName: string, lastName?: string): string {
  if (lastName) {
    return `Hello, ${firstName} ${lastName}!`;
  } else {
    return `Hello, ${firstName}!`;
  }
}

如何实现条件类型?

条件类型通常以 T extends U ? X : Y 这样的形式书写,其中 T extends U 表示条件判断,如果条件为真则返回类型 X,否则返回类型 Y。这类似于 JavaScript 中的三元操作符 condition ? trueValue : falseValue。

T extends U ? X : Y
// • T extends U:表示条件,检查类型 T 是否可以赋值给类型 U。
// • X:当 T 满足条件(即 T extends U 为 true)时返回的类型。
// • Y:当 T 不满足条件(即 T extends U 为 false)时返回的类型。

// 示例:
type IsString<T> = T extends string ? "Yes" : "No";

type A = IsString<string>;  // "Yes"
type B = IsString<number>;  // "No"

有什么常用的内置工具类型?

Partial<T>

将类型 T 的所有属性变为可选属性。

  name: string;
  age: number;
}

type PartialPerson = Partial<Person>;
// PartialPerson 等价于 { name?: string; age?: number; }

Required<T>

将类型 T 的所有属性变为必选属性。

interface Person {
  name?: string;
  age?: number;
}

type RequiredPerson = Required<Person>;
// RequiredPerson 等价于 { name: string; age: number; }

Readonly<T>

将类型 T 的所有属性变为只读属性,不允许修改。

interface Person {
  name: string;
  age: number;
}

type ReadonlyPerson = Readonly<Person>;
// ReadonlyPerson 等价于 { readonly name: string; readonly age: number; }

Pick<T, K>

从类型 T 中选取部分属性组成新类型。K 是需要选取的属性的联合类型。

interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonNameAndAge = Pick<Person, "name" | "age">;
// PersonNameAndAge 等价于 { name: string; age: number; }

Omit<T, K>

从类型 T 中排除 K 指定的属性,构造新类型。

interface Person {
  name: string;
  age: number;
  address: string;
}

type PersonWithoutAddress = Omit<Person, "address">;
// PersonWithoutAddress 等价于 { name: string; age: number; }

Record<K,T>

构造一个类型,键 K 可以是字符串、数字等,值 T 是所有属性的类型。

type Page = "home" | "about" | "contact";
type PageInfo = { title: string };

const pages: Record<Page, PageInfo> = {
  home: { title: "Home Page" },
  about: { title: "About Us" },
  contact: { title: "Contact Us" },
};

Exclude<T,U>

从联合类型 T 中排除所有属于类型 U 的子类型。

type A = "a" | "b" | "c";
type B = "a";

type Result = Exclude<A, B>; // "b" | "c"

Extract<T,U>

从联合类型 T 中提取所有可以赋值给类型 U 的子类型。

type A = "a" | "b" | "c";
type B = "a" | "b";

type Result = Extract<A, B>; // "a" | "b"

NonNullable<T>

排除类型 T 中的 null 和 undefined。

type A = string | null | undefined;

type NonNullableA = NonNullable<A>; // string

ReturnType<T>

获取函数类型 T 的返回值类型。

function getUser() {
  return { name: "Alice", age: 25 };
}

type UserType = ReturnType<typeof getUser>;
// UserType 等价于 { name: string; age: number; }

InstanceType<T>

获取构造函数类型 T 的实例类型。

class Person {
  name: string = "Alice";
  age: number = 25;
}

type PersonInstance = InstanceType<typeof Person>;
// PersonInstance 等价于 Person

Parameters<T>

获取函数类型 T 的参数类型,返回一个元组类型。

function greet(name: string, age: number): string {
  return `Hello ${name}, you are ${age} years old.`;
}

type GreetParams = Parameters<typeof greet>;
// GreetParams 等价于 [string, number]

ConstructorParameters<T>

获取构造函数类型 T 的参数类型,返回一个元组类型。

class Person {
  constructor(public name: string, public age: number) {}
}

type PersonConstructorParams = ConstructorParameters<typeof Person>;
// PersonConstructorParams 等价于 [string, number]

ThisType<T>

用于指定上下文对象 this 的类型,通常与 noImplicitThis 选项配合使用。

type ObjectDescriptor<D, M> = {
  data?: D;
  methods?: M & ThisType<D & M>; // M中的this会被推断为 D & M 类型
};

let obj: ObjectDescriptor<{ x: number }, { foo(): void }> = {
  data: { x: 10 },
  methods: {
    foo() {
      console.log(this.x); // 推断为 number
    }
  }
};

Awaited<T>

获取一个 Promise 的解析类型。

type P = Promise<string>;

type Result = Awaited<P>; // string

declare,declare global是什么?

:::commend{title="answer"}

  • declare: 告诉 TypeScript 某些外部定义存在,进行类型声明,不生成代码。
  • declare global: 用于声明或扩展全局范围内的变量、类型、或接口,让它们在全局作用域中可用。
    :::

.d.ts 和 .ts 文件有何区别?

:::commend{title="answer"}

  • .ts 文件: 用于编写 TypeScript 源代码,可以包含实现和类型定义,编译后生成 .js 文件。
  • .d.ts 文件: 用于声明类型信息,通常用于为 JavaScript 库、外部模块或没有类型的代码提供类型定义
    :::

如何为组件模板实例引用标注类型?

<template>
  <MyComponent ref="myComponentRef" message="Hello, World!" />
</template>

<script setup lang="ts">
  import { ref } from 'vue'
  import MyComponent from './MyComponent.vue';
  const myComponentRef = ref<InstanceType<typeof MyComponent> | null>(null)
</script>

typescript.json配置说明!

https://typescript.p6p.net/typescript-tutorial/tsconfig.json.html#compileoptions

{
  // 编译选项
  "compilerOptions": {
    // 指定 ECMAScript 目标版本(如 ES5, ES6, ESNext)。
    "target": "ES6",
    // 指定使用的模块系统
    "module": "ESNext", 
    // 启用所有严格类型检查选项的标志。
    "strict": true,
    // 启用对 ES 模块的互操作性支持。
    "esModuleInterop": true,
    // 生成 .map 文件,用于调试。
    "sourceMap": true,
    // 指定 JSX 代码的编译方式(如 react, react-jsx, preserve)。
    "jsx": "preserve",
    // 指定使用的库文件(如 DOM, ES6, ESNext)。
    "lib": ["ES6", "DOM"],
    // 指定使用的库文件(如 DOM, ES6, ESNext)。
    "skipLibCheck": true,
    // 在没有类型注解的情况下禁止隐式 any 类型。
    "noImplicitAny": true,
    // 生成声明文件,开启后会自动生成声明文件
    "declaration": true,
    // 指定生成声明文件存放目录
    "declarationDir": "./file",
    // 只生成声明文件,而不会生成js文件
    "emitDeclarationOnly": true,
    // 允许export=导出,由import from 导入
    "esModuleInterop": true,
    // 解析非相对模块的基地址,默认是当前目录
    "baseUrl": "./",
    // 路径映射,相对于baseUrl
    "paths": { 
       "@/*": ["./src/*"]
    },
    // ...
  },
  // 包含的文件或目录
  "include": [],
  // 排除的文件或目录
  "exclude": [],
  // 指定编译的具体文件列表,适用于小型项目。
  "files": [],
  // 用于支持项目间的引用,适用于大型项目。
  "references": []
  // 继承其他配置
  "extends": ""
}
Loading comments...