TypeScript入门(一)
参考TypeScript 入门教程http://ts.xcatliu.com/
TypeScript 的特性
类型系统
- 按照「类型检查的时机」分类
- 动态类型(JavaScript)
- 静态类型(TypeScript)
- 注:TypeScript强大的类型推论可以使大部分 JavaScript 代码经过少量的修改(或者完全不用修改)就变成 TypeScript 代码。
- 按照「是否允许隐式类型转换」分类
- 强类型(Python)
- 弱类型(JavaScript,TypeScript)
适用于任何规模
- 易于维护--适用于大型项目
- 有类型推论--同样适用于小项目
- 通过类型声明文件渐进式迁移至typeScript
与标准同步发展
与 ECMAScript 标准同步发展,一个语法进入到 Stage 3 阶段后,TypeScript 就会实现它。
原始基础数据类型
- 数值(number)
- 布尔值(boolean)
- 字符串(string)
- 空值(void): 用 void 表示没有任何返回值的函数
- null
- undefined
- symbol(es6)
- bigInt(es10)
使用let声明变量,const声明常量(阻止变量被重新赋值)
// 数字,支持二、八、十、十六进制
let num: number = 3;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;
// 布尔
let isDone: boolean = false;
// 字符串,支持模板字符串
let name: strng = 'lilili';
// 空值:用 void 表示没有任何返回值的函数
function alertName(): void {
alert('My name is Tom');
}
// null和undefined
let u: undefined = undefined;
let n: null = null;
声明一个 void 类型的变量没有什么用,因为你只能将它赋值为 undefined 和 null(只在 --strictNullChecks 未指定时)
let unusable: void = undefined;
与 void 的区别是,undefined 和 null 是所有类型的子类型,以下代码不会报错
let num: number = undefined;
let u: undefined;
let num: number = u;
Any
任意值(Any)用来表示允许赋值为任意类型。声明一个变量为任意值之后,对它的任何操作,返回的内容的类型都是任意值 变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型
let num: number = 3;
num = 'str'; // 不允许
let u: any = 5;
u = 'test' // 允许
联合类型(Union Types)
取值可以为多种类型中的一种。使用|分割每个类型。 当不确定联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型中的公共属性或方法。 联合类型在被赋值的时候会根据类型推论规则推出一个类型。
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
myFavoriteNumber = 7;
// 访问公共属性
function getString(something: string | number): string {
return something.toString();
}
let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;
console.log(myFavoriteNumber.length); // 编译时报错
对象的类型——接口(interface)
使用接口(Interfaces)来定义对象的类型。
interface LabeledValue {
label: string;
width?: number; // 可选属性
readonly y: number; // 只读属性
}
function printLabel(labeledObj: LabeledValue) {
console.log(labeledObj.label);
}
const label1 = {
label: 'drop',
y: 20
}
const label2 = {
label: 'throtle',
y: 30,
width: 30
}
如上所示,label1和label2都兼容接口LabeledValue,变量中的属性定义顺序跟接口中属性定义没关系。
数组类型
使用「类型 + 方括号」来表示数组
let fibonacci: number[] = [1, 1, 2, 3, 5];
// 数组的项中不允许出现其他的类型:
let fibonacci: number[] = [1, '1', 2, 3, 5];
使用数组泛型(Array Generic) Array
let fibonacci: Array<number> = [1, 1, 2, 3, 5];
用接口描述数组,常用于类数组,eg:IArguments, NodeList, HTMLCollection
interface NumberArray {
[index: number]: number;
}
let fibonacci: NumberArray = [1, 1, 2, 3, 5];
function sum() {
let args: {
[index: number]: number;
length: number;
callee: Function;
} = arguments;
}
interface IArguments {
[index: number]: any;
length: number;
callee: Function;
}
经常用any表示数组中可以出现任意对象
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];
函数的类型
- 函数声明
- 函数表达式
- 接口定义
// 函数声明
function sum(x: number, y: number): number {
return x + y;
}
// 函数表达式
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
return x + y;
};
- 在 TypeScript 的类型定义中,=> 用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。
- 用 ? 表示可选的参数,可选参数必须接在必需参数后面。
- 允许默认参数值,当设置了默认值,会被认为是可选参数,此时不受上一条的影响。
- 可以用数组类型定义剩余参数:...items: any[]
重载允许一个函数接受不同数量或类型的参数时,作出不同的处理。注意,TypeScript 会优先从最前面的函数定义开始匹配, 所以多个函数定义如果有包含关系,需要优先把精确的定义写在前面。
function reverse(x: number): number;
function reverse(x: string): string;
function reverse(x: number | string): number | string | void {
if (typeof x === 'number') {
return Number(x.toString().split('').reverse().join(''));
} else if (typeof x === 'string') {
return x.split('').reverse().join('');
}
}
类型断言
类型断言(Type Assertion)可以用来手动指定一个值的类型。语法:值 as 类型(推荐) 或者 <类型>值
类型断言的用途
- 将一个联合类型断言为其中一个类型
interface Cat {
name: string;
run(): void;
}
interface Fish {
name: string;
swim(): void;
}
// 类型断言只能够「欺骗」TypeScript 编译器,无法避免运行时的错误,反而滥用类型断言可能会导致运行时错误
function isFish(animal: Cat | Fish) {
if (typeof (animal as Fish).swim === 'function') {
return true;
}
return false;
}
- 将一个父类断言为更加具体的子类
class ApiError extends Error {
code: number = 0;
}
class HttpError extends Error {
statusCode: number = 200;
}
function isApiError(error: Error) {
if (typeof (error as ApiError).code === 'number') {
return true;
}
return false;
}
// 也可以用instanceof来判断以上情况,但当ApiError 和 HttpError 不是一个真正的类,而只是一个 TypeScript 的接口(interface),就只能用断言来判断了
function isApiError(error: Error) {
if (error instanceof ApiError) {
return true;
}
return false;
}
- 将任何一个类型断言为 any
// 它极有可能掩盖了真正的类型错误,所以如果不是非常确定,就不要使用 as any。
(window as any).foo = 1;
- 将 any 断言为一个具体的类型
// 通过类型断言及时的把 any 断言为精确的类型
function getCacheData(key: string): any {
return (window as any).cache[key];
}
interface Cat {
name: string;
run(): void;
}
const tom = getCacheData('tom') as Cat;
tom.run();
- 要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可
需要注意的点
- 不是任何一个类型都可以被断言为任何另一个类型,要使得 A 能够被断言为 B,只需要 A 兼容 B 或 B 兼容 A 即可。
- 可以使用双重断言将任何一个类型断言为任何另一个类型,但十有八九是非常错误,除非迫不得已,千万别用双重断言。
- 类型断言只会影响 TypeScript 编译时的类型,不会真的影响到变量的类型。
- 类型声明是比类型断言更加严格。
声明文件
当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能。 声明语法:
- export as namespace UMD库声明全局变量
- declare global扩展全局变量
- declare module扩展模块
/// <refrence />
三斜线指令
通常我们会把声明语句放到一个单独的文件(jQuery.d.ts)中,这就是声明文件,声明文件必需以 .d.ts 为后缀。 推荐使用 @types 统一管理第三方库的声明文件,使用方式: npm install @types/jquery --save-dev
可以在这里搜索第三方库的声明文件。当一个第三方库没有提供声明文件时,需要自己书写声明文件。 库的使用场景:
- 全局变量,通过script标签引进来
- npm包,符合es6规范
- UMD库,前两种方式都兼容
- 直接扩展全局变量,通过script标签引入之后,改变一个全局变量的结构
- 在npm包或UMD库中扩展全局变量,通过npm包引进来之后,改变一个全局变量的结构
- 模块插件:通过script或import导入后,改变另一个模块的结构
全局变量的声明
- declare var 声明全局变量
declare var jQuery: (selector: string) => any;
declare let jQuery: (selector: string) => any;
declare const jQuery: (selector: string) => any;
// 切勿在声明语句中定义具体的实现
declare const jQuery = function(selector) {
return document.querySelector(selector);
};
- declare function 声明全局方法
declare function jQuery(selector: string): any;
// 支持重载
declare function jQuery(selector: string): any;
declare function jQuery(domReadyCallback: () => any): any;
- declare class 声明全局类
declare class Animal {
name: string;
constructor(name: string);
sayHi(): string;
}
// 不能用来定义具体的实现
declare class Animal {
name: string;
constructor(name: string);
sayHi() {
return `My name is ${this.name}`;
};
// ERROR: An implementation cannot be declared in ambient contexts.
}
- declare enum 声明全局枚举类型
declare enum Directions {
Up,
Down,
Left,
Right
}
- declare namespace 声明含子属性的全局对象
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
const version: number;
class Event {
blur(eventType: EventType): void
}
enum EventType {
CustomClick
}
}
// 嵌套的命名空间
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
namespace fn {
function extend(object: any): void;
}
}
- interface 和 type 声明全局类型
interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
declare namespace jQuery {
function ajax(url: string, settings?: AjaxSettings): void;
}
// 将interface或者type放在namespace下,防止命名冲突
declare namespace jQuery {
interface AjaxSettings {
method?: 'GET' | 'POST'
data?: any;
}
function ajax(url: string, settings?: AjaxSettings): void;
}
// 使用对应namespace下的interface需要带上对应的namespace
let settings: jQuery.AjaxSettings = {
method: 'POST',
data: {
name: 'foo'
}
};
// 声明合并:当一个全局变量既是一个函数,又是一个对象,可以组合多个声明语句
declare function jQuery(selector: string): any;
declare namespace jQuery {
function ajax(url: string, settings?: any): void;
}
npm包的声明
npm包的声明一般可能存在两个地方:
- 查看npm包的package.json里的types字段,或者有个index.d.ts文件。(最推荐)
- 发布到@types里。尝试安装 npm i @types/foo --save-dev,如果能安装,就表示有声明文件。
如果以上两种方式都找不到对应的声明文件,则需要自己编写。有两种方式:
- 创建node_modules/@types/foo/index.d.ts文件,缺点:不易维护
- 创建types目录,管理自己的声明文件,需要在tsconfig.json中配置paths和baseUrl字段。目录如下:
├── src
| └── index.ts
├── types
| └── foo
| └── index.d.ts
└── tsconfig.json
tsconfig.json如下:
{
"compilerOptions": {
"module": "commonjs",
"baseUrl": "./",
"paths": {
"*": ["types/*"]
}
}
}
module 配置可以有很多种选项,不同的选项会影响模块的导入导出模式。经常用commonjs这个选项。
npm包的声明文件的语法:
- export 导出变量
// export 的语法与普通的 ts 中的语法类似,区别仅在于声明文件中禁止定义具体的实现
export const name: string;
export function getName(): string;
export class Animal {
constructor(name: string);
sayHi(): string;
}
export enum Directions {
Up,
Down,
Left,
Right
}
export interface Options {
data: any;
}
// 可以混用declare 和 export
declare const name: string;
declare function getName(): string;
declare class Animal {
constructor(name: string);
sayHi(): string;
}
declare enum Directions {
Up,
Down,
Left,
Right
}
interface Options {
data: any;
}
export { name, getName, Animal, Directions, Options };
- export namespace 导出含子属性的对象
export namespace foo {
const name: string;
namespace bar {
function baz(): string;
}
}
- export defaut es6默认导出
export default function foo(): string;
// 只有 function、class 和 interface 可以直接默认导出,其他的变量需要先定义出来,再默认导出,针对这种默认导出,我们一般会将导出语句放在整个声明文件的最前面
export default Directions;
declare enum Directions {
Up,
Down,
Left,
Right
}
- export = commonjs导出模块
- 在 commonjs 规范中,
// 整体导出
module.exports = foo;
// 单个导出
exports.bar = bar;
// 整体导入
const foo = require('foo');
// 单个导入
const bar = require('foo').bar;
// 还可以使用import
// 整体导入
import * as foo from 'foo';
// 单个导入
import { bar } from 'foo';
// 对于这种使用 commonjs 规范的库,假如要为它写类型声明文件的话,就需要使用到 export =
export = foo;
declare function foo(): string;
declare namespace foo {
const bar: number;
}
内置对象
ECMAScript 常见的内置对象:
- Object
- Function
- Boolean
- Symbol
- Number
- BigInt
- Math
- Date
- String
- RegExp
- Array
- Map
- Set
- WeakMap
- WeakSet
- ……
DOM 和 BOM 的内置对象:
- Document
- HTMLElement
- Event
- NodeList
它们可以直接在 TypeScript 中当做定义好了的类型。
Node.js 不是内置对象的一部分,如果想用 TypeScript 写 Node.js,则需要引入第三方声明文件:
npm install @types/node --save-dev
类型注解
轻量级,为函数或变量添加约束的方式。
额外的属性检查
[[对象字面量]]会被特殊对待而且会经过额外属性检查。 当将它们赋值给变量或作为参数传递的时候, 如果一个对象字面量存在任何“目标类型”不包含的属性时,你会得到一个错误。
interface Music {
x?:string,
y?:string,
z?:string
}
function sing(music: Music):viod {
console.log(`${music.x}`)
}
sing({ a: '123', x: '345'}) // 报错 类型“{a:string;}”与类型Music不具备有相同的属性
解决方法1:改变interface
interface Music {
x?:string,
y?:string,
z?:string,
[props: string]: any
}
解决方法2:使用as
sing({ a: '123', x: '345'} as Music)
解决方法3:使用变量(不推荐)
let music1 = { a: '123', x: '345'}
sing( music1 as Music)
函数类型
interface Phone {
(color:string, width:number, height:number): void
}
let phone : Phone ;
phone = function(c: string, w: number, h:number):void {
console.log(c)
console.log(w)
console.log(h)
}
phone('red', 12, 8)
可索引的类型
可索引类型具有一个 索引签名,它描述了对象索引的类型,还有相应的索引返回值类型。
支持两种索引签名:字符串和数字
可以同时使用两种类型的索引,但是数字索引的返回值必须是字符串索引返回值类型的子类型
interface Names {
[index:number]: string; // 索引类型为number,索引返回类型为string,表示用number类型的值去访问Names会返回string类型的值
}
interface Indexs {
[index: string]: string;
[index: number]: string;
name: string;
color: string;
num: number; //错误,与索引返回类型不匹配
}
let names:Names = ['li', 'zhang', 'deng']
console.log(names[0])
类类型
明确的强制一个类去符合某种契约
接口描述了类的公共部分,不会检查私有成员
interface Clock {
currentTime: string // 索引类型为number,索引返回类型为string,表示用number类型的值去访问Names会返回string类型的值
}
class DayClock implements Clock{
currentTime: string;
constructor() {
}
}
类
包含公共字段和构造函数
class Greeter {
greeting: string;
constructor(msg: string) {
this.greeting = msg;
}
greet() {
return "hello, "+ this.greeting;
}
}
let greeter = new Greeter('world')
继承:extents
public: 公共,默认
private: 私有,
protected: 受保护的
readonly和const的区别
readonly:定义接口或类的属性是只读的
const:定义不可变的变量
更新于: 2022年5月5日