typescript 入门学习

401 阅读11分钟

最近准备重构项目想使用ts,所以就了解下ts最基本的一些知识,ts官网地址是:www.tslang.cn/docs/handbo… 下边所有代码均来自ts官网,

1.基础类型

大约15种,分别是:布尔,数字,数组,元组,枚举,any, viod ,null, undefined, never,Object, 类型断言,关键字let

// 1布尔
let isDone: boolean = false

// 2数字
let isNumber: number = 6

// 3字符
let isName: string = 'ncy'

/**
 * 数组
 * 第一种表示方法
 * */ 
 
let list1: number[] = [1, 2, 3, 4]
// 第二种表示方法 Array<元素类型>
let list2: Array<number> = [1, 2, 3, 4]

// 元组 表示一个已知元素数量和类型的数组,各元素的类型不必相同
let x: [number, string] = [1, '2']


// 枚举
enum Color { Red = 1, Green, Blue }
let c: Color = Color.Green

// Any
let notSure: any = 4
notSure = "1111"
notSure = false

let list: any[] = [1, true, "free"];

list[1] = 100;


// Void 它表示没有任何类型  当一个函数没有返回值时,你通常会见到其返回值类型是 void
function warnUser(): void {
  console.log("这是个测试")
}
// 声明一个void类型的变量没有什么大用,因为你只能为它赋予undefined和null:
let unusable: void = undefined;

// Null 和 Undefined TypeScript里,undefined和null两者各自有自己的类型分别叫做undefined和null

// Never 表示的是那些永不存在的值的类型。
function error(message: string): never {
  throw new Error(message)
}
function fail() {
  return error('Something failed')
}

// Object 表示非原始类型
declare function create(o: object | null): void;
create({ prop: 0 });

/**
 * 类型断言
 * 其一是“尖括号”语法:
 * */ 
let someValue: any = "this is a string";
let strLength: number = (<string>someValue).length;
// as语法
let someValue1: any = '1'
let strLength1: number = (someValue as string).length

2.变量声明

主要是var let const 以及解构,前三个经常用,说下解构

// var let const 解构
let input = [1, 2]
let [first, second] = input

let [first1, ...rest] = [1, 2, 3]

// 默认值
function a(b:{c:string,d?:number}) {
  let {c, d = 1 } = b
}

3.接口

接口的属性分为可选属性和只读属性,可选属性有友好的提示,而且在定义数据类型的时候也更加灵活,建议使用。只读属性比较时候定义一些配置。

函数类型,可索引类型,类类型、继承接口、混合类型、接口继承类等6种

继承接口类似于函数的继承

/**
 * 可选属性
 * 
 * 可选属性的好处之一是可以对可能存在的属性进行预定义,好处之二是可以捕获引用了不存在的属性时的错误。 
 * 如果属性拼错,就会得到一个错误提示
 * */ 
interface SquareConfig {
  color?:string;
  width?:number;
  [propName: string]: any
}

function createSquare(config: SquareConfig): {color:string, area: number} {
  let newSquare = {color: 'white', area: 100} 
  if(config.color) {
    newSquare.color = config.color
  }
  if(config.width) {
    newSquare.area = config.width * config.width
  }
  return newSquare
}
let squareOptions = {colour: 'red', width: 100}
// let mySquare = createSquare({color: 'black', opacity: 0.5, width: 100} as SquareConfig)
let mySquare = createSquare(squareOptions)

/**
 * 只读属性
 * 
 * */ 
 interface Point {
  readonly x: number;
  readonly y: number;
}
let p1: Point = {x: 10, y: 20}
p1.x = 5 // 无法分配到 "x" ,因为它是只读属性。ts(2540)

let a: number[] = [1, 2, 3, 4]
let ro: ReadonlyArray<number> = a
ro[0] = 12
ro.push(5)
ro.length = 100
a = ro
// 可以用类型断言重写:
a = ro as number[]

