typescript 学习记录—基础篇

Posted by XuBaoshi on November 7, 2019

typescript 学习记录—基础篇

原始数据类型

boolean

let isDone: boolean = false

在 ts 中使用 new Boolean() 创建的是 Boolean 类型的对象,不是 boolean

// error
let createdByNewBoolean: boolean = new Boolean(1)

// right
let createdByNewBoolean: Boolean = new Boolean(1) // 非 boolean 类型
// right
let createdByBoolean: boolean = Boolean(1)

number

es6 中二进制及八进制表示法, ts 会将其编译至十进制

let decLiteral: number = 6
// es6 二进制
let binaryLiteral: number = 0b1010
// es6 八进制
let octalLiteral: number = 0o744
// 编译后
var decLiteral = 6
// ES6 中的二进制表示法
var binaryLiteral = 10
// ES6 中的八进制表示法
var octalLiteral = 484

string

let myName: string = 'Tom'
let myAge: number = 25
let sentence: string = `Hello, my name is ${myName}.
I'll be ${myAge + 1} years old next month.`

void(空值)

在 ts 中可以使用 void 表示没有任何返回值得函数

function alertName(): void {
  alert('name')
}

void 声明的变量不能赋值给其他对象

let u1: void
let num2: string = u1 // error 不能将类型“void”分配给类型“string”
// Type 'void' is not assignable to type 'number'.

null & undefined

undefined 和 null 是所有类型的子类型,即 undefined 类型的变量,可以赋值给 number 类型的变量

// right
let num: number = undefined
// right
let u: undefined
let num: number = u

任意类型

通常一个普通类型在赋值过程中改变类型是不允许的 , any 是可以的

// error
let myFavoriteNum: string = 'seven'
myFavoriteNum = 7 // 不能将类型“7”分配给类型“string”

// right
let myFavoritNum1: any = 'seven'
myFavoritNum1 = 7

any 可以访问任何属性、使用任意方法

let anyThing: any = 'hello'
console.log(anyThing.name)
console.log(anyThing.name.name)
anyThing.setName()

未声明的变量均是 any 类型

let any1
console.log(any1.name)
console.log(any1.name.name)
any1.setName()

类型推论

变量声明时 如果初始赋值 ts 会根据初始值进行类型推断

let myFavoriteNum = 'seven' // ts 推断变量  myFavoriteNum 为 string 类型
// error
myFavoriteNum = 7 // 不能将类型“7”分配给类型“string”

如果变量声明时没有初始赋值 类型则推断为 any

let myFavoriteNum1
myFavoriteNum1 = 'seven'
myFavoritNum1 = 7

联合类型

表示取值可以为多种类型的一种 类型之间使用 隔开
let myFavoriteNumber: string | number
myFavoriteNumber = 'seven'
myFavoriteNumber = 7

对象类型-接口

接口是对行为的抽象,而具体如何行动需要由类(class)去实现(implement),常用于对「对象的形状(Shape)」进行描述。

interface Person {
  name: string
  age: number
}
let tom: Person = {
  name: 'tom',
  age: 12
}

// 少一些 error
let jerry: Person = {
  name: 'jerry'
}
// Property 'age' is missing in type '{ name: string; }' but required in type 'Person'

// 多一些属性 error
let marry: Person = {
  name: 'marry',
  age: 16,
  sex: 0
}
// 不能将类型“{ name: string; age: number; sex: number; }”分配给类型“Person”。“sex”不在类型“Person”中

可选属性

可以同过在接口属性后添加 ? 使其变成可选属性,可选的属性可以不存在,但依然不可以添加额外的属性

interface Person1 {
  name: string
  age?: number
}

任意属性

一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

//right
interface Person2 {
  name: string
  age?: string
  [propName: string]: string
}

let tom2: Person2 = {
  name: 'tom2',
  age: '11',
  sex: '1'
}

// error
interface Person2 {
  name: string
  age?: number
  [propName: string]: string
}

