一、前言
TypeScript 现在曾经成为前端开发者必备的一项技能,它不经可能晋升开发者的开发体验还能保障我的项目的可维护性,这次要得益于 TypeScript 的动态检测机制,能够帮忙咱们在编译的时候发现错误,当然如果你还是一个 any 大法的信仰者就当我没说。
当然明天的主题当然不是去介绍 TypeScript 的语法以及用法,明天重点介绍的配角是它如何去帮咱们找到咱们定义的 类型
,这也是笔者最近遇到的问题,上面我先列举几个问题,大家先思考下:
- node_modules 中 @types 文件夹有什么作用?TypeScript 是怎么找到他的?
- tsconfig 中的
typeRoots
和types
属性有什么作用?他们和类型查找有什么关系? - tsconfig 中的
files
、include
和exclude
属性有什么作用?他们和类型查找有什么关系? - TypeScript 申明文件有
全局库
和模块化库
两种?他们的特点是什么?怎么去自定义?
如果你都能顺利的答,那么祝贺你 TypeScript 的类型定义以及查问规定这一块你曾经顺利过关。
二、配置开发环境
其实总的来说在咱们的我的项目中类型文件分为两种,一个是第三方库定义好的类型,一个就是本人我的项目中自定义的类型,1、2 这两个问题属于第一类、3、4 属于第二类,上面通过具体的实例别离解释,首先先配置好咱们的开发环境,对于第三方库咱们拿两个比拟有代表性的库来剖析,一个是 Jquery,一个是 ahooks 这是阿里开发的一个通用的 hooks 库,具体在这就不做过多介绍:
- 应用 create-react-app 创立 TypeScript 我的项目
npx create-react-app my-app --template typescript
#or
yarn create react-app my-app --template typescript
- 装置 jquery 和 ahooks
npm i -D jquery ahooks
#or
yarn add -D jquery ahooks
三、第三方库
1、Jquery
首先在 src 文件夹下新建一个 test.ts 文件,因为咱们曾经装了 jquery 咱们能够间接应用全局变量 $ 了,来试试:
当咱们输出 $ 后咱们会发现编辑器既没有语法提醒还呈现报错,而后提醒咱们去装置 @types/jquery 这个包,这是咱们和 @types
的首次见面,咱们先去装置。
装置好之后咱们再试试发现一切都是那么的美妙,这里为什么呈现这样的问题呢?其实就是 TypeScript 没有找到 $
的定义, 如果咱们间接漠视它 (禁止 ts 提醒) 而后间接运行发现我的项目还是能够间接运行起来的,因为咱们装了这个包 webpack 当然就能找到这个包就没问题了呀。
话题回到主线上,此时咱们去 node_module 目录下的 @types 目录下看一下:
果然他将 jquery 的定义文件装置到这个目录下,至于 TypeScript 是怎么找到的咱们前面会说。
2、ahooks
上面还是在 index.ts 文件上来引入 ahooks 并应用了外面的 useUpdateEffect 这个 hook:
import {useUpdateEffect} from 'ahooks'
const Test = () => {useUpdateEffect(() => {console.log()
},[])
}
这里咱们发现编辑器并没有报错,同时也能失常展现出 useUpdateEffect 这个 hook 的类型定义,也就是说咱们不须要引入对应的类型文件 TypeScript 就能找到他对应的类型定义。
3、总结
这里对于下面讲的两个库做一下总结,首先说一下 @types 这个目录的作用,它其实就是用来保留一些没有应用的 TypeScript 编写的库的类型定义文件,像 jquery 他就是用 js 编写的,而 ahooks 就是应用 ts 编写的。
那 TypeScript 到底是怎么找到这些类型的呢?这就得和 typeRoots
、types
这两个属性关联起来了,首先要说的是其实第三方库的类型查找的规定和 node 的包查找是相似的,首先会在以后文件夹找 node_modules, 在他上面递归的去查找,如果找不到就会去下层的目录找到 node_modules 目录,再递归查找,直到根目录。然而对于类型查找还是有些差异,上面拿 jquery 这个库来说:
- 在以后文件下找 node_modules 目录,发现没找到走道上一层
- 在根目录下找到 node_modules 目录,递归查找 jquery 包,发现存在
- 找到 jquery 包的 package.json 查找 types 属性发现没有,阐明这个库是 js 编写的,去 @types 找他的类型定义
- 找到 jquery 的类型定义包,而后去找他的 package.json 文件并查找 types 属性,发现存在
- 找到 types 属性指定的文件(index.d.ts),这个文件就是 jquery 类型定义的入口(如果还不晓得.d.ts 和.ts 文件的区别点击这里)
如果是应用 ts 编写的库如 ahooks 那么在第三步就能够完结,咱们看下 ahooks 的 package.json:
阐明他的类型定义的入口就在这个包的 lib 目录下,至此大家对 ts 的类型查找应该有了肯定的理解,然而可能就有同学会问了,你刚刚不是说的 typeRoots
、types
这两个属性呢,他们的作用是啥?
typeRoots
和 types
都是 tsconfig 中 compilerOptions 的一个配置项,他们上面的包会被 ts 编译器主动蕴含进来,typeRoots 默认指向 node_modules/@types,这也就阐明了为什么 ts 会去 @types 上面去找类型定义文件,既然这样阐明咱们也能够手动去调整 ts 的查找门路,比方咱们本地用 ts 开发了一个通用的组件库,他的类型定义文件保留在 typings 目录下,那咱们能够这么批改:
"typeRoots": ["node_modules/@types","./typings"]
那 types
有什么作用呢?如果不心愿主动引入 typeRoots
指定门路下的所有申明模块,那能够应用 types
指定主动引入哪些模块。比方:
{
"compilerOptions": {"types" : ["node", "lodash", "express"]
}
}
那么就只会引入 node、lodash、express 这三个申明模块,其它的申明模块则不会被主动引入。
四、自定义类型申明
自定义类型申明也分为两种,一个是 全局库
、一个是 模块化库
,它和 SCMAScript 2015 一样,任何蕴含顶级 import
或者 export
的文件都会被当成一个模块,相同,如果一个文件不带有顶级的import
或者 export
申明,那么它的内容被视为全局可见。
1、全局库
全局库就是指能在全局命名空间下拜访,上面咱们在根目录下创立一个 global.d.ts
文件:
declare const fuc:(a:number,b:number) => number
declare var n:number
declare type A = {
name:string,
age:number
}
在这外面咱们通过 declare 申明了几个全局的变量,这里要留神了此时还是不能全局应用的,咱们要去 tsconfig
文件下做一下配置:
"include": ["./**/*",]
将咱们的 include
属性改成这样,至于为什么这么改前面会讲到,而后咱们就能在任何中央去应用咱们下面定义的变量, 比方咱们在 index.js 文件下这样输出 fuc:
发现编辑器给你提醒了这个函数,并给出了他的类型定义。
2、模块化库
模块化类型库应该是咱们开发过程中用的最多的,比方咱们会在独自的一个.ts 文件中定义一些公共的类型而后导出:
export const a: number = 1
export const add = (x: number, y:number) => x + y
export interface User {
name: string,
department: string
}
当然咱们在导出模块也能够不去定义变量的类型,而在引入的模块中对引入的变量进行类型定义,如下所示:
//src/person.ts
export class Person {}
//src.index.ts
import {Person} from './person';
declare module './person' {
interface Person {greet: () => void;
}
}
Person.prototype.greet = () => {console.log('Hi!');
};
咱们在 person.ts 中之定义了一个 Person 类,在 index.ts 中引入这个类通过 declare module 将 ‘./person‘申明为一个模块,并对这个模块上面的 Person 减少对应的类型申明,这里就是给 Person 类的原型上减少了一个办法,这样咱们就能失常的在原型上应用这个办法。
在第三方库中也能够去应用模块化库,比方 ahooks 中的 createUpdateEffect
,我么看下他的 .d.ts
文件定义:
import type {useEffect, useLayoutEffect} from 'react';
declare type effectHookType = typeof useEffect | typeof useLayoutEffect;
export declare const createUpdateEffect: (hook: effectHookType) => effectHookType;
export {};
而后在 lib/index.d.ts 中通过 import
导入。
3、总结
下面次要介绍了 全局库
和模块化库
的一些应用,这里次要说一下 files
、include
和exclude
属性,他们有什么作用。
咱们首先要理解下什么样的文件才算是 ts 文件呢?答案如下:
- 拓展名为.ts、.tsx、.d.ts 的文件
- 如果咱们设置了
allowJs = true
,那么.js、jsx 也会被视为 ts 文件
接着说 files
、include
和exclude
这三个属性是管制着 ts 编译器的编译范畴,他们各自的特点如下:
- files 是一个数组,数组的元素能够是相对路径和绝对路径
- inclue 和 exclude 属性是一个数组,然而组的元素相似
glob
的文件模式,比方*
、?
、**/
这种通配符的模式 - 如果同时设置了
files
和include
,那么编译器会把两者指定的文件引入,而exclude
只会对include
无效,对files
是有效的,即files
指定的文件如果同时被exclude
排除,那么该文件依然会被编译器引入。
所以在引入 global.d.ts
时须要去配置 include
属性把它纳入编译范畴之内,此时咱们定义的变量才会被找到。