// 函数类型
interface SearchFunc {
  (source: string,subString: string): boolean
}
let mySearch: SearchFunc;
mySearch = function(src: string,sub: string) {
  let result = src.search(sub)
  return result > -1
}

/**
 * 可索引的类型
 * */ 
interface StringArray {
  [index: number]: string
}

let myArray: StringArray
myArray = ["Bob", "Fred", ""]
let myStr:string = myArray[0]

class Animal {
  name: string
}
class Dog extends Animal {
  breed: string
}
interface NotOkay {
  [x: string]: Dog,
}


// 类类型
interface ClockInterface {
  currentTime: Date;
  setTime(d: Date);
}
class Clock implements ClockInterface {
  currentTime: Date;
  setTime(d: Date) {
    this.currentTime = d;
  }
  constructor(h:number, m:number) {}
}

// 类静态部分与实例部分的区别
interface ClockConstructor {
  new (hour: number, minute: number): ClockInterface
}
interface ClockInterface {
  tick()
}
function createClock(ctor: ClockConstructor, hour: number, minute: number):ClockInterface {
  return new ctor(hour, minute)
}
class DigitalClock implements ClockInterface {
  constructor(h:number, m: number){}
  tick(){
    console.log('beep beep')
  }
}
class AnalogClock implements ClockInterface {
  constructor(h:number, m: number){}
  tick(){
    console.log('beep beep')
  }
}
let digital = createClock(DigitalClock, 12, 17);
let analog = createClock(AnalogClock, 7, 32);


// 继承接口
interface Shape {
  color: string;
}
interface PenStroke {
  penWidth: number;
}
interface Square extends Shape, PenStroke {
  sideLength: number;
}
let square = <Square> {}
square.color = 'black'
square.sideLength = 100
square.penWidth = 50

// 混合类型
interface Counter {
  (start: number): string;
  interval: number;
  reset(): void;
}

function getCounter(): Counter {
  let counter = <Counter>function (start: number) {}
  counter.interval = 123
  counter.reset = function(){}
  return counter
}

let c = getCounter()
c(10)
c.reset()
c.interval = 5.0

// 接口继承类
class Control {
  private state: any;
}

interface SelectableControl extends Control {
  select(): void;
}

class Button extends Control implements SelectableControl {
  select() {}
}

class TextBox extends Control {
  select() {}
}

class Image implements SelectableControl {
  select() {}
}

class Location {}

4.类

主要内容有继承,公共,私有与受保护的修饰符,readonly修饰符,存取器,静态属性,抽象类。

**继承主要是超类(基类),子类(派生类)的继承。如果超类中有函数,那么子类需要有super(),它会执行基类的构造函数。 **

// 继承
class Animal {
  move(distanceInMeters: number = 0) {
      console.log(`Animal moved ${distanceInMeters}m.`);
  }
}

class Dog extends Animal {
  bark() {
      console.log('Woof! Woof!');
  }
}

const dog = new Dog();
dog.bark();
dog.move(10);
dog.bark();

class Animal {
  name: string;
  constructor(theName: string) { this.name = theName; }
  move(distanceInMeters: number = 0) {
    console.log(`${this.name} moved ${distanceInMeters}m`)
  }
}
class Snake extends Animal {
  constructor(name: string) {super(name); }
  move(distanceInMeters: number = 5) {
    console.log('Slithering...')
    super.move(distanceInMeters)
  }
}

class Horse extends Animal {
  constructor(name: string) {super(name)}
  move(distanceInMeters = 45) {
    console.log('Galloping');
    super.move(distanceInMeters)
  }
}

let sam = new Snake('Sammy the Python')
let tom: Animal = new Horse('Tommy the Palomino')
sam.move();
tom.move(34);

公共,私有与受保护的修饰符

属性一般都默认为public,如果设置为private,那么只能在自己声明的类中访问。同时在比较的时候,,并不在乎它们从何处而来,如果所有成员的类型都是兼容的,我们就认为它们的类型是兼容的。

