6: Async
现在让投票变得即时。Phlo 的前端运行时拦截标记为 async 的表单和链接,在后台发送它们,并将服务器的 DOM 命令应用到页面上。无需重新加载,无需客户端状态,无需 API 层:同一路由同时响应两个世界。
6.1: 在前端切换
在 data/app.json 中添加两个资源:
{
"resources": [
"DB/DB",
"DB/model",
"DB/JSONDB",
"DB/JSON.result",
"payload",
"phlo.async",
"DOM/form"
]
}
phlo.async 是 www/app.js 中的异步请求引擎(view() 已在每个页面中包含),而 DOM/form 教会它提交表单。重新构建:
php www/app.php build::run
输出列出了 *app.js:前端包已重新生成。
6.2: 路由有同步侧和异步侧
每个 route 默认是仅同步的,除非你另有说明:
- 无修饰符:仅常规 HTTP 请求
async:仅来自 Phlo 前端的请求both:两者皆可
将投票 route 更改为同时服务两种请求,并根据请求类型进行分支:
route both POST poll vote $id {
if (!$option = type_poll::record(id: (int)$id)) return false
type_poll::change('id=?', (int)$id, votes: $option->votes + 1)
if (%req->async) return apply(
outer: ['#results' => $this->results],
)
location('/poll')
}
当 Phlo 前端发起请求时,%req->async 为 true。Async 请求会得到一个 apply(...) 响应;普通浏览器仍会得到重定向。一个 route,两种传输,零重复。
注意 apply(...) 内部的尾随逗号:在 Phlo 中,每个多行参数列表的行都以逗号结尾,包括最后一行。这个逗号告诉解析器语句仍在继续。
6.3: 将表单标记为 async
前端仅拦截具有 async 类的元素。在 choices view 中:
view choices:
<section.card>
<foreach type_poll::records() AS $option>
<form.async method=post action="/poll/vote/$option->id">
<button>$option->option</button>
</form>
</foreach>
</section>
<form.async ...> 是 class="async" 的点简写。导航同样适用:<a.async href="/poll"> 通过 async 管道加载页面,并进行视图过渡,而不是完全重新加载。
重新加载 http://localhost/poll 一次(以获取新的标记),然后投票。条形图动画到其新宽度,计数更新,页面从未重新加载。第 4 章中的 transition: width .4s 正在进行缓动。
6.4: apply() 实际上发送了什么
apply() 返回 JSON 命令,前端在 DOM 上执行这些命令。看看它是如何发生的:
curl -s -X POST -H "X-Requested-With: phlo" http://localhost/poll/vote/1
{"outer": {"#results": "<section id=\"results\" class=\"card\">..."}}
outer 替换元素的 outerHTML;服务器渲染了 results view 并将其作为字符串发送。其他命令的工作方式相同:inner、append、remove、class、value、title、path、scroll 等,都是一个 apply() 调用的命名参数。命令名称没有构建时检查,因此像 innr: 这样的拼写错误会被静默忽略;请随时参考指南中的命令表。
从两个不同的浏览器标签页投票,你会发现最后一个间隙:另一个标签页在你重新加载之前不会移动。记住这个想法,留到第 8 章。首先:更多语言。