TypeScript(二)
发布时间: 2022-03-01 17:47:46
作者: 王乐园 浏览次数:
427
TypeScript(二)
## TS中 type 和 interface的区别
type:类型别名 interface:接口
```tsx
//联合类型必须要type
//联合类型 和 交叉类型
// type StringNumber = string & number;//交叉类型
type StringNumber = string | number;//联合类型
let a: StringNumber;
a = "1";
a = 7;
//interface可以合并同名接口,type不可以
interface A{name:string}
interface A{age:number}
var x:A={name:'xx',age:20}
//interface可以继承interface,继承type,使用extends关键字,type也可继承type,也可继承interface,使用&
interface A{name:string}
interface B extends A{age:number}
type C={sex:string}
interface D extends C{name:string}
type E={name:string}&C
type F ={age:number}&A
```
关于 import type {...} from 'xxxxxxx'
- ts 编译为js 的时候不知道导入的是一个值还是类型,所已加个type 来告诉编译器这是一个类型引入
类型声明方式:
- 使用 :声明数据类型
- **类型别名:**使用 `type` 给类型起别名
- **联合类型:**使用 `|` 将类型规则隔开,表示该变量的值可为这些类型
- **交叉类型:** 使用 `&` 将类型规则隔开,表示该变量的值必须满足所有的数据类型(一般用于对象)
- **类型推论:**当声明变量并赋值没有指明类型时,会自动根据第一次赋值类型给上对应数据类型,后面再更改为其他数据类型时也会报错
- **数组类型**
- number[]=[1,2,3,4]
- **元组:若想按顺序规定数组内的元素类型时,使用元组** 但是不能超出长度
- ```ts
let user:[string,number] = ['string',1]
let user:[string,number] = ['string',1,'string'] //error
```
- 函数 `?` 可以表示函数的可选属性,但是非可选的参数不能排在可选参数后声明,当给函数参数设定默认值的时候也可表示可选
- ```ts
function add(x:number,y:number,z?:number){
if(typeof z==='number'){
return x+y+z
}else{
return x+y
}
}
//声明为any或void时可以无返回值,如果不声明则使用第一次声明函数时的返回值作为规则
function add():number{
}
```
- !和?
- !用在变量前表示取反用在赋值的内容后时使用null和undefined 类型可以赋值给其他类型并通过编译
- ?表示可选参数
- 当使用A对象属性`A.B`时,如果无法确定A是否为空,则需要用`A?.B`,表示当A有值的时候才去访问B属性,没有值的时候就不去访问,如果不使用`?`则会报错
- 但是?用法只能读操作而不能写操作,对一个可能为空的属性赋值是不会被编译通过的,此时还需用用到类型断言
- **类**
- ```ts
class Animal {
name:string;
constructor(namber:string){
this.name = name
}
say(){
console.log(`my name is ${this.name}`)
}
}
```
- 继承 和其他语言的继承差不多,能使用父类方法,也能重写父类的属性方法
- 重写方法:注意的是如果重写了父类方法,需要使用父类方法时,需要在构造函数中调用super方法,并在后续中用super去执行父类方法
- ```ts
class Cat extends Animal {
eat(){
console.log(`I am eating`)
}
}
const susie = new Cat(‘susie’)
susie.asy()
susie.eat()
```
- #### 公开与私有
- **public:**类成员可以被父子实例和子类访问到
- **private:**类成员不能被父子实例与子类访问到
- **protected:**类成员可以被子类访问到但是不能被父子实例访问到
- 静态
- **static** 静态方法内只能使用类的静态成员,不能使用实例成员
- ```ts
class Cat {
age:number=12;
static food = 'fish'
run(){
this.age=13
}
static asy(){
this.age = 14;//error
this.food = 'can'
}
}
```
- ### 接口
- 对对象的形状(shape)进行描述
- 对类(class)进行抽象
- 鸭子类型(模糊推断判断,更关注对象能否使用而非对象的类型本身)
- 接口内的方法不能实现,即只能拥有函数声明而不能拥有函数体
- 一个类只能继承一个类,但可接收多个接口并实现
- #### 规定对象
- 由于接口实例必须实现接口的全部内容,因此可以方便定义对象属性类型
- ```typescript
interface Person {
name: string;
age: number;
}
let mary: Person = {
name: 'mary',
age: 12
}
```
- 实现接口属性不能多也不能少,如果有些属性是非必须实现的,应该使用 `?` 表示该属性为可选属性
- ```ts
interface Person {
name: string;
age?: number;
}
let mary: Person = {
name: 'mary',
}
```
- 只读属性:用 `readonly` 声明,只能在实现接口的时候赋值,后面更改赋值会报错
- ```ts
interface Person {
readonly name: string;
age?: number;
}
let mary: Person = {
name: 'mary',
}
mary.name = 'jack'; // 报错
```
- #### 配合类
- 方便灵活,规定约束了具体哪些类中必须实现的哪些功能
- ```ts
interface Radio {
switchRadio():void;
}
interface Battery {
checkBatteryStatus();
}
class Car implements Radio {
switchRadio() {
console.log('实现Car的switchRadio');
}
}
class Cellphone implements Radio, Battery {
switchRadio() {
console.log('实现Cellphone的switchRadio');
}
checkBatteryStatus() {
console.log('实现Cellphone的checkBatteryStatus');
}
}
```
- ### 枚举
- 枚举本质上是用一串有顺序的 int 数值去依次表示枚举里的内容
- ```ts
enum Direction {
Up,
Down,
Left,
Right
}
console.log(Direction.Up) // 0
console.log(Direction.Down) // 1
console.log(Direction.Left) // 2
console.log(Direction.Right) // 3
console.log(Direction[0]) // Up
console.log(Direction[1]) // Down
console.log(Direction[2]) // Left
console.log(Direction[3]) // Right
```
- 当给具体某个属性赋值时,后面的属性会依据这个赋值数字往下递增赋值
- ```ts
enum Direction {
Up,
Down = 6,
Left,
Right
}
console.log(Direction.Up) // 0
console.log(Direction.Down) // 6
console.log(Direction.Left) // 7
console.log(Direction.Right) // 8
```
- **枚举实现**
```ts
enum Direction {
Up,
Down,
Left,
Right
}
// 等同于下面的js代码
var Direction;
(function (Direction) {
// 将Direction对象新增一个属性up,并赋值0,然后再新增一个属性0 并赋值up
Direction[Direction["Up"] = 0] = "Up";
Direction[Direction["Down"] = 1] = "Down";
Direction[Direction["Left"] = 2] = "Left";
Direction[Direction["Right"] = 3] = "Right";
})(Direction || (Direction = {}));
/*
最终Direction对象的结果:
{
'0': 'Up',
'1': 'Down',
'2': 'Left',
'3': 'Right',
Up: 0,
Down: 1,
Left: 2,
Right: 3
}
*/
```
- #### 常量枚举
- 常量枚举仅可在属性、索引访问表达式、导入声明的右侧、导出分配或类型查询中使用
- 可提升枚举性能
- ```ts
enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
var value = 'Up';
if(value === Direction.Up) {
console.log('do Up')
}
// 转换
var Direction;
(function (Direction) {
Direction["Up"] = "Up";
Direction["Down"] = "Down";
Direction["Left"] = "Left";
Direction["Right"] = "Right";
})(Direction || (Direction = {}));
var value = 'Up';
if (value === Direction.Up) {
console.log('do Up');
}
```
- ```ts
const enum Direction {
Up = 'Up',
Down = 'Down',
Left = 'Left',
Right = 'Right'
}
var value = 'Up';
if(value === Direction.Up) {
console.log('do Up')
}
// 转换
var value = 'Up';
if (value === "Up" /* Up */) {
console.log('do Up');
}
```
- ### 泛型
- 在使用函数或类时,声明时不指定属性的类型,使用的时候再声明属性类型
- ```ts
function echo<T>(arg: T): T { // 变量名是任意的,但是普遍使用 T 来表示泛型
return arg;
}
```
- 根据在实际调用的时候传入的值来判断返回类型
```TS
const result = echo(true); // 此时该函数的返回值为 boolean 类型
const result = echo('str'); // 此时该函数的返回值为 String 类型
```
如果不使用泛型,则想让一个函数能匹配任意类型的数据,则会返回 any 类型
如果不使用泛型,则想让一个函数能匹配任意类型的数据,则会返回 any 类型
```TS
function echo(arg) {
return arg;
}
const result1 = echo(true); // 函数返回值类型为 any
const result2 = echo('str'); // 函数返回值类型为 any
```
这样的坏处是对返回值没有数据类型的约束,可以随便更改其类型
如果使用泛型,由于已经限制了类型,因此更改返回数据的类型会报错
```TS
function echo(arg) {
return arg;
}
var result = echo(true);
result = 'srt'; // 不会报错
```
- #### 泛型元组
```TS
function swap<T, U>(tuple: [T, U]): [U, T] {
return [tuple[1], tuple[0]]
}
const result = swap([true, 'srt']) // result 的类型为 [String, Boolean]
```
- #### 约束泛型
- 限制一部分传入泛型的类型
```TS
function echoWithArr<T>(arg: T): T {
console.log(arg.length); // 由于不是所有的类型都具有 length 属性,因此那么写会报错
return arg;
}
```
- 简易改写:
即限制了传入即返回值必须为一个数组类型,数组里的元素是泛型可任意
- ```ts
function echoWithArr<T>(arg: T[]): T[] {
console.log(arg.length)
return arg
}
```
- 但是这样写很不方便,因此使用约束泛型的写法,由于继承接口,接口内的成员必须被实现,因此实际上限制了传入参数 arg 必须要拥有 length 且数据类型为 number 才可被传入
这是接口的鸭子类型特性的一种应用
- ```ts
interface IwithLength {
length: number
}
function echoWithLength<T extends IwithLength>(arg: T): T {
console.log(arg.length)
return arg
}
```
- #### 类、接口配合泛型
- 类
- ```ts
class Queue<T> {
private data = [];
push(item: T) {
return this.data.push(item);
}
pop(): T {
return this.data.pop()
}
}
// 在实例化的时候声明类的泛型所指数据类型
const queue1 = new Queue<number>()
const queue2 = new Queue<boolean>()
queue1.push(3)
queue1.push(true) // 报错
```
- 接口
- ```ts
interface keyPair<T, U> {
key: T;
value: U;
}
let kp1: keyPair<number, string> = {
key: 123,
value: 'str'
}
let kp2: keyPair<boolean, number> = {
key: true,
value: 123
}
```
- 规范函数
```ts
interface IPlus<T> {
(a: T, b: T): T
}
function plus(a: number, b: number): number {
return a + b;
}
function connect(a: string, b: string): string {
return a + b;
}
const funPlus: IPlus<number> = plus;
const funConnect: IPlus<string> = connect;
// 如果不用泛型接口,想要规范的给变量赋予函数时,应该这样写,使用泛型接口可以省了很多代码
const funPlus: (a: string, b: string) => string = plus;
```
- #### 枚举技巧
- **强制定义类型**
比如函数:一般而言,会根据实际在使用中传递的数据编译器会推断出泛型的类型,但是有些情况则不能推测出来导致报错
如一个由参数来判断类型的函数,当参数设为可选且在调用时不传递参数时,则编译器会判断为这个类型是未知的(并不是判断为any)
- ```ts
function test<T>(parms?:T):T {
return parms
}
function demo(parms:number) { }
const num = test() // num的类型为unknown
demo(num) // 报错,unknown类型不匹配number类型
```
- 这个时候,可以在调用函数的时候用 `<>` 声明此次调用函数泛型T所指的具体属性
- ```ts
function test<T>(parms?:T):T {
return parms
}
function demo(parms:number) { }
const num = test<number>()
demo(num) // 不会报错,虽然值为undefined,但是是属于number类型的undefined值
```
- **默认类型**
当编译器无法根据条件推测出泛型的具体类型时,会指定为 `unknown`,如果不想要这种情况,可以手动地在泛型定义时指定一个默认类型,这样编译器如果无法判断类型时,不会为 `unknow` 而会是指定的默认类型
- ```ts
function test<T = number>(parms?:T):T {
return parms
}
function demo(parms:number) { }
const num = test()
demo(num)
```
- ### 操作类型
- #### 类型别名
- 使用 `type` 声明要注明的类型别名
```ts
type PlusType = (x: number, y: number) => number;
function sum(x: number, y: number): number {
return x + y;
}
const sum_: PlusType = sum;
// 等同于
const sum_: (x: number, y: number) => number = sum;
```
- 联合别名
```ts
type NameResolver = () => string;
type NameOrResolver = string | NameResolver
// 判断传入的类型是一个返回值为string的方法还是一个字符串
function getName(n: NameOrResolver): string {
if(typeof n === 'string') {
return n
}else {
return n()
}
}
```
- #### 类型断言
- 当编译器不清楚数据的类型时,会默认采取类型推论的策略给对应代码添加上数据类型,如果自己本身比编译器更清楚代码应该拥有的数据类型,则使用类型断言去覆盖编译器的类型推论,即编译器不清楚数据类型时,会直接采用自定义的类型断言,而非去推论类型
- 使用类型断言,关键字 as 且数据类型是类(大写开头)
- 断言的快捷写法:在变量前加上 `<类型>` ,注意这里的类型又是小写了
- 类型断言并非类型转换,它不能推断为非法的类型
- ### 命名空间
```
内部模块称作命名空间,外部模块则简称为模块
```
- #### 模块和命名空间
**命名空间**
- 在代码量较大的情况下,为了避免各种变量命名相冲突,可将相似功能的函数、类、接口等放置到命名空间内
- 不同文件下同一个命名空间内的变量不能同名,但是这不代表可以直接使用另外一个文件的代码
- 本质上是闭包,用来隔离作用域
- ```ts
// namespace中可以定义任意多的变量,这些变量只能在shape下可见,如果要在全局内可见的话就要使用export关键字,将其导出
namespace Shape {
const pi = Math.PI
export function cricle(r: number) {
return pi * r ** 2
}
}
```
- 一个文件下的内容,如果想要另外一个文件能访问到这部分代码,应该使用 `export` 导出到外部空间(无论是否同一个命名空间)
- 如果AB文件同属于一个命名空间,当 B 想要引用 A 方数据的时候,应该使用 `///`
- ```ts
// A.ts
namespace Demo {
export interface A {
foo()
}
}
```
- ```ts
// B.ts
///<reference path="A.ts"/>
namespace Demo {
class B implements A {
foo() {
console.log('实现A接口的foo方法')
}
}
const b = new B()
b.foo() // 实现A接口的foo方法
}
```
- 如果AB文件不属于同一个命名空间,不仅要使用 `///` 引入文件,还要注明在导出的对象前加上命名空间去使用
- ```ts
// A.ts
namespace Demo_A {
export interface A {
foo()
}
}
```
- ```ts
// B.ts
///<reference path="A.ts"/>
namespace Demo_B {
class B implements Demo_A.A {
foo() {
console.log('实现A接口的foo方法')
}
}
const b = new B()
b.foo() // 实现A接口的foo方法
}
```
- **模块**
- 模块在其自身的作用域里执行,而不是在全局作用域里,意味着定义在一个模块里的变量,函数,类等只有引入模块以后才能使用
- 只要在文件中使用了 import 和 export 语法,就被编译器视为一个模块
- 一个完整功能的封装,对外提供的是一个具有完整功能的功能包
- 一个模块里可能会有多个命名空间
- 导出声明:任何声明都能通过添加 export 关键字来导出(如:变量,函数,类,类型别名,或接口)
- ```ts
为了支持CommonJS和AMD的exports, TypeScript提供了export =语法
export = 语法定义一个模块的导出对象(这里对象是指:类,接口,命名空间,函数或枚举)
若使用export = 导出一个模块,则必须使用TypeScript的特定语法import module = require("module")来导入此模块
-------------------------------------------------------------------
class Demo {
...
}
export = Demo;
---------------------------------------------------------------------
import demo = require("./Demo");
```
- 模块的一个特点是不同的模块永远也不会在相同的作用域内使用相同的名字
- 不应该对模块使用命名空间,使用命名空间是为了提供逻辑分组和避免命名冲突,模块文件本身已经是一个逻辑分组,并且它的名字是由导入这个模块的代码指定,所以没有必要为导出的对象增加额外的模块层
上一篇:(转载)追忆过往青春
下一篇:(转载)时光悠悠 岁月无言