let tom2: Person2 = {
  name: 'tom2',
  age: 11,
  sex: '1'
}
// 类型“number”的属性“age”不能赋给字符串索引类型“string”

只读类型

初始定义后不能在此地被赋值

interface Person3 {
  readonly id: number
  name: string
  age?: number
  [propName: string]: any
}
let tom3: Person3 = {
  id: 1,
  name: 'tom3',
  gender: 1
}

// error
tom3.id = 2
// Cannot assign to 'id' because it is a read-only property

数组类型

定义 [类型+方括号]

let fibobacci: number[] = [1, 2, 3, 4]

// 不允许其他类型出现
// error
let fiboacci1: number[] = [1, '2', 3]
// error
fibobacci.push('3')
// 不能将类型“string”分配给类型“number”

其他方式实现数组类型

// 数组泛型
let fiboacci2: Array<number> = [1, 2, 3, 4]

// 用接口表示数组
interface NumberArray {
  [index: number]: number
}
let fiboacci3: NumberArray = [1, 2, 3]

类数组

// 类数组
function sum() {
  // 函数内部 arguments 不是数组类型 类数组
  // error
  let args: number[] = arguments
  // Type 'IArguments' is missing the following properties from type 'number[]': pop, push, concat, join

  // 实现方式一
  let args1: {
    [index: number]: number
    length: number
    callee: Function
  } = arguments
  // 实现方式二 (IArguments 是 ts 内部定义好的(内置对象), 实际上就是 type1 的声明方式)
  let args2: IArguments = arguments
}

任意类型

let list: any[] = [1, '2', { name: '3' }]

函数类型

定义

ts 对函数的输入输出均作有约束

// 定义
function sum(x: number, y: number): number {
  return x * y
}

// 不允许输入多余的参数
// error
sum(1, 2, 3)

// 不允许少输入参数
// error
sum(1)
// No overload expects 1 arguments, but overloads do exist that expect either 0 or 2 arguments

函数表达式

// 只对右侧的匿名函数进行定义 左侧的只是赋值操作进行的类型推断
let mySum = function(x: number, y: number): number {
  return x + y
}

// 定义右侧的类型
let mySum1: (x: number, y: number) => number = function(x: number, y: number) {
  return x + y
}

用接口定义函数

interface SearchFunc {
  (source: string, substring: string): boolean
}
let mySearch: SearchFunc
mySearch = function(source: string, substring: string) {
  return source.search(substring) !== -1
}

可选参数

function buildName(firstName: string, lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName
  } else {
    return firstName
  }
}
let tomcat = buildName('Tom', 'Cat')
let tom2 = buildName('tom')

参数默认

function buildName1(firstName: string, lastName: string = 'cat') {
  if (lastName) {
    return firstName + ' ' + lastName
  } else {
    return firstName
  }
}
let tom4 = buildName1('tom3')

剩余参数

function push(array, ...items) {
  items.forEach(function(item) {
    array.push(item)
  })
}
let a = []
push(a, 1, 2, 3)

重载

我们重复定义了多次函数 reverse,前几次都是函数定义,最后一次是函数实现

function reverse(x: number): number
function reverse(x: string): string
function reverse(x: number | string): number | string {
  if (typeof x === 'number') {
    return Number(
      x
        .toString()
        .split('')
        .reverse()
        .join('')
    )
  } else if (typeof x === 'string') {
    return x
      .split('')
      .reverse()
      .join('')
  }
}

类型断言

当 ts 不确定一个联合类型的变量到底是哪个类型的时候,只能访问联合类型所有类型共有的属性和方法’

function getLength(something: string | number): number {
  // error
  return something.length // error
  // 类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”
}

function getLength(something: string | number): number {
  if ((<string>something).length) {
    return (<string>something).length
  } else {
    return something.toString().length
  }
}

通过使用 <类型> 使对象支持非公有属性和方法,但类型断言不是类型转换 ,断言成一个联合类型不存在的类型是不允许的

