跨页面(Tab和window)通信

本文介绍两种跨页面通信解决方案,可应用于以下四个使用场景。

场景一

编辑场景,在页面 A打开B页面, 在B页面操作数据,关闭B同时刷新页面 A 的数据;

用户通过一些筛选条件过滤出一些主题,且翻到了某一页,这时对某个主题进行编辑,编辑页是一个非常复杂的页面,不适合弹窗展示,因此打开了一个新的页面。

用户点击编辑,新打开一个页面,注意是window.open打开一个新的tab,而非在原来页面上更换url。

用户点击“保存”,更新了数据。

这时,合理的交互可以是:关闭新打开的编辑页,回到列表页,同时保持列表页的筛选条件和页码不变,并局部刷新列表的数据。

image.png | center | 2866x1538

image.png | center | 2240x940

场景2

编辑场景,在页面 A打开B页面, 在B页面做取消操作,关闭B同时回到A,A不刷新;

用户进行编辑主题操作(前置操作同场景1),并点击“取消”。

合理的交互可以是:关闭新打开的编辑页,列表页停留在进入编辑页前的状态,且不刷新数据。

场景3

添加场景,改变页面 A到B,B做数据操作提交或取消,页面由B回到A;

用户进行添加主题操作,并点击“保存”。

合理的交互可以是:改变当前列表页的url为添加页,在用户点击“保存”或“取消”时,跳转回列表页,筛选条件和页码都是初始状态(从1开始)。

场景4

同页面间的tab切换,TabA更新数据后,切换到TabB,B的数据需要同步

用户进入标签系统,在可用标签,待审核标签和不可用标签Tab之间切换,在某个tab页面操作完数据后,其他的tab内的数据需要同步。与前三个场景不同的是,前三个场景中,编辑页和列表页是两个不同的页面,而场景4中的tab共属于同一个页面,不同的tab内是不同的组件。

image.png | center | 2290x928

模型一:不同页面间的通信-storage事件触发

window有一个StorageEvent,每当localStorage改变的时候可以触发这个事件。(这个原理就像你给一个DOM绑定了click事件,当你点击它的时候,就会自动触发。)

每当一个页面改变了localStorage的值,都会触发StorageEvent事件。也就是说可以通过改变localStorage的值,来实现浏览器中跨页面( tab / window )之间的通讯。记住这个事件只有在localStorage发生改变的时候才会被触发,如果没改变则不会触发此事件。

1
2
3
4
5
6
7
8
9
10
11
//列表页 index.js 相关代码
...
componentDidMount(){
//添加对storge的监听事件 及时刷新页面
window.addEventListener('storage', (event)=>{
if(event.key === 'update_scg_list'){
//页面更新操作
this.refresh();
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//编辑页需要以window.open的方式打开编辑页,只有window.open打开的页面才能适用window.top.close()关闭
//antd table设置columns
this.columns = [
...
{
title: "操作",
key: "action",
render: record => {
return <a key="edit"
onClick={()=>window.open('...')} target='_blank'
>
编辑
</a>
}
}
]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//编辑页 index.js 相关代码
...
//点击取消 回到列表页面 场景3
handleCancelSubmit = () => {
//编辑 场景1
if(SCG_DATA){
window.top.close();
}
//添加 场景2
else{
const type = this.props.type || "";
location.href = `/scg/video/option.htm?from=baoluo&type=${type}`;
}
};
//更新localstorage 通知列表页面刷新 同时关闭自己
afterSaveProcess = ()=>{
if (localStorage) {
//为保证每次页面A都执行,此处需要设置一个随机字符串
localStorage.setItem('update_scg_list', randomId());
if(SCG_DATA){
setTimeout(()=>{
window.top.close();
}, 2000);
}
else{
location.href = `/scg/video/option.htm?from=baoluo&type=${this.props.type}`;
}
}
}
...
//提交表单 场景1+2
submit = value => {
...
IO.post(url, params)
.then(response => {
if (response.success) {
...
//提交成功后,通知列表页
this.afterSaveProcess();
}
...
})
...
};

注意这个方案在chrome中只能在不同页面之间生效,同个页面中监听事件无效。
参考:https://github.com/lin-xin/blog/issues/11

模型二:同页面消息通信-观察者模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
//全局观察tab切换
//事件集合
let events = {};
// 发布事件
const trigger = (event, ...data) => {
const fns = events[event];
// 如果没有对应方法
if (!fns || fns.length === 0) {
return false;
}
// 如果存在对应方法,依次执行
for ( let i = 0; i <= fns.length - 1; i++) {
fns[i](...data);
}
};
// 监听事件
const on = (event, fn) => {
// 如果尚没有该事件,创建一个数组来存储对应的方法
if (!events[event]) {
events[event] = [];
}
events[event].push(fn);
};
// 取消监听事件
const off = (event, fn) => {
const fns = events[event];
// 如果不存在事件集合
if (!fns) {
return false;
}
// 如果不存在事件
if (!fn && fns) {
fns.length = 0;
}
// 取消指定事件
else {
for (let i = fns.length - 1; i >= 0; i--) {
if (fn === fns[i]) {
fns.splice(i, 1);
}
}
}
};
const PubSub = {
on: on,
off: off,
trigger: trigger
};
export default PubSub;
1
2
3
4
5
6
7
//页面入口 相关代码
...
//tab切换时触发
onChange = activeKey => {
PubSub.trigger('tagChange',activeKey)
this.setState({ activeKey })
};
1
2
3
4
5
6
7
8
9
//每个tab组件中添加订阅事件
componentDidMount(){
//每次tab切换时,接收当前的activekey,如果是自己的key就刷新自己
PubSub.on('tagChange',(activeKey)=>{
if(activeKey === '2'){
this.refresh();
}
})
}

总结

在不使用redux等状态管理框架的情况下,多页面应用可使用模式一和模式二两种方式解决通信问题, 模式一适用于场景一,场景2和场景3;
模式二适用于场景四。