3

type, interface, 泛型(泛化的类型)

tsc

$ tsc --outDir dirName
$ tsc --outDir dirName compileName # 指定输出输入位置
$ tsc --init # tsconfig.json
$ tsc -w # 动态监视
$ ts-node file # 直接运行ts文件

$ npm bin -g # 查看-g命令目录
$ tsc file -d # 生成.d.ts文件 
tsconfig.json

tsconfig.json是编译上下文,其中的compilerOptions字段提供编译选项,可以通过tsc --init生成

Typescript作用:

  • 类型检查(静态类型,强类型)
  • 更好的避免bug
  • 自带文档特性
  • IDE或编辑器良好支持(自动完成提示)

Typescript = JavaScript + type + (some other stuff)

types

变量使用前要定义
// let/var/const 变量名:变量类型 = 默认值

let test: string = 'test'
变量类型
  • number: 数值类型
  • string: 字符串类型
  • boolean: 布尔类型
  • symbol: 符号类型,标识唯一对象
  • any: 任意类型
  • object: 对象类型(数组Array<string>,元祖[string, number],类class,接口interface,函数function等)
Boolean
Number
String
Array

Tuple(解构)
Enum(集中对数值方面进行命名)
interface(面向对象)
class(面向对象)

Any
Void
Null
Undeinfed
Never(不可返回,函数中一定会发生异常或无限循环)

any
void

boolean
number
string

null
undefined

string[]          /* or Array<string> */
[string, number]  /* tuple */

string | null | undefined   /* union */

never  /* unreachable */

enum Color {Red, Green, Blue = 4}
let c: Color = Color.Green
类型别名

类型别名常用于联合类型

type Name = string | string[]

type StrOrNum = string | number
// 使用
let sample: StrOrNum
字符串字面量类型

类型别名字符串字面量类型只能用type关键字定义

type EventNames = 'click' | 'scroll' | 'mousemove'
function handleEvent (ele: Element, event: EventNames) {
    // do something
}

handleEvent(document.getElementById('hello'), 'click');  // 没问题
handleEvent(document.getElementById('world'), 'dbclick'); // 报错,event 不能为 'dbclick'
var,let 区别
  • 限定变量的作用范围
  • 防止变量的重复定义
常量

用处:

  • 系统配置文件路径
  • 数据库连接串
  • 公司名称,电话,邮件地址
  • 画面表示信息(登录失败,系统出错)
const name:type = initial_value

const DATA:number[] = [10, 20, 30]
数组
let name:type[] = initial_value // 类型 + 方括号
let name: Array<number> = [12, 2, 23] // 数组泛型 Array<elemType>

let name:type[][] = [ // 二维数组
    [], [], []
]

interface NumberArray { // 用接口表示数组
    [index: number]: number
}
let name: NumberArray = [12, 2, 23]

function sum () { // 类数组 (常见的类数组都有接口定义,如 IArguments, NodeList, HTMLCollection)
    let args: IArguments = arguments;
}
枚举

枚举类型,可以增加代码的可读性。

enum name { name1, name2, name3 }

enum Sex {
    MALE,
    FEMALE,
    UNKNOWN
}
联合类型
let a: number | null | undefined
内置对象

TS核心库定义文件

let b: Boolean = new Boolean(1)
let e: Error = new Error('Error occurred')
let d: Date = new Date()
let r: RegExp = /[a-z]/
DOM 和 BOM 的内置对象

DocumentHTMLElementEventNodeListMouseEvent

let body: HTMLElement = document.body
let allDiv: NodeList = document.querySelectorAll('div')
document.addEventListener('click', function(e: MouseEvent) {
  // Do something
});

声明类型:
未声明类型默认当作any类型
建议:声明类型成员类型
建议:声明方法/函数的参数类型和返回类型
能明确推导时:局部变量可以不声明类型(局部变量通常不需要明确定义类型,尽可能使用类型推导
能明确推导时:函数箭头可以不声明类型。(只有可以参数,箭头函数可以不实用括号,但是返回值需要的话,就需要加上括号)

function

clipboard.png

function add (a: number, b: number): number {
  return a + b
}

// 返回类型可选
function add (a: number, b: number) { ... }

function run(a: string): string {
    return ''
}
let s = function (a: number): string {}

let t1 = (x, y) => x + y
let t2 = (x, y) => { return x + y }
let t3:(a: number, b: string) => void = function (a: number, b: string): void {}

interface P {
    (a: number, b: string): void
}
let add: P = function (a: number, b: string): void {}

TS的类型定义中,=>用来表示函数的定义,左边是输入类型,需要用括号括起来,右边是输出类型。

let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
    return x + y;
};
函数重载

