TypeScript 基础使用和相关问题

  1. tsconfig.json 配置文件
{
  "compilerOptions": {
    "target": "esnext",
    "jsx": "preserve",
    "jsxImportSource": "vue",
    "lib": ["esnext", "dom"],
    "useDefineForClassFields": true,
    "experimentalDecorators": true,
    // baseUrl 用来告诉编译器到哪里去查找模块,使用非相对模块时必须配置此项
    "baseUrl": ".",
    "module": "esnext",
    "moduleResolution": "bundler",
    // 非相对模块导入的路径映射配置,根据 baseUrl 配置进行路径计算,与 vite.config 中 alias 配置同步
    "paths": {
      "@/*": ["src/*"],
      "@@/*": ["src/common/*"]
    },
    "resolveJsonModule": true,
    "types": ["vite/client", "element-plus/global"],
    // 允许导入 .ts .mts .tsx 拓展名的文件
    "allowImportingTsExtensions": true,
    // 允许 JS
    "allowJs": true,
    // TS 严格模式
    "strict": true,
    "importHelpers": true,
    // 不输出任何编译后的文件,只进行类型检查
    "noEmit": true,
    "sourceMap": true,
    "allowSyntheticDefaultImports": true,
    "esModuleInterop": true,
    "isolatedModules": true,
    "skipLibCheck": true
  },
  // 需要被编译的文件列表
  "include": ["**/*.ts", "**/*.tsx", "**/*.vue", "**/*.d.ts"],
  // 从编译中排除的文件列表
  "exclude": ["node_modules", "dist"]
}
  1. 创建一个值为字符串的变量
let message: string = 'Hello TypeScript'
  1. 布尔值
let isDone:boolean = false;
  1. 数字
let decimal: number = 6;
let hex: number = 0xf00d;
let binay: number = 0b1010;
let octal: number = 0O744;
  1. 数组
let list: number[] = [1,2,3];
// 使用泛型数组类型:
let list1: Array<number> = [4,5,6];
  1. 元组
let x: [string, number];
x = ['hello', 10]
  1. 枚举
enum Color {Red, Green, Blue}
let c: Color = Color.Green;
  1. Any
// 不确定变量类型时,可以使用 any 类型:
let notSure: any = 4;
notSure = 'maybe a string instead';
notSure = false;
  1. Void
// 用于表示没有任何类型,常用于没有返回值的函数
function warnUser(): void {
  console.log('this is a viod log')
}
  1. Null 和 Undefined
let u: undefined = undefined;
let n: null = null;
  1. Never
// 表示那些用不存在的值的类型,例如:永远不会返回的函数表达式或箭头函数的返回值类型:
function error(message: string): never {
  throw new Error(message)
}
  1. Object
// 表示非原始类型
declare function ceeate(o: object | null): void;
create({ prop: 0 })
create(null);
  1. 接口
/**
 * 接口是 TypeScript 中的核心原则之一,用于定义对象的类型。
 * 接口能够描述一个对象的形状,能够检查对象是否符合特定的结构。
 * 示例如下:
 */
interface LabelledValue {
  label: string;
}

function printLabel(labelledObj: LabelledValue) {
  console.log(labelledObj.label);
}

let myObj = { size: 10, label: 'Size 10 Object' };

printLabel(myObj);
/**
 * 小结:LabelledValue 接口定义了一个 label 属性,并且 printLabel 函数期望一个实现了 LabelledValue 接口的对象作为参数。即使 myObj 有其他属性,但只要它至少有一个 label 属性,TS 就会认为它是合法的。
 */
  1. 可选属性
// 可选属性接口允许一些属性存在,也允许一些属性不存在:
interface SquareConfig {
  color?: string;
  width?: number;
}

function createSquare(config: SquareConfig): {color: string, area: number} {
  let newSquare = { color: 'white', area: 100 };
  if(config.color) {
    newSquare.color = config.color;
  }
  if(config.width) {
    newSquare.area = config.width * config.width;
  }
  return newSquare;
}

let mySquare = createSquare({ color: 'black' })
// 小结:SquareConfig 接口定义了 color 和 width 可选属性。CreateSquare 函数根据传入 config 对象动态地创建一个新的对象。
  1. 只读属性
