ホームページ  >  記事  >  ウェブフロントエンド  >  ノードプロジェクトにおけるTypeScriptの実践的な分析

ノードプロジェクトにおけるTypeScriptの実践的な分析

不言
不言オリジナル
2018-07-23 11:32:451535ブラウズ

この記事で共有した内容は、ノードプロジェクトにおける TypeScript の実践的な分析に関するもので、必要な方は参考にしてください。

TypeScript は JavaScript のスーパーセットとして理解できます。つまり、すべての JavaScript 関数をカバーし、その上に独自の独自の構文を持っています。
最近の新しいプロジェクトで TS の落とし穴の旅が始まりました。ここから学ぶことができるいくつかのルーチンを共有したいと思います。

なぜ TS を選ぶのか

この言語は Juhard Company によって作成された静的に強く型付けされたコンパイル言語であり、コミュニティのメンテナンスの下ですでに非常に安定した言語であると思います。
JavaScript は動的に弱い型で解釈されるスクリプト言語であるため、コードの実行中に変数の型を自由に変更して目的の目的を達成できることがわかります。
しかし同時に、これは諸刃の剣でもあります。巨大なプロジェクトが目の前に現れ、非常に複雑なロジックに直面すると、特定の変数がどのような型で、その変数が何を行うのかを理解することが困難になります。誤って穴に足を踏み入れる可能性があります。

静的で強く型付けされたコンパイルは多くの利点をもたらしますが、その中で最も重要なのは、開発者がいくつかの不注意な問題を排除できることです:
ノードプロジェクトにおけるTypeScriptの実践的な分析

この図は、ロールバーによってカウントされた数千のプロジェクトの中で最も多くの上位プロジェクトを示しています。 10 個の例外

型の不一致や null 変数によって引き起こされる例外が、あなたが敢えて認めているよりも多く存在することを理解するのは難しくありません。
例:
ノードプロジェクトにおけるTypeScriptの実践的な分析これは TS で大幅に改善されました。変数への参照は、以下のコードで使用できるものとサポートされるメソッドを上で定義する必要があります。オンラインになった後に問題が発見された後に変更する必要がないように、開発およびコンパイルの段階で開発者に提供されます。
ノードプロジェクトにおけるTypeScriptの実践的な分析静的にコンパイルされた型によってもたらされるもう 1 つの利点は、関数のシグネチャです。

上で述べたように、これは動的スクリプト言語であるため、呼び出したい関数にどのパラメータを渡す必要があるか、関数がどのような戻り値を返すかを開発中にエディタが正確に伝えるのは困難です。


ノードプロジェクトにおけるTypeScriptの実践的な分析 TS では、関数について、最初にすべてのパラメーターの型と戻り値の型を定義する必要があります。

このようにして、関数を呼び出すと、この関数の効果が明確にわかります。



ノードプロジェクトにおけるTypeScriptの実践的な分析これらは、プログラムをより安定させることができる 2 つの最も基本的な機能です。もちろん、TS にはさらに多くの関数があります。 : TypeScript | Handbook

Node での TypeScript のアプリケーション

TS の公式 Web サイトには、Express バージョンのサンプルを含む多数のサンプルがあり、これを少し修正して koa プロジェクトに適用しました。

環境依存関係

TS を使用する前に、以下のものを準備する必要があります:

  1. VS コード

    。これも Juhard Company によって作成され、TS によって開発されたため、このエディタは現在 TS A を最も高度にサポートしています。

  2. Node.js バージョン 8.11 以降を推奨
  3. npm i -g typescript、TS をグローバルにインストール、コンパイルに使用される tsc コマンドはここにあります
  4. npm i -g typescript,全局安装TS,编译所使用的tsc命令在这里

  5. npm i -g nodemon,全局安装nodemon,在tsc编译后自动刷新服务器程序

  • 官方手册

  • 官方Express示例

以及项目中使用的一些核心依赖:

  1. reflect-metadata: 大量装饰器的包都会依赖的一个基础包,用于注入数据

  2. routing-controllers: 使用装饰器的方式来进行koa-router的开发

  3. sequelize: 抽象化的数据库操作

  4. 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 コンパイル後にサーバー プログラムを自動的に更新します🎜🎜
    🎜🎜公式マニュアル🎜🎜🎜🎜公式 Express の例🎜🎜
