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编译器你担保这些变量和模块存在,并声明了相应类型,编译的时候不需要提示错误!