이 기사에서 공유한 내용은 노드 프로젝트의 TypeScript에 대한 실제 분석에 관한 것입니다. 도움이 필요한 친구들이 참고할 수 있습니다.
TypeScript는 JavaScript의 상위 집합으로 이해될 수 있습니다. 즉, 모든 JavaScript 기능을 포함하고 그 위에 고유한 구문이 있다는 의미입니다.
최근 새로운 프로젝트로 TS의 함정 여정이 시작되었습니다. 여기서 배울 수 있는 몇 가지 루틴을 여러분과 공유하고 싶습니다.
Juhard Company에서 제작한 정적으로 강력한 형식의 컴파일 언어인 이 언어는 커뮤니티의 유지 관리 하에 이미 매우 안정적인 언어로 사용되어 왔습니다.
JavaScript는 동적으로 약한 유형으로 해석되는 스크립팅 언어라는 것을 알고 있습니다. 역동성은 원하는 목적을 달성하기 위해 코드 실행 중에 변수 유형을 마음대로 수정할 수 있습니다.
그러나 동시에 이것은 양날의 검이기도 합니다. 거대한 프로젝트가 눈앞에 나타나고 극도로 복잡한 논리에 직면했을 때 특정 변수가 어떤 유형인지, 이 변수가 무엇을 하는지 알기가 어렵습니다. 실수로 구덩이에 들어갈 가능성이 매우 높습니다.
정적 강력한 형식의 컴파일은 많은 이점을 가져올 수 있으며, 그 중 가장 중요한 것은 개발자가 부주의한 문제를 제거하는 데 도움이 될 수 있다는 것입니다.
그림은 롤바로 계산된 수천 개의 프로젝트 중에서 가장 많은 상위 프로젝트 수를 보여줍니다. 10가지 예외
유형 불일치 및 null 변수로 인해 감히 인정하는 것보다 더 많은 예외가 있다는 것을 아는 것은 어렵지 않습니다.
예를 들어
TS에서는 이것이 크게 개선되었습니다. 변수에 대한 모든 참조는 아래 코드에서 사용할 수 있는 것과 지원되는 메서드를 위에서 정의해야 합니다. 온라인에 접속한 후 문제가 발견된 후 수정할 필요가 없도록 개발 및 컴파일 단계에서 개발자에게 제공됩니다.
정적으로 컴파일된 유형의 또 다른 이점은 함수 서명입니다.
TS에서는 함수의 경우 먼저 모든 매개변수의 유형과 반환 값의 유형을 정의해야 합니다.
이런 식으로 함수가 호출되면 이 함수의 효과를 명확하게 볼 수 있습니다.
이것은 프로그램을 더욱 안정적으로 만들 수 있는 가장 기본적인 두 가지 기능입니다. 물론 TS에는 더 많은 기능이 있습니다. : TypeScript | Handbook
노드에 TypeScript 적용
환경 종속성
역시 Juhard Company에서 제작하고 TS에서 개발했기 때문에 이 편집기는 현재 TS A에 대한 최고 지원을 제공합니다.
npm i -g typescript
, TS를 전역적으로 설치하고 컴파일에 사용되는 tsc 명령은 여기에 있습니다npm i -g typescript
,全局安装TS,编译所使用的tsc命令在这里
npm i -g nodemon
,全局安装nodemon,在tsc编译后自动刷新服务器程序
官方手册
官方Express示例
以及项目中使用的一些核心依赖:
reflect-metadata
: 大量装饰器的包都会依赖的一个基础包,用于注入数据
routing-controllers
: 使用装饰器的方式来进行koa-router的开发
sequelize
: 抽象化的数据库操作
sequelize-typescript
: 上述插件的装饰器版本,定义实体时使用
首先,放出目前项目的结构:
. ├── README.md ├── copy-static-assets.ts ├── nodemon.json ├── package-lock.json ├── package.json ├── dist ├── src │ ├── config │ ├── controllers │ ├── entity │ ├── models │ ├── middleware │ ├── public │ ├── app.ts │ ├── server.ts │ ├── types │ └── utils ├── tsconfig.json └── tslint.json
src
为主要开发目录,所有的TS代码都在这里边,在经过编译过后,会生成一个与src
同级的dist
文件夹,这个文件夹是node
引擎实际运行的代码。
在src
npm i -g nodemon
, nodemon을 전체적으로 설치하고 tsc 컴파일 후 자동으로 서버 프로그램을 새로 고침🎜🎜reflect-metadata
: 많은 수의 데코레이터 패키지가 데이터 주입을 위해 의존하는 기본 패키지🎜🎜🎜 🎜 routing-controllers
: 데코레이터를 사용하여 koa-router🎜🎜🎜🎜sequelize
: 추상 데이터베이스 작업🎜🎜🎜🎜sequelize -typescript
: 엔터티를 정의할 때 사용되는 위 플러그인의 데코레이터 버전 🎜🎜🎜프로젝트 구조🎜🎜먼저 현재 프로젝트의 구조를 해제합니다. 🎜router.get('/', ctx => {}) router.get('/page1', ctx => {}) router.get('/page2', ctx => {}) router.get('/page3', ctx => {}) router.get('/pageN', ctx => {})
src
가 주요 프로젝트입니다. 디렉토리에 모든 TS 코드가 있습니다. 컴파일 후 src
와 동일한 수준의 dist
폴더가 생성됩니다. 이 폴더는 노드 엔진이 실제로 실행됩니다. 🎜<code>src
아래의 메인 코드는 다음과 같은 구조로 나누어져 있습니다(프로젝트의 실제 상황에 따라 추가 또는 삭제): 🎜1 |
controllers controllers
|
用于处理接口请求,原apps 、routes 文件夹。 |
2 | middleware |
存放了各种中间件、全局 or 自定义的中间件 |
3 | config |
各种配置项的位置,包括端口、log 路径、各种巴拉巴拉的常量定义。 |
4 | entity |
这里存放的是所有的实体定义(使用了sequelize进行数据库操作)。 |
5 | models |
使用来自entity 中的实体进行sequelize 来完成初始化的操作,并将sequelize 对象抛出。 |
6 | utils |
存放的各种日常开发中提炼出来的公共函数 |
7 | types | 는 인터페이스 요청, 원래 |
미들웨어
🎜🎜 다양한 미들웨어, 전역 또는 맞춤형 미들웨어를 저장합니다.🎜🎜config
🎜🎜다양한 미들웨어의 위치 포트, log
경로 및 다양한 상수 정의를 포함한 구성 항목. 🎜🎜entity
🎜🎜모든 엔터티 정의가 여기에 저장됩니다(sequelize는 데이터베이스 작업에 사용됩니다). 🎜🎜모델
🎜🎜entity
의 엔터티를 사용하여 sequelize
하여 초기화 작업을 완료하고 sequelize
객체를 던졌습니다. 🎜🎜utils
🎜🎜일상 개발에서 추출한 다양한 공용 함수를 저장합니다.🎜🎜유형
🎜🎜 다양한 맞춤형 복합 유형 및 다양한 구조, 속성 및 메소드 반환 값의 정의(현재 일반적으로 사용되는 redis 및 qconf의 Promise 버전 포함)🎜🎜🎜🎜controllers只负责处理逻辑,通过操作model对象,而不是数据库来进行数据的增删改查
鉴于公司绝大部分的Node项目版本都已经升级到了Node 8.11
,理所应当的,我们会尝试新的语法。
也就是说我们会抛弃Generator
,拥抱async
/await
。
使用Koa
、Express
写过接口的童鞋应该都知道,当一个项目变得庞大,实际上会产生很多重复的非逻辑代码:
router.get('/', ctx => {}) router.get('/page1', ctx => {}) router.get('/page2', ctx => {}) router.get('/page3', ctx => {}) router.get('/pageN', ctx => {})
而在每个路由监听中,又做着大量重复的工作:
router.get('/', ctx => { let uid = Number(ctx.cookies.get('uid')) let device = ctx.headers['device'] || 'ios' let { tel, name } = ctx.query })
几乎每一个路由的头部都是在做着获取参数的工作,而参数很可能来自header
、body
甚至是cookie
及query
。
所以,我们对原来koa的使用方法进行了一个较大的改动,并使用routing-controllers大量的应用装饰器来帮助我们处理大部分的非逻辑代码。
原有router的定义:
module.exports = function (router) { router.get('/', function* (next) { let uid = Number(this.cookies.get('uid')) let device = this.headers['device'] this.body = { code: 200 } }) }
使用了TypeScript与装饰器的定义:
@Controller export default class { @Get('/') async index ( @CookieParam('uid') uid: number, @HeaderParam('device') device: string ) { return { code: 200 } } }
为了使接口更易于检索、更清晰,所以我们抛弃了原有的bd-router
的功能(依据文件路径作为接口路径、TS中的文件路径仅用于文件分层)。
直接在controllers
下的文件中声明对应的接口进行监听。
如果是全局的中间件,则直接在class上添加@Middleware
装饰器,并设置type: 'after|before'
即可。
如果是特定的一些中间件,则创建一个普通的class即可,然后在需要使用的controller
对象上指定@UseBefore
/@UseAfter
(可以写在class上,也可以写在method上)。
所有的中间件都需要继承对应的MiddlewareInterface接口,并需要实现use
方法
// middleware/xxx.ts import {ExpressMiddlewareInterface} from "../../src/driver/express/ExpressMiddlewareInterface" export class CompressionMiddleware implements KoaMiddlewareInterface { use(request: any, response: any, next?: Function): any { console.log("hello compression ...") next() } } // controllers/xxx.ts @UseBefore(CompressionMiddleware) export default class { }
文件只负责定义数据模型,不做任何逻辑操作
同样的使用了sequelize+装饰器的方式,entity只是用来建立与数据库之间通讯的数据模型。
import { Model, Table, Column } from 'sequelize-typescript' @Table({ tableName: 'user_info_test' }) export default class UserInfo extends Model<userinfo> { @Column({ comment: '自增ID', autoIncrement: true, primaryKey: true }) uid: number @Column({ comment: '姓名' }) name: string @Column({ comment: '年龄', defaultValue: 0 }) age: number @Column({ comment: '性别' }) gender: number }</userinfo>
因为sequelize建立连接也是需要对应的数据库地址、账户、密码、database等信息、所以推荐将同一个数据库的所有实体放在一个目录下,方便sequelize加载对应的模型
同步的推荐在config下创建对应的配置信息,并添加一列用于存放实体的key。
这样在建立数据库链接,加载数据模型时就可以动态的导入该路径下的所有实体:
// config.ts export const config = { // ... mysql1: { // ... config + entity: 'entity1' // 添加一列用来标识是什么实体的key }, mysql2: { // ... config + entity: 'entity2' // 添加一列用来标识是什么实体的key } // ... } // utils/mysql.ts new Sequelize({ // ... modelPath: [path.reolve(__dirname, `../entity/${config.mysql1.entity}`)] // ... })
model的定位在于根据对应的实体创建抽象化的数据库对象,因为使用了sequelize,所以该目录下的文件会变得非常简洁。
基本就是初始化sequelize对象,并在加载模型后将其抛出。
export default new Sequelize({ host: '127.0.0.1', database: 'database', username: 'user', password: 'password', dialect: 'mysql', // 或者一些其他的数据库 modelPaths: [path.resolve(__dirname, `../entity/${configs.mysql1.entity}`)], // 加载我们的实体 pool: { // 连接池的一些相关配置 max: 5, min: 0, acquire: 30000, idle: 10000 }, operatorsAliases: false, logging: true // true会在控制台打印每次sequelize操作时对应的SQL命令 })
所有的公共函数,都放在这里。
同时推荐编写对应的索引文件(index.ts),大致的格式如下:
// utils/get-uid.ts export default function (): number { return 123 } // utils/number-comma.ts export default function(): string { return '1,234' } // utils/index.ts export {default as getUid} from './get-uid' export {default as numberComma} from './number-comma'
每添加一个新的util
,就去index
中添加对应的索引,这样带来的好处就是可以通过一行来引入所有想引入的utils
:
import {getUid, numberComma} from './utils'
configs下边存储的就是各种配置信息了,包括一些第三方接口URL、数据库配置、日志路径。
各种balabala的静态数据。
如果配置文件多的话,建议拆分为多个文件,然后按照utils
的方式编写索引文件。
这里存放的是所有的自定义的类型定义,一些开源社区没有提供的,但是我们用到的第三方插件,需要在这里进行定义,一般来说常用的都会有,但是一些小众的包可能确实没有TS的支持,例如我们有使用的一个node-qconf
:
// types/node-qconf.d.ts export function getConf(path: string): string | null export function getBatchKeys(path: string): string[] | null export function getBatchConf(path: string): string | null export function getAllHost(path: string): string[] | null export function getHost(path: string): string | null
类型定义的文件规定后缀为 .d.ts
types下边的所有文件可以直接引用,而不用关心相对路径的问题(其他普通的model则需要写相对路径,这是一个很尴尬的问题)。
当前GitHub仓库中,有2600+的开启状态的issues,筛选bug标签后,依然有900+的存在。
所以很难保证在使用的过程中不会踩坑,但是一个项目拥有这么多活跃的issues,也能从侧面说明这个项目的受欢迎程度。
目前遇到的唯一一个比较尴尬的问题就是:
引用文件路径一定要写全。。
import module from '../../../../f**k-module'
위 내용은 노드 프로젝트에서 TypeScript의 실제 분석의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!