Edge 模板
本指南介绍在 AdonisJS 应用中使用 Edge 模板的方法。你将学习如何从控制器渲染模板、向模板传递数据、使用布局与组件、使用 Hypermedia 起步套件中的预构建组件,以及调试模板问题。
概述
Edge 是一个面向 Node.js 的服务器端模板引擎,允许你在服务端组合 HTML 标记,并将最终的静态 HTML 发送给浏览器。由于模板完全在服务器端执行,它们可以直接调用框架的核心特性,例如身份验证、授权检查以及国际化系统。
当你在 AdonisJS 中创建 Hypermedia 应用时,Edge 已预先配置好并可直接使用。模板以 .edge 为扩展名存放在 resources/views 目录中,你可以从路由处理器或控制器中,通过 HTTP 上下文的 view 属性来渲染它们。
Edge 在 edgejs.dev 上有完整的文档,详细涵盖了模板语法、组件系统以及所有可用特性。本指南专注于在 AdonisJS 应用中使用 Edge,并介绍 Hypermedia 起步套件内置的预构建组件。
你的第一个模板
让我们创建一个简单的页面,用于展示博客文章列表。这个示例演示了在 AdonisJS 中渲染模板的基本工作流。
-
创建模板文件
使用 Ace 命令生成新模板。
node ace make:view pages/posts/index # CREATE: resources/views/pages/posts/index.edge -
添加模板内容
打开
resources/views/pages/posts/index.edge并添加以下内容。resources/views/pages/posts/index.edge@layout() @each(post in posts) <div> <h2> {{ post.title }} </h2> <div> <p>{{{ excerpt(post.content, 280) }}}</p> </div> </div> @end @end关于这个模板,有几个要点需要理解:
-
@layout()组件用完整的 HTML 文档结构(包括<html>、<head>和<body>标签)包裹你的内容。我们会在本指南后面详细介绍布局。 -
@each标签遍历posts数组,并为每篇文章渲染内容。Edge 提供了多个标签用于在模板中编写逻辑,例如@if、@else和@elseif。你可以在 Edge 语法参考 中了解所有可用标签。 -
双花括号
{{ }}求值并输出一个 JavaScript 表达式。三花括号{{{ }}}功能相同,但不会转义 HTML,这在渲染富文本内容时非常有用。
-
-
定义路由
创建一个路由来处理对文章页面的请求。
start/routes.tsimport router from '@adonisjs/core/services/router' import { controllers } from '#generated/controllers' router.get('posts', [controllers.PostsController, 'index']) -
创建控制器
创建一个控制器,用文章数据渲染模板。
app/controllers/posts_controller.tsimport Post from '#models/post' import type { HttpContext } from '@adonisjs/core/http' export default class PostsController { async index({ view }: HttpContext) { /** * 渲染位于 resources/views/pages/posts/index.edge 的模板 * 第一个参数是模板路径(相对于 resources/views) * 第二个参数是模板状态(与模板共享的数据) */ return view.render('pages/posts/index', { posts: await Post.all(), }) } } -
查看结果
在浏览器中访问
http://localhost:3333/posts即可看到渲染后的页面。
你也可以不使用控制器,直接从路由渲染模板。
import router from '@adonisjs/core/services/router'
/**
* router.on().render() 简写会直接渲染一个模板。
* 第一个参数是模板路径。
*/
router.on('/').render('pages/home')
理解模板状态
你传给 view.render() 的数据对象被称为 模板状态(template state)。该对象中的所有属性在模板中都可作为变量使用。
除了你显式传递的数据外,AdonisJS 还会自动与每个模板共享某些全局对象:
- 用于访问请求数据的
request对象 - 用于检查身份验证状态的
auth对象 - Edge 辅助函数,如
excerpt()、truncate(),以及路由辅助函数
你可以在 Edge 参考指南 中查看所有可用的辅助函数和全局属性。
模板语法回顾
Edge 结合使用花括号和标签来为模板添加动态行为。以下是最常见的语法模式快速回顾:
输出变量:
{{ post.title }}
输出未转义的 HTML:
{{{ post.content }}}
条件判断:
@if(user)
<p>欢迎回来,{{ user.name }}</p>
@else
<p>请登录</p>
@end
循环:
@each(post in posts)
<h2>{{ post.title }}</h2>
@end
求值 JavaScript 表达式:
{{ post.createdAt.toFormat('dd LLL yyyy') }}
{{ posts.length > 0 ? '有文章' : '暂无文章' }}
有关 Edge 模板语法的完整讲解,包括局部模板、插槽、自定义标签等高级特性,请参阅 Edge 语法参考 。
使用布局与组件
你在第一个示例中看到的 @layout() 组件用完整的 HTML 文档结构包裹你的页面内容。该组件存放在 resources/views/components/layout.edge,包含标准的 HTML 样板代码:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My App</title>
</head>
<body>
{{{ await $slots.main() }}}
</body>
</html>
$slots.main() 方法调用会渲染你放置在页面模板中 @layout() 开始和结束标签之间的所有内容。这就是 Edge 的插槽(slots)特性,它允许组件接收来自其使用者(consumer)的内容。
创建组件
Edge 中的组件是可复用的模板片段,存放在 resources/views/components 目录中。该目录中的任何模板文件都可作为 Edge 标签使用。
例如,如果你在 resources/views/components/card.edge 创建了一个文件:
<div class="card">
{{{ await $slots.main() }}}
</div>
你就可以在模板中这样使用它:
@card()
<h2>卡片标题</h2>
<p>卡片内容</p>
@end
组件可以接收 props(参数),并支持多个命名插槽以进行更复杂的组合。有关构建和使用组件的完整指南,请参阅 Edge 组件指南 。
起步套件组件
Hypermedia 起步套件包含一组无样式的组件,用于构建表单和常见 UI 模式。每个组件最多渲染一个 HTML 元素,并将未知的 props 透传为 HTML 属性,从而允许你直接应用 class 和其他属性。
Layout
渲染包含 head 和 body 元素的 HTML 文档。
- Props:无
- Slots:
main(默认)
@layout()
<main>页面内容放在这里</main>
@end
Form
渲染一个 HTML 表单元素,并自动注入 CSRF token。
| Prop | Type | Description |
|---|---|---|
action | string | 表单 action 的 URL |
method | string | HTTP 方法。支持通过方法伪装使用 PUT、PATCH 和 DELETE |
route | string | 根据命名路由计算 action 的 URL |
routeParams | array | 命名路由所需的参数 |
routeOptions | object | 用于生成 URL 的额外选项(例如查询字符串) |
@form({ route: 'posts.store', method: 'POST' })
{{-- 表单字段 --}}
@end
@form({ route: 'posts.update', method: 'PUT', routeParams: [post.id] })
{{-- 表单字段 --}}
@end
Field 组件
表单字段组件协同工作,用于创建带有标签和验证错误显示的、可访问的表单输入。
field.root
建立子字段组件上下文的容器。
| Prop | Type | Description |
|---|---|---|
name | string | 字段名称,用于查找错误信息 |
id | string | 元素 ID,用于将标签与输入关联起来 |
field.label
渲染与字段关联的 label 元素。
| Prop | Type | Description |
|---|---|---|
text | string | 标签文本(替代使用插槽的方式) |
field.error
显示该字段的验证错误。会根据字段名称自动查找错误。
@field.root({ name: 'email' })
@!field.label({ text: '邮箱地址' })
@!input.control({ type: 'email', autocomplete: 'email' })
@!field.error()
@end
Input control
渲染一个 input 元素。必须是 @field.root 的子级。它将所有 props 作为 HTML 属性透传给 input 元素。
@field.root({ name: 'username' })
@!field.label({ text: '用户名' })
@!input.control({ type: 'text', minlength: '3', maxlength: '20' })
@!field.error()
@end
Select control
渲染一个带选项的 select 元素。必须是 @field.root 的子级。
| Prop | Type | Description |
|---|---|---|
options | array | 由包含 name 和 value 属性的对象组成的数组 |
@field.root({ name: 'country' })
@!field.label({ text: '国家' })
@!select.control({
options: countries.map((country) => ({
name: country.name,
value: country.code
}))
})
@!field.error()
@end
Textarea control
渲染一个 textarea 元素。必须是 @field.root 的子级。它将所有 props 作为 HTML 属性透传给 textarea 元素。
@field.root({ name: 'bio' })
@!field.label({ text: '个人简介' })
@!textarea.control({ rows: '4' })
@!field.error()
@end
Checkbox 组件
Checkbox 组件创建共享命名的复选框输入,便于表单提交。
checkbox.group
建立子复选框共享名称的容器。
| Prop | Type | Description |
|---|---|---|
name | string | 组中所有复选框的共享名称 |
checkbox.control
渲染一个复选框输入。必须同时嵌套在 @checkbox.group 和 @field.root 中。它将所有 props 作为 HTML 属性透传。使用 value 设置复选框的值。
@checkbox.group({ name: 'services' })
@field.root({ id: 'design' })
@!checkbox.control({ value: 'design' })
@!field.label({ text: '设计' })
@end
@field.root({ id: 'development' })
@!checkbox.control({ value: 'development' })
@!field.label({ text: '开发' })
@end
@field.root({ id: 'marketing' })
@!checkbox.control({ value: 'marketing' })
@!field.label({ text: '市场' })
@end
@end
Radio 组件
Radio 组件在组内创建互斥的选项。
radio.group
建立子单选按钮共享名称的容器。
| Prop | Type | Description |
|---|---|---|
name | string | 组中所有单选按钮的共享名称 |
radio.control
渲染一个单选输入。必须同时嵌套在 @radio.group 和 @field.root 中。它将所有 props 作为 HTML 属性透传。使用 value 设置单选按钮的值。
@radio.group({ name: 'payment_plan' })
@field.root({ id: 'free' })
@!radio.control({ value: 'free' })
@!field.label({ text: '免费' })
@end
@field.root({ id: 'pro' })
@!radio.control({ value: 'pro' })
@!field.label({ text: '专业版 $29/月' })
@end
@field.root({ id: 'enterprise' })
@!radio.control({ value: 'enterprise' })
@!field.label({ text: '定制价格' })
@end
@end
Alert 组件
Alert 组件用于显示通知消息,可选择自动消失行为。
alert.root
建立 alert 标题和描述上下文的容器。
| Prop | Type | Description |
|---|---|---|
variant | string | Alert 变体(例如 destructive、success) |
autoDismiss | boolean | 是否自动消失 |
alert.title
渲染 alert 标题。
| Prop | Type | Description |
|---|---|---|
text | string | 标题文本(替代使用插槽的方式) |
alert.description
渲染 alert 正文文本。
| Prop | Type | Description |
|---|---|---|
text | string | 描述文本(替代使用插槽的方式) |
@alert.root({ variant: 'destructive', autoDismiss: true })
@!alert.title({ text: '未授权' })
@!alert.description({ text: '你无权访问此页面' })
@end
Button
渲染一个 button 元素。
| Prop | Type | Description |
|---|---|---|
text | string | 按钮文本(替代使用插槽的方式) |
@!button({ text: '注册', type: 'submit' })
@button({ type: 'button', class: 'btn-secondary' })
<span>取消</span>
@end
Link
渲染一个 anchor 元素,并基于路由生成 URL。
| Prop | Type | Description |
|---|---|---|
text | string | 链接文本(替代使用插槽的方式) |
route | string | 根据命名路由计算 href |
routeParams | array | 命名路由所需的参数 |
routeOptions | object | 用于生成 URL 的额外选项 |
href | string | 直接 URL(替代 route 使用) |
@!link({ route: 'posts.show', routeParams: [post.id], text: '查看文章' })
@link({ route: 'posts.edit', routeParams: [post.id] })
<span>编辑</span>
<svg>{{-- 图标 --}}</svg>
@end
Avatar
渲染用户头像的图片或首字母缩写。
| Prop | Type | Description |
|---|---|---|
src | string | 头像图片 URL(渲染为 img 元素) |
initials | string | 兜底首字母(渲染为 span 元素) |
{{-- 使用图片 --}}
@!avatar({ src: user.avatarUrl, alt: user.name })
{{-- 使用首字母 --}}
@!avatar({ initials: user.initials })
调试模板
在处理模板时,你可能需要检查模板中可用的数据,或者排查某些值未按预期显示的原因。Edge 为此提供了 @dump 标签。
@dump 标签会美化打印一个变量的值,使检查数据结构变得轻松:
@dump(posts)
要查看整个模板状态(模板中所有可用的变量),使用:
@dump(state)
输出会显示在你渲染的 HTML 中,展示数据的结构和值。在开发过程中,模板在发生更改时会自动重新加载,因此你无需重启服务器即可在浏览器中立即看到更新。
配置
在 Hypermedia 应用中,Edge 已预先配置好,开箱即用。但是,如果你需要注册自定义辅助函数、标签或插件,可以通过创建一个预加载(preload)文件来自定义 Edge。
为 Edge 配置创建一个预加载文件:
node ace make:preload view
这会创建一个预加载文件,你可以在应用启动前在其中自定义 Edge。在该文件中,你可以注册 Edge 全局对象、插件和自定义标签。
如果你需要自定义模板存放的目录,可以修改 adonisrc.ts 文件中的 directories 选项。有关配置选项的更多详情,请参阅
AdonisRC 参考指南
。