TypeScript在node項目中的實踐

来源:https://www.cnblogs.com/jiasm/archive/2018/07/21/9348539.html
-Advertisement-
Play Games

TypeScript在node項目中的實踐 TypeScript可以理解為是JavaScript的一個超集,也就是說涵蓋了所有JavaScript的功能,併在之上有著自己獨特的語法。最近的一個新項目開始了TS的踩坑之旅,現分享一些可以借鑒的套路給大家。 為什麼選擇TS 作為巨硬公司出品的一個靜態強類 ...


TypeScript在node項目中的實踐

TypeScript可以理解為是JavaScript的一個超集,也就是說涵蓋了所有JavaScript的功能,併在之上有著自己獨特的語法。
最近的一個新項目開始了TS的踩坑之旅,現分享一些可以借鑒的套路給大家。

為什麼選擇TS

作為巨硬公司出品的一個靜態強類型編譯型語言,該語言已經出現了幾年的時間了,相信在社區的維護下,已經是一門很穩定的語言。
我們知道,JavaScript是一門動態弱類型解釋型腳本語言,動態帶來了很多的便利,我們可以在代碼運行中隨意的修改變數類型以達到預期目的。
但同時,這是一把雙刃劍,當一個龐大的項目出現在你的面前,面對無比複雜的邏輯,你很難通過代碼看出某個變數是什麼類型,這個變數要做什麼,很可能一不小心就會踩到坑。

而靜態強類型編譯能夠帶來很多的好處,其中最重要的一點就是可以幫助開發人員杜絕一些馬虎大意的問題:
image
圖為rollbar統計的數千個項目中數量最多的前十個異常

不難看出,因為類型不匹配、變數為空導致的異常比你敢承認的次數要多。
譬如

而這一點在TS中得到了很好的改善,任何一個變數的引用,都需要指定自己的類型,而你下邊在代碼中可以用什麼,支持什麼方法,都需要在上邊進行定義:

這個提示會在開發、編譯期來提示給開發者,避免了上線以後發現有問題,再去修改。

另外一個由靜態編譯類型帶來的好處,就是函數簽名。
還是就像上邊所說的,因為是一個動態的腳本語言,所以很難有編輯器能夠在開發期間正確地告訴你所要調用的一個函數需要傳遞什麼參數,函數會返回什麼類型的返回值。

而在TS中,對於一個函數,首先你需要定義所有參數的類型,以及返回值的類型。
這樣在函數被調用時,我們就可以很清晰的看到這個函數的效果:

這是最基礎的、能夠讓程式更加穩定的兩個特性,當然,還有更多的功能在TS中的:TypeScript | Handbook

TypeScript在node中的應用

在TS的官網中,有著大量的示例,其中就找到了Express版本的例子,針對這個稍作修飾,應用在了一個 koa 項目中。

環境依賴

在使用TS之前,需要先準備這些東西:

  1. VS code,同為巨硬公司出品,本身就是TS開發的,遂該編輯器是目前對TS支持度最高的一個
  2. Node.js 推薦8.11版本以上
  3. npm i -g typescript,全局安裝TS,編譯所使用的tsc命令在這裡
  4. npm i -g nodemon,全局安裝nodemon,在tsc編譯後自動刷新伺服器程式

以項目中使用的一些核心依賴:

  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下,主要代碼分為瞭如下結構(依據自己項目的實際情況進行增刪):

#folderdesc
1 controllers 用於處理介面請求,原appsroutes文件夾。
2 middleware 存放了各種中間件、全局 or 自定義的中間件
3 config 各種配置項的位置,包括埠、log路徑、各種巴拉巴拉的常量定義。
4 entity 這裡存放的是所有的實體定義(使用了sequelize進行資料庫操作)。
5 models 使用來自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
}

 

因為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中的一些問題


當前GitHub倉庫中,有2600+的開啟狀態的issues,篩選bug標簽後,依然有900+的存在。
所以很難保證在使用的過程中不會踩坑,但是一個項目擁有這麼多活躍的issues,也能從側面說明這個項目的受歡迎程度。

目前遇到的唯一一個比較尷尬的問題就是:
引用文件路徑一定要寫全。。

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

 

小結

初次嘗試TypeScript,深深的喜歡上了這個語言,雖說也會有一些小小的問題,但還是能客服的:)。
使用一門靜態強類型編譯語言,能夠將很多bug都消滅在開發期間。

基於上述描述的一個簡單示例:代碼倉庫

希望大家玩得開心,如有任何TS相關的問題,歡迎來騷擾。NPM loves U.

TypeScript在node項目中的實踐
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Preface I got an replication error 1236 when I modified the password of a user without starting slave threads of replication.Further more,the user was ...
  • 眾所周知資料庫連接的過程,但是最近面試的人(菜面菜),都說用的SSM框架,但是我問了一下,mybatis是怎麼連接上mysql的,基本上都會說:配置好的,直接用了,今天我來拋磚引玉一下,歡迎拍磚!願你有情人終成眷屬,願你有個有趣的靈魂,願你拍我一磚! ...
  • 我的hiveserver2一直不能啟動,命令行一直卡住不動,然後我就想是不是配置文件沒有配置相關的參數,然後就來修改hive-site.xml 最終修改完後的hive-site.xml: 配置成功後,hiveserver2就能啟動成功了。 ...
  • 轉自:http://www.maomao365.com/?p=6679 摘要: 下文將分享使用sql腳本輸出交替變換的不同背景顏色的sql腳本的方法分享,如下所示: 實驗環境:sqlserver 2008 R2 例: 下文 首先採用 over() row_number 函數生成的行編號, 然後對每行 ...
  • Ionic 需要Node.js 環境,官網下載:http://nodejs.org/,node 命令預設將安裝到 /usr/bin https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distri ...
  • Android設備列印比較麻煩,一般設備廠商都提供原生app開發的SDK,我們web開發者為難了,不會原生開發啊 給大家提供一個思路,實現web加殼,利用列印瀏覽器實現 簡單來說就是把我們的web頁面嵌入瀏覽器中 web頁面的列印功能通過js與瀏覽器互動 瀏覽器通過調用硬體SDK實現列印 1、機器安 ...
  • 揭秘黑客工具教程:微信密碼怎麼破解?如何破解別人的微信密碼? 微信的出現確實給人們的生活帶來了極大的便利,現在人們在生活中不僅使用微信社交,還會在閑來無事時視頻,發一下紅包或者是轉賬。據不完全統計,目前微信在全國共有十億人次在使用,大家在使用的過程中均對其強大的功能予以稱贊。隨著微信功能日益強大,微 ...
  • 1、JSONString轉換為字典 2、JSONString轉換為數組 3、字典轉換為JSONString ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...