TypeScript
1.解决了javascript类型系统的问题
2.TypeScript大大提高代码的可靠程度
一、JavaScript
1. 弱类型、动态语言的缺陷。
- 1.程序中的异常在运行时才能发现
const obj ={}
obj.foo()// 等到运行时才发现错误 obj.foo is not a function
setTimeout(()=>{
obj.foo()// 等到运行时才发现错误 obj.foo is not a function
}, 1000)
- 2.类型不明确函数功能会发生改变
在这里插入代码片
function getSum(a, b) {
return a + b
}
getSum(100,200)
getSum(100,'100')
- 3.对对象索引器的错误用法
const obj ={}
obj[true]= 100
console.log(obj['true'])
2. 强类型的优势
- 错误更早暴露。在编码阶段就可以发现错误,不用在运行阶段才发现。
- 代码更智能,编码更准确。编辑器时时刻刻都知道变量的类型,作出提示。
- 重构更牢靠。修改之前的代码变量名称,如果其他地方用到,就会立马做出错误的提示,但是弱类型没有这个功能。有的还会自动修改。
- 减少不必要的类型判断。
function (a b) {
// 如果是强类型语言,则根本不需要判断。
if (typeof a !=='number' ||typeof b !=='number' ) {
throw new typeError('arguments must be a number')
}
return a + b
- 强类型与弱类型 (类型安全)。
- 静态类型与动态类型(类型检查)
- javascript自有类型系统的问题
- Flow静态类型检查方案
- TypeScript语言规范与基本应用
强类型的定义:
-在语言层面就限制了函数的实参类型必须与形参类型相同。
强类型语言中不允许任意的隐式类型转换。
弱类型定义:
- 语言层面不会限制实参的类型。
- 弱类型语言中允许任意的隐式类型转换。
变量类型允许随时改变的特点,不是弱类型的差异。
静态类型:一个变量声明时它的类型就是明确的,声明过后,他的类型就不允许再修改。
动态类型:运行阶段才能明确变量类型。而且变量的类型可以随时发生变化。东态类型语言中的变量时没有类型的,而变量中存放的值是有类型的。
静态类型,动态类型: 是由允许随时修改变量的类型。
强类型,弱类型:是否允许随意的隐式类型转换。
javascript是弱类型且动态类型。
javascript是脚本语言,脚本语言不需要编译,直接运行。
2. Flow:
flow:是 javascript的类型检查器。
- Flow工作原理:让我们在代码中通过添加类型注解的方式来去标记代码当中每一个变量/参数,应该是什么类型的,然后flow根据这些类型注解就可以检查代码中会存在类型使用当中的异常,从而实现在开发阶段类型的检查。避免了再在运行阶段去发现这种类型使用上的错误。
- 并不要求对每个地方都添加类型注解。
中文官方:https://zhenyong.github.io/flowtype/docs/union-intersection-types.html#_
function sum(a: number, a:number){
return a + b
}
如何安装和使用flow的步骤:
- 安装flow的检查工具:yarn add flow-bin --dev
- 关闭javascript中的语法检查 preferences -> setting -->搜索: javascript validate 去掉勾选的:javascript enable
- 在代码中第一行添加flow注释:// @flow
- 在函数中形参后面加上冒号和类型:function sum (a: number, b: number)
- 执行yarn flow init创建.flowconfig
- 执行yarn flow
flow移除类型注解:
-
官方提供的方法:
安装:yarn add flow-remove-types
运行: yarn flow-remove-types src -d dist -
使用babel配合flow转换的插件:
- yarn add @babel/core @babel/cli @babel/preset-flow --dev
- 添加.babelrc文件
- yarn babel src -d dist
{
"presets": ["@babel/preset-flow"]
}
- Flow 开发工具插件:Flow Language Support
3.类型推断:
/**
* 类型推断
*
* @flow
*/
function square(n) {
return n * n
}
square(1)
类型注解
/**
* 类型注解
*
* @flow
*/
function square (n: number) {
return n * n
}
// 变量类型注解:
let num: number = 100
// num = 'string' // error
// 返回值类型注解为number
function foo (): number {
return 100 // ok
// return 'string' // error
}
// 没有返回值
function bar (): void {
// return undefined
}
- 原始类型
/**
* 原始类型
*
* @flow
*/
const a: string = 'foobar'
const b: number = Infinity // NaN // 100
const c: boolean = false // true
const d: null = null
const e: void = undefined
const f: symbol = Symbol()
- 数组类型:
/**
* 数组类型
*
* @flow
*/
// 全部由数字组成的数组,如果出现其他类型报错
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
// Array<number>: 范型
// 元组(数组中的元素是固定的个数)
const foo: [string, number] = ['foo', 100]
- 对象类型
/**
* 对象类型
*
* @flow
*/
// ?:表示可选的/可有可无
// : { foo: string, bar: number } 对象中元素的类型限制
const obj1: { foo: string, bar: number } = { foo: 'string', bar: 100 }
const obj2: { foo?: string, bar: number } = { bar: 100 }
//初始化数据时,限制键值对的类型。允许添加任意个数的键值对,但是键值对的类型限制为string类型。
const obj3: { [string]: string } = {}
//初始化数据时,不限制键值对的类型
obj3.key1 = 'value1'
obj3.key2 = 'value2'
- 函数类型
函数类型主要是对:参数类型,返回值类型的限制。
/**
* 函数类型
*
* @flow
*/
方式1:
function add(num: number): number{
return num
}
方式2
// void:表示没有返回值/返回undefind
function foo (callback: (string, number) => void) {
callback('string', 100)
}
foo(function (str, n) {
// str => string
// n => number
})
- 特殊类型
/**
* 特殊类型
*
* @flow
*/
字面量类型:用来限制变量必须时某一个值。
// 字面量类型
const a: 'foo' = 'foo'
const type: 'success' | 'warning' | 'danger' = 'success'
// ------------------------
// 声明类型
type:联合类型。
或类型:可以是string,可以是number
type StringOrNumber = string | number // 表示声明的变量类型可以时string,可以是number
const b: StringOrNumber = 'string' // 100
// ------------------------
// Maybe 类型: 有可能
?number:有可能是number类型,也有可能是null,undefined类型。
const gender: ?number = undefined
// 相当于
// const gender: number | null | void =
- Mixed | Any
都表示所有的类型
/**
* Mixed Any:都表示所有的类型
* Mixed是强类型,Any是弱类型。
* @flow
*/
// mixed:表示所有的类型。string | number | boolean | ....
function passMixed (value: mixed) {
if (typeof value === 'string') {
value.substr(1)
}
if (typeof value === 'number') {
value * value
}
}
passMixed('string')
passMixed(100)
// ---------------------------------
function passAny (value: any) {
value.substr(1)
value * value
}
passAny('string')
passAny(100)
所有类型查询:https://www.saltycrane.com/cheat-sheets/flow-type/latest/
/**
* 运行环境 API
*
* @flow
*/
const element: HTMLElement | null = document.getElementById('app')
三: typescript
javascript(超集):typescript
功能更为强大,生态也更健全,更完善。
前端领域的第二语言。
缺点
- 语言本身多了很多概念,提高了学习成本。
- 项目初期,typescript会增加一些成本。
- 快速上手:
1.初始化package.json: yarn init --yes
2.安装本地:yarn add typescript --dev 全局安装: yarn add typescript -g
3.编译:yarn tsc 01.ts
执行yarn tsc 01.ts这个命令,会生成一个同名的.js的文件,
安装的这个命令提供了一个tsc的命令,这个命令的作用就是编译typescript文件。在编译的过程中typescript首先会检查代码当中的使用异常,然后移除掉注解之类的扩展的语法,并且在这个过程中自动转换ECMAScript的新特性。
2. 配置文件:
1.生成tsconfig.json配置文件: yarn tsc --init
2. 运行:yarn tsc
源码:
const hello1 = (name: string) => {
console.log(`hello ${name}`)
}
// hello1('tom')
hello1('tom')
运行:yarn tsc:
生成一个同名.js文件
"use strict";
const hello1 = (name) => {
console.log(`hello ${name}`);
};
// hello1('tom')
hello1('tom');
tsconfig.json常用配置说明:
"target": "es2015", /* 编译后的代码 Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */
"module": "commonjs", /* 输出的代码采用什么样的方式进行模块化 Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
// "outDir": "dist", /* 设置编译结果输出的文件夹 Redirect output structure to the directory. */
// "rootDir": "src", /* 源代码所在的文件夹Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"strict": true, /* 开启严格模式 Enable all strict type-checking options. */
// "sourceMap": true, /* 开启源代码映射,可以调试源代码 Generates corresponding '.map' file. */
3. 原始类型
// 原始数据类型
const a: string = 'foobar'
const b: number = 100 // NaN Infinity
const c: boolean = true // false
// 在非严格模式(strictNullChecks)下,
// string, number, boolean 都可以为空
// const d: string = null
// const d: number = null
// const d: boolean = null
const e: void = undefined
const f: null = null
const g: undefined = undefined
// Symbol 是 ES2015 标准中定义的成员,
// 使用它的前提是必须确保有对应的 ES2015 标准库引用
// 也就是 tsconfig.json 中的 lib 选项必须包含 ES2015
const h: symbol = Symbol()
// Promise
// const error: string = 100
4. 标准库
标准库就是内置对象对应的声明文件
在tsconfig.json中写上:
"lib": ["ES2015", "DOM"],
5. TypeScript 中文错误消息
使用中文的错误消息:
执行:yarn tsc --locale zh-CN
vscode中显示中文的提示错误:
perference – > setting -->typescript locale. 选择:zh-CN
但是并不推荐使用,应为不好搜索
6. 作用域问题
每个文件都是全局作用域,所以在不同文件中定义同名变量会报错,解决方案:
- 使用立即执行函数,产生作用域
(function () {
const a = 123
} )()
- 使用export
const a = 11
export {} // 确保跟其他实例没有成员冲突
7. Object类型
object类型:指的的是除了原始类型之外的其他类型。
// Object 类型
export {} // 确保跟其它示例没有成员冲突
// object 类型是指除了原始类型以外的其它类型
const foo: object = function () {} // [] // {}
// 如果需要明确限制对象类型,则应该使用这种类型对象字面量的语法,或者是「接口」
const obj: { foo: number, bar: string } = { foo: 123, bar: 'string' }
// 接口的概念后续介绍
8.数组类型
// 数组类型
export {} // 确保跟其它示例没有成员冲突
// 数组类型的两种表示方式
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
// 案例 -----------------------
// 如果是 JS,需要判断是不是每个成员都是数字
// 使用 TS,类型有保障,不用添加类型判断
function sum (...args: number[]) {
return args.reduce((prev, current) => prev + current, 0)
}
sum(1, 2, 3) // => 6
9.元组类型
元组: 就是明确元素数量,以及每一个元素类型的数组。
各个元素类型可以不同。
// 元组(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
10.枚举类型
枚举类型的好处:
- 可以给一组数值取一个固定的名字
- 一个枚举中只会出现固定的值,并不会出现超出范围的值
// 枚举(Enum)
export {} // 确保跟其它示例没有成员冲突
// 用对象模拟枚举
// const PostStatus = {
// Draft: 0,
// Unpublished: 1,
// Published: 2
// }
// 标准的数字枚举
// enum PostStatus {
// Draft = 0,
// Unpublished = 1,
// Published = 2
// }
// 数字枚举,枚举值自动基于前一个值自增
// enum PostStatus {
// Draft = 6,
// Unpublished, // => 7
// Published // => 8
// }
// 字符串枚举
// enum PostStatus {
// Draft = 'aaa',
// Unpublished = 'bbb',
// Published = 'ccc'
// }
// 常量枚举,不会侵入编译结果
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
}
// PostStatus[0] // => Draft
11.函数类型
输入,输出的约束
- 传入参数个数要个形参个数相等
- 可选参数: ?按照位置进行传递
- 任意参数个数:…rest
// 函数类型
export {} // 确保跟其它示例没有成员冲突
function func1 (a: number, b?: number = 10, ...rest: number[]): string {
return 'func1'
}
func1(100, 200)
func1(100)
func1(100, 200, 300)
// -----------------------------------------
// 指定函数的形式
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
return 'func2'
}
12. 任意类型
// 任意类型(弱类型)
export {} // 确保跟其它示例没有成员冲突
function stringify (value: any) {
return JSON.stringify(value)
}
stringify('string')
stringify(100)
stringify(true)
let foo: any = 'string'
foo = 100
foo.bar()
// any 类型是不安全的
13 隐式类型推断
支持隐式类型推断
// 隐式类型推断
let age = 18 // ts推断出类型是number
// age = 'str' // 会报错 不能将类型“"str"”分配给类型“number”。
let foo // 此时无法推断具体类型,foo则是动态类型,any类型
foo = 1 // 不会报错
foo = 'string' // 不会报错
// 建议为每个变量添加明确的类型标注
14 类型断言
在有些情况下,ts不能推断出数据的类型。
断言:就是明确告诉ts数据的类型。
断言有2种:
-1. as
-2. <>
// 类型断言
export {} // 确保跟其它示例没有成员冲突
// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]
const res = nums.find(i => i > 0)
// const square = res * res
const num1 = res as number // 明确告诉ts,res的数据类型是number
const num2 = <number>res // JSX 下不能使用
15 interface 接口
interface:约定对象的结构。
约定一个对象中具体有哪些成员,而且这些成员的类型又是什么样的。
// 接口
export {} // 确保跟其它示例没有成员冲突
interface Post {
title: string
content: string
subtitle?: string // 可选成员
readonly summary: string // 只读成员
}
function printPost (post: Post) {
console.log(post.title)
console.log(post.content)
}
printPost({
title: 'Hello TypeScript',
content: 'A javascript superset'
})
- 动态成员
export {} // 确保跟其它示例没有成员冲突
interface Cache {
[prop: string]: string
}
const cache: Cache = {}
cache.foo = 'value1'
cache.bar = 'value2'
16. 类的基本使用
描述一类具体事物的抽象特征
用来描述一类聚徒对象的抽象成员
// 类(Class)
export {} // 确保跟其它示例没有成员冲突
class Person {
// 声明
name: string // = 'init name'
age: number
protected readonly gender: boolean // readonly不允许再修改
constructor (name: string, age: number) {
this.name = name
this.age = age
}
sayHi (msg: string): void {
console.log(`I am ${this.name}, ${msg}`)
}
}
- 访问修饰符
export {}
class Person {
// 声明
public name: string // 公开属性 在自身,外部,子类都可以被访问。默认是public.
private age: number // 在自身,子类中可以访问,外部访问会报错
protected gender: boolean // 在自身,子类中可以被访问,外部访问会报错
constructor(name: string, age: number){
this.name = name
this.age= age
this.gender = true
}
asyHi(msg: string): void {
console.log(msg, this.name, this.age)
}
}
class Studen extends Person{
constructor(name: string, age: number) {
// 父类的protected属性子类可以访问。
super(name, age)
console.log(this.gender)
}
}
const tom =new Person('tom', 18)
console.log(tom.name)
console.log(tom.age) // 外部访问报错提示
console.log(tom.gender) // 外部访问报错提示
- 静态属性static、构造器私有化后不能new
// 静态属性static
class Student extends Person{
constructor(name: string, age: number) {
super(name, age)
console.log(this.gender)
}
static create(name: string, age: number) {
// 创建类型的实例
return new Student(name, age)
}
}
// 创建类型的对象
const jack = Student.create('jack', 18)
- 类的只读属性 readonly
在这里插入代码片
export {} // 确保跟其它示例没有成员冲突
class Person {
// 声明
name: string // = 'init name'
age: number
protected readonly gender: boolean // readonly不允许再修改
constructor (name: string, age: number) {
this.name = name
this.age = age
}
sayHi (msg: string): void {
console.log(`I am ${this.name}, ${msg}`)
}
}
17 类与接口
// 类与接口
export {} // 确保跟其它示例没有成员冲突
interface Eat {
eat (food: string): void
}
interface Run {
run (distance: number): void
}
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}`)
}
}
18.抽象类
使用abstract只能继承,不能使用new 创建实例对象
// 抽线类
export {} // 确保跟其它示例没有成员冲突
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)
19.泛型
声明时不指定类型,等到调用时再指定类型。这样做的目的:极大程度的复用代码。
// 泛型
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
}
// T:表示类型不明确。支持创建任意类型的数组
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')
20 类型声明
// 类型声明
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')