从零开始学习typescript系列3: typescript类型声明文件d.ts和类型声明关键字declare

demo: ts版本如何引入lodash js文件

总结

  • 除了需要安装lodash(js版本) 还需要安装安装@types/lodash(类型声明文件)
ts 复制代码
// 1. 只安装lodash 
npm i lodash
// main.ts 引入和使用lodash 
import * as _ from "lodash"  // Could not find a declaration file for module 'lodash'. '/node_modules/lodash/lodash.js' 
import join from "lodash/join"  // Could not find a declaration file for module 'lodash/join'. '/node_modules/lodash/join.js'
console.log('join: ', _.join(['hello', ' ', 'world'])) // join:  hello, ,world
// 结果 
// 语法❌ 无法找到 lodash 和 lodash/join 的类型声明文件 
// 运行✅ 不影响运行,启动正常 

// 2. 安装lodash+@types/lodash
npm i lodash
npm i -D @types/lodash
// 引入和使用
import * as _ from "lodash"  // "node_modules/@types/lodash/index.d.ts"
import join from "lodash/join" // node_modules/@types/lodash/join.d.ts
_.join(['hello', ' ', 'world']);

// 结果 
// 语法✅ 正确找到类型声明文件 
// 运行✅ 不影响运行,启动正常 

demo: 纯ts文件声明类型 与 在d.ts文件中声明类型有什么区别?

总结

  • 不需要d.ts场景: ts单文件 或 ts文件引入另一个ts文件方法,因为ts文件中都有类型声明
  • 需要d.ts场景: ts文件引入另一个js文件方法,由于js文件没有类型声明,需要d.ts对js中的类型声明进行补充
  • d.ts是对js文件类型声明的补充,内部一般是使用declare实现类型声明,常见于第三方库
js 复制代码
// 1. ts单文件 方法定义greet+类型声明GreetingSettings  
// ts-test/script.ts
interface GreetingSettings { 
    greeting: string;
    duration?: number;
    color?: string;
}
function greet(setting: GreetingSettings): void { 
    console.log(`${setting.greeting}`)
}
greet({ greeting: "hello world",duration: 4000});
// 结果 
tsc // 生成 script.js 语法✅ 
node script.js // 运行✅ 

// 2. 2个ts文件,ts文件引入ts文件导出的方法
// ts-test/lib.ts 定义方法和声明类型
interface GreetingSettings { 
    greeting: string;
    duration?: number;
    color?: string;
}
export function greet(setting: GreetingSettings): void { 
    console.log(`${setting.greeting}`)
}
// ts-test/script.ts 通过 lib.ts 引入方法 
import {greet} from './lib'
greet({ greeting: "hello world",duration: 4000});
// 结果 
tsc // 生成 lib.js script.js 语法✅ 
node script.js // 运行✅ 


// 3.1 【without d.ts】ts文件引入js文件导出的方法 有问题
// ts-test/lib.ts 定义方法和声明类型
interface GreetingSettings { 
    greeting: string;
    duration?: number;
    color?: string;
}
export function greet(setting: GreetingSettings): void { 
    console.log(`${setting.greeting}`)
}

// script.ts 通过 lib.js 引入方法 
import {greet} from './lib.js'
greet({ greeting: "hello world",duration: 4000});
// 结果 
tsc lib.ts // 生成 lib.js 
rm lib.ts // 删除 tsc.ts 文件 ==》模拟引入第三方js文件 但没有类型声明文件 d.ts 
tsc // 语法❌  Could not find a declaration file for module './lib.js'

// 3.2 【with d.ts】ts文件引入js文件导出的方法 解决办法
// ts-test/lib.ts 源码
interface GreetingSettings { 
    greeting: string;
    duration?: number;
    color?: string;
}
export function greet(setting: GreetingSettings): void { 
    console.log(`${setting.greeting}`)
}
// ts-test/script.ts 通过 lib.js 引入方法 
import {greet} from './lib.js'
greet({ greeting: "hello world",duration: 4000});
// 结果 
tsc lib.ts  // 生成 lib.js 
tsc -d lib.ts  // 生成 lib.d.ts 
rm lib.ts // 删除 tsc.ts 文件 ==》模拟引入第三方js文件+类型声明文件 d.ts 
tsc // 生成 script.js 语法✅ 
node script.js // 运行✅ 


// ps 生成的lib.js和lib.d.ts文件 
// lib.js
"use strict";
exports.__esModule = true;
exports.greet = void 0;
function greet(setting) {
    console.log("" + setting.greeting);
}
exports.greet = greet;
// lib.d.ts 
interface GreetingSettings {
    greeting: string;
    duration?: number;
    color?: string;
}
export declare function greet(setting: GreetingSettings): void;
export {};