通过为同一个函数(同名函数)提供多个函数类型定义来实现多种功能的目的。
需多次声明函数头,最后一个函数头是在函数体内实现函数体,但不可以用于外部。

// 重载
function padding(all: number)
function padding(topAndBottom: number, leftAndRight: number)
function padding(top: number, right: number, bottom: number, left: number)
function padding(a: number, b?: number, c?: number, d?: number) {
  if (b === undefined && c === undefined && d === undefined) {
    b = c = d = a
  } else if (c === undefined && d === undefined) {
    c = a
    d = b
  }
  return {
    top: a,
    right: b,
    bottom: c,
    left: d
  }
}

class

静态属性,静态方法
class Point {
  x: number
  y: number
  static instances = 0
  constructor(x: number, y: number) {
    this.x = x
    this.y = y
  }
}

class Person {
    public name: string
    static age: number
    constructor (name: string) {
        this.name = name
    }
    public run () {
        console.log('run')
    }
    static work () { // 静态方法里没方法调用成员方法
        console.log('work')
    }
}

let p = new Person('s')

声明类成员:
成员变量必须声明了才能使用。(赋值,取值)。
在私有成员需要提前在类中声明。

抽象类,多态

多态:父类定义一个方法不去实现,让继承的子类去实现,每一个子类有不同的表现。

// 多态
class Animal {
    protected name: string
    constructor (name: string) {
        this.name = name
    }
    public eat () {
        console.log(this.name + ' eat')
    }
}

class Dog extends Animal {
    constructor (name: string) {
        super(name)
    }
    public eat () {
       console.log(this.name + ' eat') 
    }
}

class Pig extends Animal {
    constructor (name: string) {
        super(name)
    }
    public eat () {
        console.log(this.name + ' eat')
    }
    public hoho () {
        console.log('hoho')
    }
}

let d = new Dog('dog')
d.eat()

let p = new Pig('pig')
p.hoho()

// 抽象类: 定义一种标准

abstract class Animal {
    abstract eat (): void
    abstract name: string
}

class Dog extends Animal {
    public name = 'dog'
    constructor () {
        super()
    }
    public eat () {}
}

interface

接口运行时的影响为 0

声明空间:
类型声明空间变量声明空间

类型注解: 不能当作变量来使用,因为它没有定义在变量声明。

class Person {}
type Bas = {}
interface Test {}

变量声明:变量仅在声明空间中使用,不能用作类型注解。

内联
function printLabel (options: { label: string }) {
  console.log(options.label)
}

// 注意分号
function getUser (): { name: string; age?: number } {
}
显式
interface LabelOptions {
  label: string
}

function printLabel(options: LabelOptions) { ... }
可选属性
interface User {
  name: string,
  age?: number
}
只读
interface User {
  readonly name: string
}
动态key
{
  [key: string]: Object[]
}

接口是规范的定义。

// 属性类型接口
interface Color {
    firstName: string
    name: string
}

let a: Color = { // 对象约束
    name: 'tan',
    firstName: 'pink'
}

// 函数类型接口
interface encrypt {
    (key: string, val: string): string
}

let md5: encrypt = (key, val): string => {}

const simple: (foo: number) => string = foo => foo.toString()

// 可索引接口,数组的约束
interface UserArr {
    [index: number]: string
}

let arr:UserArr = ['a', 'b']

// 可索引接口,对象的约束
interface UserObj {
    [index: string]: string
}

let obj: UserObj = {
    name: 's'
}

// 类接口约束, 对类的约束, 实现接口类
interface Animate {
    name: string
    eat(e: string): void
}

class Pig implements Animate {
    name: string
    constructor () {}
    eat () {}
}
type与interface

能用interface实现,就用interface, 如果不能就用type

相同点:

  • 都可以描述一个对象或者函数
  • 都允许拓展(extends): 并且两者并不是相互独立的,interface可以extends type, type 也可以 extends interface
// interface
interface User {
  name: string
  age: number
}

interface SetUser {
  (name: string, age: number): void
}

// type
type User = {
    name: string
    age: number
}
type SetUser = {
    (name: string, age: number): void
}

// interface extends interface
interface Name {
    name: string
}
interface User extends Name {
    age: number
}

// type extends type
type Name = {
    name: string
}
type User = Name & { age: number }

// interface extends type
type Name = {
    name: string
}
interface User extends Name {
    age: number
}

// type extends interface
interface Name {
    name: string
}
type User = Name & {
    age: number
}

不同点:

  • type 可以声明基本类型别名,联合类型,元组等类型
  • interface 能够声明合并
// 基本类型别名
type Name = string

// 联合类型
interface Dog {
    wong();
}
interface Cat {
    miao();
}

type Pet = Dog | Cat

