Практика TypeScript в Node-проектах

Node.js база данных внешний интерфейс TypeScript

TypeScript можно понимать как надмножество JavaScript, что означает, что он охватывает все функции JavaScript и имеет собственный уникальный синтаксис поверх него.
Недавно новый проект начал пошаговое путешествие TS, теперь он делится некоторыми процедурами, которые могут быть нарисованы для всех.

Почему выбирают ТС

Будучи статическим компилируемым языком со строгой типизацией, созданным гигантской компанией, этот язык существует уже несколько лет, и я считаю, что это очень стабильный язык, поддерживаемый сообществом.
Мы знаем, что JavaScript — это динамический слабо типизированный интерпретируемый скриптовый язык, а динамический язык приносит много удобства.Мы можем изменять тип переменной по желанию для достижения желаемой цели во время выполнения кода.
Но в то же время это палка о двух концах: когда перед вами появляется огромный проект и сталкивается с чрезвычайно сложной логикой, вам сложно понять, что это за переменная и что делать с кодом. Может случайно наступить на яму.

Статическая строго типизированная компиляция может принести много преимуществ, наиболее важным из которых является то, что она может помочь разработчикам избежать некоторых небрежных проблем:

image

На картинке показана десятка аномалий с наибольшим количеством тысяч проектов, подсчитанных роллбаром.

Нетрудно заметить, что исключений больше, чем вы осмеливаетесь допустить из-за несоответствия типов и пустых переменных.
Например


И это было значительно улучшено в TS.Любая ссылка на переменную должна указывать свой собственный тип, а то, что вы можете использовать в коде ниже, и какие методы поддерживаются, должно быть определено выше:

Это приглашение будет выдаваться разработчику в период разработки и компиляции, чтобы избежать проблем, обнаруженных после подключения к сети и последующего изменения.

Еще одним преимуществом статически скомпилированных типов являются сигнатуры функций.
Или, как упоминалось выше, поскольку это динамический язык сценариев, редактору сложно правильно сказать вам, какие параметры нужно передать функции, которую вы хотите вызвать во время разработки, и какой тип возвращаемого значения функция вернет.

В TS для функции сначала нужно определить типы всех параметров и тип возвращаемого значения.
Таким образом, когда функция вызывается, мы можем ясно видеть эффект этой функции:

Это две самые основные функции, которые могут сделать программу более стабильной.Конечно, функций в TS больше:TypeScript | Handbook

Применение TypeScript в узле

На официальном сайте ТС имеется большое количествоПример, в котором нашли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: версия вышеупомянутого плагина Decorator, используемая при определении сущностей.

Структура проекта

Во-первых, освободите структуру текущего проекта:

.
├── 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Ниже основной код разделен на следующую структуру (добавляйте и удаляйте в соответствии с реальной ситуацией вашего собственного проекта):

# folder desc
1 controllers Используется для обработки интерфейсных запросов, исходныйapps,routesпапка.
2 middleware Хранит различное промежуточное ПО, глобальное или пользовательское промежуточное ПО.
3 config Расположение различных элементов конфигурации, включая порты,logПостоянные определения для путей, различные балалы.
4 entity Здесь хранятся все определения сущностей (используя sequenceize для операций с базой данных).
5 models использовать отentityсущность вsequelizeдля завершения операции инициализации иsequelizeБроски предметов.
6 utils Сохраненные общедоступные функции, извлеченные из различных ежедневных разработок
7 types Хранит определения различных настраиваемых составных типов, а также определения различных структур, атрибутов и возвращаемых значений методов (в настоящее время включая широко используемые версии Promise для redis и qconf).

controllers

Контроллеры отвечают только за логику обработки, добавление, удаление, изменение и проверку данных, манипулируя объектом модели, а не базой данных.

Учитывая, что большинство версий 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Множество декораторов приложений, помогающих нам справиться с большей частью нелогического кода.

Определение оригинального роутера:

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

Если это глобальное промежуточное ПО, добавьте его непосредственно в класс.@Middlewareдекоратор и наборtype: 'after|before'Вот и все.
Если это какое-то конкретное промежуточное ПО, просто создайте общий класс, а затем используйтеcontrollerуказано на объекте@UseBefore/@UseAfter(Это может быть написано в классе или в методе).

Все промежуточное ПО должно наследовать соответствующий интерфейс 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

Файл отвечает только за определение модели данных и не выполняет никаких логических операций.

Точно так же, как и с помощью декоратора sequenceize+, сущность используется только для создания модели данных для связи с базой данных.

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
}

Так как для продолжения требуется также соответствующий адрес базы данных, учетная запись, пароль, база данных и другая информация для установления соединения, рекомендуется поместить все объекты одной и той же базы данных в каталог, чтобы упростить загрузку соответствующей модели.
Рекомендуется создать соответствующую информацию о конфигурации в разделе config и добавить столбец для хранения ключа сущности.
Таким образом, когда связь с базой данных установлена ​​и модель данных загружена, все объекты по этому пути могут быть динамически импортированы:

// 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

Позиционирование модели заключается в создании абстрактного объекта базы данных на основе соответствующей сущности.Поскольку используется сиквелизация, файлы в этом каталоге станут очень краткими.
В основном просто инициализируйте объект продолжения и выбросьте его после загрузки модели.

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-адреса некоторых сторонних интерфейсов, конфигурации базы данных и пути журналов.
Статические данные для различных балабал.
Если файлов конфигурации много, рекомендуется разбить их на несколько файлов, а затем следовать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
На все файлы в типах можно ссылаться напрямую, не заботясь об относительных путях (другие обычные модели должны записывать относительные пути, что является неприятной проблемой).

Некоторые проблемы с использованием TS в настоящее время


В текущем репозитории GitHub есть более 2600 открытых проблем, и после фильтрации тегов ошибок остается еще 900+ проблем.
Поэтому сложно гарантировать, что в процессе использования не будет подводных камней, но проект с таким количеством активных вопросов также может объяснить популярность этого проекта со стороны.

Единственная неловкая проблема, с которой я столкнулся до сих пор, это:Путь к справочному файлу должен быть указан полностью. .

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

резюме

Когда я впервые попробовал TypeScript, я влюбился в язык, хотя и будут небольшие проблемы, но я все равно смогу их преодолеть :).
Использование статически строго типизированного скомпилированного языка может устранить многие ошибки во время разработки.

Простой пример, основанный на приведенном выше описании:репозиторий кода

Я надеюсь, что вам всем весело, и если у вас есть какие-либо вопросы, связанные с TS, пожалуйста, не стесняйтесь беспокоить их.NPM loves U..