管什么真理无穷, 进一寸有一寸的欢喜
认识 JS 静态类型检查工具 Flow
// 类型注解
function sum(a: number, b: number) {
return a + b
}
VScode 配置:
Code => 首选项 => 设置 => 搜索:javascript validate
=> 关闭 JavaScript 校验
npm init -y
// 目录下新增了一个 `package.json` 的文件
npm i flow-bin --dev
// 目录下已经安装好了 flow,并且可以看到依赖包:node_modules、package-lock.json
package.json
"scripts": {
...
"flow": "flow"
},
npm run flow init
// 目录下会新增一个 `.flowconfig` 文件
目录下新增一个测试 ts 文件:
@flow
标识才能够被 flow 捕获到
// @flow
const sum = (a:number, b:number) => a + b
sum(1, '2')
如果提示 js 语法校验,需要手动关闭(如上图)
设置 -> javascript valida -> 关闭JavaScript验证
npm run flow
// => No errors! (并没有报错,不符合预期)
// 如果并没有报错,那就执行下面的停止 flow,然后再次启动 flow 即可
// 关闭 flow
npm run flow stop
添加了类型注解的文件不能运行,所以需要在编译过程中移除类型注解
// 安装 移除插件
npm add flow-remove-types
package.json
"scripts": {
...
// src 是博主的文件目录,如果直接在当前目录,改为`src/` => `./`
"flowRemove": "flow-remove-types src/ -d dist/"
},
// 安装(博主安装失败了—_-)
npm i @babel/core @babel/cli @babel/perset-flow --dev
.babelrc
{
"presets": ["@babel/preset-flow"]
}
package.json
"scripts": {
...
"babel": "babel src/ -d dist/"
}
npm run babel
VScode 自带插件Flow language Support
// @flow
function square(n) {
return n * n;
}
square('100')
不写类型注解,但是通过计算的过程,flow 会类型推断,并提醒
=> 无法执行算术运算,因为字符串[1]不是数字
三种类型注解:函数参数、变量名、函数返回值
// @flow
// 函数参数
function square(a: number) {
return a * a
}
// 变量名
let num:number = 100
// 函数返回值
function foo():number {
return 100
}
JavaScript 的七种原始数据类型: String、Boolean、Number、Null、Undefined、Symbol、以及 ES6 新增的Symbol、BigInt
// @flow
// string
let str: string = 'string' // 字符串
// number
let num: number = Infinity //NaN //100 数字/非数字/无穷大
// boolean
let a: boolean = true // false true/false
// null
let b: null = null
// undefined
let c: void = undefined
// Symbol
let d: symbol = Symbol()
指定数组内,每一个元素的数据类型
// @flow
// 通过 Array<> 泛型指定内部元素的数据类型
const arr:Array<number> = [1, 2, 3]
// 通过 [] 指定内部元素的数据类型
const arr2: number[] = [2, 3, 4]
// 元组: 固定数组长度,类似数组字面量的方式指定内部的数据类型
const arr3: [string, number, number] = ['1', 2, 3]
指定对象的数据类型,和字面量语法非常类似
// @flow
// 类似字面量的注解方式
const obj1: { name: string; age: number } = {
name: "zoe",
age: 18,
}
// 变量名后面添加 `?` 就表示该字段非必须
const obj2: { name: string; age: number; sex?: 0 } = {
name: "zoe",
age: 18,
}
// 对后续添加的键值对做类型限制
const obj3: { [string]: number } = {}
obj3.key1 = "sex"
obj3.key2 = 0
函数参数类型、返回值类型
// @flow
// 回调函数参数类型限制, `=>` 后是返回值的类型
function foo(callback: (string, number) => void) {
callback("string", 100);
}
// @flow
// 字面量的类型限定, 后面的参数只能是 `foo`
const a: 'foo' = 'foo'
// 联合数据类型, 后面的参数只能是类型之一
const type: 'success' | 'warning' | 'danger' = 'success'
// 或类型, 后面的参数可以是数字/字符串
const b: string | number = 100
// 自定义类型
type stringOrNumber = string | number
const c: stringOrNumber = 'string'
// maybe 类型, 具体的类型之上, 拓展了undefined null 类型
const gender: ?number = undefined
// => const gender: number | undefined | void
Mixed: 所有类型的联合类型(string | number | boolean …)
Any: Any 和 Mixed 作用相同,但 Mixed 仍是强类型,而 Any 是弱类型
// @flow
const element: HTMLElement | null = document.getElementById('app')
不同文件对应的 API 有类型限制,通过右键 HTMLElement
即可查看类型的声明文件
TypeScript
是基于 JavaScript
基础之上的编程语言,超集/扩展集(superset)ECMAScript
的新特性支持// 全局安装
npm install -g typescript
// 查看版本
npm tsc --v
示例:
const hello = (name: string) => {
console.log(`Hello, ${name}`)
}
// hello(100)
hello('TypeScript')
编译项目,生成一个配置文件tsconfig.json
npm tsc --init
tsconfig.json
配置文件注解
{
"compilerOptions": {
// 设置编译后的javascript采用的ECMAScript标准
"target": "es5",
...
// 输出的代码使用什么方式进行模块化,这里用的是commonJS,会把输入输出弄成require和module.export的方式
"module": "commonjs",
// 设置编译结果输出的文件夹
"outDir": "dist",
/* Redirect output structure to the directory. */
// 源代码ts文件所在的文件夹
"rootDir": "src",
/* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
/*类型检查相关 Strict Type-Checking Options */
// 开启严格模式,对类型检查十分严格
// 例如:any类型,也要严格写出来
"strict": true,
/* Enable all strict type-checking options. */
}
// 在非严格模式下, 类型 下可以是为 null
// string
// let a: string = null
let a: string = "string"
// boolean
let b: boolean = false // true
// number
let c: number = 100 // NaN infinity
// null
let d: null = null
// undefined
let e: void = undefined
let f: undefined = undefined
// Symbol 这里的 Symbol 会报错
let g:symbol = Symbol()
内置对象所引用的声明文件
上节中看到的 Symbol 会报错,给出的提示是需要更高版本的声明文件,而 Symbol 是 ES6 新增的数据类型,右键查看定义
这里博主用的是,ES2019 版本(左上角文件名称),所以包含了 Symbol 的类型声明,就不会报错了
解决 Symbol 报错的方法:
target
改为es2015lib
选项改为[“ES2015”]console
的定义会报错,默认引用的DOM类库被覆盖,在配置文件中加上"DOM",这里的DOM是包含了DOM+BOM首选项 => 设置 => 搜索 TypeScript locale => 设置 zh-CN
(不过不建议这样使用,因为不利于报错的检索)
const a: number = 100;
// 1. 立即执行函数,将变量改为局部变量
(function () {
const a: number = 100
})()
// 2. 将文件导出,作为 模块(模块具有独立的模块作用域)
export { }
TypeScript 的 Object 类型并不单指对象类型,而是泛指除了原始类型的其他类型
// object 类型 能够接受 数组/对象/函数 等非原始类型
const foo: object = function() {} // {} // []
// 对象类型 需要用到类似字面量的语法
const obj: {name: string, age: number} = { name: 'zoe', age: 18}
// Array<> 泛型
const arr1: Array<number> = [1, 2, 3]
// 元素类型[]
const arr2: string[] = ['1', '2', '3']
明确元素数量,以及每一个元素类型的数组
// 元组 [Tuple]
// 限定每一个元素的数据类型
const tuple:[string, number] = ['zoe', 18]
// 依然可以通过数组下标的方式进行访问
const name = tuple[0]
const age = tuple[1]
// 也可以通过数组结构的方式提取
const [name2, age2] = tuple
export { } // 确保其他作用域没有成员冲突
enum
前加入 const
可以解决)export { } // 确保其他作用域没有成员冲突
// 传统方式
const postStatus = {
unPublish: 0,
published: 1
}
// 枚举
// 如果不指定值, 默认会从 第一项的值 开始累加(第一项不设值时默认为 0 )
// 如果是字符串则无累加功能
enum postStatus2 {
unPublish = 0,
published = 1
}
// 常量枚举,在编译后,枚举会被移除,设定的值会改为数字,枚举的属性名会以注释作为提示
const enum postStatus3 {
unPublish = 0,
published = 1
}
const post = {
title: 'this is title',
content: 'this is content text',
// status: 0 // 0 未发布 1 已发布
// status: postStatus.unPublish
status: postStatus2.unPublish
}
export { } // 确保其他作用域没有成员冲突
// 1. 函数声明的方式
// 函数的类型约定: 传入的参数, 返回的结果
// 可以在参数后面添加 ? , 实参内就是非必填(可选参数要在必选参数后面)
// 或者给参数设置默认参数, 也可以非必填
function fn1(a: number, b: string, c?: number, d:number = 10): boolean {
return true
}
fn1(100, 'str')
// 传递任意个参数
function fn2(a: number, ...rest:number[]): string {
return 'str'
}
// 2. 函数表达式的方式
const fn3 = function(a: string, b: number):string {
return 'str'
}
// 如果这个函数作为回调函数的参数, 那这个函数也是需要约定类型的
const fn4: (a: string, b: number) => string = function(a: string, b: number):string {
return 'str'
}
export { } // 确保其他作用域没有成员冲突
// 这里的 value 需要支持任意类型
function stringify(value: any) {
return JSON.stringify(value)
}
在声明变量时,ts 对更具变量的值推断类型,如果未赋值,则为 any
export{}
const arr = [111, 222, 333, 444, 555]
const result = arr.find(i => i > 2)
// ts 检测推断 result 的类型为 number | null,所以下面的类型会提醒
const square = result * result
// 类型断言 1. as 方式
const res = result as number
// 类型断言 2. <> 方式
// <> 会和 jsx 的标签形成语法冲突
const res2 = <number>result
约束对象的结构
export { }
// 因为使用了 post 内的数据, 所以需要对 post 进行数据约定
function printPost(post: Post) {
console.log(post.title)
console.log(post.content)
console.log(post.status)
}
// 接口
interface Post {
title: string
content: string
status: number
}
export { }
// 接口
interface Post {
title: string
subTitle?: string // ? 为可选 => 增加了 undefined 类型
readonly content: string // readonly 为只读成员 => 之后重新赋值会报错
status: number
}
const article: Post = {
title: 'title',
content: 'content',
status: 0,
}
// article.content = 'content 2'
// 动态数据 约定类型
interface Cache {
// key 为键, []: 后的为值类型
[key: string] : number
}
// 添加动态成员
const arr: Cache = {
'weight': 11,
'height': 50
}
描述具体对象的抽象特征
export { }
class person {
// 需要在 class 中添加 person 的属性, 而不能直接在 构造函数(constructor) 中直接添加
name: string
age: number
constructor(name: string, age: number) {
this.name = name
this.age = 100
}
// 声明方法 通过类型注解的方式来约定 参数 和 返回值
sayHi(msg: string):void {
console.log(`Hi, my name is ${this.name}, {msg}`)
}
}
export { }
class person {
name: string
private age: number // 私有属性
public sex?: number // 公有属性(默认)
protected gender: boolean // 受保护的属性, 只允许在子类中访问父类中的成员
constructor(name: string, age: number) {
this.name = name
this.age = age
this.gender = true
}
sayHi(msg: string): void {
console.log(`Hi, this is ${this.name}, ${msg}`)
console.log(this.age)
}
}
const tom = new person('tom', 42)
console.log(tom.name)
// console.log(tom.age)
// console.log(tom.gender)
class Students extends person {
// 将构造函数设置为了 私有属性, 外部无法访问, 可以通过内部构造后导出
private constructor(name: string, age: number) {
super(name, age)
console.log(name)
console.log(this.sex)
console.log(this.gender)
}
// 创建一个静态方法
static creat (name: string, age: number) {
// 返回一个实例方法
return new Students(name, age)
}
}
// 通过子类导出 外部就可以获取 Students 的实例对象了
const aim = Students.creat('zoe', 18)
export { }
// 不同的类型,相同的接口
// interface eatAndRun{
// // 函数签名的方式约束
// eat(food: string):void
// run(distance: string):void
// }
// 抽象的方式解耦接口
interface Eat{
eat(food: string):void
}
interface Run{
run(distance: string):void
}
// 实现对应的接口
// class Person implements eatAndRun {
class Person implements Eat, Run {
eat(food: string): void {
console.log(`优雅的进餐:${food}`)
}
run(distance: string) {
console.log(`直立行走:${distance}`)
}
}
// class Animal implements eatAndRun {
class Animal implements Eat, Run {
eat(food: string): void {
console.log(`咕噜咕噜的吃:${food}`)
}
run(distance: string) {
console.log(`爬行:${distance}`)
}
}
export { }
// 类型被定义 为抽象之后, 只能被继承, 不能通过 new 来创建对象实例了
abstract class Animal {
eat(food: string): void {
console.log(`咕噜咕噜吃东西${food}`)
}
// 抽象的类, 只能被定义抽象的方法(抽象方法可以不需要方法体)
abstract run(distance: number): void
}
// 继承 Animal 的类
class Dog extends Animal {
run(distance: number): void {
console.log(`爬行${distance}`)
}
}
// 创建子类的实例对象, 就包含父类的实例方法 和 自身的实例方法
const d = new Dog()
d.eat('热干面')
d.run(100)
定义时不能够明确的类型,定义为参数,在使用的时候再对参数进行定义
export { }
// 创建一个指定数组长度的数组
function creatNumberArray(length: number, value: number): number[] {
// 泛型参数 <>
const arr = Array<number>(length).fill(value) // fill 为填充满数组
return arr
}
// 如果要创建一个 string 的数组(硬方法, 和上方法有重复编码)
function creatStringArray(length: number, value: string): string[] {
const arr = Array<string>(length).fill(value)
return arr
}
const res = creatNumberArray(3, 100)
// => [100, 100, 100]
// 泛型<> : T 作为未定义的类型
function creatArray<T>(length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
// 在使用的时候再约定数据类型
const res2 = creatArray<string>(3, 'string')
安装 ts 的类型声明模块
// 添加 lodash
npm add lodash
// 安装 ts 声明模块
npm add @types/lodash
// 安装 query-string
npm add query-string
示例:
export { }
// 将字符串转换成 驼峰格式
import { camelCase } from 'lodash'
// 类型声明 (在使用时,单独为类型做使用声明)
// declare function camelCase(input: string): string
// 参数应该是 string , 返回值也是 string(ts 没有类型推断)
const res = camelCase('hello world')
import qs, { parse } from 'query-string'
// 解析 url 中的 query-string 字符串(包含类型声明)
qs.parse('?key=value&key2=value')