像Ajax一样异步更新Golang模板的方法有哪些?
像Ajax一样异步更新Golang模板的方法有哪些? 当页面加载时间较长时,会出现问题。在我的案例中,这个网站从数据库加载5000条记录,在所有内容加载完成之前需要较长时间。
我的意图是使用几个动态应用的主题。但这样做时,主题直到所有内容加载完成后才会加载,导致恼人的“闪烁”现象。
1. 无闪烁的静态方法 当我在根级别以“静态”方式应用主题时,没有闪烁,但更新所选按钮会有延迟。
<html data-theme="classic" lang="en">
http://94.237.92.101:2020/topics
2. 有闪烁的动态方法 当我在body级别以“即时”方式应用主题时,会出现恼人的闪烁。
<body data-theme="classic">
window.addEventListener("DOMContentLoaded", (event) => {
document.body.dataset.theme = localStorage.theme
if (localStorage.theme) {
do nothing
} else {
window.localStorage.setItem('theme', "classic")
window.location.reload()
}
})
http://94.237.92.101:3030/topics
我的问题是,在Go模板中是否有任何方法可以像Ajax一样异步加载数据?
更多关于像Ajax一样异步更新Golang模板的方法有哪些?的实战教程也可以访问 https://www.itying.com/category-94-b0.html
这看起来不像是一个Go语言的问题,而是一个JavaScript和浏览器DOM的问题。
更多关于像Ajax一样异步更新Golang模板的方法有哪些?的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
为什么要从数据库加载5000条记录?
为了展示闪烁问题 = 这个问题。 😊 如果页面因其他原因加载缓慢,它就会闪烁。所以我想找到能异步工作的方法来提高加载速度。
lutzhorn:
这看起来不像是Go的问题,而是JavaScript的问题。
Go从Postgresql获取数据并加载到Go模板中。JavaScript在哪里?据我所知,没有涉及JavaScript。请解释一下……
Sibert:
window.addEventListener(“DOMContentLoaded”, (event) => { document.body.dataset.theme = localStorage.theme if (localStorage.theme) { do nothing } else { window.localStorage.setItem(‘theme’, “classic”) window.location.reload() } })
这全是Javascript,你问Javascript在哪里是什么意思!?
window.addEventListener 在您的第二种动态方法中是 JavaScript。
我不太理解您所说的“在 Go 模板中异步加载数据”是什么意思。Go 运行在服务器端,它返回的是完全渲染后的模板扩展结果。它会将这个完整的结果发送给客户端,客户端可以对其进行任何操作。
因此,如果客户端出现任何闪烁,服务器端是无法对此进行任何处理的。
Sibert:
在我的案例中,这个网站从数据库加载了5000条记录,在所有内容加载完成之前需要很长时间。
你为什么从数据库加载5000条记录?你的例子看起来像是一个论坛页面。如果确实如此,我的建议是不要一次性加载5000条记录。也许可以加载前100条(即使这对我来说听起来也有些过度),然后提供一个搜索功能让人们来缩小范围。
所以,你在网页HTML中观察到的每一个闪烁或错误,都必须在将HTML表格插入现有HTML的JavaScript代码中处理。
你指的是这个吗?这是在将HTML表格插入到现有的HTML中。据我所见,没有JavaScript。还是我完全理解错了?
<table id="table">
<col style="width:100vw">
{{range .}}
<tr data-id{{.post_id}}>
<td>
<h5>{{.post_subject}}</h5><p>John Doe</p>
</td>
</tr>
{{end}}
</table>
不,这是服务器端执行的 Go 代码,就浏览器而言,它渲染的是静态 HTML。
在你链接的教程中,这是 JavaScript 代码,它在使用 AJAX 进行异步请求后插入 HTML 元素:
function myFunction(xml) {
var i;
var xmlDoc = xml.responseXML;
var table="<tr><th>Artist</th><th>Title</th></tr>";
var x = xmlDoc.getElementsByTagName("CD");
for (i = 0; i <x.length; i++) {
table += "<tr><td>" +
x[i].getElementsByTagName("ARTIST")[0].childNodes[0].nodeValue +
"</td><td>" +
x[i].getElementsByTagName("TITLE")[0].childNodes[0].nodeValue +
"</td></tr>";
}
document.getElementById("demo").innerHTML = table;
}
也许你应该向我们展示一个最小但完整的示例,包括你使用的服务器端 Go 代码以及客户端 HTML 和 JavaScript。
不,这是在浏览器中执行的JavaScript,而不是在服务器上执行的Go或任何其他代码。
客户端JavaScript代码基本上是对一个XML文件发起HTTP GET请求(xhttp.open("GET", "cd_catalog.xml", true);)。这个请求确实是在浏览器中异步执行的,这意味着即使在请求XML文件时,您仍然可以与HTML网站进行交互。一旦HTTP GET请求完成并且完整的XML文件加载完毕,它会在JavaScript函数myFunction中被解析,然后一个HTML表格会被添加到现有的HTML中。
关键点在于,所有这些操作都是在浏览器中完成的。提供XML文件的服务器并不关心这些。它接收对该文件的HTTP GET请求并返回该文件。也许这个文件已经存在于服务器磁盘上的某个位置,或者是从数据库中读取的。也许这个文件是动态生成的,甚至可能在此过程中使用了Go模板。所有这些对于等待HTTP GET请求响应的浏览器来说都是不可见的。一旦服务器完成了它正在做的任何事情,它就会返回完整的XML文件,仅此而已。
因此,您在网页HTML中观察到的任何闪烁或错误都必须在将HTML表格插入现有HTML的JavaScript代码中处理。
// 注意:原始HTML内容中未包含Go代码。
// 此处代码块仅为遵循格式要求而保留。
在Go模板中实现类似Ajax的异步更新,可以通过以下几种技术方案:
1. 使用Server-Sent Events (SSE)
SSE允许服务器主动向客户端推送数据,适合实时更新场景。
服务端代码:
package main
import (
"encoding/json"
"fmt"
"net/http"
"time"
)
func sseHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "text/event-stream")
w.Header().Set("Cache-Control", "no-cache")
w.Header().Set("Connection", "keep-alive")
w.Header().Set("Access-Control-Allow-Origin", "*")
flusher, ok := w.(http.Flusher)
if !ok {
http.Error(w, "Streaming unsupported", http.StatusInternalServerError)
return
}
// 模拟数据加载
for i := 1; i <= 100; i++ {
data := map[string]interface{}{
"progress": i,
"message": fmt.Sprintf("已加载 %d/5000 条记录", i*50),
}
jsonData, _ := json.Marshal(data)
fmt.Fprintf(w, "data: %s\n\n", jsonData)
flusher.Flush()
time.Sleep(100 * time.Millisecond)
}
// 发送完成事件
fmt.Fprintf(w, "event: complete\ndata: {\"status\":\"done\"}\n\n")
flusher.Flush()
}
客户端代码:
const eventSource = new EventSource('/sse');
eventSource.addEventListener('message', (event) => {
const data = JSON.parse(event.data);
document.getElementById('progress').textContent = data.message;
document.getElementById('progress-bar').style.width = data.progress + '%';
});
eventSource.addEventListener('complete', (event) => {
const data = JSON.parse(event.data);
if (data.status === 'done') {
eventSource.close();
// 加载完整数据
loadFullContent();
}
});
2. 使用WebSocket双向通信
适合需要双向数据交换的场景。
服务端代码:
package main
import (
"github.com/gorilla/websocket"
"log"
"net/http"
"time"
)
var upgrader = websocket.Upgrader{
CheckOrigin: func(r *http.Request) bool {
return true
},
}
func wsHandler(w http.ResponseWriter, r *http.Request) {
conn, err := upgrader.Upgrade(w, r, nil)
if err != nil {
log.Println("升级WebSocket失败:", err)
return
}
defer conn.Close()
// 分批发送数据
for i := 0; i < 5000; i += 100 {
batch := fetchBatchFromDB(i, 100)
err := conn.WriteJSON(map[string]interface{}{
"type": "data_batch",
"data": batch,
"progress": (i + 100) / 5000 * 100,
})
if err != nil {
log.Println("发送数据失败:", err)
break
}
time.Sleep(50 * time.Millisecond)
}
conn.WriteJSON(map[string]interface{}{
"type": "complete",
"message": "所有数据加载完成",
})
}
func fetchBatchFromDB(offset, limit int) []interface{} {
// 模拟从数据库获取批次数据
return make([]interface{}, limit)
}
3. 使用Fetch API分页加载
结合Go模板和JavaScript实现分页异步加载。
服务端路由:
func main() {
http.HandleFunc("/topics", topicsHandler)
http.HandleFunc("/api/topics", apiTopicsHandler)
http.ListenAndServe(":8080", nil)
}
func topicsHandler(w http.ResponseWriter, r *http.Request) {
tmpl := template.Must(template.ParseFiles("topics.html"))
tmpl.Execute(w, nil)
}
func apiTopicsHandler(w http.ResponseWriter, r *http.Request) {
page := r.URL.Query().Get("page")
limit := r.URL.Query().Get("limit")
// 根据page和limit查询数据库
data := fetchPaginatedData(page, limit)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(data)
}
HTML模板:
<!DOCTYPE html>
<html data-theme="{{.Theme}}">
<head>
<script>
let currentPage = 1;
const pageSize = 100;
async function loadTopics(page) {
const response = await fetch(`/api/topics?page=${page}&limit=${pageSize}`);
const data = await response.json();
const container = document.getElementById('topics-container');
data.forEach(topic => {
const div = document.createElement('div');
div.className = 'topic-item';
div.innerHTML = `
<h3>${topic.title}</h3>
<p>${topic.content}</p>
`;
container.appendChild(div);
});
if (data.length === pageSize) {
currentPage++;
// 继续加载下一页
setTimeout(() => loadTopics(currentPage), 100);
}
}
// 立即加载主题避免闪烁
document.documentElement.dataset.theme = localStorage.getItem('theme') || 'classic';
// 页面加载后开始异步加载数据
document.addEventListener('DOMContentLoaded', () => {
loadTopics(1);
});
</script>
</head>
<body>
<div id="topics-container"></div>
<div id="loading">正在加载数据...</div>
</body>
</html>
4. 使用模板片段和hx-target(HTMX)
结合HTMX实现部分模板更新。
服务端代码:
func topicsHandler(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("HX-Request") == "true" {
// 返回部分HTML
data := fetchPartialData()
tmpl := template.Must(template.ParseFiles("topics_partial.html"))
tmpl.Execute(w, data)
} else {
// 返回完整页面
tmpl := template.Must(template.ParseFiles("topics_full.html"))
tmpl.Execute(w, nil)
}
}
HTML使用HTMX:
<div hx-get="/topics" hx-trigger="load" hx-target="#content">
<div id="content">
<!-- 异步加载的内容将出现在这里 -->
</div>
</div>
5. 解决主题闪烁问题的具体方案
针对你的主题切换问题,建议:
// 在渲染模板时注入主题
func renderTemplate(w http.ResponseWriter, tmpl string, data interface{}) {
// 从cookie或session获取主题
theme := getThemeFromRequest(r)
templateData := map[string]interface{}{
"Theme": theme,
"Data": data,
}
tmpl, err := template.ParseFiles(tmpl)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
tmpl.Execute(w, templateData)
}
<!DOCTYPE html>
<html data-theme="{{.Theme}}">
<head>
<style>
[data-theme="dark"] { /* 暗色主题样式 */ }
[data-theme="light"] { /* 亮色主题样式 */ }
</style>
<script>
// 立即设置主题,无需等待DOMContentLoaded
(function() {
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
document.documentElement.dataset.theme = savedTheme;
}
})();
</script>
</head>
<body>
<!-- 页面内容 -->
<div hx-get="/api/topics" hx-trigger="load" hx-swap="innerHTML">
正在加载数据...
</div>
</body>
</html>
这些方法都可以实现Go模板的异步更新,避免页面加载时的闪烁问题。SSE和WebSocket适合实时性要求高的场景,而Fetch API分页加载更适合大数据量的分批处理。