🎜そして、プロジェクトで使用されるいくつかのコア依存関係: 🎜🎜🎜🎜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 の下で、メインコードは次の構造に分割されます (プロジェクトの実際の状況に応じて追加または削除します): 🎜

|folder|desc

は、インターフェースリクエスト、元のappsroutesフォルダーを処理するために使用されます。 。 2
1 controllerscontrollers 用于处理接口请求,原appsroutes文件夹。
2 middleware 存放了各种中间件、全局 or 自定义的中间件
3 config 各种配置项的位置,包括端口、log路径、各种巴拉巴拉的常量定义。
4 entity 这里存放的是所有的实体定义(使用了sequelize进行数据库操作)。
5 models 使用来自entity中的实体进行sequelize来完成初始化的操作,并将sequelize对象抛出。
6 utils 存放的各种日常开发中提炼出来的公共函数
7 types
ミドルウェア🎜🎜は、さまざまなミドルウェア、グローバルまたはカスタマイズされたミドルウェアを保存します🎜🎜🎜3🎜🎜config 🎜🎜さまざまなミドルウェアの場所ポート、log パス、さまざまな定数定義などの構成アイテム。 🎜🎜 🎜4🎜🎜entity🎜🎜すべてのエンティティ定義はここに保存されます (sequelize はデータベース操作に使用されます)。 🎜🎜 🎜5🎜🎜モデル🎜🎜entity のエンティティを sequelize に使用して初期化操作を完了し、sequelize オブジェクトがスローされます。 🎜🎜 🎜6🎜🎜utils🎜🎜日々の開発から抽出されたさまざまなパブリック関数を保存します🎜🎜 🎜7🎜🎜types🎜🎜の定義を保存しますさまざまなカスタマイズされた複合タイプ、およびさまざまな構造、属性、メソッドの戻り値の定義 (現在、一般的に使用されている Promise バージョンの redis と qconf が含まれます)🎜🎜🎜🎜

controllers

controllers只负责处理逻辑,通过操作model对象,而不是数据库来进行数据的增删改查

鉴于公司绝大部分的Node项目版本都已经升级到了Node 8.11,理所应当的,我们会尝试新的语法。  
也就是说我们会抛弃Generator,拥抱async/await  。

使用KoaExpress写过接口的童鞋应该都知道,当一个项目变得庞大,实际上会产生很多重复的非逻辑代码:

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
})

几乎每一个路由的头部都是在做着获取参数的工作,而参数很可能来自headerbody甚至是cookiequery

所以,我们对原来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下的文件中声明对应的接口进行监听。

middleware

如果是全局的中间件,则直接在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 { }

entity

文件只负责定义数据模型,不做任何逻辑操作

同样的使用了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

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命令
})

utils

所有的公共函数,都放在这里。  
同时推荐编写对应的索引文件(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

configs下边存储的就是各种配置信息了,包括一些第三方接口URL、数据库配置、日志路径。  
各种balabala的静态数据。  
如果配置文件多的话,建议拆分为多个文件,然后按照utils的方式编写索引文件。

types

这里存放的是所有的自定义的类型定义,一些开源社区没有提供的,但是我们用到的第三方插件,需要在这里进行定义,一般来说常用的都会有,但是一些小众的包可能确实没有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则需要写相对路径,这是一个很尴尬的问题)。

目前使用TS中的一些问题

ノードプロジェクトにおけるTypeScriptの実践的な分析当前GitHub仓库中,有2600+的开启状态的issues,筛选bug标签后,依然有900+的存在。  
所以很难保证在使用的过程中不会踩坑,但是一个项目拥有这么多活跃的issues,也能从侧面说明这个项目的受欢迎程度。

目前遇到的唯一一个比较尴尬的问题就是:
引用文件路径一定要写全。。

import module from '../../../../f**k-module'

以上がノードプロジェクトにおけるTypeScriptの実践的な分析の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。