絕大多數(shù)情況,我們都需要讀取數(shù)據(jù)后渲染模板,然后呈現(xiàn)給用戶。故我們需要引入對(duì)應(yīng)的模板引擎。
框架內(nèi)置 egg-view 作為模板解決方案,并支持多模板渲染,每個(gè)模板引擎都以插件的方式引入,但保持渲染的 API 一致。如果想更深入的了解,可以查看模板插件開(kāi)發(fā)。
以下以官方支持的 View 插件 egg-view-nunjucks 為例
引入 view 插件
$ npm i egg-view-nunjucks --save
|
啟用插件
// config/plugin.js exports.nunjucks = { enable: true, package: 'egg-view-nunjucks', };
|
配置插件
egg-view 提供了 config.view 通用配置
root {String}
模板文件的根目錄,為絕對(duì)路徑,默認(rèn)為 ${baseDir}/app/view。支持配置多個(gè)目錄,以 , 分割,會(huì)從多個(gè)目錄查找文件。
如下示例演示了如何配置多個(gè) view 目錄:
// config/config.default.js const path = require('path'); module.exports = appInfo => { const config = {}; config.view = { root: [ path.join(appInfo.baseDir, 'app/view'), path.join(appInfo.baseDir, 'path/to/another'), ].join(',') }; return config; };
|
cache {Boolean}
模板路徑緩存,默認(rèn)開(kāi)啟。框架會(huì)根據(jù) root 配置的目錄依次查找,如果匹配則會(huì)緩存文件路徑,下次渲染相同路徑時(shí)不會(huì)重新查找。
mapping 和 defaultViewEngine
每個(gè)模板在注冊(cè)時(shí)都會(huì)指定一個(gè)模板名(viewEngineName),在使用時(shí)需要根據(jù)后綴來(lái)匹配模板名,比如指定 .nj 后綴的文件使用 Nunjucks 進(jìn)行渲染。
module.exports = { view: { mapping: { '.nj': 'nunjucks', }, }, };
|
調(diào)用 render 渲染文件時(shí),會(huì)根據(jù)上述配置的后綴名去尋找對(duì)應(yīng)的模板引擎。
await ctx.render('home.nj');
|
必須配置文件后綴和模板引擎的映射,否則無(wú)法找到對(duì)應(yīng)的模板引擎,但是可以使用 defaultViewEngine 做全局配置。
// config/config.default.js module.exports = { view: { defaultViewEngine: 'nunjucks', }, };
|
如果根據(jù)文件后綴沒(méi)有找到對(duì)應(yīng)的模板引擎,會(huì)使用默認(rèn)的模板引擎進(jìn)行渲染。對(duì)于只使用一種模板引擎的應(yīng)用,建議配置此選項(xiàng)。
defaultExtension
一般在調(diào)用 render 時(shí)的第一個(gè)參數(shù)需要包含文件后綴,如果配置了 defaultExtension 可以省略后綴。
// config/config.default.js module.exports = { view: { defaultExtension: '.nj', }, };
// render app/view/home.nj await ctx.render('home');
|
渲染頁(yè)面
框架在 Context 上提供了 3 個(gè)接口,返回值均為 Promise:
- render(name, locals) 渲染模板文件, 并賦值給 ctx.body
- renderView(name, locals) 渲染模板文件, 僅返回不賦值
- renderString(tpl, locals) 渲染模板字符串, 僅返回不賦值
// {app_root}/app/controller/home.js class HomeController extends Controller { async index() { const data = { name: 'egg' };
// render a template, path relate to `app/view` await ctx.render('home/index.tpl', data);
// or manually set render result to ctx.body ctx.body = await ctx.renderView('path/to/file.tpl', data);
// or render string directly ctx.body = await ctx.renderString('hi, {{ name }}', data, { viewEngine: 'nunjucks', }); } }
|
當(dāng)使用 renderString 時(shí)需要指定模板引擎,如果已經(jīng)定義 defaultViewEngine 這里可以省略。
Locals
在渲染頁(yè)面的過(guò)程中,我們通常需要一個(gè)變量來(lái)收集需要傳遞給模板的變量,在框架里面,我們提供了 app.locals 和 ctx.locals。
- app.locals 為全局的,一般在 app.js 里面配置全局變量。
- ctx.locals 為單次請(qǐng)求的,會(huì)合并 app.locals。
- 可以直接賦值對(duì)象,框架在對(duì)應(yīng)的 setter 里面會(huì)自動(dòng) merge。
// `app.locals` 會(huì)合并到 `ctx.locals ctx.app.locals = { a: 1 }; ctx.locals.b = 2; console.log(ctx.locals); // { a: 1, b: 2 }
// 一次請(qǐng)求過(guò)程中,僅會(huì)在第一次使用 `ctx.locals` 時(shí)把 `app.locals` 合并進(jìn)去。 ctx.app.locals = { a: 2 }; console.log(ctx.locals); // 上面已經(jīng)合并過(guò)一次,故輸出還是 { a: 1, b: 2 }
// 也可以直接賦值整個(gè)對(duì)象,不用擔(dān)心會(huì)覆蓋前面的值,我們通過(guò) setter 做了自動(dòng)合并。 ctx.locals.c = 3; ctx.locals = { d: 4 }; console.log(ctx.locals); // { a: 1, b: 2, c: 3, d: 4 }
|
但在實(shí)際業(yè)務(wù)開(kāi)發(fā)中,controller 中一般不會(huì)直接使用這 2 個(gè)對(duì)象,直接使用 ctx.render(name, data) 即可:
- 框架會(huì)自動(dòng)把 data 合并到 ctx.locals。
- 框架會(huì)自動(dòng)注入 ctx, request, helper 方便使用。
ctx.app.locals = { appName: 'showcase' }; const data = { name: 'egg' };
// will auto merge `data` to `ctx.locals`, output: egg - showcase await ctx.renderString('{{ name }} - {{ appName }}', data);
// helper, ctx, request will auto inject await ctx.renderString('{{ name }} - {{ helper.lowercaseFirst(ctx.app.config.baseDir) }}', data);
|
注意:
- ctx.locals 有緩存,只在第一次訪問(wèn) ctx.locals 時(shí)合并 app.locals。
- 原 Koa 中的 ctx.state,由于容易產(chǎn)生歧義,在框架中被覆蓋為 locals,即 ctx.state 和 ctx.locals 等價(jià),我們建議使用后者。
Helper
在模板中可以直接使用 helper 上注冊(cè)的方法,具體可以參見(jiàn)擴(kuò)展。
// app/extend/helper.js exports.lowercaseFirst = str => str[0].toLowerCase() + str.substring(1);
// app/controller/home.js await ctx.renderString('{{ helper.lowercaseFirst(name) }}', data);
|
Security
框架內(nèi)置的 egg-security 插件,為我們提供了常見(jiàn)的安全輔助函數(shù),包括 helper.shtml / surl / sjs 等等等,強(qiáng)烈建議閱讀下安全。
更多建議: