HarmonyOS开发中引用的.d.ts文件

729 阅读3分钟

 .d.ts文件

.d.ts文件是做什么的?

文件扩展名为.d.ts的文件是TypeScript中的声明文件,用来对对应的Javascript文件中的函数、变量等的数据没有数据类型的情况进行声明、补充完善。比如Javascript中的函数的入参、返回值等这些数据没有定义静态的数据类型,不满足TypeScript编译器的要求。

它允许开发者为JavaScript库或没有直接包含类型信息的代码提供类型注释,使得TypeScript编译器能够理解这些代码的结构和类型,从而在开发阶段提供静态类型检查、智能提示等功能,而不会对原始代码进行任何修改或编译操作。

为什么需要.d.ts文件?

1、可以提升开发效率:通过类型提示,开发者可以在编码时获得即时反馈,减少因类型错误导致的运行时问题。

2、增强了代码的可读性和可维护性:类型定义文件为代码提供了清晰的接口描述,便于团队成员理解代码的结构和意图。

3、支持第三方库:许多流行的JavaScript库并未直接提供TypeScript支持,通过社区维护的.d.ts文件,可以让这些库无缝集成到TypeScript项目中。

.d.ts文件的结构

一个简单的.d.ts文件可能包含以下内容:

// 声明一个全局的接口
declare interface NavigationCommonTitle {
    main: string; 
    sub: string;
}

// 定义一个函数式接口并声明一个全局变量
interface ErrorConstructor {
    new(message?: string): Error;
    (message?: string): Error;
    readonly prototype: Error;
}

declare var Error: ErrorConstructor;

// 声明一个模块
declare module 'my-module' {
  export function greet(name: string): string;
} 

declare关键字用来定义类型信息而不实际使用它,var用于声明全局变量,而module则用于定义模块及其导出的成员。

模块声明

对于npm包,通常会采用外部模块的声明方式:

// my-module.d.ts
declare module 'my-module' {
  export interface User {
    id: number;
    name: string;
  }

  export function fetchUser(id: number): Promise<User>;
}

全局声明

有时侯需要向全局作用域添加类型定义,比如声明jQuery:

// jquery.d.ts
declare namespace jQuery {
  function fn(name: string, selector: string): JQuery;
  
  interface JQuery {
    ajax(url: string, settings?: any): JQuery.jqXHR;
    // ...其他jQuery方法
  }
}

declare var $: JQueryStatic;

interface JQueryStatic extends JQuery {}

这里声明了jQuery的命名空间、静态成员和JQuery接口,使在TypeScript中编辑器能够识别并提供jQuery相关的方法和属性的智能提示。

在工程中使用.d.ts文件

引入三方js库以及类型定义

a、在TypeScript工程中引入:

对于第三方库,我们通常不需要手动创建.d.ts文件,因为TypeScript社区已经维护了一个庞大的类型定义仓库——DefinitelyTyped。使用npm安装对应的类型包即可:

npm install --save-dev @types/jquery

安装后,TypeScript编译器会自动找到并使用这些类型定义。

b、在Harmony工程中引入:

在开源鸿蒙仓的js类库中找到一个js库,比如spark-md5。在terminal中,切到目标module后,可以使用以下命令引入:

ohpm install spark-md5

在代码中使用时,import该库。

import spark from 'spark-md5';

自定义类型定义

a、对于Typescript工程:

对于自定义的JavaScript库或项目内部的模块,可以手动创建.d.ts文件,并确保其路径被tsconfig.json文件正确引用。例如,对于项目内的某个模块,可以在其目录下创建同名的.d.ts文件:

// tsconfig.json
{
  "compilerOptions": {
    // ...
    "typeRoots": ["node_modules/@types", "src/types"] // 添加自定义类型定义目录
  },
  // ...
}

然后,在相应的目录下创建.d.ts文件,如src/types/myModule.d.ts。

b、对于Harmony工程:

在适配js库前,使用js-e2e工具扫描该三方库,检查库中是否存在依赖了node.js/web内置模块的情况。如果扫描结果显示库中没有依赖node.js/web内置模块,那么,这个库可以比较轻松地进行适配。注意:如果库中大量依赖了node.js/web内置组件,这时可能需要fork出源库代码,进行侵入式地修改,或者换其他三方库。

(推荐帖子:HarmonyOS 鸿蒙应用开发(十、第三方开源js库移植适配指南)_鸿蒙三方库-CSDN博客)

另外注意:

1、在移植三方js库时,对模块的导入导出是可能需要修改三方库里源码的。

因为Harmony的ArkTS(Ark TypeScript)使用的模块规范是ES6模块规范,而不是CommonJS模块规范。ES6模块规范(也称为ECMAScript 2015模块规范)是一种现代的模块系统,它使用import和export关键字来导入和导出模块成员。ES6模块规范支持静态导入和导出,具有更好的树摇(tree shaking)和代码拆分(code splitting)特性,有助于优化应用程序的性能和大小。相比之下,CommonJS模块规范是一种较旧的模块系统,它使用require和module.exports来导入和导出模块成员。CommonJS模块规范主要用于Node.js环境,并且在一些旧的浏览器环境中也有支持。

2、常用的三方开源js库一般都能在Typescript社区找到对应的.d.ts文件。

那么要在Harmony中使用时,推荐使用能在Typescript社区找到对应的.d.ts声明文件的三方js库,这样,我们只需要对该.d.ts文件进行一些稍微优化就可以引入到Harmony工程中使用了。

比如移植sm-crypto库:

a、找到其.d.ts声明文件并根据需要进行修改。比如它里面依赖了yyz116/jsbn的大数库。

//index.d.ts
import jsbn from '@yyz116/jsbn';
export interface KeyPairHex {
    privateKey: string;
    publicKey: string;
}
 
