在我初学JavaScript
的时候,就是被JavaScript
的灵活所吸引。我不需要去考虑一个变量到底是什么类型,可以随时在赋值的时候改变其初始类型。那是我还感觉这样很舒服,这是在我使用Java时得不到的体验。
但是当我工作之后才发现没有类型约束所带来的问题。我工作的时候所参与的项目是一个通过monorepo
管理的一个比较大的项目。其中有的包使用了Typescript
而有的包是使用JavaScript
。通过在这两个包中开发的对比,我可以明显感觉到使用Typescript
的优势
-
使用
Typescript
进行重构的时候可以很快识别到代码中参数或者类型不兼容的地方 -
在阅读代码的时候我可以很快知道函数中变量所传入的值,而不用在去调试打印出来。可以很快的看懂一个函数是干什么用的
-
可以有效的避免使用undefined或者null去调用一个函数而抛出下面的错误
Uncaught TypeError: Cannot read property 'a' of null Uncaught TypeError: Cannot read property 'a' of undefined 复制代码
-
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'>
复制代码
总结
-
今日学习了typescript。若要进行扩展参考:深入理解 TypeScript