Eggjs学习系列(一) 使用TypeScript快速入门

2023-05-16

Eggjs学习系列(一) 使用TypeScript快速入门

Eggjs是一个node的渐近式开发框架,用于服务端开发。而 TypeScript 是 JavaScript的超集,在兼容 JavaScript的基础上增加了类型检查、智能提示等特性,适用于大规模的企业项目开发。下面是Eggjs在 TypeScript 下的基本实践案例:

快速入门

使用 TypeScript 初始化项目

npm init egg --type=ts
npm i
npm run dev # 运行项目

骨架会生成一个极简版的示例方便我进一步深入开发。

编写Controller

通过 Controller 和 Router 来实现路由的跳转和页面的显示

// app/controller/home.ts
// JavaScript 下使用CommonJs规范的 require 导入
// TypeScript 下使用ES6的 import 导入
import { Controller } from 'egg';

export default class HomeController extends Controller {
  public async index() {
    const { ctx } = this;
    ctx.body = 'hellow world';
  }
}

配置路由映射:

// app/router.ts
import { Application } from 'egg';

export default (app: Application) => {
  const { controller, router } = app;
  // 绑定到 controller 的 index 上
  router.get('/', controller.home.index);
};

修改基本配置

// config/config.default.ts
import { EggAppConfig, EggAppInfo, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
  const config = {} as PowerPartial<EggAppConfig>;

  // 使用 config 添加配置
  // 使用自己的 Cookie 安全字符串
  config.keys = appInfo.name + 'xxxxxxxxxxxxxx_1696';

  // 添加中间件配置
  config.middleware = [];

  // 添加额外的配置
  const bizConfig = {
    sourceUrl: `https://github.com/eggjs/examples/tree/master/${appInfo.name}`,
  };

  // 合并配置后返回配置结果
  return {
    ...config,
    ...bizConfig,
  };
};

其中 PowerPartial 是递归遍历类中的属性,把类中所有属性都改为可选。

export type PowerPartial<T> = {
    [U in keyof T]?: T[U] extends object
    ? PowerPartial<T[U]>
        : T[U]
};
// 例如
// { view: { defaultEngines: string } } => { view?: { defaultEngines?: string } }

在命令行使用 npm run dev 执行命令,之后打开http://127.0.0.1:7001/ 页面,会在页面上显示出 hello world

静态资源

Egg 内置了 static 插件,线上环境建议部署到 CDN,无需该插件。

