本篇主要介绍如何让你的组件库在Typescript项目中支持使用,并且还会详细介绍声明文件的作用、使用方法以及如何编写声明文件. 希望阅读完本篇的内容可以帮助你掌握编写声明文件的技能.
如果你所编写的组件库或者插件,没有添加声明文件的话,那么当它被Typescript项目中引入时,如下图所示:
找不到 owl-ui
模块的声明文件,请尝试执行 npm install @types/owl-ui
添加声明文件,或者创建一个包含 declare module 'owl-ui'
的 .d.ts
文件. 编译器为开发者提供了两种解决办法,这两种办法在后面会介绍给大家,在这之前我简单介绍一下声明文件.
什么是声明文件
根据上面错误提示分析,声明文件就是为Javascript库提供类型声明,声明文件必须以 .d.ts
为后缀. 如果在Typescript项目中引入第三方库时,TS编译器会检查其声明文件内容,若没有找到则提示错误.
声明文件放在哪
在介绍声明文件如何编写之前,列举一般声明文件存放的方式.
- 目录
src/@types/
,在src
目录新建@types
目录,在其中编写.d.ts
声明文件,声明文件会自动被识别,可以在此为一些没有声明文件的模块编写自己的声明文件,实际上在tsconfig.json
中include
字段包含的范围内编写.d.ts
,都将被自动识别; - 与被声明的
js
文件同级目录内,创建相同名称的.d.ts
文件,这样也会被自动识别; - 设置
package.json
中的typings
属性值,如./index.d.ts
. 这样系统会识别该地址的声明文件. 同样当我们把自己的js库发布到 npm 上时,按照该方法绑定声明文件; - 同过 npm 模块安装,如
@type/react
,它存放在node_modules/@types/
路径下.
一般来讲,TS编译器会解析项目中所有的 .ts
文件,其中也包括 .d.ts
文件,所以如果你在自己的项目中为第三方插件编写声明文件的话,文件放在哪里都会被解析到.
如何发布声明文件
如果我们自己实现了一个js库,如何来写声明文件呢?目前有两种方式用来发布声明文件到 npm
上:
- 与你的
npm
包同时捆绑在一起; - 发布到
npm
上的 @types organization
npm包含声明文件
在 package.json
中,你的需要指定 npm
包的主 js
文件,那么你还需要指定主声明文件. 如下:
{
"name": "owl-ui",
"version": "0.2.6",
"description": "Owl UI",
"main": "./lib/owl-ui.common.js",
"typings": "types/index.d.ts",
}
有的 npm
包设置的 types
属性,它和 typings
具有相同意义. 如果你的 npm
包需要依赖于其他包,需要将依赖放在 dependencies
中.
发布到@types
@types 下面的包是从 DefinitelyTyped 里自动发布的,通过 types-publisher 工具. 如果想让你的包发布为@types包,提交一个pull request到 github.com/DefinitelyT…. 在这里查看详细信息 contribution guidelines page.
如何编写声明文件
目前大致分为三种类型的类库,分别为全局类库、模块类库、UMD类库. 接下来,我会带大家分析 ts
引入各自类库的用法和区别.
接下来介绍的大部分内容为代码段,可以点击查看源码同时阅读.
引入jquery
jquery 是 UMD 类库,它可以全局引用,同时也可以使用模块化方式引用.
import $ from 'jquery'
// Error: Try `npm install @types/jquery` if it exists or add a new declaration (.d.ts) file containing `declare module 'jquery';`
在 ts
文件中引入 js 文件时,会提示上述错误,原因在于缺少声明文件.
我们在使用非 ts
编写的类库时,必须为这个类库编写一个声明文件,对外暴露它的 API,有时候这些类库的声明文件是单独提供的,需要额外安装. 上述例子中就提示需要安装 @types/jquery
插件.
npm install @types/jquery -D
安装完之后,就可以正常在 ts 文件中使用 jquery
了.
- 可在
http://microsoft.github.io/TypeSearch/
中查询类库是否有声明文件- 可在
http://definitelytyped.org/
中了解如何发布声明文件
全局类库
编写 js 文件,如下所示:
function globalLib (options) {
console.log(options)
}
globalLib.version = '1.0.0'
globalLib.doSomething = function () {
console.log('global lib do something')
}
上述代码中,定义了一个函数,为函数添加了两个元素. 接下来我们用 script
标签引入该文件,让该函数作用在全局.
我们在 ts 中调用该函数,如下所示:
globalLib({ a: 1 }) // Error: Cannot find name 'globalLib'.
提示未找到该函数.
解决办法为它添加一个声明文件,在同级目录下创建一个同名 d.ts
文件,如下所示:
declare function globalLib (options: globalLib.Options): void
declare namespace globalLib {
const version: string
function doSomething (): void
interface Options {
[key: string]: any
}
}
定义了一个同名函数和命名空间,相当于为函数添加了一些默认属性. 函数参数定义了一个接口,参数指定为可索引类型,接受任意属性. declare
关键字可以为外部变量提供声明.
这样一个声明文件就定义好了.
模块类库
以下为 CommonJS 模块编写的文件:
const version = '1.0.0'
function doSomething () {
console.log('moduleLib do something')
}
function moduleLib (options) {
console.log(options)
}
moduleLib.version = version
moduleLib.doSomething = doSomething
module.exports = moduleLib
同样我们将它引入 ts
文件中使用.
import module from './module-lib/index.js'
// Error: Could not find a declaration file for module './module-lib/index.js'.
提示未找到该模块,同样我们需要为它编写文件声明.
declare function moduleLib (options: Options): void
interface Options {
[key: string]: any
}
declare namespace moduleLib {
const version: string
function doSomething(): void
}
export = moduleLib
上述 ts 与刚刚编写的全局类库声明文件大致相同,唯一的区别这里需要 export
输出.
UMD 类库
(function (root, factory) {
if (typeof define === 'function' && define.amd) {
define(factory)
} else if (typeof module === 'object' && module.exports) {
module.exports = factory()
} else {
root.umdLib = factory()
}
}(this, function () {
return {
version: '1.0.0',
doSomething () {
console.log('umd lib do something')
}
}
}))
同样我们将它引入 ts
文件中使用,如果没有声明文件也会提示错误,我们直接看 ts 声明文件
declare namespace umdLib {
const version: string
function doSomething (): void
}
export as namespace umdLib
export = umdLib
我们声明了一个命名空间,命名空间内有两个成员 version
和 doSomething
,分别对应 umd
中的两个成员.
这里与其他类库不同的是,多添加了一条语句 export as namespace umdLib
,如果为 umd
库声明,这条语句必不可少.
umd
同样可以使用全局方式引用.
插件
有时候,我们想给一个第三方类库添加一些自定义的方法. 以下介绍如何在模块插件或全局插件中添加自定义方法.
模块插件
我们使用 moment
插件,为它添加一个自定义方法. 关键字 declare module
.
import m from 'moment'
declare module 'moment' {
export function myFunction (): void
}
m.myFunction = () => { console.log('I am a Fn') }
全局插件
在上面我们有介绍全局类库,我们为它添加一个自定义方法. 关键字 declare global
.
declare global {
namespace globalLib {
function doAnyting (): void
}
}
globalLib.doAnyting = () => {}
UI组件库添加声明文件
在项目的根目录创建 types
文件. 目录结构如下:
.
├── component.d.ts
├── index.d.ts
├── owl-ui.d.ts
└── packages
├── button-group.d.ts
├── button.d.ts
├── date-picker.d.ts
├── dialog.d.ts
├── drawer.d.ts
├── input-group.d.ts
├── input.d.ts
├── picker.d.ts
├── range.d.ts
├── select-group.d.ts
├── select.d.ts
├── switch.d.ts
├── tabs.d.ts
└── toast.d.ts
component.d.ts
存放声明组件需要的继承类:
import Vue from 'vue'
export declare class OwlUIComponent extends Vue {
static install (vue: typeof Vue): void
}
export declare class OwlUIPopupComponent extends OwlUIComponent {
visible: boolean
zIndex: number
maskStyle: object
containerStyle: object
show (): any
hide (): any
}
packages
文件夹保存着每个组件的声明文件,如 button.d.ts
:
import { OwlUIComponent } from '../component'
export type ButtonType = 'default' | 'disabled' | 'light'
export declare class OwlButton extends OwlUIComponent {
type: ButtonType
inline: boolean
outline: boolean
btnStyle: object
}
owl-ui.d.ts
整合所有的声明文件,逐一输出:
import Vue from 'vue'
import { OwlUIComponent } from './component'
/**
* component common definition
*/
export type Component = OwlUIComponent
export function install (vue: typeof Vue): void
import { OwlButton } from './packages/button'
import { OwlButtonGroup } from './packages/button-group'
import { OwlDatePicker } from './packages/date-picker'
import { OwlDialog } from './packages/dialog'
import { OwlDrawer } from './packages/drawer'
import { OwlInputGroup } from './packages/input-group'
import { OwlInput } from './packages/input'
import { OwlPicker } from './packages/picker'
import { OwlRange } from './packages/range'
import { OwlSelectGroup } from './packages/select-group'
import { OwlSelect } from './packages/select'
import { OwlSwitch } from './packages/switch'
import { OwlTabs } from './packages/tabs'
import { OwlToast } from './packages/toast'
export class Button extends OwlButton {}
export class ButtonGroup extends OwlButtonGroup {}
export class DatePicker extends OwlDatePicker {}
export class Dialog extends OwlDialog {}
export class Drawer extends OwlDrawer {}
export class InputGroup extends OwlInputGroup {}
export class Input extends OwlInput {}
export class Picker extends OwlPicker {}
export class Range extends OwlRange {}
export class SelectGroup extends OwlSelectGroup {}
export class Select extends OwlSelect {}
export class Switch extends OwlSwitch {}
export class Tabs extends OwlTabs {}
export class Toast extends OwlToast {}
declare module 'vue/types/vue' {
interface Vue {
/** create Drawer instance */
$drawer(options: object): Drawer
/** create Dialog instance */
$dialog(options: object): Dialog
/** create DatePicker instance */
$datePicker(options: object): DatePicker
/** create Picker instance */
$picker(options: object): Picker
/** create Toast instance */
$toast(options: object | string): Toast
}
}
index.d.ts
为输入输出文件:
export * from './owl-ui'
import * as OwlUI from './owl-ui'
export default OwlUI
最后在 package.json
中添加 typings: "types/index.d.ts"
.
这样组件库就支持在Typescript项目中使用了.
结语
最近 Vue3.0 已经进入 Beta 阶段了,待正式版发布之后,我会继续更新迭代组件库并把开发心得分享给大家.