本篇主要介绍如何让你的组件库在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 上时,按照该方法绑定声明文件;@type/react
,它存放在 node_modules/@types/
路径下.一般来讲,TS编译器会解析项目中所有的 .ts
文件,其中也包括 .d.ts
文件,所以如果你在自己的项目中为第三方插件编写声明文件的话,文件放在哪里都会被解析到.
如果我们自己实现了一个js库,如何来写声明文件呢?目前有两种方式用来发布声明文件到 npm
上:
npm
包同时捆绑在一起;npm
上的 @types organization在 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 下面的包是从 DefinitelyTyped 里自动发布的,通过 types-publisher 工具. 如果想让你的包发布为@types包,提交一个pull request到 github.com/DefinitelyT…. 在这里查看详细信息 contribution guidelines page.
目前大致分为三种类型的类库,分别为全局类库、模块类库、UMD类库. 接下来,我会带大家分析 ts
引入各自类库的用法和区别.
接下来介绍的大部分内容为代码段,可以点击查看源码同时阅读.
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
输出.
(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 = () => {} 复制代码
在项目的根目录创建 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 阶段了,待正式版发布之后,我会继续更新迭代组件库并把开发心得分享给大家.
OwlUI GitHub
Typescript 开发教程