// 一些属性可以在对象刚刚船舰的时候修改其值,而在此之后将是只读的
interface Point {
  readonly x: number;
  readonly y: number;
}
let p1: Point = { x: 10, y: 20 };
pi.x = 5; // 一旦赋值就会报错误,不能改变只读属性
  1. 函数类型
// 接口也可以描述函数类型
interface SeachFunc {
  (source: string, subString: string): beelean;
}

let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
  let result = source.Search(subString);
  return result > -1;
};
  1. 可索引类型
// 接口可以描述那些能够通过索引得到某种类型的对象,这在对象上可以用数字索引或字符串索引
interface StringArray {
  [index: number]: string;
}

let myArray: StringArray;
myArray = ['Bob', 'Fred'];

let myStr: string = myArray[0];
// StringArray 接口描述了具有数字索引的数组类型,并且返回值是字符串类型
  1. 类-简单类
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hello, ' + this.greeting;
  }
}
let greeter = new Greeter('world');
// Greeter 类有一个 greeting 属性和一个 greet 方法, greet 方法返回一个字符串。
  1. 类-继承
// 类可以扩展其他类,一个类可以继承另一个类的属性和方法;
class Animal {
  name: string;
  constructor(theName: string) {
    this.name = theName;
  }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m.`);
  }
}

class Snke extends Animal {
  constructor(name: string) {
    super(name);
  }
  move(distanceInMeters = 5) {
    console.log('Slithering...');
    super.move(distanceInMeters);
  }
}

let sam = new Snake('Sammy the Python');
sam.move();
// Snke 类扩展了 Animal 类,并且重写了 move 方法.
  1. 类-公共,私有与受保护修饰符
// TS 中的成员默认是公共的,也可以用 private 修饰符将成员标记为私有的:
class Animal {
  private name: string;
  constructor(theName: string) {
    this.name = theName;
  }
}

new Animal('Cat').name; // 错误: name 是私有的



// 可以使用 protected 修饰符声明受保护的成员,这些成员只能在类本身及其子类中访问:
class Person {
  protected name: string; // 受保护的成员,只能在类本身及其子类访问
  constructor(name: string) {
    this.name = name;
  }
}

class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
    super(name);
    this.department = department;
  }

  getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`
  }
}

let howard = new Employee('Howard', 'Sales');
console.log(howard.getElevatorPitch()); //有效
console.log(howard.name); // 错误
  1. 类-readonly 修饰符
// readonly 关键字将属性设置为只读
class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 0;

  constructor(theName: string) {
    this.name = theName;
  }
}
let dad = new Octopus('Man width the 8 strong legs');
dad.name = 'Man with the 3'; // 错误 name是只读的;
  1. 类-存取器
// 支持通过 get 和 set 关键字来定义存取器
class Employee {
  private _fullName: string; // 设置私有属性
  get fullName(): string {
    return this._fullName;
  }
  set fullName(newName: string) {
    this._fullName = newName;
  }
}

let employee = new Employee();
employee.fullName = 'Bob Smith';
console.log(employee.fullName)
// Employee 类有一个私有属性 _fullName, 并且通过存取器来设置和获取它的值。
  1. 函数-(和JavaScript函数类似,但在参数和返回类型上提供了更多类型的检查)
  2. 函数类型
// 为函数的参数和返回值指定类型
function add(x: number, y: number): number {
  return x + y;
}

let myAdd:(x: number, y: number) => number = function (x: number, y: number): number {
  return x + y;
}
  1. 函数-可选参数和默认参数
