我正在参与掘金创作者训练营第6期,点击了解活动详情
什么是TypeScript
引用官方定义:
Typed JavaScript at Any Scale.
添加了类型系统的 JavaScript,适用于任何规模的项目。
特性
类型系统,弥补了JavaScript的缺点
我们都知道,JavaScript中是没有类型可言的。
- 一个变量定义的时候是
string类型, 可能一会就变成了number类型或者其他的类型 JavaScript隐式类型转换,有些变量在运行前很难明确类型。比如下面的例子
const { log } = console
let foo = '123'
foo = 456 // 类型改变
const sum = foo + '789'
log('sum', sum, typeof sum)
const bool = 0
// bool会被当成false
if (bool) {
log(true)
} else {
log(false)
}
可以看出JavaScript是很灵活的,这使得JavaScript发展迅速,同时也带来了很多问题:
- 项目不易维护
- 代码质量参差不齐
- 运行时错误多
所以说Typescript弥补了JavaScript的缺点
任何规模的项目适用
这是显而易见的,随着项目复杂度,项目功能的增加,TypeScript能带来更好的可维护性。
静态类型,编译阶段就会进行类型检查
JavaScript是动态类型,在运行阶段执行类型检查,因为JavaScript是一门解释型语言,没有编译阶段,这段代码在运行时才会报错:
const f = 0
f.split('')
但如果在ts中这么写,就会是下面这样:
在编写代码的时候就把一些明显的错误提示出来了。很完美😊
弱类型
为什么说Typescript是弱类型的,看下面的例子:
变量f初始化值为0,是number类型,与空字符串相加后,结果的类型为string,说明 + 被识别为字符串拼接,与JavaScript一致。
TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以它们都是弱类型。
TS 文件
- 文件后缀为
.ts - 文件后缀为
.tsx(用TypeScript编写React)
运行:
ts文件不能直接运行,要先编译为js文件,才能运行。tsx文件也要通过插件处理编译后才能运行。
安装TypeScript
yarn add typescript -g // 全局安装
ts文件不能直接运行, 将ts文件编译为js文件,再调用node才能执行
编译
tsc编译当前目录下的所有ts文件tsc ts文件编译指定的文件
编译配置文件
快速生成配置文件:
tsc --init
执行后会在当前页面生成一个tsconfig.json的配置文件
配置项如下:
{
"compilerOptions": {
/* Visit https://aka.ms/tsconfig to read more about this file */
/* Projects */
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
/* Language and Environment */
"target": "ES2015", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
"lib": ["ES2017", "DOM"], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
/* Modules */
"module": "CommonJS", /* Specify what module code is generated. */
"rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
// "resolveJsonModule": true, /* Enable importing .json files. */
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
/* JavaScript Support */
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
/* Emit */
"declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
"outDir": "dist", /* Specify an output folder for all emitted files. */
// "removeComments": true, /* Disable emitting comments. */
// "noEmit": true, /* Disable emitting files from a compilation. */
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
// "newLine": "crlf", /* Set the newline character for emitting files. */
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
/* Interop Constraints */
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
基础
在 TypeScript 中,使用 : 指定变量的类型,: 的前后有没有空格都可以。
基础数据类型
JS 中基础数据类型包括:boolean、number、string、null、undefined 以及 ES6 中的新类型 Symbol 和 ES10 中的新类型 BigInt。
对象类型
对象类型包括:Object、Array、function、Set、Map
boolean
// 推荐
const bool: boolean = true
// 或者
const bool1: boolean = Boolean(1)
⚠️ 在 TypeScript 中,boolean 是 JavaScript 中的基本类型,而 Boolean 是 JavaScript 中的构造函数。其他基本类型(除了 null 和 undefined)一样
举个🌰,下面的写法编译不通过:
const bool1: boolean = new Boolean(1) // 错误的
number
const num:number = 2
const nan: number = NaN;
const infinity: number = Infinity;
// 二进制
const binary: number = 0b1010 // 10
// 八进制
const octal: number = 0o020 // 16
string
const str: string = 'haha'
const templateStr = `${str},你好`
null 和 undefined
⚠️在非严格模式下,null 和 undefined可以赋值给其他的类型。比如:
const nu:null = null
const undef: undefined = undefined
let str: string = ''
let bool: boolean = false
let num: number = null
str = nu
bool = nu
num = nu
str = undef
bool = undef
num = undef
严格模式下上面的代码编译不通过。
❓如何配置为非严格模式呢
tsconfig.json文件中,添加下列配置。
{
"compilerOptions": {
"strictNullChecks": false
}
}
void
void 用来表示函数没有任何返回值。比如:
// 用void 表示printName函数没有任何返回值
function printName(name: string): void{
console.log(name)
}
// 指定返回值为string
function getStrName(name: string): string{
return 'STR' + name
}
还可以声明 void 类型的变量,但没什么用,只能赋值为 null 或者 undefined
any
任意值(any)用来表示允许赋值为任意类型。
定义为 any 类型代表着与在JavaScript中定义变量一样,想怎么写就怎么写
let a:any = 20
a = ''
a = true
a = null
// 访问任意属性
a.s = 4
// 访问任意方法
a.say()
unknown
unknown也代表着 任意值,类似于any 但是比any安全
let unk: unknown = 0
never
可用于表示函数抛出异常:
function fail(msg: string): never {
throw new Error(msg);
}
或者表示我们想不到的,比如官网的🌰:
类型推论
如果我们在定义变量时没有为变量指定明确的类型,TypeScript将会根据类型推论的规则去推断出一个类型。
举个简单的🌰:
定义noType变量时,没有明确指定其类型,然后推理的类型为number,当我们把一个string 赋值给 noType 就会报错
联合类型
联合类型指:一个变量的值可以是多个类型中的任意一种。
举个简单的🌰:
// 联合类型
// strOrNum变量既可以为string,又可以为number
let strOrNum: string | number = 20
strOrNum = ''
strOrNum = 300
interface(接口/对象的类型)
interface Person {
readonly id: string, // id只能读取值,不能更改
name: string, // 必选项, 即定义的对象中必须有name属性
age: number, // 必选项
isMarry: boolean, // 必选项
height?: number, // 可选项
[propName: string]: any // 可扩展,并且扩展项的值可以是任意类型
}
const tom: Person = {
id: '0',
name: 'tom',
age: 12,
isMarry: false,
// 扩展项
extA: '',
extB: 2,
extC: true
}
⚠️Person中 name age isMarry是必选项,也就是我们创建的Person类型的对象中必须包含这三个属性。否则编译将不通过。
⚠️如果更改tom 对象的 id 属性,会报以下错误:
⚠️当interface定义了可以有任意属性,就是指配置了[propName: string]: 类型,意味着接口中的其他属性的类型都必须是 任意属性的类型或其子类型。在上例中任意属性的类型为any,满足这一要求。举个不满足的🌰:
数组的类型
// 用类型加方括号
const arr: number[] = [1, 2, 3]
// 用数组泛型表示数组
const arr1: Array<number> = [1, 2, 3]
// 用接口表示数组
interface MyArray {
[index: number] : number
}
const myarr: MyArray = [1, 2, 3]
// 类数组
interface LikeArray {
[index: number] : any,
length: number,
[propName: string]: any
}
function func(): void {
const argu:LikeArray = arguments
}
函数的类型
函数声明
// 函数声明
function testFunc1(a: string, b: string): boolean {
return a === b
}
函数表达式
// 自动推理类型
const expessionFunc1 = function (a: string, b: string): boolean {
return a === b
}
// 手动添加类型
const expessionFunc2: (a: string, b: string) => boolean = function (a: string, b: string): boolean {
return a === b
}
⚠️调用时,参数比定义时多 或 少都会编译不通过
可选参数
function funcRestArgu(first: string, two?: number) {
}
⚠️可选参数只能在固定参数的后面
剩余参数
function funcRestArgu(first: string, two?: number, ...rest: string[]) {
}
⚠️剩余参数只能放在最后
类型断言
语法:
- 变量 as 类型 👍推荐
- <类型>变量
❓为什么不推荐第二种写法呢,因为在tsx中 <ddd> 表示一个ReactNode, 为了防止冲突。
类型断言有啥用
将联合类型断言为其中一个类型
将父类型断言为一个子类型
interface HttpErrorOptions {
httpCode: number,
[propName: string]: any
}
interface OtherErrorOptions {
errorMsg: string,
[propName: string]: any
}
class HttpError extends Error {
httpCode: number
constructor(options: HttpErrorOptions) {
super()
this.httpCode = options.httpCode
}
}
class OtherError extends Error {
errorMsg: string
constructor(opts: OtherErrorOptions) {
super()
this.errorMsg = opts.errorMsg
}
}
function isHttpError(error: Error) {
return (error as HttpError).httpCode !== 200
}
将任何一个类型断言为any
(window as any).x = 20
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。
当我们需要使用一个第三方的npm包的时候,它的声明文件一般可能存在于两个地方:
- 与该 npm 包绑定在一起。判断依据是
package.json中有types字段,或者有一个index.d.ts声明文件。这种模式不需要额外安装其他包,是最为推荐的,所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。 - 发布到
@types里。我们只需要尝试安装一下对应的@types包就知道是否存在该声明文件,安装命令是npm install @types/foo --save-dev。这种模式一般是由于 npm 包的维护者没有提供声明文件,所以只能由其他人将声明文件发布到@types里了。
假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了。由于是通过 import 语句导入的模块,所以声明文件存放的位置也有所约束,一般有两种方案:
- 创建一个
node_modules/@types/foo/index.d.ts文件,存放foo模块的声明文件。这种方式不需要额外的配置,但是node_modules目录不稳定,代码也没有被保存到仓库中,无法回溯版本,有不小心被删除的风险,故不太建议用这种方案,一般只用作临时测试。 - 创建一个
types目录,专门用来管理自己写的声明文件,将foo的声明文件放到types/foo/index.d.ts中。这种方式需要配置下tsconfig.json中的paths和baseUrl字段。
npm 包的声明文件
npm 包的声明文件主要有以下几种语法:
export导出变量export namespace导出(含有子属性的)对象export defaultES6 默认导出export =commonjs 导出模块
export
// type.d.ts
export interface Person {
readonly id: string // id只能读取值,不能更改
name: string // 必选项, 即定义的对象中必须有name属性
age: number // 必选项
isMarry: boolean // 必选项
height?: number // 可选项
[propName: string]: any // 可扩展,并且扩展项的值可以是任意类型
}
export function getName():void
export const strOrNum: string | number
export enum week{Mon, Tus = 100, Wed, Thu, Fri, Sat, Sun}
export class People{
name: string
private readonly age: number
constructor()
getAge(): number
}
使用的时候
import { Person, People, week, strOrNum, getName } from './type'
export namespace
export namespace 用来导出一个拥有子属性的对象
export default
在 ES6 模块系统中,使用 export default 可以导出一个默认值。只有 function、class 和 interface 可以直接默认导出,其他的变量需要先定义出来,再默认导出。
export =
对于使用 commonjs 规范的库,假如要为它写类型声明文件的话,就需要使用到 export = 语法
export = foo
declare namespace foo {
const name: string
function getName(): string
}
使用时推荐的用法:
import foo = require('./commonjs')
自动生成声明文件
如果库的源码本身就是由 ts 写的,那么在使用 tsc 脚本将 ts 编译为 js 的时候,添加 declaration 选项,就可以同时也生成 .d.ts 声明文件了。
我们可以在命令行中添加 --declaration(简写 -d),或者在 tsconfig.json 中添加 declaration 选项。这里以 tsconfig.json 为例:
{
"compilerOptions": {
"declaration": true,
"outDir": "dist",
}
}
执行tsc命令后
进阶
类型别名
语法: type 类型。相当于一个类型的简写,常用于联合类型
字符串字面量类型
用来约束取值只能是某几个字符串中的一个。
语法: type 类型。
type AorB = 'A' | 'B'
let h: AorB = 'B'
枚举 enum
用于取值被限定在一定范围内的场景, 🌰:
一周有七天:
enum week {Mon, Tus, Wed, Thu, Fri, Sat, Sun}
❓编译过后什么样
var week;
(function (week) {
week[week["Mon"] = 0] = "Mon";
week[week["Tus"] = 1] = "Tus";
week[week["Wed"] = 2] = "Wed";
week[week["Thu"] = 3] = "Thu";
week[week["Fri"] = 4] = "Fri";
week[week["Sat"] = 5] = "Sat";
week[week["Sun"] = 6] = "Sun";
})(week || (week = {}));
就相当于这样的一个对象:
所以代码中我们可以这么用
week.Mon // 0
week[0] // 'Mon'
// ...
枚举成员会被赋值为从 0 开始递增的数字,同时也会对枚举值到枚举名进行反向映射,也可以手动赋值
为枚举成员手动赋值
// 赋值为常数
enum week {Mon, Tus = 100, Wed, Thu, Fri, Sat, Sun}
// 赋值为计算的值
enum week1 {Mon, Tus, Wed, Thu, Fri, Sat, Sun = h.length}
// 如果不是最后一个枚举项 被 赋值为计算的值,那这个枚举项后面的一项必须手动赋值,否则编译不通过
enum week2 {Mon, Tus = h.length, Wed = 2, Thu, Fri, Sat, Sun}
编译后:
常数枚举
用const enum 定义的枚举类型:
const enum constweek { Mon = 20, Tus, Wed, Thu, Fri, Sat, Sun }
let sun = constweek.Sun
它会在编译阶段被删除,并且不能包含计算成员。
编译过后:
var sun = 26 /* constweek.Sun */;
外部枚举
用 declare enum 定义的枚举类型:
declare enum color {red, green = 1, blue}
它会在编译阶段被删除,并且不能包含计算成员。
元组
数组中是相同类型的对象,元组则是不同类型的对象。
let xx: [string, number] = ['', 2]
// 可以添加项,可添加的项是元组中每个类型的联合类型,按照上例来说就是 string | number
xx.push(5)
class
关于类的介绍,请参考Class 的基本语法
TypeScript中可以使用public private proteced来修饰类的属性,方法和构造函数
public修饰的属性或方法是公有的,可以在任何地方被访问到,默认所有的属性和方法都是public的private修饰的属性或方法是私有的,不能在声明它的类的外部访问,只能在类本身访问protected修饰的属性或方法是受保护的,它和private类似,区别是它在子类中也是允许被访问的
看个🌰:
private constructor
当用private修饰构造函数,那这个类不能被实例化,也不能被继承
protected constructor
当用protected修饰构造函数,那这个类不能被实例化,可以被继承
抽象类
用abstract修饰的类,只能被继承,不能被实例化。
泛型
泛型就是指在定义接口,函数,类的时候不明确定义类型,使用泛型代替,等真正用的时候才明确类型的一种特性。
单个类型参数
// 单个类型参数
function getArray<T>(arrayItem: T): Array<T> {
return [arrayItem]
}
多个类型参数
// 多个类型参数
function moreArgu<T, U>(argu1: T, argu2: U): [T, U]{
return [argu1, argu2]
}
const val1 = getArray(2)
const val2 = moreArgu('1', 2)
泛型定义接口中的函数
// 接口中的函数也可以使用泛型来定义
interface X{
getArr<T>(argu1: T): Array<T>
}
const r: X = {
getArr(argu1){
return [argu1]
}
}
const val3 = r.getArr('')
泛型类
// 泛型类
class M<T> {
getArr: (argu1: T) => Array<T>
constructor() {
this.getArr = (argu1) => [argu1]
}
}
const m = new M()
const mArr = m.getArr('1')
为泛型指定默认类型
// 默认类型
function defaultType<T = number>(arrayItem: T): Array<T> {
return [arrayItem]
}
泛型约束
在函数内部使用泛型变量的时候,由于事先不知道它是哪种类型,所以不能随意的操作它的属性或方法,如果需要操作一些属性和方法,就可以通过以下的示例使用泛型约束来实现。
❌ 编译不通过
function ErrH<T>(argu1: T): Array<T> {
if (argu1.length) {
return new Array(argu1.length).fill(argu1)
}
return [argu1]
}
✅ 使用泛型约束
function H<T extends LikeArray>(argu1: T): Array<T> {
if (argu1.length) {
return new Array(argu1.length).fill(argu1)
}
return [argu1]
}