2020还不会TypeScript?

什么是TypeScript

TypeScript 是 JavaScript 的类型的超集,它能够编译成纯 JavaScript。编译出来的 JavaScript 能够运行在任何浏览器上。TypeScript 编译工具能够运行在任何服务器和任何系统上。javascript

为何选择TypeScript

JavaScript是一门弱类型/动态类型脚本语言。过于灵活,没有类型检查。在大型项目中代码愈来愈复杂,JavaScript这种灵活的优点就变成了短板html

TypeScript 增长了代码的可读性和可维护性

  • 类型系统其实是最好的文档,大部分的函数看看类型的定义就能够知道如何使用了
  • 能够在编译阶段就发现大部分错误,这总比在运行时候出错好
  • 加强了编辑器和 IDE 的功能,包括代码补全、接口提示、跳转到定义、重构等

TypeScript 很是包容

  • TypeScript 是 JavaScript 的超集,.js 文件能够直接重命名为 .ts 便可
  • 即便不显式的定义类型,也可以自动作出类型推论
  • 能够定义从简单到复杂的几乎一切类型
  • 即便 TypeScript 编译报错,也能够生成 JavaScript 文件
  • 兼容第三方库,即便第三方库不是用 TypeScript 写的,也能够编写单独的类型文件供 TypeScript 读取

TypeScript 的缺点

  • 有必定的学习成本,须要理解接口(Interfaces)、泛型(Generics)、类(Classes)、枚举类型(Enums)等前端工程师可能不是很熟悉的概念
  • 可能和一些库结合的不是很完美
  • 对于小型项目会有额外的开发成本

安装、使用TypeScript

TypeScript 的命令行工具安装方法以下:前端

npm install -g typescript
复制代码

以上命令会在全局环境下安装 tsc 命令,安装完成以后,咱们就能够在任何地方执行 tsc 命令了。java

注意:mac 的话 须要加上sudonode

sudo npm install -g typescript
复制代码

Hello TypeScriptreact

首先建立一个01-ts.ts.ts文件git

将如下代码复制到 01-ts.ts 中:es6

const hello = name => {
 console.log(`hello, ${name}`)  } hello ('TS') 复制代码

而后执行github

tsc 01-ts.ts
复制代码

这时候会生成一个编译好的文件 01-ts.ts:web

var hello = function (name) {
 console.log("hello, " + name); }; hello('TS');  复制代码

TypeScript 中,使用 : 指定变量的类型,: 的先后有没有空格均可以。

上述例子中,咱们用 : 指定name 参数类型为 string。可是编译为 js 以后,并无什么检查的代码被插入进来。

TypeScript 只会进行静态检查,若是发现有错误,编译的时候就会报错。

const hello = (name:string) => {
 console.log(`hello, ${name}`)  } hello (1) 复制代码

编辑器中会提示错误,编译的时候也会出错:

01-ts.ts:5:8 - error TS2345: Argument of type '1' is not assignable to parameter of type 'string'.
 5 hello (1)  ~ 复制代码

就算出现错误仍是会生成js文件:

var hello = function (name) {
 console.log("hello, " + name); }; hello(1);  复制代码

TypeScript 配置文件

开始使用 tsconfig.json 是一件比较容易的事,你仅仅须要写下:

{}
复制代码

或者使用 tsc --init建立tsconfig.json

在项目的根目录下建立一个空 JSON 文件。经过这种方式,TypeScript 将 会把此目录和子目录下的全部 .ts 文件做为编译上下文的一部分,它还会包含一部分默认的编译选项。

编译选项

你能够经过 compilerOptions 来定制你的编译选项:

