为什么要学习 TypeScript
前端老法师应该都知道,一路走来js有太多不完善的地方;呃,它是弱类型语言,它是解释型脚本,它入门其实很简单但深入挺难。想要知道为什么学习 TypeScript,那么我们首先要学习下什么是强类型、弱类型、静态类型、动态类型、类型系统。
强类型与弱类型(类型安全)
强类型,形参和实参的类型必须保持一致
强类型,不允许随意的隐式类型转换,而弱类型是允许的
弱类型的问题
缺失了类型系统的可靠性:
- 一些类型异常要等到运行时才能发现
- 类型不明确造成函数功能的改变
- 对象索引器的错误用法
// 1. 异常需要等到运行时才能发现
const obj = {}
// obj.foo()
setTimeout(() => {
obj.foo()
}, 1000000)
// =========================================
// 2. 函数功能可能发生改变
function sum (a, b) {
return a + b
}
console.log(sum(100, 100))
console.log(sum(100, '100'))
// =========================================
// 3. 对象索引器的错误用法
const obj = {}
obj[true] = 100 // 属性名会自动转换为字符串
console.log(obj['true'])
强类型的优势
- 更早的暴露错误,可提前在语法阶段
- 代码编写更智能,编码更准确(智能提示,开发工具能更有效的推断)
- 重构更牢靠
- 减少了代码层面的不必要的类型判断
// 1. 强类型代码错误更早暴露
// 2. 强类型代码更智能,编码更准确
function render (element) {
element.className = 'container'
element.innerHtml = 'hello world'
}
// =================================
// 3. 重构更可靠
const util = {
aaa: () => {
console.log('util func')
}
}
// =================================
// 4. 减少了代码层面的不必要的类型判断
function sum (a, b) {
if (typeof a !== 'number' || typeof b !== 'number') {
throw new TypeError('arguments must be a number')
}
return a + b
}
静态类型与动态类型(类型检查)
- 静态类型:一个变量声明时它的类型就是明确的,声明过后不允许在修改
- 动态类型:运行阶段才明确变量的类型,而且可以修改(或者说变量没有类型,变量存放的值有类型)
Flow工具:javascript的类型检查器
官网:https://flow.org/en/docs/types/
手册:https://www.saltycrane.com/cheat-sheets/flow-type/latest/
注意事项:
1、Flow工具可以在代码编写过后检查类型错误;
2、通常将源码写在src目录,编译后移除类型注解到dist目录用于生产环境;
安装/初始化Flow工具
1、安装Flow工具:yarn add flow-bin --dev(安装Flow)
2、初始化Flow工具:yarn flow init(初始化Flow,生成.flowconfig文件)
如何使用Flow工具
1、在js文件顶部添加一行:@flow
2、在命令行执行命令:yarn flow
// @flow
function sum (a: number, b: number) {
return a + b
}
sum(100, 100)
// sum('100', '100')
// sum('100', 100)
Flow工具移除类型注解
1、方法一:执行命令:yarn flow-remove-types ./ -d dist
2、方法二:使用babel插件来转换源码,需要安装3个babel插件,然后使用babel命令:
@babel/core
@babel/cli
@babel/preset-flow
.babelrc文件
{
"presets": ["@babel/preset-flow"]
}
3、方法二:在命令行使用babel命令:yarn babel src -d dist
vscode 插件:直接代码编写页面展示语法错误
插件:Flow Language Support,可以在js文件保存后自动检查错误
TypeScript语言的特点
Typescript 特点
Typescript = (js、es6+、类型系统) => 编译成js
Typescript 是js的超集
Typescript 功能更强大,生态更健全、完善
Angular、Vue3.0使用TS开发
属于渐进式,边学边写
Typescript 缺点
多了很多概念:类型、泛型、枚举等
小型项目,项目初期 Typescript 会增加一些成本
TypeScript配置文件
安装ts
> yarn add typescript --dev
编译ts
> yarn tsc index.ts
初始化ts配置项:生成 tsconfig.json 文件
> yarn tsc --init
tsconfig.json文件只有在运行整个项目的时候才能生效:yarn tsc
> "target": "es5", // 编译成es版本: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'.
> "module": "commonjs", // 使用哪种模块加载方式:'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'.
> "sourceMap": true, // 源码映射,方便调试
> "outDir": "./", // 代码输出目录
> "rootDir": "./", // 源码输出目录
TS原始类型
/**
* 基础数据类型的使用
*/
const a:string = "string"
const b:number = 100 //NaN Infinity
const c:boolean = true //false
//非严格模式下string、number、boolean都可以为空
//严格模式下会报错
const t:string = null
//非严格模式下可以是null、undefined
//严格模式下只能是undefined
const d:void = undefined
const e:null = null
const f:undefined = undefined
//默认情况下使用Symbol()会报错
//symbol类型只能在es2015,es6+中才能使用;Promise也有相同问题,也是es2015中引入
//可以在tsconfig.json配置文件lib中添加ES2015、DOM(解决console报错问题)
//"lib": ["ES2015","DOM"],
const g:symbol = Symbol()
TS强制显示中文的错误消息
命令行显示中文:
> yarn tsc --locale zh-CN
vscode配置显示中文:
setting 里面搜索:typescript local,第一个选项选择:zh-CN
TS Object类型,泛指对象、函数、数组
- Object类型,指除了原始类型外的其它类型
- 对象属性类型限制,可以使用类似对象字面量的方式,但最好用接口
const foo: object = function(){}
const obj: {a: number, b: string} = {a: 1, b: 's'}
TS数组类型
数组类型的声明
const arr1: Array<number> = [1,2,3]
const arr2: number[] = [1,2,3]
TS元组类型
在数组中使用不同类型的元素
const triple: [string, number] = ['s',18]
// const name: string = triple[0]
// const age: number = triple[1]
const [name, age] = triple
TS枚举类型,enum
通常用于表示不同的状态,它有两个好处:
1、可以给一组数值去上一个更好的名字
2、只会存在几个固定的值
// 标准的数字枚举,可以不赋值从0开始计数,后面自动+1
enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2
}
// 数字枚举,枚举值自动基于前一个值自增+1
enum PostStatus {
Draft = 6,
Unpublished, // => 7
Published // => 8
}
// 字符串枚举,用的不多
enum PostStatus {
Draft = 'aaa',
Unpublished = 'bbb',
Published = 'ccc'
}
// const常量枚举,不会侵入编译结果
// 侵入编译结果,即编译后为双向键值对,也可以通过索引访问值:PostStatus[0] // => Draft
const enum PostStatus {
Draft,
Unpublished,
Published
}
const post = {
title: 'Hello TypeScript',
content: 'TypeScript is a typed superset of JavaScript.',
status: PostStatus.Draft // 3 // 1 // 0
}
TS函数类型
函数声明
// 形参和实参的个数必须一致
// 使用 ? 添加可选参数
// 使用 = 添加默认参数
// 可选参数、默认参数都需要放在最后
// ...reset放到最后,接受任意个数的参数
function func1 (a: number, b: number = 10, ...rest: number[]): string {
return 'func1'
}
func1(100, 200)
func1(100)
func1(100, 200, 300)
函数表达式
// 普通函数表达式,TS可以推断出来返回值类型
const func2 = function (a: number, b: number): string {
return 'func2'
}
// 函数作为参数,使用箭头函数
// const func2 = (a, b) => "func2"
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
return 'func2'
}
TS任意类型:any,不安全
TS隐式类型推断
let age = 18 // number => 推断为number类型
// age = 'string' //报错,已经推断为number类型
let foo // 推断为any类型
foo = 100
foo = 'string'
TS类型断言
// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]
const res = nums.find(i => i > 0)
// const square = res * res
const num1 = res as number // 方法一:as
const num2 = <number>res // 方法二:JSX 下不能使用
TS接口,interface
约定对象中有哪些成员,以及成员的类型
接口约束的成员,对象中就必须要有这些成员
interface Post {
title: string
content: string
subtitle?: string // 可选成员,可有可无
readonly summary: string // 只读成员,初始化后不能在修改
}
const hello: Post = {
title: 'Hello TypeScript',
content: 'A javascript superset',
summary: 'A javascript'
}
// hello.summary = 'other'
// ----------------------------------
interface Cache {
[prop: string]: string // prop为动态属性,prop为属性名(自定)
}
const cache: Cache = {}
cache.foo = 'value1'
cache.bar = 'value2'
TS类
基本使用继承了ES6中class的用法,但需要明确类中的成员有哪些以及类型
class Person {
name: string // = 'init name'
age: number
constructor (name: string, age: number) {
this.name = name
this.age = age
}
sayHi (msg: string): void {
console.log(`I am ${this.name}, ${msg}`)
}
类的访问修饰符
public
private 不允许外部访问,只允许在类的内部访问成员
protected 不允许外部访问,只允许在子类中访问成员
在构造函数前也能使用private,只能通过static静态函数去生城实例
class Person {
public name: string // = 'init name'
private age: number
protected gender: boolean
constructor (name: string, age: number) {
this.name = name
this.age = age
this.gender = true
}
sayHi (msg: string): void {
console.log(`I am ${this.name}, ${msg}`)
console.log(this.age)
}
}
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)
const jack = Student.create('jack', 18)
类的只读属性
属性只能在声明时或者构造函数中初始化,两者选其一
class Person {
public name: string // = 'init name'
private age: number
// 只读成员
protected readonly gender: boolean
constructor (name: string, age: number) {
this.name = name
this.age = age
this.gender = true
}
sayHi (msg: string): void {
console.log(`I am ${this.name}, ${msg}`)
console.log(this.age)
}
}
const tom = new Person('tom', 18)
console.log(tom.name)
// tom.gender = false
类与接口
接口可以约束类中的成员函数
一个接口尽量只实现一个功能
// 接口可以约束类中的成员函数
// 参数、返回值
interface Eat {
eat (food: string): void
}
interface Run {
run (distance: number): void
}
// implements关键字
// 约束过后类中必须要有接口中的成员,否则报错
class Person implements Eat, Run {
eat (food: string): void {
console.log(`优雅的进餐: ${food}`)
}
run (distance: number) {
console.log(`直立行走: ${distance}`)
}
}
class Animal implements Eat, Run {
eat (food: string): void {
console.log(`呼噜呼噜的吃: ${food}`)
}
run (distance: number) {
console.log(`爬行: ${distance}`)
}
}
TS抽象类(可以参考C++的理解)
约束子类中必须要有某个成员(类似于接口)
不包含成员的具体实现
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)
TS泛型
不指定类型,调用的时候再指定具体的类型
以下实例中,T的类型可以根据情况指定具体的类型,但所有T应该保持一致
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')
TS函数声明,类型声明模块
- 在ts中引用第三方模块时,如果不存在类型声明,可以尝试安装一个类型声明模块
- 例如:lodash,可以安装一个:@types/lodash
- 如果没有类型声明模块,只能使用declare 去声明模块类型
- 目前有些模块已经集成了类型声明文件(没有报错就表示有?例如:qs模块)
import { camelCase } from 'lodash'
import qs from 'query-string'
// qs内部已经集成了类型声明
qs.parse('?key=value&key2=value2')
// declare function camelCase (input: string): string
const res = camelCase('hello typed')
TS断言推断等简写
- 变量! => 变量结尾加上感叹号,有值断言
elm = oldVnode.elm!
// ===>
if(oldVnode.elm)
elm = oldVnode.elm
else
return
- 变量 as 类型 => 变量后面 as 某种类型,类型断言
- 变量? => 变量结尾加上问号,有值判断
let init = hook?.init
//===>
let init = hook?hook.init:undefined
特别鸣谢:拉勾前端高薪训练营