static 插件默认映射 /public/* -> app/public/* 目录

此处,我们把静态资源都放到 app/public 目录即可:

app/public
├── css
│   └── news.css
└── js
    ├── lib.js
    └── news.js

模板渲染

绝大多数情况,我们都需要读取数据后渲染模板,然后呈现给用户。故我们需要引入对应的模板引擎。框架并不强制你使用某种模板引擎,开发者可以引入不同的插件来实现差异化定制。

例如使用 Nunjucks 来渲染,需要先安装对应插件

$ npm i egg-view-nunjucks --save

开启插件

// config/plugin.ts
import { EggPlugin } from 'egg';

const plugin: EggPlugin = {
  nunjucks: { // 开启插件
    enable: true,
    package: 'egg-view-nunjucks',
  },
};

export default plugin;
// config/config.default.ts
export default (appInfo: EggAppInfo) => {
    const config = {} as PowerPartial<EggAppConfig>;
    config.keys = appInfo.name + '_1586659496760_1696';
	 // 配置 view 
    config.view = {
        defaultViewEngine: 'nunjucks',
        mapping: {
            '.tpl': 'nunjucks',
        },
    };
};

为列表页编写模板文件,一般放置在 app/view 目录下

<!-- app/view/news/list.tpl -->
<html>
    <head>
        <title>Hacker News</title>
        <link rel="stylesheet" href="/public/css/news.css" />
    </head>
    <body>
        <ul class="news-view view">
            {% for item in list %}
            <li class="item">
                <a href="{{ item.url }}">{{ item.title }}</a>
            </li>
            {% endfor %}
        </ul>
    </body>
</html>

添加相应的 Controller 和 Router

// app/controller/news.ts
import { Controller } from 'egg';

export default class NewsController extends Controller {
  public async list() {
    // 返回的数据格式
    const dataList = {
      list: [
        { id: 1, title: 'this is news 1', url: '/news/1' },
        { id: 2, title: 'this is news 2', url: '/news/2' },
      ],
    };
    await this.ctx.render('news/list.tpl', dataList);
  }
}
// app/router.ts
export default (app: Application) => {
  const { controller, router } = app;

  router.get('/', controller.home.index);
  router.get('/news', controller.news.list);
};

这个时候在 router.ts 中使用 controller 时,代码并没有智能提示新创建的 news,这是因为没有在 typings/app/controller/index.d.ts 中给 IController

接口绑定新的 Controller。这时,我们可以手动给 IController 添加,也可以使用 Eggjs 提供的 egg-ts-helper 自动生成配置。

最后,启动浏览器,访问 http://localhost:7001/news 即可看到渲染后的页面。

egg-ts-helper 使用方法

安装egg-ts-helper

npm i egg-ts-helper --save-dev

只够修改 package.json 配置,让 egg-ts-helper 能够在开发期间自动生成对应的 d.ts

{
  "egg": {
    "declarations": true
  },
  "scripts": {
    "dev": "egg-bin dev",
    "test-local": "egg-bin test",
    "clean": "ets clean"
  }
}

之后,使用 npm run dev 运行项目,便会在开发期间自动生成d.ts配置。 生成后配置文件的内容是

// typings/app/controller/index.d.ts
// This file is created by egg-ts-helper@1.25.7
// Do not modify this file!!!!!!!!!
import 'egg';
import ExportHome from '../../../app/controller/home';
import ExportNews from '../../../app/controller/news';

declare module 'egg' {
  interface IController {
    home: ExportHome;
    news: ExportNews;
  }
}

这样,在 router 中使用 controller 使,便会给出 news 提示。

编写 service

在实际应用中,Controller 一般不会自己产出数据,也不会包含复杂的逻辑,复杂的过程应抽象为业务逻辑层 Service。添加一个 Service 抓取数据 ,如下:

// app/service/news.ts
import { Service } from 'egg';
// 新闻的Service类
export default class NewsService extends Service {
    public async list(page = 1) {
        // 读取配置
        const { serverUrl, pageSize } = this.config;

        // 发起请求
        const { data: idList } = await this.ctx.curl(`${serverUrl}/topstories.json`, {
            timeout: 300000,
            data: {
                orderBy: '"$key"',
                startAt: `"${pageSize * (page - 1)}"`,
                endAt: `"${pageSize * page - 1}"`,
            },
            dataType: 'json',
        });

        // 同时发送多个请求
        const newsList = await Promise.all(
            Object.keys(idList).map(key => {
                const url = `${serverUrl}/item/${idList[key]}.json`;
                return this.ctx.curl(url, { dataType: 'json' });
            }),
        );
        return newsList.map(res => res.data);
    }
}

然后稍微修改下之前的 Controller:

// app/controller/news.ts
export default class NewsController extends Controller {
    public async list() {
        const ctx = this.ctx;
        const page = ctx.query.page || 1;
        const newsList = await ctx.service.news.list(page);
        await this.ctx.render('news/list.tpl', { list: newsList });
    }
}

最后,添加请求网址的配置

// config/config.default.ts
export default (appInfo: EggAppInfo) => {
  // 新闻页配置
  const news = {
    pageSize: 5,
    serverUrl: 'https://hacker-news.firebaseio.com/v0',
  };

  // 合并配置
  return {
    // ...
    ...news,
  };
};

编写扩展

扩展内编写一些常用的函数,用于加快开发。

这里,使用 View 插件支持的 Helper 来实现:

npm i moment --save
// 添加扩展
import moment = require('moment');

export default {
  relativeTime(time: number) {
    return moment(new Date(time)).fromNow();
  },
};

在模板里面使用:

<!-- app/view/news/list.tpl -->
{{ helper.relativeTime(item.time) }}

编写 Middleware

通过编写中间件,来扩展请求的处理。假设有个需求:我们的新闻站点,禁止百度爬虫访问。这种需求就可以通过 Middleware 实现

// app/middleware/robot.ts
// 中间件
import { Context, Options } from 'egg';

// 自定义的中间件
export default function robotMiddleware(options: Options): any {
  return async (ctx: Context, next: () => Promise<any>) => {
    const source = ctx.get('user-agent') || '';
    const match = options.ua.some(ua => ua.test(source));
    if (match) {
      ctx.status = 403;
      ctx.message = 'Go away, robot.';
    }
    await next();
  };
}

在 Config 添加配置

export default (appInfo: EggAppInfo) => {
    // ...
    // 使用 robot 中间件, robot为中间件的文件名
    config.middleware = [
        'robot',
    ];

    // 额外的配置
    const bizConfig = {
        // 指定匹配百度爬虫
        robot: {
            ua: [
                /Baiduspider/i,
            ],
        },
    };

    // 新闻页配置
    const news = {
        pageSize: 5,
        serverUrl: 'https://hacker-news.firebaseio.com/v0',
    };

    return {
        ...config,
        ...bizConfig,
        ...news,
    };
}

最后,为了使用 TypeScript 的 只能提示,在 typings 中给egg添加 Options 类型

// typings/index.d.ts
import 'egg';
declare module 'egg' {
    interface Options {
        ua: Array<RegExp>
    }
}

注意:Middleware 目前返回值必须都是 any,否则使用 route.get/all 等方法的时候因为 Koa 的 IRouteContext 和 Egg 自身的 Context 不兼容导致编译报错。

现在可以使用 curl http://localhost:7001/news -A "Baiduspider" 看看效果。

配置文件

框架提供了强大的配置合并管理功能:

  • 支持按环境变量加载不同的配置文件,如 config.local.jsconfig.prod.js 等等。
  • 应用/插件/框架都可以配置自己的配置文件,框架将按顺序合并加载。
  • 配置在使用的时候支持多级提示,并自动关联
// app/config/config.default.ts
import { EggAppInfo, EggAppConfig, PowerPartial } from 'egg';

export default (appInfo: EggAppInfo) => {
  const config = {} as PowerPartial<EggAppConfig>;
  // 覆盖框架,插件的配置
  config.keys = appInfo.name + '123456';
  config.view = {
    defaultViewEngine: 'nunjucks',
    mapping: {
      '.tpl': 'nunjucks',
    },
  };

  // 应用本身的配置
  const bizConfig = {};
  bizConfig.news = {
    pageSize: 30,
    serverUrl: 'https://hacker-news.firebaseio.com/v0',
  };

  // 目的是将业务配置属性合并到 EggAppConfig 中返回
  return {
    // 如果直接返回 config ,将该类型合并到 EggAppConfig 的时候可能会出现 circulate type 错误。
    ...config as {},
    ...bizConfig,
  };
};

EggAppConfig 合并 config.default.ts 的类型后,在其他 config.{env}.ts 中这么写就也可以获得在 config.default.ts 定义的自定义配置的智能提示:

// app/config/config.local.ts
import { EggAppConfig, } from 'egg';

export default () => {
  const config = {} as PowerPartial<EggAppConfig>;
  // 这里就可以获得 news 的智能提示了
  config.news = {
    pageSize: 20,
  };
  return config;
};

单元测试

单元测试用于测试代码功能是否完善,并保证不同版本情况下代码执行结果的一致性。

测试文件应该放在项目根目录下的 test 目录下,并以 test.js 为后缀名,即 {app_root}/test/**/*.test.ts