// 可以通过在参数名旁使用 ? 来实现可选参数
function buildName(firstName: string, lastName?: string): string {
  if(lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}

let result1 = buildName('Bob'); // 有效
let result2 = buildName('Bob', 'Adams'); // 有效
// 还可以为参数提供默认值
function buildName(firstName: string, lastNAme = 'Smith'): string {
  return firstName + ' ' + lastName;
}

let result1 = buildName('Bob'); // 有效 返回 'Bob Smith'
let result2 = buildName('Bob', 'Adams'); // 有效 返回 'Bob Adams' 
  1. 函数-剩余参数
// 可以使用 ... 语法将所有参数收集到一个变量中
function buildName(firstName: string, ...restOfName: string[]) {
  return firstNAme + '' + resetOfName.join('');
}

let employeeName = buildName('Joseph', 'Samuel', 'Lucas', 'MackinZie');
  1. 函数-this和箭头函数
// 在 TypeScript 中, this 的值取决于函数调用的位置,可以使用箭头来正确的捕获this 的值
let dect = {
  suits: ['hearts', 'spades', 'cluds', 'diamonds'],
  cards: Array(52),
  createCardPicker: function () {
    return () => {
      let pickedCard = Math.floor(Math.random() * 52);
      let pickedSuit = Math.floor(pickedCard / 13);

      return { suit: this.suits[pickedSuit], card: pickedCard % 13 };
    };
  };
};

let cardPicker = deck.createCardPicker();
let pickedCard = cardPicker();

console.log('card:', pickedCard.card)
console.log('suit:', pickedCard.suit)
// 例子中,箭头函数不会创建自己的 this, 它会捕获 deck 对象的 this 值。
  1. 泛型-泛型函数
function identity<T>(arg: T): T {
  return arg;
}

let output = identity<string>('mtstring'); // 手动指定类型
let output2 = identity('mystring'); // 类型推断
  1. 泛型-泛型接口
interface CenericIdentityFn<T> {
  (arg: T): T;
}

function identity<T>(arg: T): T {
  return arg;
}

let myIdentity: GenericIdentityFn<number> = identity;
// 上述例子中,GenericIdentityFn 接口描述了一个泛型函数类型,并且 myIdentity 是一个特定类型的泛型函数。
  1. 泛型- 泛型类
class GenericNumber<T> {
  zeroValue: T;
  add: (x: T, y: T) => T;
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) {
  return x + y;
}
// GenericNumber 是一个泛型类,它可以处理任意类型的数字 
  1. 泛型-泛型约束
// 想要限制某种类型的泛型函数,就可以使用泛型约束
interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise>(arg: T): T {
  console.log(arg.length)
  return arg;
}

loggingIdentity({length: 10, value: 2});
// loggingIdentity 函数要求传入的参数必须有 length 属性。
  1. 泛型-在泛型约束中使用类型参数
function getProperty<T, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

let x = {a: 1, b: 2, c: 3, d: 4};

getProperty(x, 'a') // 有效
getProperty(x: 'm') // 错误:类型‘m’的参数不能赋给类型"a" | "b" | "c" | "d" 的参数
// 上述例子中,getProperty 函数接收一个对象和一个属性名称,并返回该属性的值,K被约束为 T的属性名称。
  1. 类型推断
  • TypeScript 能够根据代码中的一些简单的规则推断变量的类型。如果变量声明时没有指定类型,TypeScript会自动推断出一个类型
  1. 类型推断-基础示例
let x = 3; // x 被推断为 number 类型
  1. 类型推断-最佳通用类型
// 当需要从多个表达式中推断类型时, TypeScript 会选择最合适的通用类型
let x = [0, 1, null]; // x 的类型推断为(number | null)
  1. 类型兼容性
  • TypeScript 中的类型兼容性是基于结构子类型的。结构类型系统是基于类型的成员来确定类型的兼容性。
  1. 类型兼容性-接口兼容性
interface Named {
  name: string;
}

class Person {
  name: string;
}

let p: Named;
p = new Person(); // 正确 因为 Person 有一个兼容的 name 属性
  1. 类型兼容性-函数兼容性
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;

y = x; // OK
x = y; // Err
  1. 高级类型
  • TypeScript 提供了许多高级类型操作,可以在编辑复杂类型定义时提供更强的灵活性
  1. 高级类型-交叉类型
// 交叉类型 & 是将多个类型合并为一个类型
function extend<T, U>(first: T, second: U): T & U {
  let result = <T & U>{};
  for(let id in first) {
    (result as any)[id] = (first as any)[id]
  }
  for(let id in second) {
    if(!result.hasOwnProperty(id)) {
      (result as any)[id] = (second as any)[id];
    }
  }
  return result;
}

let x = extend({ a: 'hello' }, { b: 42 });
let a = x.a; // string
let b = x.b; // number
  1. 高级类型-联合类型
  • 联合类型 | 表示一个值可以是几种类型之一
function padLeft(value: string, padding: string | number) {
  if(typeof padding === 'number') {
    return Array(padding + 1).join(' ') + value;
  }
  if(typeof padding === 'string') {
    return padding + value;
  }
  throw new Error(`Expected string or number, got ${typeof padding}.`);
}

padLeft('Hello world', 4); // 返回 "    Hello world"
padLeft('Hello world', '>>>'); // 返回 ">>>Hello world"
  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();
  }
}
  1. 高级类型-字面量类型
  • 字面量类型约束一个变量的值只能是某个特定的值
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out';

