# TypeScript
- 基于javascript语言之上的语言,解决了JavaScript类型系统的问题
- 是JavaScript的超集(扩展集)
- 在javaScript之上,多了一些类型系统和es6新特性的支持,最终会被编译为原始的javaScript
# 概述
- 强类型与弱类型
- 安全层面
- 强类型有更强的类型约束,而弱类型几乎没有什么约束
- 强类型不允许有隐式转换,弱类型可以
- 语言语法层面就限制了数据类型
- 静态类型与动态类型,什么是静态类型,什么是动态类型,有什么区别
- 类型检查
- 静态类型语言: 一个变量声明时它的类型就是明确的,声明过后类型不允许更改
- 动态类型: 在运行阶段才能明确,语言类型可以更改,变量没有类型(javascript)
- JavaScript自有类型系统的问题(弱类型的问题)
- JavaScript类型系统特征(弱类型 且 动态类型),脚本语言不需要编译,没有编译环节,丢失了类型系统的可靠性
- 在语法上就会报错,不会等到运行的时候才会报错
- 强类型优势:
- 错误更早暴露
- 代码更智能,编码更准确
- 重构更牢靠
- 减少不必要的类型判断
- Flow静态类型检查方案
- javascript的类型的检查器
- TypeScript语言规范与基本应用
# Flow 检测代码中的问题
- 数组类型
// 全部由数组组成的数组 const arr1: Array<number> = [1, 2, 3] const arr2: number[] = [1, 2, 3] // 固定长度的数组,称为元组 const foo: [string, number] = ['foo', 100] - 对象类型
// 这个obj1 必须要有两个成员 一个foo (string类型)一个bar(一个number类型) const obj1 : { foo: string, bar: number } = { foo: 'string', bar: 100} // ?表示可选可不选,成员可有可无 const obj2 : { foo?: string, bar: number } = { bar: 100} // 限制键值对的类型 键和值的类型必须都是字符串 const obj3: { [string]: string} = {} - 函数类型
// 指定函数必须要有两个参数,一个为string类型,一个为number类型 function foo (callback: (string, number) => void) { callback('string', 100) } foo(function (str, n){ // str => string // n => number }) - 特殊类型,字面量额类型
const a : 'foo' = 'foo' const type: 'success' | 'warning' | 'danager' = 'success' // 值只能是这几种的一个 const b: string | number = 'string' // 20 值为string或number都可以 type StringOrNumber = string | number const c: stringOrNumber = 'string' const gender: ?number = null // undefined maybe类型 - mixed Any
function passMixed (value: mixed) { } passMixed('string') passMixed(100) function passAny(value: any) { if (typeof value === 'string') { value.substr(1) } if (typeof value === 'number') { value * value } <!-- value.substr(1) --> <!-- value * value --> } passAny('string') passAny(100)
# TypeScript JavaScript的超集(superset) 前端领域的第二语言,
缺点:语言本身多了很多概念
TypeScript 属于 渐进式
# TypeScript 原始数据类型
const a: string = 'foobar'
const b: number = 100 // NaN Infinity
const c: boolean = true // false
const e: void = undefined
const f: null = null
const g: undefined = undefined
const h: symbol = Symbol()
// 标准库就是内置对象所对应的声明
// 作用域 添加
export {}
# Object类型
export {} // 确保跟其他示例没有冲突
const foo: object = function() {} // [] {}
const obj: { foo: numnber, bar: string } = { foo: 123, bar: 'string' }
# 数组类型
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
function sum (...args: numberp[]) { // 只能传入数字的数组
return args.reduce((prev, current) => prev + current, 0)
}
sum(1, 2, 3)
# 元组类型(明确元素数量,以及各个数组的类型的数组)
export {} // 确保跟其他代码块没有冲突
const tuple: [number, string] = [18, 'zer']
const age = typle[0] // 下标去获取值
const [age, name] = tuple // 解构去取值
# 枚举类型
实际开发中,会用到一些数值表示一些方法,比如常用的status 1 2 3 表示不同的状态,这是用枚举会方便记录,识别
const PostStatus = {
Draft: 0,
Up: 1,
Pub: 2
}
enum PostStatus {
Draft= 0,
Up= 1,
Pub= 2
}
const enum PostStatus { // 常量枚举
Draft, // 默认为0
Up, // 依次累加1
Pub // 2
}
const post = {
title: 'Hello ',
content: 'XXX',
status: PostStatus.Draft
}
// 入侵运行时的代码,编译后不会删除
# 函数类型 输入输出做限制
// 函数声明
function func1 (a: number, b?: number, c=10, ...rest: number[]): string { // string为函数返回值的限制 ? 可选参数 c 参数默认值
// reset 任意个数的参数
return 'func1'
}
func1(100, 200)
// 型参与传入的参数必须保持一致,设置参数默认值的参数,就是可以选的
// 函数表达式
const func2 = function(a: number, b: number): string {
return 'func2'
}
// 可以用箭头函数来表示函数返回值的类型
const func2: (a: number, b: number) => string = function (a: number, b: number): string {
return 'func2'
}
# 任意类型就(any Types)
// any接受任意类型
function stringify (value: any) {
return JSON.stringify(value)
}
let foo: any = 'string'
foo = 100
foo.bar()
// any类型不是安全的
# 隐式类型推断
let age = 19 // 隐式推断为number
age = 'string' // 会报错
let foo // 不赋值 就是任意类型的值
// 建议为每个类型去添加类型
# 类型断言
// 嘉定这个nums来自一个明确的接口
const nums = [112, 123, 342]
const res = nums.find(i => i > 0) // number || undefined
// **const square = res * res** //报错 因为ts不知道res返回的是什么,需要断言,明确告诉typescript这里是个什么值
const num1 = res as number // 这是一个number
const num2 = <number>res // 等价于上面的as写法 (jsx下不能使用)
# 非空断言
function myFunc(maybeString: string | undefined | null) {
// Type 'string | null | undefined' is not assignable to type 'string'.
// Type 'undefined' is not assignable to type 'string'.
const onlyString: string = maybeString; // Error
const ignoreUndefinedAndNull: string = maybeString!; // Ok
}
# 接口 interfaces (契约)
用来约束一个对象的结构,一个对象去实现一个接口,就必须拥有接口中所约束的所有成员
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'
})
// 动态定义接口
interface Cache {
[prop: string] : string // 动态只能是string类型,动态属性的值只能是string
}
const cache: Cache = {}
cache.foo = 'value'
# 类
- 用来描述一类具体事物的抽象特征
class Person {
public name: string = 'init name'
private 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}`)
}
}
// 访问修饰符
// private 私有属性
// public 共有属性
// protected 受保护的不能访问 只允许子类成员访问
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 tom = new Person('tom', 18)
console.log(tom.name) // 只云允许子类成员访问
- 类与接口
// interface EatAndRun {
// eat (food: string): void
// run (distance: number): void
//}
// 理想化 一个接口只抽象一个成员,一个类使用多个接口
interface Eat {
eat (food: string): void
}
interface Eat {
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 EatAndRun {
eat (food: string): void {
console.log(`咕噜咕噜的值${food}`)
}
run (distance: number) {
console.log(`爬行${distance}`)
}
}
- 抽象类
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('enxima')
d.run(100)
# 泛型 Generics
const hello = (name: string) => {
console.log(`hello,${name}`)
}
hello('bar')
const nums = [110, 1]
function creatNumberArray (length: number, value: number): number[] {
const arr = Array<number>(length).fill(value)
return arr
}
function creatStringArray (length: number, value: string): string[] {
const arr = Array<string>(length).fill(value)
return arr
}
function creatArray<T> (length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const res = creatNumberArray(3, 100)
const createArray<string>(3, 100)
// res => [100, 100, 100]
泛型变量 如果我们想同时打印出arg的长度。 我们很可能会这样做:
function loggingIdentity<T>(arg: T): T {
console.log(arg.length); // Error: T doesn't have .length
return arg;
}
如果这么做,编译器会报错说我们使用了arg的.length属性,但是没有地方指明arg具有这个属性。 记住,这些类型变量代表的是任意类型,所以使用这个函数的人可能传入的是个数字,而数字是没有.length属性的。
现在假设我们想操作T类型的数组而不直接是T。由于我们操作的是数组,所以.length属性是应该存在的。 我们可以像创建其它数组一样创建这个数组:
function loggingIdentity<T>(arg: T[]): T[] {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
你可以这样理解loggingIdentity的类型:泛型函数loggingIdentity,接收类型参数T和参数arg,它是个元素类型是T的数组,并返回元素类型是T的数组。 如果我们传入数字数组,将返回一个数字数组,因为此时T的的类型为number。 这可以让我们把泛型变量T当做类型的一部分使用,而不是整个类型,增加了灵活性。
我们也可以这样实现上面的例子:
function loggingIdentity<T>(arg: Array<T>): Array<T> {
console.log(arg.length); // Array has a .length, so no more error
return arg;
}
# 类型声明 Type Declaration
引入第三方模块,当不包含时,就尝试安装一个类型声明模块
// 一个函数在使用的时候没有函数声明,我们在用的是偶在单独做一个函数声明
import { camelCase } from 'lodash'
declare function camelCase (input: string): string
const res = camelCase('hello, typed')
# infer
export {}
interface Foo {
name: string,
age: number
}
type P = '12' | '233' | 'sdf'
interface D {
P: '12' | '233' | 'sdf'
}
let type: D["P"] = '12'
// const obj: T = {
// name: 'fds'
// // age: 2,
// }
const b: T = "age"
const c: T = "name"
// const d: T = 123
type T = keyof Foo
type Obj = {
[p in T]: any // ==> { name: any, age: any }
}
const aa: Obj = { "name": 1, age: 2}
const bb: Obj = { name: 1, age: 2}
// keyof 产生联合类型, in 则可以遍历枚举类型, 所以他们经常一起使用, 看下 Partial 源码
type Partial<D> = { [P in keyof D]?: D[P] }
const cc: Partial<Foo> = {
name: "345",
age: 18
}
- 用与提取参数类型
interface User {
name: string
age: number
}
//infer 最早出现在此 PR 中,表示在 extends 条件语句中待推断的类型变量。
type ParamType<T> = T extends (...args: infer P) => any ? P : T;
// 在这个条件语句 T extends (...args: infer P) => any ? P : T 中,infer P 表示待推断的函数参数。
// 整句表示为:如果 T 能赋值给 (...args: infer P) => any,则结果是 (...args: infer P) => any 类型中的参数 P,否则返回为 T。
type Func = (user: User) => void
type Param = ParamType<Func> // Param = User
type AA = ParamType<string>; // string
- 用于提取函数返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer P ? P : any;
相比于文章开始给出的示例,ReturnType'T' 只是将 infer P 从参数位置移动到返回值位置,因此此时 P 即是表示待推断的返回值类型。
type Func = () => User;
type Test = ReturnType<Func>; // Test = User
- 用于提取构造函数中参数(实例)类型: 一个构造函数可以使用 new 来实例化,因此它的类型通常表示如下:
type Constructor = new (...args: any[]) => any;
当 infer 用于构造函数类型中,可用于参数位置 new (...args: infer P) => any; 和返回值位置 new (...args: any[]) => infer P;。
因此就内置如下两个映射类型
// 获取参数类型
type ConstructorParameters<T extends new (...args: any[]) => any> = T extends new (...args: infer P) => any
? P
: never;
// 获取实例类型
type InstanceType<T extends new (...args: any[]) => any> = T extends new (...args: any[]) => infer R ? R : any;
class TestClass {
constructor(public name: string, public age: number) {}
}
type Params = ConstructorParameters<typeof TestClass>; // [string, number]
type Instance = InstanceType<typeof TestClass>; // TestClass
# 小技巧
- 显示中文错误消息 ,以中文方式显示错误消息
yarn tsc --locale zh-CN
← typeScript基础 实现Pick →