demo: 如何生成d.ts文件?

  • tsc加编译参数 -d 或 --declaration 即可自动生成d.ts文件
ts 复制代码
// xxx.ts 源码
interface IBasicLayout {
    loading: any;
    [key: string]: any;
}
function print (ib: IBasicLayout): void {
    console.log('print: ',JSON.stringify(ib));
}
let data: IBasicLayout = {loading: false,name: 'abc',age: 10}
print(data);
export {print}

// xxx.d.ts 文件(通过 tsc -d xxx.ts 命令生成) 
interface IBasicLayout {
    loading: any;
    [key: string]: any;
}
declare function print(ib: IBasicLayout): void;
export { print };

demo: npm包类型声明管理

ts 复制代码
// npm 包的声明文件可能存在于两个地方
// 1. 与该 npm 包绑定在一起。
// 判断依据是 package.json 中有 types 字段,或者有一个 index.d.ts 声明文件。
// 这种模式不需要额外安装其他包,是最为推荐的,
// 所以以后我们自己创建 npm 包的时候,最好也将声明文件与 npm 包绑定在一起。

// 2. 发布到 @types 里。
// 我们只需要尝试安装一下对应的 @types 包就知道是否存在该声明文件,
// 安装命令是 npm install @types/foo --save-dev。
// 这种模式一般是由于 npm 包的维护者没有提供声明文件,
// 所以只能由其他人将声明文件发布到 @types 里了。
// 比如 lodash 不在同一个查看 
// 源码文件 https://github.com/lodash/lodash 
// 声明文件 https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/lodash 

// 3. 自定义声明文件
// 假如以上两种方式都没有找到对应的声明文件,那么我们就需要自己为它写声明文件了。
// 创建一个 types 目录,专门用来管理自己写的声明文件
// 将 foo 的声明文件放到 types/foo/index.d.ts 中 (declare方式或export方式)
// declare方式 ---- 注意 interface 没有export 
eclare const name: string;
declare function getName(): string;
declare class Animal {
    constructor(name: string);
    sayHi(): string;
}
declare enum Directions {Up,Down,Left,Right}
interface Options {data: any;}
export { name, getName, Animal, Directions, Options };
// export方式 ----
export const name: string;
export function getName(): string;
export class Animal {
    constructor(name: string);
    sayHi(): string;
}
export enum Directions {Up,Down,Left,Right}
export interface Options {data: any;}
// 这种方式需要配置下 tsconfig.json 中的 paths 和 baseUrl 字段。
{
    "compilerOptions": {
        "baseUrl": "./",
        "paths": {
            "*": ["types/*"]
        }
    }
}
// src/index.ts 类型声明生效 
import { name, getName, Animal, Directions, Options } from 'foo';
console.log(name);
let myName = getName();
let cat = new Animal('Tom');
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];
let options: Options = {
    data: {
        name: 'foo'
    }
}; 

// 总结和建议
// 将自定义书写好的声明文件,发布到开源社区中,用自己力所能及的方式回馈开源社区 
// 方式1:给第三方仓库添加类型声明文件,需要发起pull request,获得原作者同意合并
// 方式2:如果原作者不愿意,就需要将声明文件发布到 @types 下。 @types 是统一由 DefinitelyTyped 管理的。要将声明文件发布到 @types 下,就需要给 DefinitelyTyped 创建一个 pull-request,其中包含了类型声明文件,测试代码,以及 tsconfig.json 等。

demo: declare语法

ts 复制代码
// 1.1 variable 自定义变量
// main.ts 例如 引入wx jssdk 后 ,调用 wx 全局变量的方法  会出现报错 Error:找不到名称"wx"。ts(2304)
wx.chooseImage({ /** */});
// type.d.ts 
declare var wx: any;

// 1.2 variable 内置变量,无需声明就可以使用 JSON、Math或Object等全局变量
// typescript/lib/lib.es5.d.ts
declare var JSON: JSON;
declare var Math: Math;
declare var Object: ObjectConstructor;
// main.ts 
Math.max(1,2,3) // max 指向 /Applications/Visual Studio Code.app/Contents/Resources/app/extensions/node_modules/typescript/lib/lib.es5.d.ts

// 2. function 
// types.d.ts 
declare function getWidget(n: number): Widget;
declare function getWidget(s: string): Widget[];
// main.ts 
let x: Widget = getWidget(43);
let arr: Widget[] = getWidget("all of them");

// 3. class 
// src/Animal.d.ts
declare class Animal {
    name: string;
    constructor(name: string);
    sayHi(): string;
}
// src/index.ts
let cat = new Animal('Tom');
cat.name = 'Toby'
cat.sayHi()