// 具体定义数组每个位置的类型
type PetList = [Dog, Pet]

// 获取一个变量的类型时,使用typeof, 如果不存在,则获取该类型的推论类型
let div = document.createElement('div')
type B = typeof div

interface Person {
  name: string;
  age: number;
  location?: string;
}

const jack: Person = { name: 'jack', age: 100 };
type Jack = typeof jack; // -> Person

function foo(x: number): Array<number> {
  return [x];
}

type F = typeof foo; // -> (x: number) => number[] // 类型推导

// interface 声明合并 // 定义相同的接口,合并成新的接口描述
interface User {
  name: string
  age: number
}

interface User {
  sex: string
}

generics

参数化的类型,一般用来限制集合的内容。
任何出现类型的地方都可以使用
泛 -- 宽泛

作用:解决类,接口,方法的复用性,以及对不特定数据类型的支持(类型校验)

当使用简单的泛型时,泛型常用T, U, V表示。如果在参数里,不止拥有一个泛型,那应该使用更加语义化的名称。例如:TkeyTValue

// 泛型函数
function getData<T> (value: T): T {
    return value
}


// 泛型类
class Greeter<T> {
  greeting: T
  constructor(message: T) {
    this.greeting = message
  }
}

let greeter = new Greeter<string>('Hello, world')


// 接口泛型
interface Array<T> {
  reverse(): T[]
  sort(compare?: (a:T, b:T) => number): T[]
}

// 接口函数泛型
interface ConfingFn {
    <T>(val1: T, val2: T): T
}
interface ConfingFn<T> {
    (val1: T, val2: T): T
}
类型断言

作用:显式指定变量值的类型。
使用方法:在需要断言的变量前加上<Type>

类型断言并不是类型转换,断言成一个联合类型中不存在的类型时不允许的。

let len: number = (input as string).length
let len: number = (<string> input).length  /* 不能再 JSX 中使用 */
多个类型参数
function swap<T, U>(tuple: [T, U]): [U, T] { // 一次定义多个类型参数
    return [tuple[1], tuple[0]];
}

swap([7, 'seven']); // ['seven', 7] // 用来交换输入的元组

other

使用风格
  • 箭头函数代替匿名函数表达式
    x => x + x
    (x, y) => x + y
    <T>(x: T, y: T) => x === y
  • 使用{}把循环体和条件语句括起来
    for (let i = 0; i < 10; i++) {}
    if (x < 10) {}
  • 每一个变量声明语句只声明一个变量。let x = 1; let y = 1; 并不是let x = 1, y = 1
  • 如果函数没有返回值,使用void
  • 需要的时候才把箭头函数的参数括起来。
处理json和字符串
let person = '{"name": "pink", "age": 22}'

const jsonParse: ((key: string, value: any) => any) | undefined = undefined
let objectConverted = JSON.parse(person, jsonParse)
转换为Number, String

ts中推荐使用Number(), String()

Number('10') // 10
String(10) // '10'
对象属性不存在错误
  1. 能修改该值的类型声明,那么添加上缺损值的属性
  2. 使用// @ts-ignore
  3. 使用类型断言,强制为any类型: (this.props as any).flag
类型不明确的错误

优先使用类型保护。

  1. 使用类型保护(type guards)
  2. 使用类型断言
  3. 使用 // @ts-ignore 注释
声明文件

当使用第三方库时, 需要引用它的声明文件。

约定俗成的声明文件以.d.ts为后缀

AlloyTeam团队 的 TSLint 配置

tslint-config-alloy

npm install --save-dev tslint-config-alloy

安装之后修改

{
    "extends": "tslint-config-alloy",
    "rules": {
        // 这里填入项目需要的个性化配置,比如:
        //
        // 一个缩进必须用两个空格替代
        // "indent": [
        //     true,
        //     "spaces",
        //     2
        // ]
    },
    "linterOptions": {
        "exclude": [
            "**/node_modules/**"
        ]
    }
}
ES6修改成TS
  • 声明类成员,声明类型,变量,函数,类(识别类型的意识)
  • 模块语法

  • 添加TypeScript配置文件tsconfig.json
  • 改写webpack.config.js使.ts编译成.js
    /.js -> /.ts
  • module.exports -> export或者export default
  • require -> import
  • 在类中需要定义私有属性
  • 第三方库@types/xxx
  • 在函数中需要泛型,也需要默认值,需要使用重载
  • 类型定义
  • 自定义类型或使用对象字面量,使用interface
  • 为函数,参数,返回值,类属性添加类型注解,函数中的局部变量通过类型推导。常用:number [], class, interface, boolean, string

更改构建脚本:

module: {
    rules: [
        {
            test: /\.ts/,
            use: [
                {
                    loader: 'babel-loader',
                    options: {
                        presets: ['es2015']
                    }
                },
                'ts-loader'
            ]
            exclued: /node_modules/
        }
    ]
}

在函数中需要泛型,也需要默认值:

class MarixToolkit {
    mackRow(): number[];
    mackRow<T>(v: T): T[];
    mackRow(v: any = 0): any[] {
        return new Array(9)
    }
}

自定义类型或使用对象字面量,使用interface:

interface IBoxCoord {
    boxIndex: number
    cellIndex: number
}

总结:封装继承模块方法重载接口

封装
  • 使用class关键词来声明类
  • 使用constructor关键字来声明构造函数
  • 使用private, protected关键字来控制访问权限
  • 可以使用get/set来实现私有成员访问器
  • 支持静态属性static
class Person {
    privated _Name: string
    privated _Sex: boolean
    constructor (Name: string, Sex: boolean) {
        this._Name = Name
        this._Sex = Sex
    }
    get Name () {
        return this._Name
    }
    set Name (Name: string) {
        this._Name = Name
    }
    get Sex () {
        return this._Sex
    }
    set Sex (Sex: boolean) {
        this._Sex = Sex
    }
    static SayHello (person: Person) {
        return 'hello world'
    }
}

let p = new Person('t', true)

Person.SayHello(p)
继承
  • 使用extends关键字完成继承
  • 使用super关键字来访问父类
class Person {
    privated _Name: string
    privated _Sex: boolean
    constructor (Name: string, Sex: boolean) {
        this._Name = Name
        this._Sex = Sex
    }
}

class Student extends Person {
    private _Grade: string
    get _Grade () {
        return _Grade
    }
    set _Grade (grade: string) {
        return this._Grade = grade
    }
    constructor (Name: string, Sex: boolean) {
        super(Name, Sex)
    }
}

let p = new Person('s', true)

let s = new Student('t', false)
模块
  • 模块的概念等同于命名空间
  • 使用module关键字来声明模块
  • 在模块中默认成员都是私有的
  • 在模块中使用export关键字声明共有资源
  • 模块支持跨文件分隔
  • Node.js/Require.js中使用require关键字导入模块
  • 可以使用import关键字来声明模块的别名

定义:

module com.test {
    export class Person {
        privated _Name: string
        privated _Sex: boolean
        constructor (Name: string, Sex: boolean) {
            this._Name = Name
            this._Sex = Sex
        }
        get Name () {
            return this._Name
        }
        set Name (Name: string) {
            this._Name = Name
        }
        get Sex () {
            return this._Sex
        }
        set Sex (Sex: boolean) {
            this._Sex = Sex
        }
        static SayHello (person: Person) {
            return 'hello world'
        }
    }    
}

使用:

import test = com.test // 目录
方法
  • 方法是JavaScript中一个特殊的对象
  • 方法分为命名方法和匿名方法
  • 方法类型
  • 方法推断类型
  • 方法参数可以定义默认值
  • 方法参数可以使用...去定义可变参数组
function add (n1, n2) {
    return n1 + n2
}

let add1 = function (n1, n2) { // 数据类型存在
    return n1 + n2
}

let sum = function (n1: number, n2: number): number {
    return n1 + n2
}

let sum1: (num1: number, num2: number) => number = function (num1: number, num2: number): number {
    return num1 + num2
}

let sum2: (num1: number, num2: number) => number = function (x, y) {
    return x + y
}

function sum3 (n1: number = 5, n2: number = 10, ...num2: Array<number>) {
    return n1 + n2
}
重载

JavaScript中本身不支持重载。

在TypeScript中使用重载的方法:

  1. 先声明出所有的方法重载的定义,不包含方法实现。
  2. 再声明出一个参数为Any类型的重载方法。
  3. 实现Any类型的方法,并通过参数类型不同来去实现重载。

使用了泛型下也需要默认值:

class MarixToolkit {
    mackRow(): number[] // 定义
    mackRow<T>(v: T): T[] // 定义
    mackRow(v: any = 0): any[] { // 实现
        return new Array(9)
    }
}
接口

在接口中可以包含属性,方法。
使用interface关键字定义接口。
属性可以使用?标示允许为空类型。
TypeScript支持隐式接口
接口可以标示为方法类型
接口可以多继承
可以使用<接口类型>{}来去隐式声明匿名对象实现接口

interface Count {
    (num1: number, num2: number): number
}

let add: Count = function (a, b) {
    return a + b
}

interface Count {
    num1: number
    num2: number
}

let _count = <Count>{}

_count.num1 = 10
_count.num2 = 100

alogy
1.3k 声望121 粉丝

// Designer and Developer