前段时间项目忙,加之疫情原因,就没有更新。😊😊😊

这篇文章主要是类型声明过程中遇到的需求和问题做的记录。

持续更新中…

如何编写.d.ts声明文件?

.d.ts文件是在编写typescript项目中必不可少的,它是让我们能在ts中调用的js的声明文件。比如有很多主流的npm库都是基于js编写的,那么在你的ts项目中引用时,会提示你找不到对应包或函数的声明,此时并不需要我们用ts对组件重写,只需在你的项目中编写包含该库类型声明的.d.ts文件即可。

通常在定制化编写组件时,.d.ts会统一放到@types或者typings文件夹下,当然你也可以不写.d.ts,而通过tscts文件做编译生成对应文件的.d.ts

eg. 扩展 axios 模块请求的 config 配置。 axios.interceptors.response. errors.config 亦可拿到

declare module 'axios' {
  export interface AxiosRequestConfig {
    metadata?: {
      start: number;
    };

    /**
     *  用于控制该请求出现错误时, 是否由默认message显示错误信息
     *
     * @type {boolean}
     * @memberof AxiosRequestConfig
     */
    suppressError?: boolean;

    /**
     * 用于请求在401状态码时, 是否自动重定向到登陆页。
     *
     * @type {boolean}
     * @memberof AxiosRequestConfig
     */
    suppressUnauthRedirect?: boolean;
  }
}

声明语句中只能定义类型,切勿在声明语句中定义具体的实现

///三斜线指令

/// <reference path=”…” />

三斜线引用告诉编译器在编译过程中要引入的额外的文件。三斜线指令仅可放在包含它的文件的最顶端。 一个三斜线指令的前面只能出现单行或多行注释。

declare namespace详解

namespace中文称命名空间,现在已经不建议再使用ts中的namespace,而推荐使用ES6的模块化方案了,虽然目前仍能使用

eg. jQuery提供了一个jQuery.ajax方法可以调用,那么我们就应该使用declare namespace jQuery来声明这个拥有多个子属性的全局变量。

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  const version: number;
  class Event {
    blur(eventType: EventType): void;
  }
  enum EventType {
    CustomClick,
  }
}

注意,在declare namespace内部,我们直接使用function ajax来声明函数,而不是使用declare function ajax。类似的,也可以使用 const, class, enum等语句`

  • 嵌套的命名空间

使用场景: 诸如 jQuery.fn.extend({})

declare namespace jQuery {
  function ajax(url: string, settings?: any): void;
  namespace fn {
    function extend(object: any): void;
  }
}
  • 当然你也可以这样写:
// 这样写有很多局限性, 比如 jQuery不仅仅有 fn属性的时候
declare namespace jQuery.fn {
  function extend(object: any): void;
}

typeinterface如何抉择?

推荐任何时候都是用typetype使用起来更像一个变量,与interface相比,type的特点如下:

  • 表达功能更强大,不局限于object/class/function
  • 要扩展已有type需要创建新type,不可以重名
  • 支持更复杂的类型操作

基本上所有用interface表达的类型都有其等价的type表达。

给函数添加属性申明

interface FuncWithAttachment {
  (param: string): boolean;
  someProperty: number;
}

const testFunc: FuncWithAttachment = (param) => {};
const result = testFunc('mike'); // 有类型提醒
testFunc.someProperty = 3; // 有类型提醒

等价的声明合并写法:

// 组合多个声明语句,它们不会产生冲突
type FuncWithAttachment = (param: string) => boolean;
type FuncWithAttachment = {
  someProperty: number;
};

枚举类型声明及类型使用

// 联合类型
type A = 'a' | 'b' | 'c';

// 枚举类型
enum A {
  'a' = 'a',
  'b' = 'b',
  'c' = 'c',
}

注意 (type)A 等价 keyof typeof (enum 的 keys)A,如果想使用 (enum 的 values 的联合类型) 直接使用 A 即可

泛型的其他使用场景

列举除了泛型函数外的一些常见使用场景:

  • 接口返回数据类型约束
// 有这么一个 interface ,显然, 很多接口返回数据都是这种结构,所以这里使用了 泛型约束
interface A<T> {
  page: number;
  size: number;
  results: T[];
  total: number;
}

interface B {
  name: string;
  age?: number;
}

const res = await request<A<B>>('/api/xx');
  • Promise 类型

在做异步操作时我们经常使用 async 函数,函数调用时会 return 一个 Promise 对象,可以使用 then 方法添加回调函数。

Promise 是一个泛型类型,T 泛型变量用于确定使用 then 方法时接收的第一个回调函数(onfulfilled)的参数类型。

// 某 props 接收异步处理函数
onTaskRequest?: (args: API.X[]) => Promise<API.X[]>;
  • event 事件类型
// click 使用 React.MouseEvent 加 dom 类型的泛型
// HTMLInputElement 代表 input标签 另外一个常用的是 HTMLDivElement
const onClick = (e: React.MouseEvent<HTMLInputElement>) => {};
  • 作为组件 Propshooks 参数
interface FnComProps {}
const FnCom: React.FC<FnComProps> = (props) => {
  return <>组件</>;
};

动态更新 Objectkey

type Person = {
  name: string;
  age?: number;
  [k: string]: any; // 指定 k 可以为任意的 string
};

反向使用 typeof

// NewsCard 组件接收的 props 类型
interface NewsCardProps {
  // ficusService.getNews: (params: API.NewsQuery) => Promise<API.PaginationNewsResult<API.News>>
  // 通过 typeof 反向获取类型
  provider: typeof ficusService.getNews;
}

巧用Partial

使用 Partial 将所有的 props 属性都变为可选值。—— 这对我们在接口定义时很有用

type Partial<T> = { [P in keyof T]?: T[P] };
// 在 A 接口中, 我们使用 interface X 完成了对某数据的返回类型定义。
// 在 B 接口中, 我们可能会对 X 中定义的字段的部分字段进行更新, 但在 A 接口 为必选字段, B 接口为可选

export const conditionService = {
  async query(query: Query) {
    const res = await axios.get<PageResult<ConditionTemp>>(`/v1.0/condition`, {
      params: query,
    });
    return res.data;
  },
  async update(data: Partial<ConditionTemp>) {
    await axios.put(`/v1.0/condition`, data);
  },
};

为 Window 添加参数

使用第三方库时(ga),ga 是全局方法,在使用时会提示” 类型“Window”上不存在属性“ga”

interface Window {
  ga: (
    command: 'send',
    hitType: 'event' | 'pageview',
    fieldsObject: GAFieldsObject | string,
  ) => void;
  reloadAuthorized: () => void;
}

不想在 Window 中增加,但是想要全局使用,比如通过 define 注入的参数,我们通过 declare 关键字在 /src/typings.d.ts 注入。

declare const REACT_APP_ENV: 'test' | 'dev' | 'pre' | false;

装包没有相对应的 @types

  • 安装包对应的@types文件包,如@types/react@types/react-dom

  • 自定义其为any

declare module 'xxx'; // d.ts
import xxx from 'xxx'; // tsx

@ts-ignore

// @ts-ignore
xxx;

遇到动态性比较强的代码,不妨使用 as unknown as XXX

typescript 其他使用姿势