// test/app/middleware/robot.test.ts
import { app } from 'egg-mock/bootstrap';

describe('test/app/middleware/robot.test.js', () => {
  it('should block robot', () => {
    return app.httpRequest()
      .get('/')
      .set('User-Agent', 'Baiduspider')
      .expect(403);
  });
});

执行测试

npm test
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Eggjs学习系列(一) 使用TypeScript快速入门 的相关文章

  • uniapp 发布网站遇到的问题(跨域,nginx代理失败,index无法打开,手机端无法访问等)

    跨域 如果开发的应用直接是作为手机APP是不存在跨域问题的 xff0c 但是如果是网站形式就要考虑这个问题了 分为两点 xff1a 1 调试时 可通过设置maintest 2 发布后 可通过Nginx配置文件设置代理 nginx代理失败 1
  • 怎么在linux上安装vnc

    1 首先检查是否安装了VNC服务 输入命令 xff1a rpm qa grep vnc 2 安装VNC xff0c 首次执行vncserver需要设置密码 xff0c 可以创建多个桌面 xff0c 执行多次vncserver命令即可 roo
  • VNC修改端口号

    1 vnc的默认端口是自己配置的 xff0c 想要修改vncserver的配置 xff0c 需要先找配置文件路径 root 64 node04 which vncserver usr bin vncserver 2 通过查找以前配置的端口
  • onNewIntent使用遇到的坑

    onCreate是用来创建一个Activity也就是创建一个窗体 xff0c 但一个Activty处于任务栈的顶端 xff0c 若再次调用startActivity去创建它 xff0c 则不会再次创建 若你想利用已有的Acivity去处理别
  • CentOS7使用firewall-cmd打开关闭防火墙与端口

    一 centos7版本对防火墙进行加强 不再使用原来的iptables 启用firewalld 1 firewalld的基本使用 启动 xff1a systemctl start firewalld 查状态 xff1a systemctl
  • 算法数学基础-排列组合(题目取自牛客网)

    基础理论 xff1a 排列 有限集的子集按某种条件的序化法排成列 排成一圈 不许重复或许重复等 从n个不同元素中每次取出m xff08 1 m n xff09 个不同元素 xff0c 排成一列 xff0c 称为从n个元素中取出m个元素的无重
  • 关于对Spring框架的详解

    Spring框架 基本概念Spring的形成主要模块三层架构Spring的优点对于Spring 框架中都用到设计模式 xff1a 基本概念 Spring框架是由于软件开发的复杂性而创建的 Spring使用的是基本的JavaBean来完成以前
  • vs2015 提示严重性代码说明项目文件行禁止显示状态 错误C4996 'scanf': This function or variable may be unsafe.

    在 vs2015 中使用scanf时总是提示 unsafe 报出错误 xff0c 解决办法是在已经建立的项目中 xff0c 以后可能需要经常进行再不同的项目进行这项操作 xff1a 项目 gt 属性 gt c c 43 43 gt 常规 g
  • base7. ThreadPool线程池类——生产者与消费者模型

    ThreadPool类图 数据成员 xff1a MutexLock mutex xff1a 一个MutexLock类型的互斥变量mutex Condition cond xff1a 一个Condition类型的条件变量cond string
  • Ubuntu下anaconda中PyCharm遇到的环境问题

    Table of Contents 运行找不到包文件 可以run xff0c 不能debug 运行找不到包文件 解救办法 xff1a 将包的路径添加到interpreter中 可以run xff0c 不能debug 去掉setting中py
  • origin画图——同一图中多组数据

    origin画图 同一图中多组数据 xff08 2维 xff09 导入数据分别确定X Y轴列数据 xff08 选中列 set as X Y xff0c 全选数据 xff0c 点击下方绘图图标
  • pacman用法

    Pacman 是archlinux 下的包管理软件 它将一个简单的二进制包格式和易用的构建系统结合了起来 不管软件包是来自官方的 Arch 库还是用户自己创建 xff0c Pacman 都能方便得管理 pacman Sy abc 和源同步后
  • Spring注解-1-SpringBoot是如何处理注解的

    本文基于Spring 5 2 7 这是个很大的话题 xff0c 但是是个非常实在的话题 xff0c 注解天天用 xff0c 处处用 xff0c 请问你知道他是怎么起作用的吗 xff1f 如果你使用了注解 xff0c 那么一定有代码在什么地方
  • Qt对当前界面进行截图并保存

    Qt提供了对界面截图的功能 xff0c 非常简单 xff0c 只需要2行即可截图并保存下来 xff0c 如下 xff1a QPixmap pixMap 61 QPixmap grabWidget this pixMap save 34 my
  • Qt中通过Qpixmap设置图片透明度

    最近看到美图秀秀的一些功能 xff0c 可以手动设置图片的透明度并显示在其它图片上 xff0c 所以自己动手做了个小Demo xff0c 实际效果如下 xff1a xff08 图片仅供参考使用 xff09 可以看到拖动下方进度条 xff0c
  • QTableWidget中添加QComboBox/QPushButton控件并响应控件点击

    QTableWidget是QT程序中常用的显示数据表格的空间 xff0c 里面不仅仅可以添加文字 xff0c 也可以添加控件 xff0c 图片等等 xff0c 此处以添加QCombobox和QPushButton举例 xff0c 点击选择控
  • QMap的简单使用(增删改查等)

    前言 QMap是Qt提供的容器类 xff0c 是一种由key到value的映射 与C 43 43 中STL提供的map使用方法相同 xff0c 部分使用细节上 xff0c QMap做了优化 插入 QMap lt int QString gt
  • vector<char>与char*相互转换

    最近在做网络通信 xff0c 看到很多人使用vector lt char gt 来存储二进制网络数据 xff0c 而我有时需要用char 数据 xff0c 所以记录下二者的相互转换方法 xff0c 代码如下 xff1a include lt
  • Qt中自定义qDebug打印信息的宏(文件名,行数,时间等等)

    前言 QDebug类提供用于调试信息的输出流 当开发人员需要将调试或跟踪信息写到设备 xff0c 文件 xff0c 字符串或控制台时 xff0c 将使用QDebug 当我们使用qDebug 打印信息时 xff0c 只有我们所需要打印的信息
  • QT实现鼠标右键快捷菜单(QTableWidget)

    对于右键点击事件 xff0c 这里用到的是Qt CustomContextMenu方法 xff0c 本例通过右点QTableWidget xff0c 显示下拉菜单 xff0c 同时获得点击的行号 ui界面如下图 xff1a customCo

