如何在纯JavaScript中,让页面UI与通过请求获取的侧边栏组件的UI状态保持同步?
我正在用纯Vanilla JS(不使用框架)开发一个多页面应用。为了避免代码重复,我获取一个共享的侧边栏(sidebar.html),并通过脚本把它注入到每个页面的占位符中(例如profile.html)。
设置
在profile.html中,我像这样加载侧边栏片段:
fetch('../components/sidebar.html')
.then(r => r.text())
.then(html => {
document.getElementById('sidebar-placeholder').innerHTML = html;
// Initial UI sync for user name/avatar happens here
});
问题
当用户在个人资料页更新显示名称或头像时,我会更新sessionStorage并向后端发送PUT请求。
然而,侧边栏(其中包含用户的名称和头像)无法实时更新,因为它是一个独立的DOM片段,已经完成了初始渲染。目前需要刷新整页,才能在侧边栏中看到更新。
我也遇到两个相关的问题:
头像显示:即使将Cloudinary的 URL保存到数据库和sessionStorage,重新加载时个人资料页往往仍然显示为首字母而不是 标签。
激活状态:由于侧边栏是静态片段,它不知道当前在哪个页面,导致无法为正确的导航链接应用active CSS类。
我尝试过的办法
从个人资料页的“保存”函数中,手动选择侧边栏元素(例如document.getElementById('sidebarName'))。这看起来很脆弱,因为它将页面逻辑与侧边栏的内部ID紧密耦合。
在每次更新时重新获取整个侧边栏HTML,这会造成页面闪烁。
问题
在不重新加载页面的情况下,利用Vanilla JS的最佳实践方式,如何通知一个获取到的组件(侧边栏)全局状态(用户数据)已经改变,以便它重新渲染自己的特定部分?
是否有推荐的事件驱动模式(如CustomEvent),或在使用获取的HTML片段时,处理“活动”链接高亮的简洁方法?
解决方案
基本上你希望实现元素的“纠缠”。示例:
let beingEntangled = false;
let elements = document.querySelectorAll("[data-entangle-input=my-change]");
for (let myChange of elements) {
const trigger = "input";
myChange.addEventListener(trigger, function() {
if (!beingEntangled) {
beingEntangled = true;
for (let other of document.querySelectorAll("." + myChange.getAttribute("data-entangle-input"))) {
if (this !== other) {
other.value = this.value;
}
}
beingEntangled = false;
}
});
}
<input type="text" class="my-change" data-entangle-input="my-change">
<input type="text" class="my-change" data-entangle-input="my-change">
<input type="text" class="my-change" data-entangle-input="my-change">
<input type="text" class="my-change" data-entangle-input="my-change">
<hr>
<input type="text" data-entangle-input="my-change">
在上面的示例中,所有带有 data-entangle-input=my-change 的元素将刷新与之匹配的那些具有相同类名的元素,即 my-change。因此前四个输入会彼此刷新(因为它们都带有data-entangle-input的类),彼此之间通过相互匹配的data-entangle-input的类而互相刷新。第五个输入没有这个类,因此不会被刷新,但其他元素会被刷新。
我们使用一个名为 beingEntangled 的标志,来确保不会让触发同一事件的元素之间形成无限的递归调用。
所以基本思路是:如果你创建一个属性,用来通过类名“纠缠”事件,并在你想要的位置添加相应的类,那么你就可以这样做:
let entanglementMap = {
click: ['click1', 'click2', 'click3'],
input: ['input1', 'input2', 'input3'],
//...
};
beingEntangled = false;
for (let event in entangleMap) {
for (let className of entangleMap[event]) {
let elements = document.getElementsByClassName(className);
for (let element of elements) {
element.addEventListener(event, function() {
beingEntangled = true;
for (let other of elements) {
if (other !== element) other.value = element.value;
}
beingEntangled = false;
})
}
}
}
这与第一种方法略有不同,因为它更通用,假设在 entanglementMap 内具有相同类名的所有元素都应该被纠缠在一起。同样地,在这里我假设你需要刷新元素的值。你也可以通过使用参数化函数来替代 if (other !== element) other.value = element.value; 部分,进一步抽象,但为了简洁起见,我保持了简单,展示了通过类名对元素进行“纠缠”并通过事件将它们连接起来的思路。