protected修饰符与 private修饰符的行为很相似,但有一点不同, protected成员在派生类中仍然可以访问

class Animal {
  private name: string;
  constructor(theName: string) { this.name = theName; }
}

class Rhino extends Animal {
  constructor() {super('Rhino')}
}

class Employee {
  private name: string
  constructor(theName: string) {this.name = theName}
}

let animal = new Animal('Goat')
let rhino = new Rhino()
let employee = new Employee('Bob')

animal = rhino
animal = employee // 错误: Animal 与 Employee 不兼容.

class Person {
  protected name: string;
  constructor(name: string) {this.name = name}
}

class Employee extends Person {
  private department: string;
  constructor(name: string, department: string) {
    super(name)
    this.department = department
  }
  public getElevatorPitch() {
    return `Hello, my name is ${this.name} and I work in ${this.department}.`
  }
  
}

let howard = new Employee('howard', "Sales")
console.log(howard.getElevatorPitch())
console.log(howard.name) // 属性“name”受保护,只能在类“Person”及其子类中访问。ts(2445)



class Person {
  protected name: string;
  protected constructor(theName: string) { this.name = theName; }
}

// Employee 能够继承 Person
class Employee extends Person {
  private department: string;

  constructor(name: string, department: string) {
      super(name);
      this.department = department;
  }

  public getElevatorPitch() {
      return `Hello, my name is ${this.name} and I work in ${this.department}.`;
  }
}

let howard = new Employee("Howard", "Sales");
let john = new Person("John"); // 错误: 'Person' 的构造函数是被保护的.

readonly修饰符,只可读

class Octopus {
  readonly name: string;
  readonly numberOfLegs: number = 8;
  constructor (theName: string) {
    console.log(theName)
      this.name = theName;
  }
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // 错误! name 是只读的.

存取器

class Employee {
  fullName: string;
}

let employee = new Employee();
employee.fullName = "Bob Smith";
if(employee.fullName) {
  console.log(employee.fullName)
}

let passcode = 'secret passcode'
class Employee {
  private _fullName: string;
  get fullName(): string {
    return this._fullName
  }

  set fullName(newName: string) {
    if (passcode && passcode === 'secret passcode') {
      this._fullName = newName;
    } else {
      console.log('Error: Unauthorized update of employee!')
    }
  }
}

let employee = new Employee()
employee.fullName = 'Bob Smith'
if (employee.fullName) {
  alert(employee.fullName)
}

静态属性,注意下静态属性的获取Grid.origin.x

class Grid {
  static origin = {x: 0, y: 0};
  calculateDistanceFromOrigin(point: {x: number; y: number;}) {
      let xDist = (point.x - Grid.origin.x);
      let yDist = (point.y - Grid.origin.y);
      return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
  }
  constructor (public scale: number) { }
}

let grid1 = new Grid(1.0);  // 1x scale
let grid2 = new Grid(5.0);  // 5x scale

console.log(grid1.calculateDistanceFromOrigin({x: 10, y: 10}));
console.log(grid2.calculateDistanceFromOrigin({x: 10, y: 10}));

抽象类

抽象类做为其它派生类的基类使用。 它们一般不会直接被实例化。 不同于接口,抽象类可以包含成员的实现细节。 abstract关键字是用于定义抽象类和在抽象类内部定义抽象方法。

abstract class Animal {
  abstract makeSound(): void;
  move(): void {
      console.log('roaming the earch...');
  }
}

5.函数

主要是函数的参数定义,剩余参数和this与箭头函数

完整的函数类型,在函数和返回值类型之前使用=>符号,如果没有返回值,需要指定void

let myAdd: (baseValue: number, increment: number) => number = function(x: number, y: number): number { return x + y; };

推断类型,在一边指定类型另一边没有指定类型的情况下,ts自动识别出类型来

// The parameters `x` and `y` have the type number
let myAdd: (baseValue: number, increment: number) => number =
    function(x, y) { return x + y; };

可选参数和默认参数,可选参数可以让我们在传值时,非必要值不传,可选参数需要在必选参数后边,如果没有指定类型,那么就和之前的类型一致。默认参数和可选类型有相似的效果。

function buildName(firstName: string, lastName?: string) {
    if (lastName)
        return firstName + " " + lastName;
    else
        return firstName;
}

let result1 = buildName("Bob");  // works correctly now
let result2 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result3 = buildName("Bob", "Adams");  // ah, just right

默认参数

function buildName(firstName: string, lastName = "Smith") {
    return firstName + " " + lastName;
}

let result1 = buildName("Bob");                  // works correctly now, returns "Bob Smith"
let result2 = buildName("Bob", undefined);       // still works, also returns "Bob Smith"
let result3 = buildName("Bob", "Adams", "Sr.");  // error, too many parameters
let result4 = buildName("Bob", "Adams");         // ah, just right

剩余参数 剩余参数,就是可以在接参的过程中只接部分参数,剩余参数在另外一个数组里

function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
// 或者
function buildName(firstName: string, ...restOfName: string[]) {
  return firstName + " " + restOfName.join(" ");
}

let buildNameFun: (fname: string, ...rest: string[]) => string = buildName;

this,一般需要我们给一个显示的参数

function f(this: void) {
    // make sure `this` is unusable in this standalone function
}

函数的重载

let suits = ["hearts", "spades", "clubs", "diamonds"];

function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    }
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
    }
}

