前端百日进阶-4.类型编程

在我初学JavaScript的时候,就是被JavaScript的灵活所吸引。我不需要去考虑一个变量到底是什么类型,可以随时在赋值的时候改变其初始类型。那是我还感觉这样很舒服,这是在我使用Java时得不到的体验。

但是当我工作之后才发现没有类型约束所带来的问题。我工作的时候所参与的项目是一个通过monorepo管理的一个比较大的项目。其中有的包使用了Typescript而有的包是使用JavaScript。通过在这两个包中开发的对比,我可以明显感觉到使用Typescript的优势

  1. 使用Typescript进行重构的时候可以很快识别到代码中参数或者类型不兼容的地方

  2. 在阅读代码的时候我可以很快知道函数中变量所传入的值,而不用在去调试打印出来。可以很快的看懂一个函数是干什么用的

  3. 可以有效的避免使用undefined或者null去调用一个函数而抛出下面的错误

    Uncaught TypeError: Cannot read property 'a' of null
    Uncaught TypeError: Cannot read property 'a' of undefined
    复制代码
  4. Typescript的代码提示真的很舒服

当然类型约束的库不只是typescript还有比如flow这样的库。不过本篇文章主要记录typescirpt的使用

关于类型概念的补充

弱类型: 类型检查不严格,允许隐式类型转换

强类型: 有严格的类型检查,不允许隐式类型转换

动态类型: 可以在代码执行中改变变量初始类型

静态类型: 变量在声明时约束类型后不可在更改

typescript的安装和基本使用

安装

npm install -g typescript

npm install -g ts-node

初始化

tsc --init

// tsconfig.js中常用配置
{
  "compilerOptions": {

    /* 基本选项 */
    "target": "es5",                       // 指定 ECMAScript 目标版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs",                  // 指定使用模块: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],                             // 指定要包含在编译中的库文件
    "allowJs": true,                       // 允许编译 javascript 文件
    "checkJs": true,                       // 报告 javascript 文件中的错误
    "jsx": "preserve",                     // 指定 jsx 代码的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,                   // 生成相应的 '.d.ts' 文件
    "sourceMap": true,                     // 生成相应的 '.map' 文件
    "outFile": "./",                       // 将输出文件合并为一个文件
    "outDir": "./",                        // 指定输出目录
    "rootDir": "./",                       // 用来控制输出目录结构 --outDir.
    "removeComments": true,                // 删除编译后的所有的注释
    "noEmit": true,                        // 不生成输出文件
    "importHelpers": true,                 // 从 tslib 导入辅助工具函数
    "isolatedModules": true,               // 将每个文件作为单独的模块 (与 'ts.transpileModule' 类似).

    /* 严格的类型检查选项 */
    "strict": true,                        // 启用所有严格类型检查选项
    "noImplicitAny": true,                 // 在表达式和声明上有隐含的 any类型时报错
    "strictNullChecks": true,              // 启用严格的 null 检查
    "noImplicitThis": true,                // 当 this 表达式值为 any 类型的时候,生成一个错误
    "alwaysStrict": true,                  // 以严格模式检查每个模块,并在每个文件里加入 'use strict'

    /* 额外的检查 */
    "noUnusedLocals": true,                // 有未使用的变量时,抛出错误
    "noUnusedParameters": true,            // 有未使用的参数时,抛出错误
    "noImplicitReturns": true,             // 并不是所有函数里的代码都有返回值时,抛出错误
    "noFallthroughCasesInSwitch": true,    // 报告 switch 语句的 fallthrough 错误。(即,不允许 switch 的 case 语句贯穿)

    /* 模块解析选项 */
    "moduleResolution": "node",            // 选择模块解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                       // 用于解析非相对模块名称的基目录
    "paths": {},                           // 模块名到基于 baseUrl 的路径映射的列表
    "rootDirs": [],                        // 根文件夹列表,其组合内容表示项目运行时的结构内容
    "typeRoots": [],                       // 包含类型声明的文件列表
    "types": [],                           // 需要包含的类型声明文件名列表
    "allowSyntheticDefaultImports": true,  // 允许从没有设置默认导出的模块中默认导入。

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定调试器应该找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定调试器应该找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成单个 soucemaps 文件,而不是将 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 将代码与 sourcemaps 生成到一个文件中,要求同时设置了 --inlineSourceMap 或 --sourceMap 属性

    /* 其他选项 */
    "experimentalDecorators": true,        // 启用装饰器
    "emitDecoratorMetadata": true          // 为装饰器提供元数据的支持
  }
}
复制代码

Typescript 类型约束

当给变量约束类型后不可以在赋予其他的变量,下面是常用的几个类型