随机推荐

  • git使用遇到问题

    文中自己理解的偏多 xff0c 有错误的地方还请指正 xff08 会有很多错误 xff09 commit in detached head 解决办法 xff1a 进入到工程所在路径运行 git branch f master HEAD am
  • 从零开始学写脚本【第一天】

    废话我就不多说了 xff0c 直接开干 新建一个项目 安装 selenium selenium是浏览器自动化测试工具 控制台输入 pip install selenium 下载Chromedriver 浏览器驱动 xff0c 代码打开浏览器
  • 超简单图文并茂基于Linux使用Docker部署Node.js项目

    一看就懂图文并茂基于CentOS Linux release 7 8 2003 Core 安装并Docker化你的Node js应用 64 TOC 基于CentOS Linux release 7 8 2003 Core 安装并使用Dock
  • ERROR 1064 (42000): You have an error in your SQL syntax; check the manual that c

    1 ERROR 1064 42000 You have an error in your SQL syntax check the manual that corresponds to your MySQL server version f
  • Android 电量监控、关机、重启功能的实现

    本文主要是介绍Android电量监控 关机 重启功能的实现 xff0c 需要具备的条件是手机需要root过 xff0c 才能实现相关操作 1 MainActivity java import java io IOException impo
  • 拓扑排序1图文详解面试常考算法 —— 拓扑排序

    前言 Topological sort 又称 Topological order xff0c 这个名字有点迷惑性 xff0c 因为拓扑排序并不是一个纯粹的排序算法 xff0c 它只是针对某一类图 xff0c 找到一个可以执行的线性顺序 这个
  • Android签名总结

    一 为什么要签名 开发Android 的人这么多 xff0c 完全有可能大家都把类名 xff0c 包名起成了一个同样的名字 xff0c 这时候如何区分 xff1f 签名这时候就是起区分作用的 由于开发商可能通过使用相同的Package Na
  • 消息队列技术介绍

    一 消息队列概述 消息队列中间件是分布式系统中重要的组件 xff0c 主要解决应用耦合 异步消息 流量削锋等问题 实现高性能 高可用 可伸缩和最终一致性架构 是大型分布式系统不可缺少的中间件 目前在生产环境 xff0c 使用较多的消息队列有
  • Ubuntu 循环登录 解决办法

    Ubuntu 经常出现循环登录的情况 但需要根据不同原因 采用不同的解决方案 已知情况有 Xauthority 权限变为root 修改到自己账户权限即可nvidia 显卡驱动问题 卸载重装即可 Xauthority 权限问题 参考Ubunt
  • Kotlin基础(一)android studio中配置Kotlin

    1 何为Kotlin xff1f Kotlin是一门运行在JVM之上的语言 它由Jetbrains创建 xff0c 而Jetbrains则是诸多强大的工具 xff08 如知名的Java IDE IntelliJ IDEA xff09 背后的
  • 关于Ubuntu18.04 root账户登录的问题

    关于Ubuntu18 04 root账户登录的问题 一 Ubuntu 18 04添加root用户登录1 设置root用户2 修改 root profile3 修改 96 etc pam d 96 目录下的 96 gdm autologin
  • Ubuntu下fcitx崩溃,搜狗输入法乱码

    转载 xff1a https www findhao net res 786 预防原文删除 xff0c 侵删 方法 直接重启fcitx即可 xff1a fictx自带的重启 fcitx r 或者执行以下三条 xff1a 获得fcitx的进程
  • KVM虚拟化

    KVM虚拟化 文章目录 KVM虚拟化虚拟化简介 KVMKVM部署CPU虚拟化功能kvm管理界面安装 虚拟化简介 虚拟化 xff1a 在一台计算机上虚拟出多个逻辑的计算机 xff0c 而且每个逻辑计算机它可以是不同的操作系统 虚拟化技术 xf
  • Android系统Camera图片反转的一个问题

    一 问题提出 目前遇到项目问题 xff0c Camera预览图像是反的 xff0c 于是考虑设置180度反转以便正常 通过如下两种方式 xff1a params setRotation 180 java部分 p set CameraPara
  • I2C总线的SDA和SCL

    串行数据线SDA 负责在设备间传输串行数据 串行时钟线SCL 负责产生同步时钟脉冲 SCL SDA是I2C总线的信号线 I2C总线是共享的总线系统 xff0c 因此可以将多个I2C设备连接到该系统上 连接到I2C中总线上的设备既可以用作主设
  • ubuntu18.04输入密码登录不进去一直循环

    我是把ubuntu分辨率调了之后变成这样的 其实这个解决方法我也很无语 自己乱按弄到的 就是在登录的隔壁 xff0c 勾选第二个就可以了QUQ 评论区所知 xff1a 选择这个话 xff0c 会关掉nvidia显卡 选择wayland进入后
  • rhce2

    1 配置chrony时间服务器 xff0c 确保客户端主机能和服务主机同步时间 两台机器 第一台机器作为时间服务器从ntp aliyun com同步时间 xff08 注意包含意外情况 xff0c 如果不能上外网 xff0c 不能从阿里云同步
  • FFmpeg Android编译运行出现 Abort message: 'JniInvocation instance already initialized'

    signal 5 SIGTRAP code 1 TRAP BRKPT fault addr 0x272000000d03 Abort message JniInvocation instance already initialized 把
  • layui图标用法总结

    本文参考官方文档 layui图标使用官方文档 xff0c 建议先看此文章 xff0c 并结合本文的前两个步骤使用即可 1 下载layui js相关文档 xff0c layui js下载 xff0c 下载之后里面的内容如下 xff1a 只需要
  • Eggjs学习系列(一) 使用TypeScript快速入门

    Eggjs学习系列 xff08 一 xff09 使用TypeScript快速入门 Eggjs是一个node的渐近式开发框架 xff0c 用于服务端开发 而 TypeScript 是 JavaScript的超集 xff0c 在兼容 JavaS