// 4. enum
// src/Directions.d.ts
declare enum Directions {Up,Down,Left,Right}
// src/index.ts
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

// 5. namespace 
// 未来不用再学了: 随着 ES6 的广泛应用,现在已经不建议再使用 ts 中的 namespace,而推荐使用 ES6 的模块化方案了 esm ,故我们不再需要学习 namespace 的使用了。
// 但需要了解: namespace 被淘汰了,但是在声明文件中,declare namespace 还是比较常用的,它用来表示全局变量是一个对象,包含很多子属性
// types.d.ts  
declare namespace myLib {
    let numberOfGreetings: number;
    function makeGreeting(s: string): string;
}
// src/main.ts 
let count = myLib.numberOfGreetings; // 属性 
let result = myLib.makeGreeting("hello, world"); // 方法 
console.log("The computed greeting is:" + result);

// 6. module 
// declare module 用来声明一个模块,通常用于
// 1. 声明 js 模块(如 npm 包中的module)
// 2. 非 ts 后缀文件(如 Vue 的单文件组件 xxx.vue)提供类型信息
// 例子 声明 js 模块 ------
// my-js-module.js
function greet(name) {return 'Hello, ' + name;}
module.exports = {greet};
// main.ts
import { greet } from 'my-js-module';
// my-js-module.d.ts 为了main.ts中可以识别模块 'my-js-module'
declare module 'my-js-module' {
  export function greet(name: string): string;
}

// 例子 声明 xxx.vue 后缀文件类型 ------
// shims-vue.d.ts 详解如下
declare module '*.vue' {
  import type { DefineComponent } from 'vue';
  const component: DefineComponent<{}, {}, any>;
  export default component;
}

namespace和module区别?

  • 个人理解:由于历史原理,ts中 namespace 先出现,用于防止全局变量污染,module是随es6发展而来
  • 两者有些类似,但是又有区别,namespance用于某个对象内属性的隔离,module用于模块化导出和导入

demo: 常见的declare声明集合

ts 复制代码
// [jQuery]
// src/jQuery.d.ts
declare var jQuery: (selector: string) => any;
// src/index.ts
jQuery('#foo');
ts 复制代码
// [vue]
// src/shims-vue.d.ts
// 声明一个模块,用于匹配所有以 ".vue" 结尾的文件
declare module '*.vue' {
  // 从 "vue" 中导入 DefineComponent 类型
  import type { DefineComponent } from 'vue'
  // 定义一个类型为 DefineComponent 的变量 component
  // 它具有三个泛型参数,分别表示组件的 props、组件的 data 和其他的类型。
  // 在这里,我们使用空对象({})表示没有 props,使用空对象({})表示没有 data,使用 any 表示其他类型可以是任意值。
  const component: DefineComponent<{}, {}, any>;
  // 导出 component 变量,这样其他地方在导入 ".vue" 文件时,TypeScript 编译器会将它识别为一个 Vue 组件
  export default component;
}
// src/app.vue 
// 定义组件 

总结

declare解决什么痛点?

  • 问题: 代码用 ts 写的, ts代码最后会编译成 js 代码,供他人使用。js代码中,类型信息就丢失了。如果 ts 文件引入 js文件,就会报错,找到js文件的类型声明。
  • 解决办法: ts 可以编译同时生成对外的 xxx.d.ts 文件和 xxx.js 文件。js 文件是给运行引擎用的,.d.ts中类型声明帮助编译器识别类型,其中 xxx.d.ts 内就是使用 declare 进行类型声明
  • declare就是告诉TS编译器你担保这些变量和模块存在,并声明了相应类型,编译的时候不需要提示错误!
相关推荐
Martin -Tang1 小时前
Vue 3 中,ref 和 reactive的区别
前端·javascript·vue.js
懒惰的bit2 小时前
基础网络安全知识
学习·web安全·1024程序员节
FakeOccupational3 小时前
nodejs 020: React语法规则 props和state
前端·javascript·react.js
放逐者-保持本心,方可放逐3 小时前
react 组件应用
开发语言·前端·javascript·react.js·前端框架
曹天骄4 小时前
next中服务端组件共享接口数据
前端·javascript·react.js
Natural_yz5 小时前
大数据学习09之Hive基础
大数据·hive·学习
龙中舞王5 小时前
Unity学习笔记(2):场景绘制
笔记·学习·unity
Natural_yz5 小时前
大数据学习10之Hive高级
大数据·hive·学习
love_and_hope5 小时前
Pytorch学习--神经网络--完整的模型训练套路
人工智能·pytorch·python·深度学习·神经网络·学习
夜雨星辰4876 小时前
Android Studio 学习——整体框架和概念
android·学习·android studio