# ts 语法进阶
# 初始化一个 ts 开发环境
npm init -y
tsc --init
npm install ts-node -D
npm install typescript --save
然后配置 package.json
"dev": "ts-node ./demo.ts"
# 联合类型和类型保护
联合类型就是一个变量存在多个类型。 解决联合类型中调用属性或方法报错就是类型保护。
interface Bird {
fly: boolean
sing: () => {}
}
interface Dog {
fly: boolean
bark: () => {}
}
// 联合类型
function trainAnial(animal: Bird | Dog) {
// 类型保护
if (animal.fly) {
// 使用类型断言
;(animal as Bird).sing()
}
;(animal as Dog).bark()
// 公用的属性或者方法会有提示
animal.fly
}
// 使用 in 语法来做类型保护
function trainAnialSecond(animal: Bird | Dog) {
// 另一种类型保护
// 如果有 sing 方法
if ('sing' in animal) {
animal.sing()
} else {
animal.bark()
}
}
// typeof 语法来做类型保护
function add(first: string | number, second: string | number) {
if (typeof first === 'string' || typeof second === 'string') {
return `${first}${second}`
}
return first + second
}
// 使用 instanceof 语法做类型保护
class NumberObj {
count: number
}
function addSecond(first: object | NumberObj, second: object | NumberObj) {
if (first instanceof NumberObj && second instanceof NumberObj) {
return first.count + second.count
}
return 0
}
# 枚举类型
js 的例子:
const Status = {
OFFLINE: 0,
ONLINE: 1,
DELETED: 2,
}
function getResult(status) {
if (status === Status.OFFLINE) {
return 'offline'
} else if (status === Status.ONLINE) {
return 'online'
} else if (status === Status.DELETED) {
return 'deleted'
}
return 'error'
}
const result = getResult(Status.OFFLINE)
console.log(result)
ts 实现:
// ts 写法 -使用功能枚举类型
enum Status {
OFFLINE,
ONLINE,
DELETED,
}
console.log(Status.OFFLINE) // 0
console.log(Status.ONLINE) // 1
console.log(Status.DELETED) // 2
// 可以进行反查
console.log(Status[0]) // OFFLINE
// 如果想要上面的值都加 1 只需要对枚举类型的 OFFLINE 设置为 1:
// enum Status {
// OFFLINE = 1,
// ONLINE,
// DELETED
// }
// 如果直接设置中间的值 ONLINE 为 4,那么这几个值分别为 0 4 5;
function getResult(status) {
if (status === Status.OFFLINE) {
return 'offline'
} else if (status === Status.ONLINE) {
return 'online'
} else if (status === Status.DELETED) {
return 'deleted'
}
return 'error'
}
const result = getResult(Status.OFFLINE)
const result1 = getResult(0)
console.log(result)
console.log(result1)
// 字符串枚举
enum Direction {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
const value = 'UP'
if (value === Direction.Up) {
console.log('go up')
}
// 常量枚举 -- 提升性能
const enum Direction1 {
Up = 'UP',
Down = 'DOWN',
Left = 'LEFT',
Right = 'RIGHT',
}
枚举实现反查的代码:
var Dirsction
;(function (Dirsction) {
Dirsction[(Dirsction['Up'] = 0)] = 'Up'
Dirsction[(Dirsction['Down'] = 1)] = 'Down'
Dirsction[(Dirsction['Left'] = 2)] = 'Left'
Dirsction[(Dirsction['Right'] = 3)] = 'Right'
})(Dirsction || (Dirsction = {}))
console.log(Dirsction.Down)
console.log(Dirsction[0])
# 泛型
就是泛指的类型,也就是说,定义的时候不确定是什么类型,等到调用的时候采取指定类型:
// 泛型 generic 泛指的类型
function join<T>(first: T, second: T) {
return `${first}${second}`
}
function anoghterJoin<T>(first: T, second: T): T {
return first
}
function map<T>(params: Array<T>) {
return params
}
// 如果前面传 number 后面必须要 number
join<string>('1', '1')
map<string>(['123'])
// 泛型还可以定义多个类型
function add<T, U>(first: T, second: U) {
return `${first}${second}`
}
add<string, number>('1', 1)
add(1, 1) // 不写,会进行默认的推断
类中的泛型以及泛型类型:
// 在类中使用泛型
class DataManager<T> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index]
}
}
const data = new DataManager<string>(['1'])
data.getItem(0)
泛型还可以继承类型
interface Item {
name: string
}
// 泛型继承 Item ,也就是说 T 里面必须包含 Item 里面所有的东西
class DataManager<T extends Item> {
constructor(private data: T[]) {}
getItem(index: number): string {
return this.data[index].name
}
}
const data = new DataManager([
{
name: 'jiegiser',
},
])
data.getItem(0)
泛型可以类似实现联合类型:
// string 或者 number
class DataManager<T extends number | string> {
constructor(private data: T[]) {}
getItem(index: number): T {
return this.data[index]
}
}
interface Test {
name: string
}
const data = new DataManager<number>([])
泛型还可以声明类型:
function hello<T>(param: T) {
return param
}
const func: <T>(param: T) => T = hello
约束泛型:例如只传入有 length 属性的变量;也就是通过 extends 关键字实现
interface Iwithlength {
length: number
}
function echoWithLength<T extends Iwithlength>(args: T): T {
console.log(args.length)
return args
}
定义接口使用泛型
interface KeyPair<T, U> {
key: T
value: U
}
let kp1: KeyPair<number, string> = { key: 123, value: 'd' }
# 定义全局变量
如果我们使用第三方的库,不是通过 npm 包管理,比如 jquery,可以直接新建一个 jQuery.d.ts 文件,然后定义:
declar var jQuery:(selector: string) => any
这样我们直接在代码写 jQuery 就不会报错,如果 ts 检测不到,可以在 tsconfig.json 中,配置:
{
"include": ["**/*"]
}
上面的配置就是告诉编译器,编译所有 ts 文件。或则安装 @types/jquery 模块就可以。
# 命名空间 namespace
# 基本用法
类似模块化开发的方式,不必要将所有的变量暴露出去;如果想要将一个类暴露出去使用,可以使用 export 语法来实现:
// 将四个变量变成一个变量
namespace Home {
class Header {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Header'
document.body.appendChild(elem)
}
}
class Content {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Content'
document.body.appendChild(elem)
}
}
class Footer {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Footer'
document.body.appendChild(elem)
}
}
// 编译完之后 page header content footer 都变成了全局变量,我们真正需要的全局变量是 page
// 使用命名空间的方式将 page 暴露出去
export class Page {
constructor() {
new Header()
new Content()
new Footer()
}
}
}
HTML 中调用:
这样就可以使用暴露出的 Page 类;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./dist/page.js"></script>
</head>
<body>
<script>
new Home.Page()
</script>
</body>
</html>
# 多模块打包以及子命名空间等
将上面的代码我们可以拆分成两个文件 page.ts 跟 components.ts 文件,这两个文件分别定义了两个命名空间;这样方便代码查看,拆分代码后,我们可以这样使用 commponent.ts 中的类 Components.Header() ;可以发现打包结果会是两个文件,我们可以修改 tsconfig.json 的 "outFile": "./build/page.js";将所有的模块打包成一个文件中;
这样打包的结果还是不能使用,需要我们声明互相引用的声明;表示 page.ts 文件依赖这个文件。
///<reference path='./components.ts'>
// page.ts
// 下面的写法是表示这个命名空间引用了 components 的命名空间
///<reference path='./components.ts'>
// 将四个变量变成一个变量
namespace Home {
// 编译完之后 page header content footer 都变成了全局变量,我们真正需要的全局变量是 page
// 使用命名空间的方式将 page 暴露出去
export class Page {
user: Components.User = {
name: 'jiegiser',
}
constructor() {
new Components.Header()
new Components.Content()
new Components.Footer()
}
}
}
// component.ts
namespace Components {
// 导出子命名空间
export namespace SubComponents {
export class Test {}
}
// 导出接口这种格式
export interface User {
name: string
}
export class Header {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Header'
document.body.appendChild(elem)
}
}
export class Content {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Content'
document.body.appendChild(elem)
}
}
export class Footer {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Footer'
document.body.appendChild(elem)
}
}
}
多模块打包的时候需要修改 tsconfig.json 里面的配置:
"module": "amd",
"outFile": "./build/page.js", // 将所有的模块打包成一个文件
"outDir": "./dist",
"rootDir": "./src",
我们还可以在 page.ts 文件中使用 commponent.ts 中声明的接口类型:
// component.ts
namespace Components {
// 导出接口这种格式
export interface User {
name: string
}
}
// 在 page.ts 中使用
///<reference path='./components.ts'>
namespace Home {
// 使用命名空间的方式将 page 暴露出去
export class Page {
user: Components.User = {
name: 'jiegiser',
}
}
}
在命名空间中也可以导出子命名空间:
// component.ts
namespace Components {
// 导出子命名空间
export namespace SubComponents {
export class Test {}
}
}
// 使用
Components.SubComponents.Test
# import 对应的模块化
上面的代码中不太容易看到不同的命名空间之间的引入,可以使用 import 做代码的模块化组织; 修改上面的 page.ts 以及 components.ts 文件:
// page.ts
import { Header, Content, Footer } from './components'
export default class Page {
constructor() {
new Header()
new Content()
new Footer()
}
}
// components.ts
export class Header {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Header'
document.body.appendChild(elem)
}
}
export class Content {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Content'
document.body.appendChild(elem)
}
}
export class Footer {
constructor() {
const elem = document.createElement('div')
elem.innerHTML = 'This is Footer'
document.body.appendChild(elem)
}
}
然后需要修改页面中引用的方式,需要采用 amd 的方式进行引用,浏览器不能识别这种 require 语法,所以需要引入 require.js ,如果在 webpack 中使用 ts,他会做很多编译以及加载,不需要引入 require.js 等。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="https://cdn.bootcdn.net/ajax/libs/require.js/2.3.6/require.min.js"></script>
<script src="./build/page.js"></script>
</head>
<body>
<script>
require(['page'], function (page) {
new page.default()
})
</script>
</body>
</html>
# 使用 Parcel 打包 TS 代码
首先进行安装 parcel :npm install parcel@next -D,然后修改 package.json 配置:
"scripts": {
"test": "parcel ./src/index.html"
},
这个打包工具会分析我们打包的 index.html 文件,我们打包的文件引入了 ts 文件,他会进行自动编译,然后进行引入;
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<script src="./page.ts"></script>
</head>
<body></body>
</html>
# 描述文件中的全局类型 (d.ts)
比如我们在页面中引用了 cdn 的 jquery 工具,但是在编译器中我们没有进行安装 jquery 包,这样会导致在编写 jquery 相关的代码时候编辑器会提示报错。我们可以创建一个 jquery.d.ts 的 jquery 描述文件里面去定义 jquery 相关的方法,这个编辑器就不会提示找不到 $ 等的变量以及方法:
jquery.d.ts
// 声明全局变量 $,参数为一个函数,返回值为空
declare var $: (param: () => void) => void
interface jQueryInstance {
html: (html: string) => jQueryInstance
}
// 定义全局函数
// 函数重载
declare function $(readyFunc: () => void): void
declare function $(params: string): jQueryInstance
// 可以使用 interface 的语法,实现上面一样的重载,表示 $ 可以通过两种方法实现函数。
interface JQuery {
(readyFunc: () => void): void
(params: string): jQueryInstance
}
declare var $: JQuery
// 声明一个对象
// 定义对象 new $.fn.init(), 以及对类进行定义,以及命名空间的嵌套
declare namespace $ {
namespace fn {
// init 是一个 class
class init {}
// init 是一个 function
function init(): void
}
}
# 模块代码的类型描述文件
比如下面对 jquery 库进行类型描述文件的定义: 下面是 ts 代码
// 如果没有类型文件定义页面会报错
import $ from 'jquery'
$(function () {
$('body').html('<div>123</div>')
new $.fn.init()
})
我们定义一个 jquery.d.ts 的类型描述文件:
// ES6 模块化
declare module 'jquery' {
interface jQueryInstance {
html: (html: string) => jQueryInstance
}
// 定义全局函数
// 函数重载
function $(readyFunc: () => void): void
function $(params: string): jQueryInstance
// 定义对象 $.fn.init(), 以及对类进行定义,以及命名空间的嵌套
namespace $ {
namespace fn {
class init {}
}
}
// 关键需要导出
export = $
}
# 泛型中 keyof 语法的使用
keyof 相当于对 Person 中的属性进行循环;
interface Person {
name: string
age: number
gender: string
}
class Teacher {
constructor(private info: Person) {}
getInfo(key: string): {
if (key === 'name' || key === 'age' || key === 'gender') {
return this.info[key]
}
}
}
const teacher = new Teacher({
name: 'jiegiser',
age: 18,
gender: 'male',
})
// 如果传入 Person 中没有的属性,就会报错,如果通过类型保护,如上面代码,返回的结果就是 string | number | undefined,类型不能明确
console.log(teacher.getInfo('name'))
// 可以使用功能 keyof 以及 泛型解决上面的问题
// keyof 相当于对 Person 中的属性进行循环
// 第一次循环
// type T = 'name'
// key: 'name'
// Person[key]: string
// // 第二次循环
// type T = 'age'
// key: 'age'
// Person[key]: number
// // 第三次
// type T = 'gender'
// key: 'gender'
// Person[key]: string
// 原理就是使用了 type
type NAME = 'name'
// 会报错
// const names: NAME = '1231'
const names: NAME = 'name'
class Teacher {
constructor(private info: Person) {}
getInfo<T extends keyof Person>(key: T): Person[T] {
return this.info[key]
}
}
# 进阶
- 官方文档
- https://ts-ast-viewer.com/# 根据 ts 代码生成抽象语法树
- 在第三方库的官网搜索与 ts 的结合使用
评 论:
← typescript基础语法 装饰器 →