当前位置: 首页 > 后端技术 > Node.js

搭建节点服务(三):使用TypeScript

时间:2023-04-03 20:39:34 Node.js

JavaScript是一种动态的弱类型语言,对变量类型的容忍度非常高。JavaScript在开发上灵活、快速,但由于缺乏类型思维,一个小小的修改就可能导致意想不到的错误。使用TypeScript可以很好的解决这个问题。TypeScript是JavaScript的超集,扩展了JavaScript的语法,增加了静态类型、类、模块、接口、类型注解等功能,可以编译成纯JavaScript。本文将介绍如何在节点服务中使用TypeScript。1、安装依赖npminstalltypescript--savenpminstallts-node--savenpminstallnodemon--save或者yarnaddtypescriptyarnaddts-nodeyarnaddnodemon另外还需要安装依赖模块的类型库:npminstall@types/koa--savenpminstall@types/koa-router--save...或者yarnadd@types/koayarnadd@types/koa-router...2.tsconfig.json用tsc命令编译时,如果没有ts指定文件,编译器会从当前目录中找到tsconfig.json文件,并根据tsconfig.json的配置进行编译。1、指定文件可以通过files属性指定要编译的文件,如下所示:{"files":["src/server.ts"]}另外,也可以使用"include"指定和"exclude"属性,使用类似glob的文件匹配模式,如下:{"include":["src/**/*"],"exclude":["node_modules","**/*.spec.ts"]}支持的通配符:匹配0个或多个字符(不包括目录分隔符)?匹配任意字符(不包括目录分隔符)**/递归匹配任意子目录选项相同,常用配置如下:{"compilerOptions":{//指定编译哪个版本的ECMAScript。默认为"ES3""target":"ES6",//要编译到哪个模块系统。如果目标是“ES3”或“ES5”,则默认为“CommonJS”,否则默认为“ES6”"module":"CommonJS",//模块解析策略,"Classic"或"Node"。如果模块是“AMD”、“System”或“ES6”,则默认为“Classic”,否则默认为“Node”"moduleResolution":"Node",//是否支持commonjsimportcjsfrom'cjs'Package"esModuleInterop":true,//编译时需要导入的库。当target为"ES5"时,默认导入["DOM","ES5","ScriptHost"];当target为"ES6"时,[默认导入"DOM","ES6","DOM.Iterable","ScriptHost"]"lib":["ES6"],//编译后js的根目录文件输出,默认输出到ts文件所在目录"outDir":"dist",//生成对应的.map文件"sourceMap":true},"include":["src/**/*"],"exclude":["node_modules","**/*.spec.ts"]}1)targettarget为编译目标,可以指定编译到哪个版本的ECMAScript,默认为"ES3"。ECMAScript的版本是:“ES3”、“ES5”、“ES6”或“ES2015”、“ES2016”、“ES2017”、“ES2018”、“ES2019”、“ES2020”、“ESNext”。2)modulemodule指定编译成哪个模块系统。如果目标是“ES3”或“ES5”,则默认为“CommonJS”,否则默认为“ES6”。可用的模块系统是:“None”、“CommonJS”、“AMD”、“System”、“UMD”、“ES6”或“ES2015”、“ESNext”。3)moduleResolutionmoduleResolution指定模块解析策略。模块解析策略包括:“Classic”和“Node”。如果模块是“AMD”、“System”或“ES6”,则默认为“Classic”,否则默认为“Node”。示例1:在/root/src/moduleA.ts中以import{b}from"./moduleB"的形式相对引用一个模块。经典解析策略,搜索过程:/root/src/moduleB.ts/root/src/moduleB.d.ts节点解析策略,搜索过程:/root/src/moduleB.ts/root/src/moduleB.tsx/root/src/moduleB.d.ts/root/src/moduleB/package.json(如果指定了“types”属性)/root/src/moduleB/index.ts/root/src/moduleB/index.tsx/root/src/moduleB/index.d.ts示例2:在/root/src/moduleA.ts中,以import{b}from"moduleB"的形式非相对引用一个模块。经典分析策略,搜索过程:/root/src/moduleB.ts/root/src/moduleB.d.ts/root/moduleB.ts/root/moduleB.d.ts/moduleB.ts/moduleB.d.ts节点分析策略,查找过程:/root/src/node_modules/moduleB.ts/root/src/node_modules/moduleB.tsx/root/src/node_modules/moduleB.d.ts/root/src/node_modules/moduleB/package.json(如果指定了“types”属性)/root/src/node_modules/moduleB/index.ts/root/src/node_modules/moduleB/index.tsx/root/src/node_modules/moduleB/index.d.ts/root/node_modules/moduleB.ts/root/node_modules/moduleB.tsx/root/node_modules/moduleB.d.ts/root/node_modules/moduleB/package.json(如果指定了“types”属性)/root/node_modules/moduleB/指数。ts/root/node_modules/moduleB/index.tsx/root/node_modules/moduleB/index.d.ts/node_modules/moduleB.ts/node_modules/moduleB.tsx/node_modules/moduleB.d.ts/node_modules/moduleB/package.json(如果指定了“types”属性)/node_modules/moduleB/index.ts/node_modules/moduleB/index.tsx/node_modules/moduleB/index.d.ts4)当esModuleInteropesModuleInterop为真时,表示importdfrom'支持cjs'引入c的方式ommonjs包会添加__importStar和__importDefault方法来处理commonjs模块转换为esm时的转换。例子:cjs是一个commonjs模块,代码如下:module.exports={name:'cjs'};另一个模块以esm方式引用cjs模块,代码如下:importcjsDefaultfrom'cjs';import*ascjsStarfrom'cjs';console.log('cjsDefault=',cjsDefault);console.log('cjsStar=',cjsStar);输出为:cjsDefault={name:'cjs'}cjsStar={name:'cjs',default:{name:'cjs'}}编译后生成的代码如下:var__importDefault=(this&&this.__importDefault)||函数(mod){返回(mod&&mod.__esModule)?mod:{"default":mod};};var__importStar=(this&&this.__importStar)||函数(mod){如果(mod&&mod.__esModule)返回mod;变量结果={};if(mod!=null)for(varkinmod)if(Object.hasOwnProperty.call(mod,k))result[k]=mod[k];结果[“默认”]=mod;返回结果;};Object.defineProperty(exports,"__esModule",{value:true});constcjs_1=__importDefault(require("cjs"));constcjsStar=__importStar(require("cjs"));控制台。日志('cjsDefault=',cjs_1.default);console.log('cjsStar=',cjsStar);5)当liblib指定编译时要导入的库目标为"ES5"时,默认导入["DOM","ES5","ScriptHost"];当目标为"ES6"时,默认导入["DOM","ES6","DOM.Iterable","ScriptHost"]。由于本例中的TypeScript是在服务端使用的,不需要DOM和ScriptHost,所以lib设置为["ES6"]。6)outDir输出目录,编译好的js文件输出的根目录,默认输出到ts文件所在目录。7)sourceMap是否生成sourcemap文件,使用sourcemap可以在错误信息中显示源代码位置。为了根据sourcemap显示错误信息的源代码位置,还需要在入口文件中引入source-map-support模块,如下:import'source-map-support/register';3、脚本命令入口文件为src/server.ts,package.json中的脚本配置如下:package.json{"scripts":{"dev":"nodemon--watchsrc-ets,tsx--exects-nodesrc/server.ts","build":"tsc","start":"nodedist/server.js"},…}执行npmrundev命令启动开发环境。当src下的文件被修改时,服务会自动重启。执行npmrunbuild命令将编译。由于tsconfig.json中的outDir指定了输出目录为dist,所以编译后的js文件会输出到dist目录下。执行npmrunstart命令启动应用,启动前执行npmrunbuild编译。4.自定义类型TypeScript会自动从node_modules/@types目录下获取模块的类型定义,引用的模块需要安装相应的类型库,如:npminstall@types/koa--save安装后,它会在node_modules/@types目录下找到koa文件夹,里面有koa相关的类型定义文件。引用koa模块时会自动导入node_modules/和node_modules/@types下的koa包。如果模块没有类型库或者扩展模块时需要修改类型定义,那么就需要引入自定义类型。示例:给koa添加bodyparser中间件1.设置typeRootstsconfig.json{"compilerOptions":{...//类型声明文件所在目录"typeRoots":["./node_modules/@types","./src/types"],},"include":["src/**/*"],"exclude":["node_modules","**/*.spec.ts"]}src/types是目录用于存储自定义类型,在本例中,src/types目录已被include包含。如果自定义类型目录没有被include包含,则需要添加到include中。2.写类型定义文件src/types/koa/index.d.tsimport*asKoafrom"koa";declaremodule"koa"{interfaceRequest{body?:object;原始主体:字符串;}}这里是为koa的request对象添加了body和rawBody两个属性,分别用来存放json对象和请求body的原始字符串。3.编写jsonBodyParser插件src/middleware/jsonBodyParser.tsimportKoafrom"koa";functiongetRawBody(ctx:Koa.Context):Promise{returnnewPromise((resolve,reject)=>{try{letpostData:string='';ctx.req.addListener('data',(data)=>{postData+=data;});ctx.req.on('end',()=>{resolve(postData);});}catch(e){console.error('获取正文内容失败',e);reject(e);}})}exportdefaultfunctionjsonBodyParser():Koa.Middleware{returnasync(ctx:Koa.Context,next:Koa.Next)=>{constrawBody:string=awaitgetRawBody(ctx);常量请求:Koa.Request=ctx.request;request.rawBody=rawBody;如果(rawBody){尝试{request.body=JSON.parse(rawBody);}catch(e){request.body={};}}等待下一个();};}jsonBodyParser()会返回一个koa中间件,它会获取请求体的内容,并将Raw内容字符串赋值给ctx.request.rawBody,将requestbody内容json对象赋值给ctx.request.body,由于src/types/koa/index.d.ts自定义类型扩展了Koa.Request的这两个属性,执行npmrunbuild命令,使用tsc编译,可以编译成功。但是在执行npmrundev的时候会提示编译错误,因为ts-node在配置中默认不会根据files,include和exclude加载所有的ts文件,而是根据references和entry文件加载文件依赖项。最简单的方案是在ts-node命令后加上--files参数,意思是根据配置的文件加载ts文件,包含和排除,如下:package.json{"scripts":{"dev":"nodemon--watchsrc-ets,tsx--exects-node--filessrc/server.ts",}}5.解释本文介绍如何在节点服务中使用TypeScript。具体的TypeScript语法规则网上有很多相关资料,这里不再介绍。本文相关代码已提交GitHub参考,项目地址:https://github.com/liulinsp/node-server-typescript-demo。作者:宜信工学院刘林