// 布尔
const loading: boolean = false; 
// 数字
const count: number  = 1;
// 字符串
const str: string = "haha";
// 数组
const arr: number[] = [1,2,3]; 
const arr: Array<number> = [1,2,3]; 
// 任意值: 避开类型检查可以赋予任何值
const a: any = 1; 
a = '1'
// 元组: 长度固定,类型固定
const arr: [number, string] = [1, 'haha']
// 空值: 表示没有任何类型,一般用于函数返回值
function sayHello(): void {
  console.log('hello');
}
// Never: 指那些不存在的值
function error(message: string): never {
  // 抛出异常  
  throw new Error(message);
}
// null 和 undefined
const n: null = null
const u: undefined = undefined; ; 
// 联合类型
const un: string | number = 1
un = 'str'
// 交叉类型
const j = {name: string} & {age: number} = {name: 'hah', age: 19}
复制代码

interface和type

interface用于对一个类型的约束

interface User {
  name: string
  age: number
  // ?代表这个值可有可无
  like?: string
  // readonly代表这个值只读不能更改
  readonly id: string
}

// 可以对接口进行扩展
interface Superman extends User {
    skill: () => void
}

// 实现接口: 必须满足接口所约束不然编译会报错
const user: User  = {
  name: 'hah',
  age: 17
}
复制代码

type的使用

type主要用于对一个类型的别名

type s = string | number
type obj = {
  age: number,
  name: string
}
复制代码

函数

function add(x: number, y: number): number {
    return x + y;
}

type Func = {
    callback: (a: number, b: number) => void
}

const obj: Func = {
    callback: add
}
复制代码

访问修饰符

  • public (公有) 成员和纯 JavaScript 的成员一样,是默认的修饰符。
  • private (私有) 成员对外界来说不可访问。
  • protected(保护) 成员和私有成员的区别在于,它能够被继承类访问。
class User {
	public readonly id: number
	constructor(name: string) {
  		this.name = name
  	}
}
复制代码

abstract 修饰的类为抽象类,修饰的方法为抽象方法

  • *抽象类* 不会直接被实例化。抽象类主要用于继承,继承抽象类必须实现它所有的抽象方法。
  • *抽象成员* 不包含具体实现,因此不能被直接访问。这些成员必须在派生类中实现。
abstract class User {
	abstract sayHello(): void;
}

class Man extends User {
	sayHello() {
        console.log('hello')
    }
}
复制代码

命名空间

主要解决变量命名冲突问题,所以独立出来一个命名空间

namespace G { 
    export class User {
        static dosome() {
            console.log('dosome')
        }
    }

    export const a = 100

    export const sayHello = () => {
        console.log('hello')
    }
}

console.log(G.a)
G.sayHello()
G.User.dosome()
复制代码

类型推论

当没有指定类型,ts会自动推断类型

let a = 100
a = 'str' // 这里会报错因为在上面ts已经把a推断为了number
复制代码

类型保护

当有时候typescript不知道具体传入的是什么类型的时候需要实现类型保护来避免报错

class Bird {
    fly() {
        console.log('fly')
    }
}

class Fish {
    swim() {
        console.log('swim')
    }
}

function printf(v: Fish | Bird) {
    // 1. 使用instanceof推断
    if (v instanceof Fish) {
        v.swim()
    }

    // 2. 使用in来推断
    if ('fly' in v) {
        v.fly()
    }

    // 3. 使用typeof推断
    if (typeof v === "object") {
        console.log(v)
    }

    // 4. 使用as进行强制推断(类型断言)
    (v as Bird).fly()
}

printf(new Fish())
复制代码

枚举

enum Color {
    red,
    blue
}

console.log(Color.blue)

// 编译后可以看出其实是通过反向映射(可以通过0来获得也可以通过red来获得值)来实现枚举的
"use strict";
var Color;
(function (Color) {
    Color[Color["red"] = 0] = "red";
    Color[Color["blue"] = 1] = "blue";
})(Color || (Color = {}));
console.log(Color.blue);
复制代码

泛型

泛型是必须包含或引用其他类型才能完成的类型。它加强了变量之间有意义的约束。

// T是根据传入时获取类型
function sort<T>(items: T[]): T[] {
    return items.sort();
}

console.log(sort([1,2,3]))
console.log(sort(['2','3','1']))
复制代码

映射类型

通过泛型还可以去实现映射类型(传入一个类型根据映射返回新的类型),ts中有很多内置的映射类型

  • Partial: 把所有类型都返回成可选的
type Partial<T> = {
  // keyof 运算符会查询给定类型的键集。
  // type K = keyof {name: string, age: number} // 'name' | 'age'
  [P in keyof T]?: T[P];
}
复制代码
  • Readonly: 把所有类型都返回成只读的
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};
复制代码
  • Exclude: 可以从其他类型中排除某些类型
interface User {
    name: string
    age: number
}

type U = Exclude<keyof User, 'age'> // type U = "name"
复制代码
  • Pick: 可以从其他类型中选择某些类型
interface User {
    name: string
    age: number
}

/* 
提取后
{
    age: number;
}
*/
type U = Pick<User, 'age'> 
复制代码
  • Omit: 可以从类型中提取值
interface User {
    name: string
    age: number
}

/* 
提取后
{
    name: string;
}
*/
type U = Omit<User, 'age'> 
复制代码

总结

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享