export interface KeyPairPoint extends KeyPairHex {
    k: jsbn.BigInteger;
    x1: jsbn.BigInteger;
}
 
/**
 * Cipher Mode
 * - `0`:C1C2C3
 * - `1`:C1C3C2
 */
export type CipherMode = 0 | 1;
 
export namespace sm2 {
    // TODO Type of parameter of jsbn.BigInteger constructor
    function generateKeyPairHex(): KeyPairHex;
    function doEncrypt(msg: string | ArrayLike<number>, publicKey: string, cipherMode?: CipherMode): string;
    function doDecrypt(encryptData: string, privateKey: string, cipherMode?: CipherMode, outputType?: {
        output?: "string" | "array";
    }): string;
    function doSignature(msg: string | number[], privateKey: string, options?: {
        pointPool?: KeyPairPoint[] | undefined;
        der?: boolean | undefined;
        hash?: boolean | undefined;
        publicKey?: string | undefined;
        userId?: string | undefined;
    }): string;
    function doVerifySignature(msg: string | number[], signHex: string, publicKey: string, options?: {
        der?: boolean | undefined;
        hash?: boolean | undefined;
        userId?: string | undefined;
    }): boolean;
    function getPoint(): KeyPairPoint;
}
 
export function sm3(input: string | ArrayLike<number>, hmac?: {
    key: HexString | ArrayLike<number>;
    mode?: "hmac";
}): string;
 
// SM4.encrypt() expects UTF8 strings (such as "hello"), while SM4.decrypt() expects hex strings (such as "8d0a1f").
export type HexString = string;
export type UTF8String = string;
 
export interface SM4ModeBase {
    padding?: "none" | "pkcs#5" | "pkcs#7";
    mode?: "cbc";
    iv?: number[] | HexString;
}
 
export interface SM4Mode_StringOutput extends SM4ModeBase {
    output: "string";
}
 
export interface SM4Mode_ArrayOutput extends SM4ModeBase {
    output: "array";
}
 
export namespace sm4 {
    function encrypt(
        inArray: number[] | UTF8String,
        key: number[] | HexString,
        mode?: SM4ModeBase | SM4Mode_StringOutput,
    ): string;
    function encrypt(inArray: number[] | UTF8String, key: number[] | HexString, mode: SM4Mode_ArrayOutput): number[];
 
    function decrypt(
        inArray: number[] | HexString,
        key: number[] | HexString,
        mode?: SM4ModeBase | SM4Mode_StringOutput,
    ): string;
    function decrypt(inArray: number[] | HexString, key: number[] | HexString, mode: SM4Mode_ArrayOutput): number[];
}

b、以es6的模块引入方式对index.js文件修改如下:

// index.js
import * as Sm2 from './sm2/index.js';
import {sm3}  from './sm3/index.js';
import * as Sm4 from './sm4/index.js';
 
export { Sm2 as sm2 };
export { sm3 };
export { Sm4 as sm4 };

c、sm3.js中的原有的CommonJS模块规范,需要修改如下:

// sm3.js
import  {sm3 as Sm3, hmac } from '../sm2/sm3'
......
/*
module.exports = function (input, options) {
  input = typeof input === 'string' ? utf8ToArray(input) : Array.prototype.slice.call(input)
  if (options) {
    const mode = options.mode || 'hmac'
    if (mode !== 'hmac') throw new Error('invalid mode')
    let key = options.key
    if (!key) throw new Error('invalid key')
    key = typeof key === 'string' ? hexToArray(key) : Array.prototype.slice.call(key)
    return ArrayToHex(hmac(input, key))
  }
  return ArrayToHex(sm3(input))
}
* */
export function sm3 (input, options) {
  input = typeof input === 'string' ? utf8ToArray(input) : Array.prototype.slice.call(input)
 
  if (options) {
    const mode = options.mode || 'hmac'
    if (mode !== 'hmac') throw new Error('invalid mode')
 
    let key = options.key
    if (!key) throw new Error('invalid key')
 
    key = typeof key === 'string' ? hexToArray(key) : Array.prototype.slice.call(key)
    return ArrayToHex(hmac(input, key))
  }
 
  return ArrayToHex(Sm3(input))
}

混合使用JavaScript和TypeScript

当项目中同时包含了JavaScript和TypeScript文件时,.d.ts文件可以帮助TypeScript理解JavaScript文件中的类型信息。对于未使用ES6模块的JavaScript文件,可以通过/// 指令来引入类型定义:

// myScript.js
/// <reference path="./myScript.d.ts" />
function sayHello(name) {
  console.log(`Hello, ${name}!`);
}

sayHello('World');

对应的.d.ts文件中定义该JavaScript文件的类型信息:

// myScript.d.ts
declare function sayHello(name: string): void;

interface里的方法没有名称

在TypeScript中,interface里的方法如果没有名称,则表示这是一个函数的类型声明,而不是interface的一个方法。这通常用于函数的重载。

示例:

interface MyInterface {
    (someParam: string): number;
}

这里的interface MyInterface是表示一个函数,它接受一个字符串参数,并返回一个数字。这是TypeScript中的函数类型的重载机制。

在Harmony的SDK中有很多.d.ts文件里有这样的情况,大多数是由于要适配Javascript中的函数而定义的interface。

函数示例:

function MyInterface(someParam: string): number {
    // 实现
    return 42;
}

参考帖子:

TypeScript 中的.d.ts文件是什么?如何在项目中使用? TypeScript 中的.d.ts文件是什么?如何在项目中使用?-CSDN博客

HarmonyOS 鸿蒙应用开发(十、第三方开源js库移植适配指南) HarmonyOS 鸿蒙应用开发(十、第三方开源js库移植适配指南)_鸿蒙三方库-CSDN博客