TypeScript介绍及类型检查工具Flow

  1. TypeScript 大大提高代码的可靠程度
  2. JavaScript 自有类型系统的问题

介绍TypeScript前先介绍一下类型检查

介绍模块

  1. 强类型与弱类型
  2. 静态类型与动态类型
  3. JavaScript 自有类型系统的问题
  4. Flow静态类型检查方案
  5. TypeScript语言规范与基本应用

1. 类型介绍

1. 强类型 vs 弱类型

  1. 强类型: 语言层面限制函数的实参类型必须与形参类型相同

eg:

class Main {
    static void foo(int num) {
        System.out.println(num);
    }
    
    public static void main(String[] args) {
        Main.foo(100); // ok
        
        Main.foo("100"); // error "100" is a string
        
        Main.foo(Integer.parseInt("100 ")); // ok
    }
}
  1. 弱类型:语言层面不会限制实参的类型
    eg
function foo(num) {
    console.log(num)
}
foo(100) // ok
foo('100') // ok
foo(parseInt('100')) // ok

由于这种强弱类型之分根本不是某个权威机构的定义;

强类型有更强的类型约束,而弱类型中几乎没有什么约束

个人理解:
  1. 强类型语言中不允许什么问题的隐式类型转换
  2. 弱类型语言则允许任意的数据隐式类型转换
  3. 强类型不允许随意的隐式类型转换,而弱类型是允许的
  4. 变量类型允许随时改变的特点,不是强毕竟类型的差异

2. 类型系统

静态类型与动态类型

  1. 静态类型: 一个变量声明时它的类型就是明确的,声明过后,它的类型就不允许修改了
  2. 动态类型:在运行阶段才能够明确变量类型,而且变量的类型随时可以改变
    eg:
var foo = 100
foo = 'bar' // ok
console.log(foo)

也可以说在动态类型语文中的变量没有类型,而变量中存放的值是有类型的

类型安全分为 强类型弱类型

类型检查分为 静态类型动态类型

如下图

2.1 JavaScript 类型系统特征

弱类型动态类型

<font color="red">js的类型用一个词描述:【任性</font> 缺失了类型系统的可靠性 --> <font color="red">不靠谱</font>

? 为什么JavaScript不是强类型 / 静态类型
1. 早前的 JavaScript 应用简单
2. JavaScript 没有编译环节
3. 选择为 弱类型 / 动态类型
4. 在大规模应用下,这种【优势】就变成了短板

弱类型的问题

举例

// 01
const obj = {}
obj.foo()
// 报错要等到运行阶段才能发现类型异常
// ======================
// 02
function sum(a, b) {
    return a + b
}
console.log(sum(100, 100)) // 200
console.log(sum(100, '100')) // 100100
// ======================
// 03
const obj = {}
obj[true] = 100 // 会自动转换成字符串

console.log(obj['true'])

强类型的优势

  1. 错误更早暴露
  2. 代码更智能,编码更准确
  3. 重构更牢靠
  4. 减少不必要的类型判断
  5. ......

2. Flow js的类型检查工具

Flow

JavaScript的类型的检查器

Flow只是一个小工具,So Easy

// 类型注解
function sum (a: number, b: number) {
    return a + b
}
sum(100, 50) // ok
sum('100', 50) // error

快速上手

// 安装 yarn add flow-bin --dev

// 增加.flowconfig文件 yarn flow init
/*
.flowconfig
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
*/

// 使用的时候需要在文件顶上增加 @flow,并且需要关闭掉vscode的语法校验
// @flow
function sum (a, b) {
    return a + b
}

// 终端使用命令检查 
// 执行 yarn flow
// 停止 yarn flow stop

编译

  • 写好的js文件没办法用node执
// 所以安装 yarn add flow-remove-types --dev

// 执行 yarn flow-remove-types [文件目录] . -d [输出目录]

尝试使用babel
// @babel/core 核心模块
// @babel/cli cli工具,可以让我们在命令行中直接使用babel命令去完成编译
// @babel/preset-flow --dev
// 安装
// yarn add @babel/core @babel/cli @babel/preset-flow --dev

// 2. 添加babel的配置文件 .babelrc
/*
// .babelrc
{
    "presets": ["@babel/preset-flow"]
}

*/
// 执行命令 yarn babel src -d dist

开发工具插件 Flow Lanugage Support

让错误直接在开发工具上显示出来

  • 下载插件 Flow Lanugage Support

类型推断 Type Inference