{
 "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 // 为装饰器提供元数据的支持  } } 复制代码

配置文件配好后 就能够直接使用tsc 进行编译

TypeScript 做用域问题

在默认状况下,当你开始在一个新的 TypeScript 文件中写下代码时,它处于全局命名空间中。如在 foo.ts 里的如下代码。

const foo = 123;
复制代码

若是你在相同的项目里建立了一个新的文件 bar.ts,TypeScript 类型系统将会容许你使用变量 foo,就好像它在全局可用同样:

const bar = foo; // allowed
复制代码

毋庸置疑,使用全局变量空间是危险的,由于它会与文件内的代码命名冲突。咱们推荐使用下文中将要提到的文件模块

解决这个咱们只须要在文件最后 使用export {} 这样每一个文件就是一个模块,单独的做用域

TypeScript 基础

TypeScript 原始数据类型

// 数字,2、8、十六进制都支持
let num:number =0 let num2:number=0xf00a  // 字符串 let v:string="a" //let name:string="liu" // 这里会提示 'name' was also declared here  // 缘由是在默认状态下,typescript 将 DOM typings 做为全局的运行环境,因此当咱们声明 name时, 与 DOM 中的全局 window 对象下的 name 属性出现了重名。 // const a:string=null  // 这里是在tsconfig.json 配置了严格模式 "strict": true 改为false 就行了  const c:boolean=false // void只能存放null/undefined 注意:在严格模式下只能是 undefined const d:void=undefined  const f:null=null  const g:undefined=undefined // 这里报错 'Symbol' only refers to a type, but is being used as a value here. Do you need to change your target library? Try changing the `lib` compiler option to es2015 or later. //是由于Symbol是es2015才有的 ts使用的标准库没有 在tsconfig.json设置 "lib": ["es2015","DOM"] //之后遇到内置方法报错的时候可能就是标准库的关系 在在tsconfig.json设置 "lib" 就行了 const h:symbol=Symbol()   复制代码

TypeScript Object类型

const foo:object =  {}//能够是 function (){} //[]//{}
const obj:{foo:number,bar:string}={foo:1,bar:'w'} // 这里只是简单的定义,跟专业的须要用接口来定义  export { } //确保跟其余成员没有冲突 复制代码

TypeScript 数组类型

// 写法1
const arr1:Array<number>=[1,2,3] // 写法2 const arr2:number[]=[1,2,3]  let arr3:string[] = ["1","2"] let arr4:Array<string> = ["1","2"]  // 小案例 // 若是是 JS,须要判断是否是每一个成员都是数字 // 使用 TS,类型有保障,不用添加类型判断 function sum (...args: number[]) {  return args.reduce((prev, current) => prev + current, 0) }  sum(1, 2, 3) // => 6 复制代码

TypeScript 元组类型

// 元组(Tuple)
 export {} // 确保跟其它示例没有成员冲突  const tuple: [number, string] = [18, 'zce']  // const age = tuple[0] // const name = tuple[1]  const [age, name] = tuple  // ---------------------  const entries: [string, number][] = Object.entries({  foo: 123,  bar: 456 })  const [key, value] = entries[0] // key => foo, value => 123 复制代码

TypeScript 枚举类型

枚举是对JavaScript标准数据类型集的扩充,常被用来限定在必定范围内取值的场景,如一周只能有七天,颜色限定为红绿蓝等。串的枚举。咱们能够用enum来实现。

简单例子

// 枚举
// 注意 enum 是采用 = 赋值  // enum PostStatus { // Draft = 0, // Unpublished = 1, // Published = 2 // }  // 字符串枚举 // enum PostStatus { // Draft = "a", // Unpublished = "b", // Published = "c" // }  //---------------------------- // 能够不用赋值 默认从0开始  // enum PostStatus { // Draft , //0 // Unpublished , //1 // Published //2 // }  //---------------------------- // 给第一个赋值 后面的会在第一个基础上自加  // enum PostStatus { // Draft =6, //6 // Unpublished , //7 // Published //8 // }    复制代码

常量枚举

// 常量枚举
const enum PostStatus {  Draft , //0  Unpublished , //1  Published //2 } const post ={ status:PostStatus.Draft } // 编译后 var post = {  status: 1 /* Draft */ }; 复制代码

我一般用 = 1 初始化,由于在枚举类型值里,它能让你作一个安全可靠的检查。

TypeScript 函数类型

// 简单案例
 // function func1 (a: number, b: number): string { // return 'func1' // }  // func1(100, 200)   // 默认值或者能够选参数 // b: number=1 // b?: number 问号标示可选 // function func1 (a: number, b: number=1): string { // return 'func1' // }  // func1(100, 200)  // 任意个数的参数 使用es6扩展运算符  // function func1 (...res:number): string { // return 'func1' // }  // func1(100, 200) 复制代码

TypeScript 任意(any)类型

// 任意类型(弱类型)
 export {} // 确保跟其它示例没有成员冲突  function stringify (value: any) {  return JSON.stringify(value) }  stringify('string')  stringify(100)  stringify(true)  let foo: any = 'string'  foo = 100  foo.bar()  // any 类型是不安全的  复制代码

TypeScript 隐式类型推断

若是没有明确的指定类型,那么 TypeScript 会依照类型推论(Type Inference)的规则推断出一个类型。

案列

let foo = 123; // foo 是 'number'
let bar = 'hello'; // bar 是 'string'  foo = bar; // Error: 不能将 'string' 赋值给 `number`  //数组 const bar = [1, 2, 3]; bar[0] = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型  //对象 const foo = {  a: 123,  b: 456 };  foo.a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型 复制代码

建议尽可能每一个变量都添加明确的类型,便于后期更直观的理解代码

注意

当心使用参数

若是类型不能被赋值推断出来,类型也将不会流入函数参数中。例如以下的一个例子,编译器并不知道 foo 的类型,所它也就不能推断出 a 或者 b 的类型。

const foo = (a, b) => {
 /* do something */ }; 复制代码

然而,若是 foo 添加了类型注解,函数参数也就能被推断(a,b 都能被推断为 number 类型):

type TwoNumberFunction = (a: number, b: number) => void;
const foo: TwoNumberFunction = (a, b) => {  /* do something */ }; 复制代码

当心使用返回值

尽管 TypeScript 通常状况下能推断函数的返回值,可是它可能并非你想要的。例如以下的 foo 函数,它的返回值为 any:

function foo(a: number, b: number) {
 return a + addOne(b); }  // 一些使用 JavaScript 库的特殊函数 function addOne(a) {  return a + 1; } 复制代码

这是由于返回值的类型被一个缺乏类型定义的 addOne 函数所影响(a 是 any,因此 addOne 返回值为 any,foo 的返回值是也是 any)。

TypeScript 进阶

TypeScript 类型断言

在某些状况TS没法检测出类型,须要你告诉它。TypeScript 类型断言用来告诉编译器你比它更了解这个类型,而且它不该该再发出错误

// 类型断言
 export {} // 确保跟其它示例没有成员冲突  // 假定这个 nums 来自一个明确的接口 const nums = [110, 120, 119, 112]  const res = nums.find(i => i > 0)  // res TypeScript 是不知道是什么类型的 全部须要你告诉它 // const square = res * res  // 推荐使用 as const num1 = res as number  const num2 = <number>res // JSX 下不能使用 复制代码

注意: 类型断言不是类型转换,是由于转换一般意味着某种运行时的支持。可是,类型断言纯粹是一个编译时语法,同时,它也是一种为编译器提供关于如何分析代码的方法。

TypeScript 接口

在面向对象语言中,接口(Interfaces)是一个很重要的概念,它是对行为的抽象,而具体如何行动须要由类(classes)去实现(implement)。

简单案例

// 接口
interface Post {  title:string  content:string } // 使用接口 function printPost (post: Post) {  console.log(post.title)  console.log(post.content)  } printPost({title:'234',content:'23'})  复制代码

可选成员/只读成员/任意成员

// ? 标示可选 // interface Post { // title:string // content:string, // subtitle?:string // } // // 使用接口 // function printPost (post: Post) { // console.log(post.title) // console.log(post.content)  // }  // readonly 只读 interface Post {  title:string  content:string,  subtitle?:string,  readonly sun:string } // 使用接口 function printPost (post: Post) {  console.log(post.title)  console.log(post.content)  }   // 任意类型的属性 interface Post {  [prop:string]:string } // 使用接口 const cache:Post={  name:'sdffs',  title:'s' }  复制代码

TypeScript 类的使用

简单例子

export { }
class Person {  // 首先先定义类型  name: string  age: string  constructor(name: string, age: string) {   this.name = name  this.age = age  }  sayHi(msg:string):void{  console.log(`${this.name}${msg}`);   }  } 复制代码

类的访问修饰符

  • private : 私有
  • public :公共
  • protected :受保护的
class Person {
 // 首先先定义类型  public name: string //公共  private age: string // 私有  protected gender: string //  constructor(name: string, age: string) {   this.name = name  this.age = age  this.gender = 'sss'  }  sayHi(msg: string): void {  console.log(`${this.name}${msg}`);   }  }  class Student extends Person {  constructor(name: string, age: string) {  super(name, age)  console.log(this.gender) // 能够访问到   }  } const tom = new Person('tom', '18') // console.log(tom.age); //访问不到 // console.log(tom.gender); //也访问不到  复制代码

constructor 加修饰符

class Student extends Person {
 private constructor (name: string, age: number) {  super(name, age)  console.log(this.gender)  }   static create (name: string, age: number) {  return new Student(name, age)  } }  const tom = new Person('tom', 18) console.log(tom.name) // console.log(tom.age) // console.log(tom.gender)  //不能直接经过 new来创造对象 const jack = Student.create('jack', 18)  复制代码

只读属性

使用`readonly`来定义只读,若是有修饰符的话 跟在修饰符后面
class Person {  // 首先先定义类型  public name: string //公共  private age: string // 私有  // 只读成员 readonly  protected readonly gender: boolean  constructor(name: string, age: string) {   this.name = name  this.age = age  this.gender = 'sss'  }  sayHi(msg: string): void {  console.log(`${this.name}${msg}`);   }  } 复制代码

类与接口

实现(implements)是面向对象中的一个重要概念。通常来说,一个类只能继承自另外一个类,有时候不一样类之间能够有一些共有的特性,这时候就能够把特性提取成接口(interfaces),用 implements 关键字来实现。这个特性大大提升了面向对象的灵活性。

举例来讲,门是一个类,防盗门是门的子类。若是防盗门有一个报警器的功能,咱们能够简单的给防盗门添加一个报警方法。这时候若是有另外一个类,车,也有报警器的功能,就能够考虑把报警器提取出来,做为一个接口,防盗门和车都去实现它:

export{} // 先定义接口  interface Eat {  // 定义方法类型  eat (food:string):void  }  interface Run {  // 定义方法类型  run (distance:string):void  }  class Person implements Eat,Run{  eat(food:string):void{  console.log(food);   }  run (distance:string):void {  console.log(distance);  }  } 复制代码

抽象类

使用abstract来定义 抽象类

abstract class Animal {
 eat (food: string): void {  console.log(`呼噜呼噜的吃: ${food}`)  }  // 抽象方法不须要方法体  abstract run (distance: number): void }  class Dog extends Animal {  run(distance: number): void {  console.log('四脚爬行', distance)  }  }  const d = new Dog() d.eat('嗯西马') d.run(100) 复制代码

不能直接经过 new Animal() 建立实例

TypeScript泛型

泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定类型的一种特性。

// 泛型
 export {} // 确保跟其它示例没有成员冲突  function createNumberArray (length: number, value: number): number[] {  const arr = Array<number>(length).fill(value)  return arr }  function createStringArray (length: number, value: string): string[] {  const arr = Array<string>(length).fill(value)  return arr }  function createArray<T> (length: number, value: T): T[] {  const arr = Array<T>(length).fill(value)  return arr }  // const res = createNumberArray(3, 100) // res => [100, 100, 100]  const res = createArray<string>(3, 'foo') 复制代码

类型声明

有一些三方的库 引入不知道是什么类型 可使用declarelai 声明类型

还有些第三方库会有专门的类型声明文件

import { camelCase } from 'lodash'
import qs from 'query-string'  qs.parse('?key=value&key2=value2')  // declare function camelCase (input: string): string  const res = camelCase('hello typed') 复制代码

参考

深刻理解 TypeScript

TypeScript 入门教程

相关文章
相关标签/搜索