声明文件

当使用第三方库时,我们需要引用它的声明文件,才能获得对应的代码补全、接口提示等功能

声明语句

使用第三方库 jQuery, 常见的方式是在 html 中通过 script 标签引入 jQuery 就可以使用 $、jQuery 对象, 但在 ts 中并不知道 $、jQuery 是什么东西, 这时需要 declare var 声明

// test.d.ts
declare var JQuery: (selector: string) => any

声明文件

通常会把声明语句放到一个单独的文件如:*.d.ts, 声明文件必需以 .d.ts 为后缀, 当将 *.d.ts 文件放入到项目中, 其他所有 _.ts 文件就都可以获得 _.d.ts文件的订单,但如果还没有引入,需要确认tsconfig.json 文件是否配置正确

第三方声明文件

@types 统一管理第三方库的声明文件,发布至 npm ,下载须通过

npm install @types/jquery --save-dev

全局变量 书写声明文件

declare var 声明全局变量

declare let jQ: (selector: string) => any

declare function 声明全局方法(支持重载)

declare function jq(selector: string): any
declare function jq(callback: () => any): any

declare class 声明全局类

declare class Animal {
  name: string
  constructor(name: string)
  sayHi(): string
}

declare enum 声明全局枚举类型

declare enum Directtions {
  Up,
  Down,
  Left,
  Right
}

declare namespace 声明全局对象(含有子属性)

// ps: 在 namespace 中在定义声明函数 function ajax 时 ,使用 function ajax 而不是使用  declare function ajax, 也可以使用  const、class、enum
declare namespace JQuery2 {
  function ajax(url: string, settings?: any): void
  const version: number
  class Event {
    blur(eventType: EventType): void
  }
  enum EventType {
    CustomClick
  }
}
// 嵌套 namespace
declare namespace JQuery3 {
  function ajax(url: string, settings: any): void
  namespace fn {
    function extend(Object: any): void
  }
}
// 如果 Jquery4 只有 fn 没有 ajax 情况下可以这样定义
declare namespace JQuery4.fn {
  function extend(Object: any): void
}

interface 和 type 声明全局类型

interface AjaxSettings {
  method?: 'GET' | 'POST'
  data?: any
}
declare namespace JQuery5 {
  function ajax(url: string, settings?: AjaxSettings): void
}
// 防止命名冲突 最好放置在 namespace 下
declare namespace JQuery6 {
  interface AjaxSettings {
    method?: 'GET' | 'POST'
    data?: any
  }
  function ajax(url: string, settings?: AjaxSettings): void
}
// 声明合并
declare function JQuery7(selector: string): any
declare namespace JQuery7 {
  function ajax(url: string, settings?: any): void
}

npm 包

  1. 一般我们通过如 import foo from ‘foo’ 引入
  2. 尝试给 npm 包创建对应的声明文件前需确认他的声明文件是否存在。如果存在一般会在两个地方 2.1. 与 npm 包绑定在一起, 判断依据是在 npm 包 package.json 中有 types 字段,或者 index.d.ts 2.2. 放置在 types 中 2.3. 如果没有找到需要自己书写声明文件
  3. npm 包创建声明文件两种方式 (如 foo) 3.1. 创建一个文件 index.d.ts , 放置在 node_modules/@types/foo,由于 node_modules 不稳定这种方式不建议使用 3.2. 项目目录下创建 types 文件夹,将 foo 对应的声明文件放置在 types/foo/index.d.ts, 需要配置 tsconfig.json

export 导出变量

npm 声明文件主要有以下几种写法

  1. export 导出变量
  2. export namespace 导出(含有子属性的)对象
  3. export default ES6 默认导出
  4. export = commonjs 导出模块

npm 包的声明文件与全局变量的声明有很大区别, npm 包中使用 declare 不会再声明一个全局变量只会生成一个局部变量, 使用是需要配合 export import 一起使用
export 的语法与普通的 ts 中的语法类似,区别仅在于声明文件中禁止定义具体的实现

