TS的基本使用和常见面试题整理

6,594 阅读7分钟

类型系统

  1. 强类型与弱类型
  • 强类型:不允许隐式类型转换
  1. 静态类型与动态类型
  • 动态类型:运行阶段才能能够明确变量类型,变量类型能随时发生变化(变量本身无类型,变量值有类型)
  1. JavaScript
  • 弱类型、动态类型
  • 常见问题
    • 运行时才报错
    • 类型不明确,造成函数功能发生改变
    • 对对象索引用法错误
  1. TypeScript
  • JavaScript + ES6+ + 类型系统
  • 强类型优势
    • 编译时报错
    • 更加智能,编码更准确
    • 重构更加牢靠
    • 减少代码层面不必要的类型判断
  • 缺点
    • 语言本身多了很多概念,增加了学习成本
    • 项目初期,需要编写很多类型声明

编译运行

  1. 基本使用

    • 安装
    yarn init
    yarn add typescript --dev
    
    • 编译
    yarn tsc demo.ts
    
  2. 配置文件:

    • 初始化
    yarn tsc --init
    
    • 修改 tsconfig.json
    • 运行
    yarn tsc
    
    • 如果要使用ES6的语法,需要引用对应的标准库,否则会报错
    "target": "es5", 
    "lib": ["ES6","DOM"],  
    
  3. 编译流程:

    • Scanner 扫描器 (scanner.ts)
    • Parser 解析器 (parser.ts)
    • Binder 绑定器 (binder.ts)
    • Checker 检查器 (checker.ts)
    • Emitter 发射器 (emitter.ts)

基本使用

  1. 原始类型
  • string

  • number:包含NaN、Infinity

  • boolean

  • void:可以为null

  • null

  • undefined

  • Symbol

    • 在非严格模式下:
    • string、number、boolean可以为null、undefined;
    • void可以为undefined
    • 关闭严格模式:
    "strictNullChecks": false,  
    
  1. 对象类型
const foo: object = function(){} // object不单止对象
const obj: {foo: string} = { foo: 'bar' } // 对象类型设置方法
  1. 数组类型
// 两种方式
const arr1: Array<number> = [1, 2, 3, 4]
const arr2: number[] = [1, 2, 3, 4]
  1. 元组类型
const tuple: [number, string] = [18, 'tt']
const [age, names] = tuple
  1. 枚举类型
// 对象枚举
const obj = {
    a: 0,
    b: 1,
    c: 2,
}
// 数字枚举
enum num {
    a = 1,
    // 下面的数值会累加
    b,
    c,
}
// 字符串枚举
enum str {
    a = 'a',
    b = 'b',
    c = 'c',
}
// 常量枚举(编译后的结构更加简单)
const enum val {
    a = 'a',
    b = 'b',
    c = 'c',
}
const post = {
    a: num.a,
    b: str.b,
    c: val.c,
}
  1. 函数类型
// 函数声明
function func (a: number, b?: number, c: number = 200, ...args: number[]): string {
    console.log(a,b,c,args);
    return 'func'
}
func(100, 200, 300, 400, 500, 600)
// 函数表达式
const func2 = function(a: number): void {
    console.log(a)
}
  1. 任意类型
  • 属于动态类型
const func = function(value: any) {
    console.log(value)
}
  1. 隐式类型推断
// 会自动推断成数字类型,但不建议这样
let age = 18
  1. 类型断言
const num1 = res as number;
const num2 = <number>res; // 可能会和JSX的标签产生冲突
  1. 接口interface
  • 用来约束对象的结构,一个对象使用接口,就要定义接口中所有的成员
interface Post {
    title: string
    content?: string // 可选成员
    readonly summary: string // 只读成员
}
function print (post: Post){
    console.log(post.title)
    console.log(post.content)
}
print({
    title: 'tt',
    summary: 'zz'
})

// 动态成员
interface Cach {
    [prop: string]: string
}
const Cach: Cach = {}
Cach.foo = 'bar'
  1. 类class
  • 定义
class Person {
    public name: string;
    private age: number; // 私有属性,不可访问
    protected gender: string; // 不可访问,可以继承
    readonly full: boolean; // 只读属性

    constructor(name: string, age: number) {
        this.name = name;
    }
    say(msg: string): void {
        console.log(msg);
    }
}
  • 与接口
interface Eat {
    eat(food: string): void
}
interface Run {
    run(way: string): void
}
class Person implements Eat,Run  {
    eat(food: string): void {

    }
    run(way: string): void {

    }
}
  • 抽象类:只能被继承,不能被实例化
abstract class Person  {
    eat(food: string): void {

    }
    abstract run(way: string): void
}
class Dog extends Person {
    run(food: string): void {}
}
  1. 泛型
  • 定义函数、接口、类时不指定类型,使用时才指定
function func<T> (lenfth: number, val: T): T[] {
    const arr = Array<T>(lenfth).fill(val)
    return arr
}
  1. 联合类型 | (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)

  2. 交叉类型 & (联合类型一次只能一种类型;而交叉类型每次都是多个类型的合并类型。)

  3. typeof:

  • typeof 操作符可以用来获取一个变量声明或对象的类型