let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);

let pickedCard2 = pickCard(15);
alert("card: " + pickedCard2.card + " of " + pickedCard2.suit);

6.泛型

泛型的概念:它的作用就是可复用性的提升,一个组件支持多种类型。实质的价值在我看来就是省事!!!

1.基础

function identity<T>(arg: T): T {
    return arg;
}

有两种使用方式

第一种:传入所有的参数,包含类型参数:

let output = identity<string>("myString")

第二种: 利用了类型推论 -- 即编译器会根据传入的参数自动地帮助我们确定T的类型

let output = identity("myString");

2.使用泛型变量

function loggingIdentity<T>(arg:T): T {
  console.log(arg.length) // 类型“T”上不存在属性“length”。
  return arg
}
function loggingIdentity<T>(arg:T[]): T[] {
  console.log(arg.length) // 类型“T”上不存在属性“length”。
  return arg
}

对比以上代码,区别在于加了[],它是个元素类型是T的数组,并返回类型是T的数组,我们把泛型变量T当做类型的一部分使用,增加了灵活性。

例如下:

function loggingIdentity<T>(arg: Array<T>): Array<T> {
    console.log(arg.length);  // Array has a .length, so no more error
    return arg;
}

3.泛型类型

我的理解是这个就是给函数做了规范,比如你的传值和返回值等

function identity<T>(arg:T):T { return arg }
let myIdentity: <T>(arg:T) => T = identity

例如上边写的,函数的类型为T,参数类型是T,返回为T等,赋值给myIdentity,它的类型为T,可以给它传的参数为T,返回的参数也是T。并且这个泛型的参数名改为什么都行,A,B,C,D都成,只需要数量和使用方式上一致。如下

function identity<T>(arg:T): T { return arg }
let myIdentity:<U>(arg:U) => U = identity

还可以使用字面量定义泛型函数

function identity<T>(arg:T): T {return arg}
let myIdentity:{<U>(arg:U): U } = identity

字面量的方式可以让我们来定义泛型的接口,如下

interface GemericIdentityFn {
  <T>(arg:T): T
}

function identity<T>(arg:T): T {return arg}
let myIdentity:GemericIdentityFn = identity

感觉这种有点厉害了,直接定义了整个函数的接口。还有这种

interface GemericIdentityFn<T> {
  (arg:T): T
}

function identity<T>(arg:T): T {return arg}
let myIdentity:GemericIdentityFn<number> = identity

整体区别不大,但是第二种的好处可以指定泛型类型。

4.泛型类

class GenericNumber<T> {
  zeroValue: T;
  add:(x:T, y:T) => T
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = (x, y) => {return x + y}

和泛型接口相似度还挺高,我们定义了类的泛型,在new 对象的时候,可以执行类的泛型,当前为number,那么我们修改指定类型改为string。如下

class GenericNumber<T> {
  zeroValue: T;
  add:(x:T, y:T) => T
}
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = ''
stringNumeric.add = (x, y) => {return x + y}
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"))

5.泛型的约束

interface Lengthwise {
  length: number;
}
function loggingIdentity<T extends Lengthwise>(arg:T): T { 
  console.log(arg.length)
  return arg
}

T继承Lengthwise,所有我们的参数必须包含length且类型为number

后边还有两个例子,我不太懂,有懂的可以指点下,谢谢!!!

在泛型约束中使用类型参数

function getProperty(obj: T, key: K) {
    return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a")
getProperty(x, "m")

在泛型里使用类类型

在TypeScript使用泛型创建工厂函数时,需要引用构造函数的类类型

function create<T>(c: {new(): T; }): T {
    return new c();
}

使用原型属性推断并约束构造函数与类实例的关系。

class BeeKeeper {
    hasMask: boolean;
}

class ZooKeeper {
    nametag: string;
}

class Animal {
    numLegs: number;
}

class Bee extends Animal {
    keeper: BeeKeeper;
}

class Lion extends Animal {
    keeper: ZooKeeper;
}

function createInstance<A extends Animal>(c: new () => A): A {
    return new c();
}

createInstance(Lion).keeper.nametag;  // typechecks!
createInstance(Bee).keeper.hasMask;   // typechecks!

7.枚举

使用枚举我们可以定义一些带名字的常量。 使用枚举可以清晰地表达意图或创建一组有区别的用例。 TypeScript支持数字的和基于字符串的枚举。

1.数字枚举

enum Direction {
    Up = 1,
    Down,
    Left,
    Right
}

数字枚举的值有自增的行为,但每个值是不同的,当初始定义第一个值后,其余的自增。

2.字符串枚举

没有自增行为,但是可读的值

enum Direction {
    Up = "UP",
    Down = "DOWN",
    Left = "LEFT",
    Right = "RIGHT",
}

3.计算的和常量成员

枚举成员如果是枚举的第一个成员且没有初始化器,会被赋予值0,如果它是数字常量,成员还是依次递增。

enum E1 { X, Y, Z }

enum E2 {
    A = 1, B, C
}

enum FileAccess {
    // constant members
    None,
    Read    = 1 << 1,
    Write   = 1 << 2,
    ReadWrite  = Read | Write,
    // computed member
    G = "123".length
}

4.联合枚举与枚举成员的类型

非计算的常量枚举成员的子集:字面量枚举成员

字面量枚举成员是指不带有初始值的常量枚举成员,或者是值被初始化为

  1. 任何字符串字面量(例如: "foo", "bar", "baz")
  2. 任何数字字面量(例如: 1, 100)
  3. 应用了一元 -符号的数字字面量(例如: -1, -100)

而且可以通过联合枚举捕获到一些比较错误

enum ShapeKind {
    Circle,
    Square,
}

interface Circle {
    kind: ShapeKind.Circle;
    radius: number;
}

interface Square {
    kind: ShapeKind.Square;
    sideLength: number;
}

let c: Circle = {
    kind: ShapeKind.Square,
    //    ~~~~~~~~~~~~~~~~ Error!
    radius: 100,
}

enum E {
    Foo,
    Bar,
}

function f(x: E) {
    if (x !== E.Foo || x !== E.Bar) {
        //             ~~~~~~~~~~~
        // Error! Operator '!==' cannot be applied to types 'E.Foo' and 'E.Bar'.
    }
}

5.运行时的枚举

会默认number

enum E {
    X, Y, Z
}
function f(obj: { X: number }) {
    return obj.X;
}

// Works, since 'E' has a property named 'X' which is a number.
f(E);