一、需求及前提

1、说明

文章内容主要来自:egg.js 文档:快速入门,与其差不多但是并不完全一样,也有自己的实践。

实际开发过程中,直接使用 egg-init更合适,官方也是如此推荐,不用 egg-init 主要对 egg 的开发模式和框架进行了解。

对 MVC 尤其是 PHP MVC 框架熟悉的人更容易上手。

2、最终的目录结构:

其中,logsrun 文件夹是框架自动生成的,其他是手动构建的。

1.jpg

3、使用的依赖

yarn add egg egg-view-nunjucks dayjs

其中 dayjs 是日期处理使用的包。

    "dayjs": "^1.7.2",
    "egg": "^2.9.1",
    "egg-bin": "^4.7.1",
    "egg-view-nunjucks": "^2.2.0"

二、Config:config/config.default.js

基础的配置都放在 config/config.default.js 里面,下面用到的配置:

  • keys:cookie 加密字符串
  • view: 视图配置,使用默认的 Nunjucks 模板引擎
  • news: 后面使用的 news 请求的一些配置
  • middleware:中间件配置,注意是数组
// cookie 加密字符串
exports.keys = 'asd';
// 视图配置
exports.view = {
  defaultViewEngine:'nunjucks',
  mapping:{
    '.html':'nunjucks'
  }
};
// 新闻页面变量配置
exports.news = {
  serverUrl:'https://www.v2ex.com/api',
  pageSize:5
};
// 中间件配置
exports.middleware = ['log'];

三、Router:app/router.js

router 是访问的入口文件,app/router.js是定义路由的位置,其中传入的 app 是应用实例。

拿到 router 和 controller 之后,就能够在 router 中直接映射到 Controller。

// 路由
module.exports = app => {
  const {router,controller} = app;
  router.get('/',controller.home.index);
  router.get('/news',controller.news.index);
}

四、Controller:app/controller/home.js

app/controller/ 是创建 Controller 的文件夹,比如我要构建一个首页或者是基础页面的 HomeController

创建 HomeController 首先引入 app.Controller 并且 导出 HomeController

如果熟悉 koa2 的语法,使用 ctx 上下文则非常方便,渲染模板直接 await this.ctx.render('home/index') 即可,这里不过多解释。

const Controller = require('egg').Controller;

class HomeController extends Controller{
  async index(){
    const title = 'Egg.js 手动构建开发骨架示例';
    await this.ctx.render('home/index',{title});
  }
} 

module.exports = HomeController;

五、Service

1、获取数据:app/service/news.js

Service 实际上是数据的获取或者操作结果,可以在 Controller 中通过 this.ctx.service.xxxx() 调用。

比如,我要使用通过 v2ex 的 API 获取新闻列表,使用 egg 自带的 curl 进行网络请求:

const Service = require('egg').Service;

class NewsService extends Service {
  async list(page = 1) {
    const {serverUrl,pageSize} = this.config.news;
    let {data} = await this.ctx.curl(`${serverUrl}/topics/hot.json`,{
      dataType:'json'
    });
    return data;
  }
}

module.exports = NewsService;

2、控制器中使用 service:app/controller/news.js

const Controller = require('egg').Controller;
class NewsController extends Controller{
  async index(){
    const page = this.ctx.query.page || 1;
    const list = await this.ctx.service.news.list();
    // console.log(list.length);
    await this.ctx.render('home/news',{list})
  }
}

module.exports = NewsController;

3、视图中显示新闻列表:app/view/home/news.html

需要注意的是,模板这部分内容,在下面有说明。

{%extends '../base.html'%} {%block title%}新闻页面{%endblock%} {%block stylesheet%} {%endblock%} {%block main%}
<div class="container">
  <h1>新闻</h1>
  <table class="table">
    <thead>
      <tr>
        <th>ID</th>
        <th>标题</th>
        <th>链接</th>
        <th>最近更新</th>
      </tr>
    </thead>
    <tbody>
      {% for item in list %}
      <tr>
        <td>{{ item.id }}</td>
        <td>{{ item.title }}</td>
        <td>
          <a href="{{ item.url }}">{{ item.url }}</a>
        </td>
        <td>{{ helper.dateFormat(item.last_modified) }}</td>
      </tr>
      {% endfor %}
    </tbody>
  </table>
</div>
{%endblock%} {%block script%}
<script>
    $(document).ready(function(){
      addNavbarActiveClass('#newsNav');
    });
  </script>
{%endblock%}

4、效果

2.jpg

六、View:app/service/

有了 controller 如果只是直接显示页面,则需要 视图

egg 能够使用多种模板引擎,只要开发 egg-view-* 的模板插件即可,如果不知道模板引擎有没有支持的插件,可以搜一下:

我自己一直都是使用 nunjucks 的,从使用 express 、koa 的时候就是,很好用,不过也有其不好的地方,这也不多说。

安装模板插件后,需要在配置中启用插件。

egg 中致力于使用插件解决复杂的问题,因此 Config/plugin.js 很重要。

1、开启模板插件:config/plugun.js

exports.nunjucks = {
  enable:true,
  package:'egg-view-nunjucks'
}

2、egg视图配置:config/config.default.js

// 视图配置
exports.view = {
  defaultViewEngine:'nunjucks',
  mapping:{
    '.html':'nunjucks'
  }
};

3、编辑模板:app/view/home.html

模板我使用了 bootstrap V4.0 框架,模板继承上使用了 nunjucks 的模板继承规则,不过多解释。

{%extends '../base.html'%} {%block title%}新闻页面{%endblock%} {%block stylesheet%} 
<style>
  .text-center{
    margin-top: 200px;
  }
</style>
{%endblock%} {%block main%}
<div class="container">
  <div class="text-center">
      <h1>{{title}}</h1>
  </div>
</div>
{%endblock%} {%block script%}
<script>
  $(document).ready(function(){
    addNavbarActiveClass('#homeNav');
  });
</script>
{%endblock%}

4、效果:

3.jpg

七、Middleware:app/middleware/

中间件直接在 app/middleware 中编写即可,需要在 config 中开启中间件(在config/config.default.js中配置)

egg 中间件 和 koa2 差不多,没什么不同的,比如我下面访问记录中间件 app/middleware/log.js

const dayjs = require('dayjs');
// log 中间件
module.exports =  () => {
  return async  (ctx,next) => {
    const {url,method,ip} = ctx.request;
    const time = Date.now();
    console.log(`[${method}] ${url} ${ip} ${dayjs(time).format('YYYY-MM-DD HH:mm:ss')}`);
    await  next();
  }
}

八、扩展 Extend :app/extend/

1、撰写 extend:app/extend/helper.js

nunjucks 的过滤器虽然强大,但其实有时候并不是特别好用,而 egg 的扩展弥补了这一点(只是 Extend 的一种用法)。

比如我下面的日期转换,可以使用 nunjucks 的过滤器实现,但是如果使用 extend 则更方便 app/extend/helper.js

const dayjs = require('dayjs');

// 格式化时间
exports.dateFormat = (date) => {
  date = Number.parseInt(date) * 1000;
  return dayjs(date).format('YYYY-MM-DD HH:mm:ss');
};

2、在模板中使用

上面新闻列表的页面中就已经使用了这个扩展

<td>{{ helper.dateFormat(item.last_modified) }}</td>