function toArray(x: number): Array<number> {
  return [x];
}

type Func = typeof toArray; // -> (x: number) => number[]
  1. keyof:
  • keyof 操作符可以用来遍历一个对象中的所有 key 值
interface Person {
    name: string;
    age: number;
}

type K1 = keyof Person; // "name" | "age"
  1. in:
  • in 用来遍历枚举类型
type Keys = "a" | "b" | "c"

type Obj =  {
  [p in Keys]: any
} // -> { a: any, b: any, c: any }

  1. extends :
  • 有时候我们定义的泛型不想过于灵活或者说想继承某些类等,可以通过 extends 关键字添加泛型约束。
interface ILengthwise {
  length: number;
}

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

loggingIdentity(3);
loggingIdentity({length: 10, value: 3});
  1. Paritial

Partial 的作用就是将某个类型里的属性全部变为可选项 ?。

  1. Reuqired

Required 的作用就是将某个类型里的属性全部变为必选项。

  1. Readonly

Readonly 的作用是将某个类型所有属性变为只读属性,也就意味着这些属性不能被重新赋值。

  1. Record

Record<K extends keyof any, T> 的作用是将 K 中所有的属性的值转化为 T 类型。

interface PageInfo {
  title: string;
}

type Page = "home" | "about" | "contact";

const x: Record<Page, PageInfo> = {
  about: { title: "about" },
  contact: { title: "contact" },
  home: { title: "home" }
};
  1. Exclude

Exclude<T, U> 的作用是将某个类型中属于另一个的类型移除掉。

type T0 = Exclude<"a" | "b" | "c", "a">; // "b" | "c"
type T1 = Exclude<"a" | "b" | "c", "a" | "b">; // "c"
  1. Extract

Extract<T, U> 的作用是从 T 中提取出 U。

type T0 = Extract<"a" | "b" | "c", "a" | "f">; // "a"
type T1 = Extract<string | number | (() => void), Function>; // () => void
  1. 一些问题
  • 解决不同文件相同变量定义报错的作用域问题
    • 使用立即执行函数
    (function(){
        const b: number = 1
    })()
    
    • 使用模块化
    const b: number = 1
    export {}
    
  • 类型声明第三方模块
    • 安装
    <!-- lodash第三方类型声明模块 -->
    yarn add @types/lodash --dev
    <!-- 已经包含类型声明的库 -->
    yarn add query-string
    
    • 使用
    import { camelCase } from 'lodash'
    
    // 类型声明(没安装模块则需要手动声明)
    // declare function camelCase(str: string): string
    
    camelCase('hello type') // 把字符串转化为驼峰格式
    

常见TS面试题

一、 你觉得使用ts的好处是什么?

1. 拓展了语法:TypeScript是JavaScript的加强版,它给JavaScript添加了可选的静态类型和基于类的面向对象编程,它拓展了JavaScript的语法。
2. 明确的数据类型:作为强类型语言,你可以明确知道数据的类型。代码可读性极强。
3. 友好的报错提示:TS 在开发时就能给出编译错误, 而 JS 错误则需要在运行时才能暴露。
4. 方便的特性:ts中有很多很方便的特性, 比如可选链.
5. 纯面向对象:Typescript 是纯面向对象的编程语言,包含类和接口的概念.

二、type 和 interface的异同

  1. 用interface描述数据结构,用type描述类型
  2. type 可以声明基本类型别名,联合类型,元组等类型
// 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

// 当你想获取一个变量的类型时,使用 typeof
let div = document.createElement('div');
type B = typeof div

// type 支持类型映射
type Keys = "firstname" | "surname"

type DudeType = {
  [key in Keys]: string
}

const test: DudeType = {
  firstname: "Pawel",
  surname: "Grzybek"
}
  1. 都可以描述一个对象或者函数
interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void;
}

type User = {
  name: string
  age: number
};

type SetUser = (name: string, age: number)=> void;
  1. 都允许拓展(extends)并且两者并不是相互独立的,也就是说 interface 可以 extends type, type 也可以 extends interface 。
// interface extends interface
interface Name { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

// type extends type
type Name = { 
  name: string; 
}
type User = Name & { age: number  };

// interface extends type
type Name = { 
  name: string; 
}
interface User extends Name { 
  age: number; 
}

// type extends interface
interface Name { 
  name: string; 
}
type User = Name & { 
  age: number; 
}

三、 如何基于一个已有类型, 扩展出一个大部分内容相似, 但是有部分区别的类型?

  1. 通过Pick和Omit
interface Test {
    name: string;
    sex: number;
    height: string;
}

type Sex = Pick<Test, 'sex'>;

const a: Sex = { sex: 1 };

type WithoutSex = Omit<Test, 'sex'>;

const b: WithoutSex = { name: '1111', height: 'sss' };
  1. Partial, Required
  2. 通过泛型

泛型是指在定义函数、接口或类的时候,不预先指定具体的类型,使用时再去指定类型的一种特性。可以把泛型理解为代表类型的参数。

interface Test<T = any> {
    userId: T;
}

type TestA = Test<string>;
type TestB = Test<number>;

const a: TestA = {
    userId: '111',
};

const b: TestB = {
    userId: 2222,
};