class UIElement {
  animate(dx: number, dy: number, easing: Easing) {
    if(easing === 'ease-in') {
      // do something...
    } else if(easing === 'ease-out') {
      // do something...
    } else if(easing === 'ease-in-out') {
      // do something
    } else {
      throw new Error('参数必须是ease-in, ease-out,ease-in-out')
    }
  }
}

let button = new UIElement();
button.animate(0, 0, 'ease-in'); // ok
button.animate(0, 0, 'unesay'); // error
  1. 装饰器
  • 装饰器是一个特殊类型的声明,能够被附加到类声明、方法、访问器、属性或参数上,装饰器使用 @expressoon 这种形式, expression必须求值为一个函数,它将在运行时被调用,被装饰的声明信息作为参数传入。
  1. 装饰器-类装饰器
function sealed(constructor: Function) {
  Object.seal(constructor);
  Object.seal(constructor.prototype);
}

@sealed
class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }
  greet() {
    return 'Hellow, ' + this.greeting;
  }
}
  1. 装饰器-方法装饰器
function enumerable(value: boolean) {
  return function (target: any, propertyKey: string, descriptor: PropertyDescriptor) {
    descriptor.enumerable = value;
  }
}

class Greeter {
  greeting: string;
  constructor(message: string) {
    this.greeting = message;
  }

  @enumerable(false)
  greet() {
    return 'Hello, ' + this.greeting;
  }
}
  1. 编译选项
  • TypeScript 编译器 可以通过命令行参数和 tsconfig.json 文件进行配置。
  • tsconfig.json 文件用于配置 TypeScript 项目,示例如下:
{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "esModuleInterop": true,
    "skipLibCheck": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules", "**/*.spec.ts"]
}
  1. 常见问题与最佳时间
  2. 如何进行类型定义
  • 解答: 在 TypeScript 中进行类型定义时,推荐尽量使用接口,因为接口可以被类实现和扩展,并且更加灵活和易于阅读。
  1. 何时使用类型断言?
  • 解答: 类型断言用于告诉编译器某个值的具体类型:
let someValue: any = 'this is a string';
let strLength: number = (someValue as string).length;
// 尽量避免过度使用类型断言,因为它可能隐藏潜在的类型错误
  1. 如何处理第三方库的类型定义
  • 解答:可以使用 DefinitelyTyped 项目提供的类型定义文件
npm install @type/jquery --save-dev
  • 这样就可以在 TypeScript 项目中使用 jQuery 的类型定义文件
  1. 如何调试 TypeScript 代码
  • 解答: 可以通过生成 Source Map 文件来调试 TypeScript 代码
{
  "compilerOptions": {
    "sourceMap": true
  }
}
// 这样就可以在调试工具中直接看到  TypeScript 代码,并且设置断点进行调试
  1. 如何提高代码质量
  • 启用严格模式:在 tsconfig.json 中启用 strict选项
  • 使用代码格式化工具, 如 Prettier 来保持代码风格一致。
  • 使用代码静态分析工具: 如 ESLint 来发现和修复代码中的潜在问题
  • 编写单元测试: 确保代码的正确性和稳定性

增加常用方法

  • 如何避免冗余定义类型
  1. 使用 type 或 interface 复用类型
    示例:避免重复定义多个 type
    不推荐(冗余定义)
type UserName = string;
type UserAge = number;

type User1 = {
  name: UserName;
  age: UserAge;
};

type User2 = {
  name: UserName;
  age: UserAge;
  gender: string;
};
  • 推荐(复用 UserBase)
type UserBase = {
  name: string;
  age: number;
};

type User1 = UserBase;
type User2 = UserBase & { gender: string };

  1. 使用 extends 继承 interface
    适用于对象类型的复用。这样 Admin 和 Guest 只扩展 BaseUser,避免重复定义 id 和 name。
interface BaseUser {
  id: number;
  name: string;
}

interface Admin extends BaseUser {
  role: "admin";
}

interface Guest extends BaseUser {
  role: "guest";
}

  1. 使用 Partial<>、Pick<>、Omit<> 进行类型复用
    示例:避免重复定义修改某个字段的类型
interface User {
  id: number;
  name: string;
  age: number;
  email?: string;
}

/* 只需要部分字段 */
type UserPreview = Pick<User, "id" | "name">;

/* 省略某些字段 */
type UserWithoutEmail = Omit<User, "email">;

/* 将所有字段变为可选 */
type PartialUser = Partial<User>;

/* 将所有字段变为必填 */
type RequiredUser = Required<User>;

这样我们可以复用 User,而不需要手写多个类似的 interface。
  1. 使用泛型减少类型重复
    适用于某些通用的数据结构,如 API 请求响应、列表数据。

示例:

// 定义一个通用 API 响应类型
interface ApiResponse<T> {
  code: number;
  message: string;
  data: T;
}

// 具体的用户数据类型
interface User {
  id: number;
  name: string;
}

// 使用泛型,不用重复定义多个响应类型
type UserResponse = ApiResponse<User>;
type UsersResponse = ApiResponse<User[]>;

这样 ApiResponse 可以适用于不同的 data,避免写多个类似的 Response 类型。
  1. 使用 Record<K, V> 代替重复的键值定义
    示例:
    不推荐(冗余定义多个 key-value 对象)
type UserRole = {
  admin: string;
  user: string;
  guest: string;
};
推荐(用 Record<K, V> 复用)
type UserRole = Record<"admin" | "user" | "guest", string>;

这样如果要添加新的角色,比如 “moderator”,只需要加到 Record 里即可。
如果想要在两个已有类型种选取呢?
  1. 使用 & 取交集
    如果 TypeA 和 TypeB 有重叠字段,你可以用 & 组合它们:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };

type Selected = TypeA & TypeB;

✅ 最终 Selected 结构

{
  id: number;
  name: string;
  age: number;
  gender: string;
}

适用场景:用于合并两个对象类型,避免重复定义相同的字段。

  1. 使用 Pick<> 选取公共字段
    如果你只想要 TypeA 和 TypeB 里 相同的字段,可以用 keyof 获取公共字段:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };

type CommonKeys = Extract<keyof TypeA, keyof TypeB>; // "name"
type Selected = Pick<TypeA, CommonKeys>;

✅ 最终 Selected 结构

{
  name: string;
}

适用场景:当你只想保留两个类型中 相同的字段。

  1. 选取两个类型中的某些字段
    如果你要选取 两个类型中的部分字段,可以先 创建联合类型,再用 Pick<>:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };

type SelectedKeys = "id" | "name" | "gender"; // 自定义想要的字段
type Selected = Pick<TypeA & TypeB, SelectedKeys>;

✅ 最终 Selected 结构

{
  id: number;
  name: string;
  gender: string;
}

适用场景:当你想在 两个类型里手动挑选字段,而不只是取公共部分。

  1. 使用 Omit<> 选择非交集字段
    如果你想 去掉交集部分,保留两个类型独有的字段,可以用 Omit<>:
type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };

type UniqueA = Omit<TypeA, keyof TypeB>; // 去掉 `name`
type UniqueB = Omit<TypeB, keyof TypeA>; // 去掉 `name`

✅ 最终 UniqueA 结构

{
  id: number;
  age: number;
}

✅ 最终 UniqueB 结构

{
  gender: string;
}

适用场景:当你只想保留两个类型中 不重叠的部分。

  1. 组合 Pick<> 和 Omit<> 进行复杂选取
    如果你需要更灵活的选择方式,例如:

选取 A 中某些字段
选取 B 中某些字段
合并它们

type TypeA = { id: number; name: string; age: number };
type TypeB = { name: string; gender: string };

type Selected = Pick<TypeA, "id"> & Pick<TypeB, "gender">;

✅ 最终 Selected 结构

{
  id: number;
  gender: string;
}```
适用场景:当你需要在 不同类型里选取特定字段,然后合并。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值