拿 types 中 foo 插件举例

// types/foo/index.d.ts
// *.d.ts 文件
export const name: string
export function getName(): string
export class Animal1 {
  constructor(name: string)
  sayHi(): string
}
export enum Directtions1 {
  Up,
  Down,
  Left,
  Right
}
export interface Options {
  data: any
}
// 使用
import { name, getName, Animal1, Directtions1, Options } from 'foo'

混用 declare 和 export

可以使用 declare 事先声明多个变量并一次性导出

// 混用 declare 和 export
declare const name1: string
declare function getName1(): string
declare class Animal2 {
  constructor(name: string)
  sayHi(): string
}
declare enum Directtions2 {
  Up,
  Down,
  Left,
  Right
}
interface Options1 {
  data: any
}
export { name1, getName1, Animal2, Directtions2, Options1 }

export namespace 导出(含有子属性的)对象

export namespace foo2 {
  const name: string
  namespace bar1 {
    function baz(): string
  }
}

export default es6 默认导出

es6 中 export default 可以导出一个默认值, 使用时可以同 import foo from 'foo' 而不是 import { foo } from 'foo'

export default function foo(): string

ps:只有 function、class 和 interface 可以默认导出,其他的变量需要先定义出来再导出

export = commonjs 导出模块

在 commonjs 规范中,我们用以下方式来导出一个模块

// 整体导出
module.exports = foo
// 单个导出
exports.bar = bar

在 ts 中,针对这种模块导出,有多种方式可以导入。

方式一

// 整体导入
const foo = require('foo')
// 单个导入
const bar = require('foo').bar

方式二

// 整体导入
import * as foo from 'foo'
// 单个导入
import { bar } from 'foo'

方式三

// 整体导入
import foo = require('foo')
// 单个导入
import bar = foo.bar

对于这种使用 commonjs 规范的库,假如要为它写类型声明文件的话,需要使用到 export = 语法

// types/foo/index.d.ts
export = foo

declare function foo(): string
declare namespace foo {
  const bar: number
}

ps: 上例中使用了 export = 之后,就不能再单个导出 export { bar } 了。所以我们通过声明合并,使用 declare namespace foo 来将 bar 合并到 foo 里

UMD 库

UMD 库既可以通过 script 标签引入也可以通过 import 引入,ts 提供了一个新的方法 export as

export as namespace foo
export = foo
// 或 export default foo

declare function foo(): string
declare namespace foo {
  const bar: number
}

直接扩展全局变量

有的第三方库扩展了一个全局变量,但全局变量的类型却没有更新,会导致 ts 编译错误,此时需要扩展全局变量的类型

interface String {
  prependHello(): string
}
'foo'.prependHello()

在 npm 包或者 UMD 库中扩展全局变量

npm 包或者 UMD 库只有 export 导出的类型才能被导入,对此如果导入此库会扩展全局变量,则需要使用另一种语法在声明文件中扩展全局变量类型

declare global {
  interface String {
    prependHello(): string
  }
}
export {}

ps: 即使此声明文件不需要导出任何东西但还需要导出一个空对象, 用来告诉编译器这是一个模块的声明文件而不是全局变量的声明文件

模块插件

ts 提供一个 declare module 语法,可以扩展原有模块的类型

import * as moment from 'moment'
declare module 'moment' {
  export function foo(): moment.CalendarKey
}

也可以用于一个文件中声明多个模块的类型

declare module 'foo' {
  export interface Foo {
    foo: string
  }
}

declare module 'bar' {
  export function bar(): string
}

声明文件中依赖

除了可以在声明文件中通过 import 导入另一个声明文件中的类型之外,还有一个语法也可以用来导入另一个声明文件,那就是三斜线指令

/// <reference types="jquery" />
declare function foo3(options: JQuery6.AjaxSettings): string