/**
* 类型推断
* @flow
*/
function square (n) {
    return n * n
}
square('100') // 这里用了字符串,flow会推导出 n*n是错误的

类型注解 Type Annotations

// 1. 函数的参数上以及返参
funcion square(n:  number): number {
    return n * n
}
// 2. 变量
let num: number = 100;

// 3. 没有返回值标记成void
function say(): void {}

原始类型 Primitive Types

let a: string = 'foobar'

let b: number = Infinity // NaN // 100

let c: boolean = true

let d: null = null

let e: void = undefined

let f: symbol = Symbol()

数组类型 Array Types

// 泛型
const arr1: Array<number> = [1, 2, 3]

const arr2: number[] = [1, 2, 3]

// 元组
const f00: [string, number] = ['foo', 100]

对象类型 Object Types

const obj1: {foo: string, bar: number} = { foo: 'string', bar: 100 }

const obj2: {foo?: string, bar: number} = {bar: 100}  // foo可选

const obj3: {[string]: string} = {}
obj3.key1 = 'value1'
obj3.key2 = 'value2'

函数类型 Function Types


function foo(callback: (string, number) => void) {
    callback('string', 100)
}

foo(function(str, n) {
    // str => string
    // n => number
})

特殊类型


// 字面量类型
const a: 'foo' = 'foo' // a变量只能为 foo字符串

const type: 'success' | 'warning' | 'danger' = 'success'

const b: string | number = 100 // 'string'

type StringOrNumber = string | number

const c: StringOrNumber = 'string' // 100

// maybe 类型
const gender: ?number = null // undefined // number
// 上面等价于下面这个
const gender: number | null | void = undefined // null // number

Mixed & Any 任意类型

// mixed 就是所有类型 ==> string | number | boolean | ....
function passMixed(value: mixed) {
    
}
passMixed('string')
passMixed(100)

// -----------------------

function passAny(value: any) {
    
}
passAny('string')
passAny(100)

// 区别是  any是弱类型  mixed是强类型
function passMixed(value: mixed) {
    value.substr(1)
    value * value
    // 这里会提示报错
    // 改成下面
    if (typeof value == 'value') {
        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)

类型小结

运行环境API -> 内置对象

const element: HTMLElement | null = document.getElementById('app') // 这里必须传字符串 传数字会提示报错

// https://github.com/facebook/flow/blob/master/lib/core.js
// https://github.com/facebook/flow/blob/master/lib/dom.js
// https://github.com/facebook/flow/blob/master/lib/bom.js
// https://github.com/facebook/flow/blob/master/lib/cssom.js
// https://github.com/facebook/flow/blob/master/lib/node.js

3. TypeScript

TypeScript JavaScript的超集 (superset)

任何一种JavaScript运行环境都支持

  • 功能更为强大,生态也更健全、更完善
  • 开源框架使用ts Angular / Vue.js3.0
  • TypeScript -- 前端领域中的第二语言

TypeScript属于渐进式

缺点

  1. 语言本身多了很多概念
  2. 项目初期,TypeScript会增加一些成本

快速上手

yarn init --yes

yarn add typescript --dev

yarn tsc xxxx.ts

// 可以完全按照 JavaScript 标准语法编写代码

配置文件

yarn tsc --init 生成一个tsconfig.json

compilerOptions
    target: 编译之后的ES版本
    module: 输出的代码采用什么样的方式进行模块化
    lib: 指定所引用的标准库 ["ES2015", "DOM"]
    outDir: 设置编译结果输出到的文件夹 dist
    rootDir: 配置我们源代码的文件夹 src
    sourceMap: 是否开启源代码映射
    strice: 是否开启严格模式
需要直接运行tsc命令编译整个项目
yarn tsc

原始类型 Primitive Types

const a: string = 'foobar'

const b: number = 100 // NaN Infinity

const c: boolean = true // false
// 以上是在非严格模式下均可设置为 null

const e: void = undefined

const f: null = null

const g: undefined = undefined

const h: symbol = Symbol()

标准库声明

就是内置对象所对应的声明

中文错误信息

const error: string = 100

作用域问题

两个.ts文件使用相同变量量会提示错误

  • 解决就是用单独的作用域,可以使用 export {}

Object 类型 Object Types

不单指对象

const foo: object = {} // [] // function() {}

// 可以这样定义指定为对象 可以用接口
const obj = {foo: number } = { foo: 123 }

数组类型 Array Types

const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]

// -------------------------

function sum(...args: number[]) {
    return args.reduce((prev, current) => prev + current, 0)
}
sum(1, 2, 3)

元组类型 Tuple Types

明确元素数量以及每个元素类型的一个数组,类型不必要完全相同

const tuple:  [number, string] = [18, 'string']

tuple[0]
tuple[1]

const [age, name] = tuple

// ------------------------

枚举类型 Enum Types

  1. 给一组数值去分别取上更好理解的名字
  2. 一个枚举中只会存在几个固定的值,并不会出现超出范围的可能性
enum OrderStatus {
    WaitPay = 0,
    Pay = 1,
    Cancel = 2
}

// 枚举的值可以是字符串
enum OrderStatus {
    WaitPay = 'waitPay',
    Pay = 'pay'
}
// 会被编译成以下代码
var OrderStatus;
(function (OrderStatus) {
    OrderStatus["WaitPay"] = "waitpay";
    OrderStatus["Pay"] = "pay";
})(OrderStatus || (OrderStatus = {}));

// 如果枚举的值是数值的,可以这样取 OrderStatus[0]

// 在enum 的前面加上 const
const enum OrderStatus {
    WaitPay = 0,
    Pay
}
// 编译的时候会去掉枚举相关的信息,只留下 0 , 1 , 3

函数类型 Function Types

输入输出类型限制

function func1(a: number, b: number): string {
    return 'func1'
}
// 可选参数 可以设置 a?: number

// -------------------------
// 函数的表示式
const func2: (a: number, b: number) => string = function(a: number, b: number): string {
    return 'func2'
}

任意类型 Any Types

不会有任何的类型检查

function stringify(value: any) {
    return JSON.stringify(value)
}

隐式类型推断 Type Inference

建议为每个变量添加明确的类型

let age = 18 // 推断为age: number

age = 'string' // 会报错

let foo; // 推断为any
foo = 100
foo = 'string'
// 都是可以的

类型断言 Type assertions

// 假定这个 nums 来自一个明确的接口
const nums = [110, 120, 119, 112]

const res = nums.find(i => i > 0) // 推导出res 为 number | undefined

// 我们需要告诉Ts,这就是一个number类型
const num1 = res as number
const num1 = <number>res  // JSX 下不能使用

接口 Interfaces

一种规范,约定


interface IPost {
    title: string;
    content: string
}

function printPost(post: IPost) {
    console.log(post.title)
    console.log(post.content)
}
  • 可选成员、只读成员
interface IPost {
    title: string;
    content: string;
    subtitle?: string // 可选参数  string | undefined
    readonly summary: string // 不允许修改
}

// 动态成员
interface Cache {
    [prop: string]: string
}

用来描述一类具体对象的抽象成员

  1. ES6以前, 函数 + 原型 模拟实现类
  2. ES6开始 JavaScript中有了专门的class
  3. TypeScript 增强了class 的相关语法
1. 基本使用
class Person {

    name: string;
    age: number;

    constructor(name: string, age: number) {
        this.name = name;
        this.age = age;
    }

    sayHi(msg: string) {
        console.log(`I am ${this.name} , ${msg}`)
    }
}
2. 访问修饰符
  1. private 私有属性,只能在内部用this访问
  2. public public 公有成员
  3. protected 受保护的 不能在外部访问

protected在子类中使用this是可以访问到的

3. 只读属性

在访问修饰符后面加上 readonly

// 比如
public readonly name: string;
4. 类与接口
// 规定协议 

interface Eat {
    eat(food: string): void
}

interface Run {
    run(distance: number): void
}

class Person implements Eat, Run {
    eat(food: string): void {

    }
    run(distance: number): void {

    }
}

class Animal implements Eat, Run {
    eat(food: string): void {

    }
    run(distance: number): void {

    }
}
5. 抽象类

抽象类使用 关键字 abstract

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()
6. 泛型 Generics

定义函数或接口、类的时候没有指定具体的类型,等到我们使用的时候再去指定具体类型的特征

function createArray<T>(length: number, value: T): T[] {
    const arr = Array<T>(length).fill(value)
    return arr
}
const res = createArray<string>(3, 'foo')

7. 类型声明 Type Declaration

为了兼容普通的js模块

import { camelCase } from 'lodash'

declare function camelCase (input: string): string
const res = camelCase('hello typed') // 在这里函数没有提示,需要声明 如上

引用第三方模块,如果模块中不包含类型声明文件,就需要自己声明

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容