TS特有de概念
元组
指定数组每个元素的类型
特点
- 在初始化时,必须按限定的类型和个数赋值;
- 可以通过数组的方法突破限制;
示例
let arr: [string, number, any]
arr = ['1', 1, 1] // OK
arr = [1, 1, 1] // Error
arr = ['1', 1, 1, true] // Error
arr.push(true) // OK
接口
只是在
ts
编译阶段做类型检查,并不会转译成JS
代码
用接口声明可调用的类型
示例
// 定义函数
interface ISum {
(x: number, y: number, z?: number): number;
}
let sum: ISum
sum = (a) => {
// ^ = OK
return 1
}
sum('1', 2)
// ^ Argument of type '"1"' is not assignable to parameter of type 'number'.
// 定义类的new调用
interface IConstructor {
new(arg: string): IConstructor;
}
function Constr (this: any, name: string) {
this.name = name
}
const instance = new (Constr as any as IConstructor)('111')
以上示例注意:
-
interface
定义函数输入、输出的类型作为预置值内置在函数声明中- 函数声明时无需二次定义参数类型,函数输出值的类型推断要与
interface
定义的输出类型一致,否则会报错。 -
interface
定义函数没有限制函数声明时传入的参数个数,只有在调用时才会报参数个数错误;
- 函数声明时无需二次定义参数类型,函数输出值的类型推断要与
- 函数声明无法直接其它类型,需要使用双重断言
Constr as any as IConstructor
;- ==尽可能不要使用双重断言==,它会影响
ts
的判断
// 示例: let num: number = 0 let str: string = 's' num = str // Error num = str as any as number // OK //^ = num === 's' //这里str认为是number类型,赋值成功
- ==尽可能不要使用双重断言==,它会影响
联合类型
一个数据声明为联合类型,使用时,若不确定是联合类型中具体的类型时(通过if条件、as断言、in操作符、typeof缩小未知范围),只能访问联合类型共有的方法。
断言
断言是联合类型缩小未知范围时使用,但,断言强制浏览器相信数据是我们指定的类型,实际上是不安全的。【推荐】使用类型收缩
typeof
、instanceof
...来缩小未知范围。
当两个类型声明有交集时,才可以使用断言,否则会报错(because neither type sufficiently overlaps with the other.)
如果两个类型声明没有交集,可以使用双重断言强制断言成另一种类型,示例如上:
Constr as any as IConstructor
readonly & Readonly泛型
readonly标识符,用于对象属性
Readonly映射类型,接收一个泛型T,用来把泛型T的所有属性标记为只读类型;
示例
type Foo = {
bar: number;
bas: number;
}
type ReadFoo = Readonly
/** ^ = type ReadFoo = {
* readonly bar: number;
* readonly bas: number;
* }
*/
示例
function fn(x: number | string): number {
return x.length;
}
// Property 'length' does not exist on type 'string | number'.
// Property 'length' does not exist on type 'number'.
对象结构
示例
// type定义对象结构,不可重载
type TObjectProps = {
x: number;
y: number;
}
const obj: TObjectProps = {
x: 1,
y: 1
}
// interface定义对象结构
interface IObjectProps {
x: number;
y: number;
}
const obj: IObjectProps = {
x: 1,
y: 1,
add() {}
//^ = 'add' does not exist in type 'IObjectProps'
}
// interface定义重载
interface IObjectProps {
x: number;
y: number;
}
const obj: IObjectProps = {
x: 1,
y: 1,
add() {} // OK
}
interface IObjectProps {
add: () => void;
}
// let & typeof定义对象结构,不可重载
let objectProps: {
x: number;
y: number;
}
const obj: typeof objectProps = {
x: 1,
y: 1
}
Function
函数类型声明方式有多种,应用场景两种: 固定参数,不固定参数;
示例
固定参数:
// type定义
type Tfn = (a: number, b: number) => number;
let fn1: Tfn
fn1 = function(a, b) {
return a + b
}
fn1(1, 2)
// type定义重载
type Tfn = {
(a: string): string;
(a: number, b: number): number;
}
let fn1: Tfn
fn1 = function(a: any, b?: any): any {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// interface定义
interface Ifn {
(x: string): string;
}
let fn1: Ifn
fn1 = function(a) {
return a
}
fn1('1')
// interface定义重载
interface Ifn {
(x: string): string;
(x: number, y: number): number;
}
let fn1: Ifn
fn1 = function(a: any, b?: any): any {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// let & typeof 声明
let fn: {
(x: string): string;
}
let fn1: typeof fn
fn1 = function(a) {
return a
}
fn1('1')
// let & typeof声明重载
let fn: {
(x: string): string;
(x: number, y: number): number;
}
let fn1: typeof fn
fn1 = function(a: any, b?: any) {
if (b) {
return a + b
} else {
return a
}
}
fn1('1')
// ^ = let fn1: (a: string) => string (+1 overload)
fn1(1, 2)
// ^ = let fn1: (a: number, b: number) => number (+1 overload)
fn1(1) //Error
// function声明
function fn(x: string): string {
return x
}
fn('1')
// function声明重载:fn实现必须紧跟着fn头声明
function fn(x: string): string;
function fn(x: number, y: number): number;
function fn(x: any, y?: any) {
if (y) {
return x + y
} else {
return x
}
}
fn('1')
// ^ = let fn: (a: string) => string (+1 overload)
fn(1, 2)
// ^ = let fn: (a: number, b: number) => number (+1 overload)
fn(1) //Error
通过重复定义函数头实现重载,而函数声明输入、输出最好使用any
类型(不使用any
的话,函数体的操作逻辑使用的必须是联合声明类型共有的成员),调用时会根据参数个数匹配预定义的函数头进行校验。
不固定参数:
// 应用场景:作为回调函数,通过`apply`调用;
interface IFn {
(...args: any[]): any;
}
function invoke(fn: IFn) {
fn.apply(null, [...arguments])
}
枚举Enum
定义索引和值的双向映射;
enum Direction {
UP,
DOWN,
LEFT,
RIGHT
}
console.log(Direction[0]) // 'UP'
console.log(typeof Direction[0]) // 'string'
console.log(Direction['UP']) // 0
console.log(typeof Direction['UP']) // 'number'
console.log(Direction[0] === 'UP') // true
分类
数字枚举
数字枚举默认从0开始;
若有指定的索引,则后续数据索引++
// 场景:Code编码语义化
enum Direction {
Up,
Down = 10,
Left,
Right
}
console.log(Direction[0]) // 'UP'
console.log(Direction['Up']) // 0
console.log(Direction['Left']) // 11
console.log(Direction[10]) // 'Down'
字符串枚举
// 场景:游戏按键?
enum Direction {
Up = 'u',
Down = 'd',
Left = 'l',
Right = 'r'
}
console.log(Direction['Up']) // '上'
console.log(Direction['Down']) // '下'
常量枚举
和上述枚举类型编译结果不同;
enum Dir {
Up,
Down = 10,
Left,
Right
}
const enum Direction {
Up,
Down = 10,
Left,
Right
}
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
/////编译输出如下:
"use strict";
var Dir;
(function (Dir) {
Dir[Dir["Up"] = 0] = "Up";
Dir[Dir["Down"] = 10] = "Down";
Dir[Dir["Left"] = 11] = "Left";
Dir[Dir["Right"] = 12] = "Right";
})(Dir || (Dir = {}));
let directions = [
0 /* Up */,
10 /* Down */,
11 /* Left */,
12 /* Right */,
];
字面量类型
const str: 'name' = 'name' // str只能是'name'字符串,赋值其他字符串或其他类型会报错;
const number: 1 = 1 // number只能是1,赋值其他数值或其他类型会报错;
type Directions = 'UP' | 'DOWN' | 'LEFT' | 'RIGHT'
let toWhere: Directions = 'LEFT'
对应场景:
interface IO {
'y+': number;
'M+': number;
'd+': number;
'h+': number;
'm+': number;
's+': number;
'q+': number;
'S+': number;
}
type TKeyProps = keyof IO
// ^ = type TKeyProps = "y+" | "M+" | "d+" | "h+" | "m+" | "s+" | "q+" | "S+"
var o: IO = {
'y+': this.getFullYear(),
'M+': this.getMonth() + 1,
'd+': this.getDate(),
'h+': this.getHours(),
'm+': this.getMinutes(),
's+': this.getSeconds(),
'q+': Math.floor((this.getMonth() + 3) / 3),
'S+': this.getMilliseconds()
}
o['y++'] // OK
let kkk = 's+'
o[kkk] // Error
o[kkk as TKeyProps] // OK
泛型
泛型de目的是在成员之间(至少有两个地方用到了泛型占位)提供有意义的约束
成员:
- 类的属性
- 类的方法
- 函数参数
- 函数返回值
泛型在定义时,不能明确数据类型,声明一变量占位,调用时通过传入类型或
ts
类型推断来确定具体的数据类型。
- 相对于
any
:泛型未丢失数据结构信息; - 相对于联合声明:泛型明确具体的类型结构,联合声明并未明确具体类型;
逻辑中只能调用泛型数据的通用成员属性/方法,否则会报错;
示例
function identity(arg: T): T {
return arg;
}
// 明确指定T是string类型
let output = identity("myString"); // type of output will be 'string'
// 利用类型推论 -- 即编译器会根据传入的参数自动地帮助确定T的类型
let output = identity("myString"); // type of output will be 'string'
any & never & unknown
-
any
- 称为
top type
,任何类型的值都能赋给any
类型的变量 - 又称为
bottom type
,任何类型(除never
外)的子类型 - 可以理解为没有类型
- 称为
-
never
称为
bottom type
,任何类型的子类型,也可以赋值给任何类型-
推断场景1:
无法执行到函数终止点的函数表达式
- 场景:总抛出异常的函数表达式的返回值类型;
// 需要是函数表达式,若是函数声明为assertNever: () => void const assertNever = function (x: any) { // ^ = type assertNever = never throw new Error("Unexpected object: " + x); }
- 场景:永不结束函数表达式的返回值类型;
let loop = function () { // ^ = type loop = never while (true) {} }
推断场景2:被永不为真的类型保护约束的变量类型;
// 示例: type result = 1 & 2 // 结果为never // ^ = type result = never // 尤大示例: interface Foo { type: 'foo' } interface Bar { type: 'bar' } type All = Foo | Bar function handleValue(val: All) { switch (val.type) { case 'foo': // 这里 val 被收窄为 Foo break case 'bar': // val 在这里是 Bar break default: const exhaustiveCheck = val // ^ = type exhaustiveCheck = never break } }
-
unknown
-
top type
:任何类型都是它的subtype
; - 不能将
unknown
赋值其它类型,unknown
类型的数据只能赋值给unknown
、any
类型; - 相对于
any
,ts
会为unknown
提供有效的类型检测; - 若
unknown
类型数据的属性或方法,需要通过类型断言/类型收缩来缩小未知范围;
-
any
的危害
使用
any
做类型声明或者做断言,会丧失原始数据的结构类型信息,即:再也无法知道原始数据是什么结构了,更不会有报错信息,推荐使用unknown
& 类型收缩;
索引签名
索引签名用于定义对象/数组de通配结构
索引签名的名称(如:
{ [key: string]: number }
的key
)除了可读性外,没有任何意义,可以任意定义。
其它成员都必须符合索引签名值de结构,所以,索引签名值de结构必须是其它成员属性类型的
top type
。
尽量不要使用字符串索引签名与有效变量混合使用——如果属性名称中有拼写错误,这个错误不会被捕获。【同级的其它属性应该是对索引签名的限制增强】
示例
// 1. 对象示例
interface Foo {
[key: string]: number; // 该通配结构[key: string]即是签名索引。
}
// 1. 数组示例
interface Foo {
[idx: number]: string;
length: number;
}
const arr: Foo = ['1', '2', '3', '4']
// 2. 所有明确的成员都必须符合索引签名
interface Bar {
[key: string]: number;
x: number; // OK
y: string;
//^ = Property 'y' of type 'string' is not assignable to string index type 'number'.
}
// 2. 可以使用交叉类型突破限制
interface Bar {
[key: string]: number;
x: number;
}
type Composition = Bar && {
y: string;
}
// 3. 有效变量和索引签名不要同级定义
interface CSS {
[selector: string]: string;
color?: string;
}
const failsSilently: CSS = {
colour: 'red' // 'colour' 不会被捕捉到错误
};
TS
类型声明
ts
报错对应查找:做词典用;
declare
声明通过各种途径(导入、
new webpack.ProvidePlugin({//...})
)引入全局命名空间的变量/模块
函数声明中显式使用this
// 场景示例:
function Person() {
var vm = this;
// ^ = this具有隐式类型any;
}
// 解决方案:
function Person(this: void) {
var vm = this;
}
this
作为函数的第一参数,由于ts
只是类型检查,编译成JS
时不会将this
参数输出。
全局库声明
// 场景示例:
$('div')[]
//^ = not find name '$'
// 解决方案:追加zepto.d.ts声明
interface ZeptoStatic {
//...
}
interface ZeptoCollection {
// ...
}
declare var Zepto: (fn: ($: ZeptoStatic) => void) => void;
declare var $: ZeptoStatic;
declare function $(selector?: string, context?: any): ZeptoCollection;
JS
内置对象追加属性声明
// 场景示例:
Date.prototype.format = function() {
//...
}
const arg = 2423423413;
new Date().format(arg)
// ^ = format 不在Date上
// 解决方案:追加声明*.d.ts声明
declare global {
interface Date {
Format(arg: string): string;
}
}
图片...静态资源声明
// 场景示例:
import Logo from './assets/logo.png'
// ^ = not find Module
// 解决方案:追加声明*.d.ts声明
declare module '*.svg'
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
declare module '*.gif'
declare module '*.bmp'
declare module '*.tiff'
给vue3
配置全局成员
// 场景示例:
// main.ts
import { createApp } from 'vue'
import axios from 'axios'
import qs from 'qs'
import App from './App.vue'
const vueInstance = createApp(App)
vueInstance.config.globalProperties.$http = axios
vueInstance.config.globalProperties.$qs = qs
// *.vue
...
this.$http.post(...).then(() => {})
// ^ = Property '$http' does not exist on type 'ComponentPublicInstance<>'...
...
// 解决方案:追加声明*.d.ts
import Axios from "axios";
import qs from 'qs'
import Store from "../store";
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$http: Axios;
$store: Store;
$qs: qs;
}
}
第三方ES Module:export default
声明
// 场景示例:
// utils.ts
export default (function() {
const utils = {
}
return utils
})()
// *.ts
utils.default.isString(123)
// ^ = Property 'default' does not exist on type 'typeof utils'
// 解决方案:追加声明*.d.ts
declare namespace utils {
const UtilsProps: {
isString: (arg: unknown) => boolean;
isArray: (arg: unknown)=> boolean;
isObject: (arg: unknown)=> boolean;
isDate: (arg: unknown)=> boolean;
app: any;
}
export default UtilsProps;
}
第三方ES Module:export
声明
// *.d.ts
declare namespace utils {
export function isString (arg: unknown): boolean;
export var app: any;
}
第三方CommonJS
模块声明
// 模块类库 module-lib.ts
function moduleLib(options) {
console.log(options);
}
const version = "1.0.0";
function doSomething() {
console.log('moduleLib do something');
}
moduleLib.version = version;
moduleLib.doSomething = doSomething;
module.exports = moduleLib;
// *.d.ts
declare function moduleLib(options: Options): void;
interface Options {
[key: string]: any,
}
declare namespace moduleLib{
const version: string;
function doSomething(): void;
}
export = moduleLib;
第三方'UMD'声明
// UMD库 umd-lib.js
(function (root, factory) {
if (typeof define === "function" && define.amd) {
define(factory);
} else if(typeof module === "object" && module.exports) {
module.exports = factory();
} else {
root.umdLib = factory();
}
})(this, function () {
return {
version: "1.0.2",
doSomething() {
console.log('umdLib do something');
}
}
});
// *.d.ts文件
declare namespace umdLib {
const version: string;
function doSomething(): void;
}
export as namespace umdLib // 专门为umd库准备的语句,不可缺少
export = umdLib // commonjs导出
参考书
- 在线编译工具
- 深入理解TypeScript
- ts官网
- w3c/ts
- ...