模板状态
模板状态指的是模板在渲染阶段可以访问的数据。Edge 提供了四个层级来传递或定义模板状态。
由于 Edge 模板在服务器端渲染,数据永远不会与客户端运行时共享。
全局状态(Globals)
全局状态指的是使用 edge.global 方法定义的状态。全局状态对所有模板(包括组件)都可用。
例如,你可以使用全局状态来与所有模板共享网站配置。
edge.global('config', {
colorScheme: 'dark',
menu: [],
socialLinks: [],
})
<html class="{{ config.colorScheme }}">
<head>
<head>
<body>
<header>
@each(item in config.menu)
@end
</header>
<footer>
@each(link in config.socialLinks)
@end
</footer>
</body>
</html>
你也可以共享类、函数以及几乎任意 JavaScript 数据类型作为全局属性。
edge.global('findUser', async function (id) {
return User.findById(id)
})
@let(user = await findUser(1))
{{ user.username }}
局部状态(Locals)
locals 与全局状态类似,但在多次渲染调用之间是隔离的。
在下面的示例中,我们使用 edge.createRenderer 方法创建多个 edge 的子实例,并分别与它们共享独立的数据对象。
这些数据将全局可用于所有作为局部(partials)或组件被包含的模板,但对隔离的子实例不可用。
const templ1 = edge.createRenderer()
const templ2 = edge.createRenderer()
templ1.share({
url: '/posts',
})
templ2.share({
url: '/posts/1',
})
await templ1.renderRaw('{{ url }}') // /posts
await templ2.renderRaw('{{ url }}') // /posts/1
为什么要使用局部状态?
你可能会想,既然可以在 .render() 方法调用时直接传入数据,为什么还要创建一个新的隔离实例并使用 .share() 方法来与模板共享局部状态呢?
为什么要这样?
const view = edge.createRenderer().share({
url: req.url
})
await view.render('template-path')
为什么不这样?
await edge.render('template-path', {
url: req.url
})
让我们用一个具体的例子来理解,什么时候局部状态会很有用。
假设你将 Express.js 与 Edge 一起使用,并希望通过中间件与模板共享数据。同时,所共享的数据应该在应用处理的并发请求之间相互隔离。
你可以为每个请求创建一个全新的 Edge 渲染器实例,并使用 share 方法共享在多个 HTTP 请求之间隔离的全局数据。
app.use(function (req, res) {
res.view = edge.createRenderer()
})
app.use(function (req, res) {
res.view.share({
url: req.url
})
})
app.use(function (req, res) {
res.view.share({
user: req.auth.user
})
})
// 最后渲染一个模板
app.get('/posts', async (req, res) => {
const html = await res.view.render('posts')
res.send(html)
})
渲染数据对象
渲染数据指的是调用 edge.render 方法时传入的数据对象。渲染数据不会与模板所使用的组件共享。
const renderingData = {}
await edge.render('template-path', renderingData)
内联变量
内联变量是作为 let 变量在模板内部定义的。你可以使用 @let 标签定义内联变量,并使用 @assign 标签为它们重新赋值。
@let(config = await loadConfig())
{{ config.someKey }}
内联变量的作用域与 JavaScript 中的 let 变量类似。我们来看下面的例子,其中我们在 each 循环内部对一个内联变量进行变更。
{{-- 定义变量 --}}
@let(total = 0)
<ul>
@each(item in items)
{{-- 为它重新赋一个新值 --}}
@assign(total = total + item.price)
<li> {{ item.name }} = {{ item.price }} </li>
@end
<li> Gross total = {{ total }} </li>
<ul>
数据层级及其作用域
最终的模板状态是所有层级通过 Object.assign 合并出的副本。因此,优先级最高的层级会覆盖之前层级的值。
const finalState = Object.assign(
{},
globals,
locals,
renderingData
)
| 名称 | 是否与组件共享? | 是否隔离? |
|---|---|---|
| 全局状态(Globals) | ✅ | ❌ |
| 局部状态(Locals) | ✅ | ✅ |
| 渲染数据对象 | ❌ | ✅ |
| 内联变量 | ❌ | ✅ |