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.asyncwww/app.js 中的异步请求引擎(view() 已在每个页面中包含),而 DOM/form 教会它提交表单。重新构建:

php www/app.php build::run

输出列出了 *app.js:前端包已重新生成。

6.2: 路由有同步侧和异步侧

每个 route 默认是仅同步的,除非你另有说明:

将投票 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 并将其作为字符串发送。其他命令的工作方式相同:innerappendremoveclassvaluetitlepathscroll 等,都是一个 apply() 调用的命名参数。命令名称没有构建时检查,因此像 innr: 这样的拼写错误会被静默忽略;请随时参考指南中的命令表。

从两个不同的浏览器标签页投票,你会发现最后一个间隙:另一个标签页在你重新加载之前不会移动。记住这个想法,留到第 8 章。首先:更多语言。

我们使用必要的cookie来使该网站正常工作。在您的许可下,我们还使用分析工具来改善网站。