数据类型
- 原始类型
let bool: boolean = true
let num: number = 123
let str: string = 'abc'
- 数组
let arr1: number[] = [1, 2, 3]
let arr2: Array<number | string> = [1, 2, 'a']
- 元组
let tuple: [number, string] = [1, 'a'] // 元组中的元素类型和个数必须匹配
- 函数
let add = (x: number, y: number): number => x + y
// 定义一个函数
let compute: (x: number, y: number) => number
//
compute = (a, b) => a + b
- 对象
let obj: { x: number, y: number } = { x: 1, y: 2 }
// 对象中包含属性和方法
let person: {
name: string
age: number
say: () => void
sing: (sing: string) => void
}
person = {
name: 'abc',
age: 18,
say: function() {
console.log(this.name)
},
sing: function(sing: string) {
console.log(sing)
}
}
- symbol
let s1: symbol = Symbol()
- undefined null
let un: undefined = undefined
let nu: null = null
- void
let npReturn = () => {}
- any
let x
- never
// 抛错、死循环都会使得变量的类型为never类型
let error = () => {
throw new Error('error')
}
let endless = () => {
while(true) {}
}
- 枚举
// 数字枚举
enum Role {
Reporter = 1,
Developer,
Maintainer,
Owner,
Guest
}
console.log(Role.Developer); // 2
// 字符串枚举
enum Message {
Success = '恭喜你,成功了',
Fail = '抱歉,失败了'
}
// 异构枚举---数字枚举和字符串枚举
enum Answer {
N,
Y = 'Yes'
}
// 枚举成员
enum Char {
// const
a,
b = Char.a,
c = 1 + 3,
// computed
d = Math.random(),
e = '123'.length
}
console.log(Char.a) // 0
console.log(Char.b) // 0
console.log(Char.c) // 4
console.log(Char.d) // 0.4829982047336656
console.log(Char.e) // 3
// 常量枚举
const enum Month {
Jan,
Feb,
Mar
}
let month = [Month.Jan, Month.Feb, Month.Mar]
// 枚举类型
enum E {a, b}
enum F {a = 0, b = 1}
enum G {a = 'aa', b = 'bb'}
let e: E = 3
let f: F = 3
console.log(e) // 3
console.log(E.a) // 0
// e === f 不同的枚举成员不能相互比较
let e1: E.a = 1
let e2: E.b
console.log(e1) // 1
// e1 === e2
let e3: E.a = 1
e1 === e3
let g1: G = G.b
let g2: G.a = G.a
接口
接口的作用,在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范,在程序设计里面,接口起到一种限制和规范的作用。接口定义了某一批类所需要遵守的规范,接口不关心这些类的内部状态数据,也不关心这心类里方法的实现细节,它只规定了这批类里必须提供某种方法,提供了这些方法的类就可以满足实际需要,typescript中接口类似与Java,同时还增加了更灵活的接口类型,包括属性、函数、可索引和类等。
- 属性接口
// 方式一: ts中自定义传入参数对json数据进行约束
function printLabel(labelInfo:{label:string}):void{
console.log(labelInfo);
}
printLabel({label:'aaa'})
// 方式二: 使用interface定义接口
interface FullName{
firstName:string
secondName:string
}
function printName(name:FullName){
// 必须传入firstName secondName
console.log(name.firstName + '----' + name.secondName)
}
var obj = {
age: 20,
firstName: 'zz',
secondName: 'sss'
}
// 传入的参数必须包含firstName secondName
printName(obj)
- 接口可选参数
interface FullName1 {
firstName: string
secondName?: string // 使用 ? 标识是可选参数
}
function printName1(name: FullName1) {
// 必须传入firstName secondName
console.log(name.firstName + '----' + name.secondName)
}
printName1({
firstName:'first',
// secondName:'second'
})
- 函数类型接口: 对传入的参数以及返回值进行约束
interface encrypt{
(key:string,value:string):string
}
var md5:encrypt = function(key:string,value:string):string{
// 模拟加密操作
return key+value
}
console.log(md5('name','张三'));
- 可索引接口:数组、对象的约束(不常用)
interface UserArr{
[index:number]:string
}
var arr1:UserArr = ['111','aaaa']
console.log(arr1[0]);
interface UserObj{
[index:string]:string
}
var arr2:UserObj = {name:'aaa', age: '18'}
console.log(arr2.name);
// 直接定义数组的方式
var arr:string[] = ['111','aaaa']
- 类类型接口
interface Animal{
name:string
eat(str:string):void
}
class Dog implements Animal{
name:string
constructor(name:string){
this.name = name
}
eat(){
console.log(this.name);
}
}
var d = new Dog('asdd')
d.eat()
- 接口的扩展
interface Animal1{
eat():void
}
interface Person extends Animal1{
work():void
}
class Web implements Person {
public name:string
constructor(name:string) {
this.name = name
}
eat(){
console.log(this.name + '喜欢吃馒头');
}
work(){
console.log(this.name + '不喜欢工作');
}
}
var w = new Web('andy')
w.work()
- 接口案例---ajax请求函数
interface Config{
type:string
url:string
data?:string
dataType:string
}
function ajax(config:Config){
var xhr = new XMLHttpRequest
// 请求类型 请求地址 异步请求
xhr.open(config.type,config.url,true)
xhr.send(config.data)
xhr.onreadystatechange = function(){
if (xhr.readyState === 4 && xhr.status === 200){
console.log('成功');
if (config.dataType === 'json')
console.log(JSON.parse(xhr.responseText));
console.log(xhr.responseText);
}
}
}
ajax({
type:'get',
url:'https:www.baidu.com',
dataType:'json'
})
类
下面是一个类的例子
class Person {
name: string; // name属性 前面省略了 public 关键词
constructor(n: string) { // 构造函数
this.name = n
}
getName(): string {
return this.name
}
setName(name:string):void{
this.name = name
}
}
var p = new Person('andy')
p.setName('ddd')
alert(p.getName())
- ts实现类的继承
class Person {
name:string
constructor(name:string){
this.name = name
}
run():string{
return `${this.name}在运动`
}
}
class Web extends Person{
constructor(name:string){
super(name)
}
}
var w = new Web('李斯')
alert(w.run())
- 类的三种修饰符
public: 公有类型 在类里面、子类、外部都可以访问
protected:保护类型 在类里、子类可以访问
private: 私有类型 在类里面可以访问
class Person {
public name: string
// protected name: string
// private name: string
constructor(name: string) {
this.name = name
}
run(): string {
return `${this.name}在运动`
}
}
class Web extends Person {
constructor(name: string) {
super(name)
}
run(): string {
return `${this.name}在运动--子类`
}
work(): string {
return `${this.name}在工作--子类`
}
}
var w = new Web('李斯')
// 子类访问
alert(w.work())
var p1 = new Person('嬴政')
// 类里面
alert(p1.run())
// 外部访问
alert(p1.name)
- 静态方法和属性---只能通过类名来访问
class Person{
public name: string
static sex: string = '男'
constructor(name: string) {
this.name = name
}
run(): string {
return `${this.name}在运动`
}
static print():void{
alert('print方法' + Person.sex) // 通过类名访问类的静态方法
}
}
class Web extends Person {
constructor(name: string) {
super(name)
}
run(): string {
return `${this.name}在运动--子类`
}
work(): string {
return `${this.name}在工作--子类`
}
}
var w = new Web('李斯')
var p1 = new Person('嬴政')
Person.print()
- 多态
父类定义一个方法不去实现,让继承它的子类去实现,每一个子类都有不同的表现
// 定义父类
class Animal{
name:string
constructor(name:string){
this.name = name
}
eat(){}
}
// 定义子类实现父类中的方法
class Dog extends Animal{
constructor(name:string){
super(name)
}
eat(){
return this.name + '吃粮食'
}
}
class Cat extends Animal {
constructor(name: string) {
super(name)
}
eat() {
return this.name + '吃老鼠'
}
}
var dog = new Dog('dog')
alert(dog.eat())
- 抽象类
抽象类,它是提供给其他类继承的基类,不能被直接实例化 用abstract关键字定义抽象类,抽象类中的额抽象方法不包含具体实现并且必须在派生类中实现
抽象方法只能放在抽象类中
抽象类和抽象方法用来定义的标准,Animal 这个类要求它的子类必须包含eat方法
abstract class Animal1 {
public name:string
constructor(name:string){
this.name = name
}
abstract eat():any;
}
class Dog1 extends Animal {
constructor(name:string){
super(name)
}
eat(){
alert(this.name + '吃粮食')
}
}
var d = new Dog1('aaaa')
d.eat()
函数
- 可选参数---可选参数必须配置到参数的最后
function getInfo(name: string, age?: number): string {
if (age)
return `${name}---${age}`
return `${name}-----年龄保密`
}
alert(getInfo('zhangsan'))
- 默认参数
function getInfo1(name: string, age: number = 20): string {
if (age)
return `${name}---${age}`
return `${name}---年龄保密`
}
alert(getInfo1('zhangsan', 23))
- 剩余参数
function sum(a:number, b: number, c: number, d: number): number {
return a + b +c + d
}
// alert(sum(1,2,3,4))
- 三点运算符
function sum1(...result:number[]): number {
var sum = 0
for(var i = 0;i<result.length;i++)
sum += result[i]
return sum
}
function sum2(a: number,...result: number[]): number {
var sum = a
for (var i = 0; i < result.length; i++)
sum += result[i]
return sum
}
console.log(sum1(1, 2, 3, 4)) // ...result:number[]
// console.log(sum1([1,2,3,4])) // result:number[]
console.log(sum2(1,3,5,2))
- 函数重载
// 函数重载
function getInfo2(name: string) : string;
function getInfo2(age: number): string;
function getInfo2(str: any): any{
if(typeof str === 'string')
return '我叫' + str
return '我的年龄是:' + str
}
alert(getInfo2('zs'))
泛型
泛型:软件工程中,我们不仅要创建一致的定义良好的API,同时也要考虑可重用性,组件不仅能够支持当前的数据类型, 同时也能支持未来的数据类型,这在创建大型系统时为你提供十分灵活的功能。 在像Java这样的语言中,可以使用泛型来创建可重用的组件,一个组件可以支持多种类型的数据,这样用户就可以以自己的数据类型来使用组件。 通俗理解泛型就是解决 类 接口 方法的复用性,以及对不特定数据类型的支持
定义一个函数返回值时string
function getData(val:string):string {
return val
}
如果想要定义一个函数返回值为string或者number
function getData(val:string):string {
return val
}
function getData2(val:number):number {
return val
}
如果使用any类型就意味放弃了类型检查
function getData2(val:any):any {
return val
}
- 函数的泛型 这样就出现了泛型
function getData<T>(val:T):T {
return val
}
console.log(getData<number>(123))
getData<string>('aaa')
function getData<T>(val:T):any {
return 'val'
}
console.log(getData<number>(123))
- 类的泛型
class MinClass<T> {
public list:T[] = []
add(num:T):void {
this.list.push(num)
}
min():T {
var minNum = this.list[0]
for (let i = 0; i < this.list.length; i++) {
if (minNum > this.list[i]) {
minNum = this.list[i]
}
}
return minNum
}
}
var m = new MinClass<string>() // 指定类的类型为string
m.add('z')
m.add('b')
m.add('c')
console.log(m.min())
- 案例
/*
功能:定义一个操作数据库 支持 Mysql Mongodb
要求:Mysql Mssql Mongodb功能一样 都有add delete get方法
注意:约束统一的规范以及代码重用
解决方案:需要约束规范所以要定义接口需要代码重用所以用到泛型
1、接口:在面向对象的编程中,接口是一种规范的定义,它定义了行为和动作的规范
2、泛型通俗理解:泛型就是解决 类 接口 方法的复用性
*/
// 定义一个泛型接口
interface DBI<T> {
add(info: T): boolean;
update(info: T, id: number): boolean;
delete(id: number): boolean;
get(id: number): any[];
}
// 定义一个操作MySQL数据库的类 注意:要实现泛型接口 这个类也应该是一个泛型类
class MysqlDb<T> implements DBI<T> {
add(info: T): boolean {
console.log(info)
return true
}
update(info: T, id: number): boolean {
throw new Error("Method not implemented.");
}
delete(id: number): boolean {
throw new Error("Method not implemented.");
}
get(id: number): any[] {
throw new Error("Method not implemented.");
}
}
// 定义一个操作Mssql数据库的类
class MsSqlDb<T> implements DBI<T> {
add(info: T): boolean {
throw new Error("Method not implemented.");
}
update(info: T, id: number): boolean {
throw new Error("Method not implemented.");
}
delete(id: number): boolean {
throw new Error("Method not implemented.");
}
get(id: number): any[] {
throw new Error("Method not implemented.");
}
}
// 操作用户表 定义一个User类和数据表做映射
class User {
username:string | undefined;
password:string | undefined;
}
var u = new User()
u.username = '张三'
u.password = '123456'
var oMysql = new MysqlDb<User>(); // 类作为参数来约束数据传入的类型
oMysql.add(u)
泛型工具
Partical
Partical<T>:将泛型中全部属性变为可选的。
type Partial<T> = {
[P in keyof T]?: T[P]
}
举个例子:
type Animal = {
name: string,
category: string,
age: number,
eat: () => string
}
使用 Partial 包裹一下:
type PartOfAnimal = Partical<Animal>;
const dog: PartOfAmimal = { name: 'aaa' }
Record
Record<K, T>:将 K 中所有的属性值转化为 T 类型,常用来声明一个普通的 object 对象。
type Record<K extends keyof any, T> = {
[key in K]: T
}
keyof any对应的类型为 number | string | symbol,也就是可以做对象键(索引index)的类型集合。
举个例子:
const obj: Record<string, string> = { 'name': 'mbg', 'tag': 'aaa' }
// 或
type T11 = Record<'a' | 'b' | 'c', Person>; // -> { a: Person; b: Person; c: Person; }
Pick
Pick<T, K>:将 T 类型中的 K 键列表提取出来,生成新的子键值对类型。
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
使用上面定义的 Animal ,看下 Pick 的使用。
const bird: Pick<Animal, 'name' | 'age'> = { name: 'bird', age: 1 }
Exclude
Exclude<T, U>:在 T 类型中,去除 T 类型和 U 类型的交集,返回剩余部分。
type Exclude<T, U> = T extends U ? never : T
举个例子:
type T1 = Exclude<'a' | 'b', 'a' | 'b' | 'c'> // 'c'
type T2 = Exclude<string | number | (() => void), Function> // string | number
Omit
Omit<T, K>:去除类型 T 中包含 K 的键值对。
type Omit = Pick<T, Exclude<keyof T, K>>
在定义中,第一步先从 T 的key中去掉与 K 重叠的key,接着使用 Pick 把 T 类型和剩余的key提取出来。 使用 Animal 的举例:
const OmitAnimal: Omit<Animal, 'name' | 'age'> = { category: 'lion', eat: () => { console.log('eat')} }
ReturnType
ReturnType<T>:获取 T 类型(函数)对应的返回值类型。
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any
稍微简化下:
type ReturnType<T extends func> = T extends () => infer R ? R : any
通过使用 infer 推断返回值类型,然后返回此类型。 举个例子:
function foo(x: string | number): string | number
type FooType = ReturnType<foo> // string | number
Required
Required<T>:将类型 T 中所有的属性变为必选项。
type Required<T> = {
[P in keyof T]-?: T[P]
}
-?可以理解为 TS 把?可选属性减去的意思。
除了这些以外,还有很多的内置的类型工具,TypeScript Handbook,同时 Github 上也有很多第三方类型辅助工具,如utility-types等。
模块
模块的概念(官方):
关于术语的一点说明:请务必注意一点,typescript1.5里术语已经发生了改变。内部模块现在称作命名空间。外部模块现在简称模块在其自身的作用域中执行,而不是在全局作用域里。
自己理解:
我们可以把一些公共的功能单独抽离成一个文件作为一个模块
模块里的变量 函数 类等默认是私有的,如果我们要在外部访问里面的数据(变量、函数、类)
我们需要通过export暴露模块里面的数据(变量 函数 类)
暴露后通过import引入模块就可以使用里面暴露的数据
export function getData():any[] {
console.log('获取数据库中的数据')
return [
{title: '123'},
{title: '245'},
]
}
import { getData } from './modules/db'
getData()
命名空间
命名空间:
在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内。同Java的包,Typescript的命名空间可以将代码包裹起来,只对外暴露需要在外部访问的对象。 命名空间内的对象通过export导出。
命名空间和模块的区别:
命名空间:内部模块,主要用于组织代码,避免命名冲突。
模块:ts的外部模块的简称,侧重代码的复用,一个模块里可能会有多个命名空间
在一个ts文件中使用命名空间,避免变量命名冲突
namespace A{
interface Animal{
name: string;
eat(): void;
}
export class Dog implements Animal{
name: string;
constructor(theName: string){
this.name = theName
}
eat() {
console.log(`${this.name}吃狗粮`)
}
}
export class Cat implements Animal {
name: string;
constructor(theName: string){
this.name = theName
}
eat() {
console.log(`${this.name}吃猫粮`)
}
}
}
let dog = new A.Dog('狗狗')
dog.eat()
在外部导出
export namespace B{
interface Animal{
name: string;
eat(): void;
}
export class Dog implements Animal{
name: string;
constructor(theName: string){
this.name = theName
}
eat() {
console.log(`${this.name}吃狗粮`)
}
}
export class Cat implements Animal {
name: string;
constructor(theName: string){
this.name = theName
}
eat() {
console.log(`${this.name}吃猫粮`)
}
}
}
import {B} from './modules/a';
let cat = new B.Cat('猫猫')
cat.eat()
装饰器
装饰器是一种特殊类型的声明,它能够被附加到类声明、属性、方法或形参上,可以修改类的行为。
通俗的讲装饰器就是一个方法,可以注入到类、方法、属性参数上来扩展类、属性、方法、参数的功能。
常见的装饰器有:类装饰器、属性装饰器、方法装饰器、参数装饰器
装饰器的写法:普通装饰器(无法传参)、装饰器工厂(可传参)
装饰器是过去几年中js最大的成就之一,已是Es7的标准特性之一
- 若要启用实验性的装饰器特性,你必须在命令行或tsconfig.json里启用experimentalDecorators编译器选项:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
- 普通装饰器(无法传参)
function sealed(target) {
// do something with "target" ...
}
- 装饰器工厂(可以传参)
function color(value: string) { // 这是一个装饰器工厂
return function (target) { // 这是装饰器
// do something with "target" and "value"...
}
}
类装饰器
类装饰器:类装饰器在类声明之前被声明(紧靠类声明)。装饰器应用于类构造函数,可以用来监视,修改或替换类定义。
传入一个参数
// 使用普通装饰器
function logClass(target: any) {
console.log(target)
target.prototype.apiUrl = '动态扩展的属性'
target.prototype.run = function() {
console.log('我是一个run方法')
}
}
@logClass
class HttpClient{
constructor() {
}
getData() {
}
}
var http:any = new HttpClient()
console.log(http.apiUrl)
http.run()
// 装饰器工厂
function logClass1(params: string) {
return function(target: any) {
console.log(target)
console.log(params)
target.prototype.apiUrl = '动态扩展的属性'
target.prototype.run = function() {
console.log('我是一个run方法')
}
}
}
@logClass1('hello')
class HttpClient1{
constructor() {
}
getData() {
}
}
var http1:any = new HttpClient1()
console.log(http1.apiUrl)
http1.run()
- 重载构造函数的例子
function logClass2(target: any){
console.log(target)
return class extends target{
apiUrl2: any = '我是修改过的apiUrl'
getData() {
}
}
}
@logClass2
class HttpClient2{
public apiUrl2: string | undefined
constructor() {
this.apiUrl2 = '我是构造函数的api'
}
getData() {
console.log(this.apiUrl2)
}
}
var http2 = new HttpClient2()
http2.getData()
属性装饰器
属性装饰器表达式会在运行时当作函数调用,传入下列2个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象。
2、成员的名字---装饰的属性名称
function logProperty(params: any) {
return function(target: any, attr: any) {
console.log(target) // 原型对象
console.log(attr)
target[attr] = params
}
}
class HttpClient3{
@logProperty('http://www.baidu.com')
public url: any | undefined
constructor() {
}
getData() {
console.log(this.url)
}
}
var http3:any = new HttpClient3()
http3.getData()
方法装饰器
它会被应用到方法的属性描述符上,可以用来监视、修改或者替换方法定义
方法装饰器会在运行时传入下列三个参数
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、成员的名字
3、成员的属性描述符
注意 如果代码输出目标版本小于ES5,属性描述符将会是undefined。
如果方法装饰器返回一个值,它会被用作方法的属性描述符。
注意 如果代码输出目标版本小于ES5返回值会被忽略。
function get(params: any){
return function(target:any,methodName:any,desc: any){
console.log(target);
console.log(methodName);
console.log(desc);
// 可以使用类装饰器来实现
target.apiUrl = params // 可以扩展类中的属性和方法和类装饰器一样
target.run = function(){
console.log('run')
}
// 修改getData方法,把装饰器方法里面传入的所有参数修改为string类型
// 1、保存当前的方法
var oMethod = desc.value
desc.value = function(...args: any[]) { // 扩展getData方法
args = args.map(item => String(item))
console.log(args)
oMethod.apply(this, args) //再调用类中原有的getData方法, 同时将扩展的结果传入
}
}
}
class HttpClient4{
// public url: any | undefined
constructor() {
}
@get('hello')
getData() {
console.log('getData')
}
}
var http4: any = new HttpClient4()
console.log(http4.apiUrl)
http4.run()
http4.getData(123, 'xxx')
方法参数装饰器
参数装饰器表达式会在运行时当作函数被调用,可以使用参数装饰器为类的原型添加一些元素数据,传入下列3个参数:
1、对于静态成员来说是类的构造函数,对于实例成员是类的原型对象
2、方法的名字
3、参数在函数参数列表中的索引
function logParams(params: any){
return function(target: any, methodName: any, paramsIndex: any) {
console.log(target);
console.log(methodName);
console.log(paramsIndex);
target.apiUrl = params
}
}
class HttpClient5{
public url: any | undefined
constructor() {
}
getData(@logParams('www.baidu.com') uuid: any) {
console.log(uuid)
}
}
var http5: any = new HttpClient5()
http5.getData(12345)
console.log(http5.apiUrl)
装饰器的执顺序:
属性装饰器
方法装饰器
方法参数装饰器
类装饰器
如果有多个从后往前执行
类型保护
Typescript能够在特定的区块中保证变量属于某种确定的类型
可以在此区块中放心地引用此类型的属性,或者调用用此类型的方法
enum Type { Strong, week }
class Java {
helloJava() {
console.log('hello java')
}
java: any
}
class Javascript {
helloJavascript() {
console.log('hello javascript')
}
javascript: any
}
let lang = type === Type.Strong ? new Java() : new Javascript()此时,lang的类型不确定,无法准确的调用对应的属性和方法。
解决方法
- 方式一:使用断言跳过类型检查
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new Javascript()
if((lang as Java).helloJava) {
(lang as Java).helloJava()
}else{
(lang as Javascript).helloJavascript()
}
return lang
}
- 方法二:使用instanceof
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new Javascript()
// instanceof
if(lang instanceof Java) {
lang.helloJava()
}
else{
lang.helloJavascript()
}
return lang
}
- 方法三:使用in
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new Javascript()
// in
if('java' in lang){
lang.helloJava()
}else{
lang.helloJavascript()
}
return lang
}
- 方法四:使用typeof
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new Javascript()
// typeof
if (typeof x === 'string') {
x.length
}else{
x.toFixed(2)
}
return lang
}
- 方法四:定义一个函数isJava
function isJava(lang: Java | Javascript): lang is Java {
return (lang as Java).helloJava !== undefined
}
function getLanguage(type: Type, x: string | number) {
let lang = type === Type.Strong ? new Java() : new Javascript()
//
if (isJava(lang)){
lang.helloJava()
}else{
lang.helloJavascript()
}
return lang
}
高级类型
交叉类型
interface DogInterface{
run(): void
}
interface CatInterface{
jump(): void
}
let pet: DogInterface & CatInterface ={
run(){},
jump(){}
}
联合类型
let a: number | string = 1
let b: 'a' | 'b' | 'c' = 'a'
class Dog implements DogInterface{
run(){}
eat(){}
}
class Cat implements CatInterface {
jump(){}
eat(){}
}
enum Master { Boy, Girl }
function getPet(master: Master) {
let pet = master === Master.Boy ? new Dog() : new Cat()
pet.eat() // 只能取到两个类共有的方法
return pet
}
- 可区分的联合模式---利用两种类型的共有属性,来创建不同类型的保护区块
interface Square {
kind: 'square';
size: number
}
interface Rectangle {
kind: 'rectangle';
width: number;
height: number
}
interface Circle {
kind: 'circle';
r: number
}
type Shape = Square | Rectangle | Circle
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size
case "rectangle":
return s.height * s.width
case 'circle':
return Math.PI * s.r ** 2
default:
return ((e: never) => new Error(e))(s)
}
}
console.log(area({kind: 'circle', r: 1}))
索引类型
实现对object类型的查询和访问
keyof T类型查询操作符
interface Obj {
a: number,
h: string
}
let key: keyof Obj
// key的类型为联合类型: 'a' | 'h'
T[k]索引访问操作符
let value: Obj['a']
// value 的类型为number
T extends U- 索引类型的使用
let obj1 = {
a: 1,
b: 2,
c: 3
}
// T 为 Obj 类型 K为 'a' | 'b' | 'c'
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][]{
return keys.map(key => obj[key])
}
console.log(getValues(obj1, ['a', 'b']))
console.log(getValues(obj1, ['c', 'd'])) // 此时,Obj中不存在 'd' 所以会报错
映射类型
TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。
// 定义一个接口
interface Obj1{
a: string,
b: number,
c: boolean
}
- 将Obj1中的属性映射为只读类型
type readonlyObj1 = Readonly<Obj1>
/*
type readonlyObj1 = {
readonly a: string;
readonly b: number;
readonly c: boolean;
}
*/
- 将Obj1中的属性映射为可选类型
type PartialObj1 = Partial<Obj1>
/*
type PartialObj1 = {
a?: string | undefined;
b?: number | undefined;
c?: boolean | undefined;
}
*/
- 映射为obj1的子集
type PickObj1 = Pick<Obj1, 'a' | 'b'>
/*
type PickObj1 = {
a: string;
b: number;
}
*/
以上几种映射,也被称作为同态:只作用于obj1属性,而不作用于其他属性
- Record 映射
Record并不需要输入类型来拷贝属性,所以它不属于同态。非同态类型本质上会创建新的属性,因此它们不会从它处拷贝属性修饰符。
type RecordObj1 = Record<'x'|'y', Obj1>
/*
type RecordObj1 = {
x: Obj1;
y: Obj1;
}
*/
条件类型
条件类型的实现方式:T extends U ? X : Y
type TypeName<T> =
T extends string ? 'string':
T extends number ? 'number':
T extends boolean ? 'boolean':
T extends undefined ? 'undefined':
T extends Function ? 'Function':
"object";
type T1 = TypeName<string> // type T1 = "string"
type T2 = TypeName<string[]> // type T1 = "string"
// (A | B) extends U ? X : Y ===> (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]> // type T3 = "string" | "object"
// 过滤掉T继承U 的类型
type Diff<T, U> = T extends U ? never : T
type T4 = Diff<'a' | 'b' | 'c', 'a' | 'e'> // type T4 = "b" | "c"
// Diff<'a', 'a' | 'e'> | Diff<'b', 'a' | 'e'> | Diff<'c', 'a' | 'e'>
// nerve | 'b' | 'c'
// 'b' | 'c'
// 去除不需要的类型undefined null
type NotNull<T> = Diff<T, undefined | null>
type T5 = NotNull<string | number | undefined | null> // Diff<'a' | 'b' | 'c', 'a' | 'e'>
TypeScript 2.8在lib.d.ts里增加了一些预定义的有条件类型:
Exclude<T, U>-- 从T中剔除可以赋值给U的类型。--- T4Extract<T, U>-- 提取T中可以赋值给U的类型。 --- T6NonNullable<T>-- 从T中剔除null和undefined。--- T5ReturnType<T>-- 获取函数返回值类型。--- T7InstanceType<T>-- 获取构造函数类型的实例类型。
type T6 = Extract<'a' | 'b' | 'c', 'a' | 'e'> // type T6 = "a"
// ReturnType<T> 获取一个函数返回值的类型
type T7 = ReturnType<() => string> // type T7 = string
声明合并
“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。 任何数量的声明都可被合并;不局限于两个声明。
接口合并
interface A {
x: number;
foo(bar: number): number // 5
foo(bar: 'a'): number // 2
}
interface A{
y: number
foo(bar: string): string // 3
foo(bar: number[]): number[] // 4
foo(bar: 'b'): number // 1
}
// 此时函数声明的列表顺序为接口之间就近原则 接口内部从上往下, 但是函数声明存在字面量会存在提升
let ab: A = {
x: 1,
y: 2,
foo(bar: any) {
return bar
}
}
命名空间合并
namespace Animals {
export class Zebra { }
}
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Dog { }
}
// 等同于:
namespace Animals {
export interface Legged { numberOfLegs: number; }
export class Zebra { }
export class Dog { }
}
命名空间---函数合并
此时,相当于给函数添加了一个静态属性
function Lib() {}
namespace Lib {
export let version = 1.0
}
console.log(Lib.version)
命名空间---类合并
此时,相当于给类添加了静态属性
class C {}
namespace C {
export let status = 1
}
console.log(C.status)
命名空间---枚举合并
扩展枚举的属性
enum Color {
Red,
Blue
}
namespace Color {
export function mix() {}
}
console.log(Color)
// { '0': 'Red', '1': 'Blue', Red: 0, Blue: 1, mix: [Function: mix] }
模块扩展
第三方类库扩展
import m from 'moment'
declare module 'moment' {
export function myFun(): void
}
m.myFun = () => {}
全局扩展
/*
* globalLib 全局的类库名
*/
declare global {
namespace globalLib {
function doAnything(): void
}
}
globalLib.doAnything = () => {}