前言
我记得之前有做过这样的一个需求——可以简化的看作为一个 TODO List
,我打开了两个 tab
页,都是对应这个 TODO List
。
然后我在 A
页面新增一个,不出意外的话,在 B
页面是看不到的。产品当时跟我提了一下这个,让测试给我提了个 Bug
。
我那个时候下意识的反应就是找后端接一下 WebSocket
,但接一个 WebSocket
谈何简单,单单是公司网关那里分分钟让你搞一天都搞不定。
我就接着搜了一下,看看有没有纯前端的实现方案,还真让我找到了。今天我们就一起讨论下:纯前端如何实现不同标签页下的数据通信。
TODO List Demo
这里我为了演示,做了一个最简版本的 TODO List
,大家知道意思就行:
import { Button, Input, Row } from "antd";
import { useState } from "react";
const Todo = () => {
const [list, setList] = useState([]);
const [value, setValue] = useState("");
const handleCreate = () => {
if (!value) {
return;
}
const arr = [...list];
arr.push(value);
setList(arr);
setValue("");
};
return (
<div style={{ margin: 20 }}>
<Row>
<Input
value={value}
onInput={(e) => setValue(e.target.value)}
style={{ width: 200, marginRight: 16 }}
placeholder="输入点什么"
/>
<Button onClick={handleCreate} type="primary">
增加
</Button>
</Row>
<ul>
{list.map((item) => {
return <li>{item}</li>;
})}
</ul>
</div>
);
};
export default Todo;
代码很简单,仅为了后续演示数据同步使用。就是用一个输入框加一个确认按钮,然后把输入的东西 push
到数组里面,把这个数组渲染出来。
localStorage
localStorage
相信各位前端同学都不会陌生,数据存储在 localStorage
中是持久的,即使用户关闭了浏览器窗口,数据也会保留下来,直到用户手动清除或者被JS脚本清除。
在同源的情况下,localStorage存储的数据是共享的,也就是说假设我打开了两个同源的标签页 A
和 B
,我在 A
中写入了 localStorage
,在 B
标签页是可以获取到的。
且我们是可以监听 localStorage
的变化的,既然数据能共享,取到的是同一份。而且还能监听数据的变化,这不就可以实现数据通信同步了吗?
首先,在 list
变化的时候写入 localStorage
:
useEffect(() => {
window.localStorage.setItem(CACHE_KEY, JSON.stringify(list));
}, [list]);
然后在 localStorage
变更的时候更新 state
。注意:localStorage
的变化事件 (storage
事件) 在其他标签页或窗口修改 localStorage
时才会触发。
const handleStorageChange = useCallback(
(event) => {
const { key, newValue } = event;
if (isEmpty(newValue)) {
setList([]);
}
if (key === CACHE_KEY) {
if (isEmpty(newValue)) {
setList([]);
} else if (JSON.stringify(list) !== newValue) {
setList(JSON.parse(newValue));
}
}
},
[list]
);
useEffect(() => {
window.addEventListener("storage", handleStorageChange);
return () => {
window.removeEventListener("storage", handleStorageChange);
};
}, [handleStorageChange]);
所以为了验证这个功能,我们可以打开两个标签页来看看效果:
可以看到,在 A
标签页输入的东西可以顺利同步到 B
标签页中,也是实现了纯前端下的数据通信。
但可能有人觉得,本身我这个功能不需要依赖 localStorage
,搞得我现在又需要在本地存储再存一份。有没有不依赖本地存储的跨标签页数据通信方式呢?
Broadcast Channel API
答案是有的,那就是——Broadcast Channel API,我们可以一起来看看 MDN
对它的解释:
Broadcast Channel API 可以实现同 源 下浏览器不同窗口,Tab 页,frame 或者 iframe 下的 浏览器上下文 (通常是同一个网站下不同的页面) 之间的简单通讯。
也就是说这个 API
设计出来就是为了让我们对同源的不同窗口做数据通信的。
看一下兼容性:
除了 IE
不支持,其他大多数主流浏览器都支持。
整体的 API
用起来十分简单,我们一起来看看。
首先刚开始需要加入或者创建一个频道,跟我们使用 ws
的时候一样,需要创建或者加入一个房间,组件销毁时退出频道。
useEffect(() => {
const bc = new BroadcastChannel(CHANNEL_NAME);
channel.current = bc;
channel.current.onmessage = function (ev) {
if (ev?.data?.type === "add") {
const arr = [...list];
arr.push(ev.data.value);
setList(arr);
}
};
return () => {
channel.current && channel.current.close();
};
}, [list]);
在 onmessage
中就可以接收到频道中其他标签页发送过来的消息,在这里获取到消息之后更新 state
。同样的,在新增数据的时候,通过频道发送消息。
const handleCreate = () => {
if (!value) {
return;
}
const arr = [...list];
arr.push(value);
setList(arr);
setValue("");
channel.current.postMessage({
type: "add",
value,
});
};
一样也是可以实现跨标签页的数据通信的。
最后
以上就是本文的全部内容,主要介绍了两种纯前端实现不同标签页下的数据通信的方式,如果你觉得有意思的话,点点关注点点赞吧~