本文介绍两种跨页面通信解决方案,可应用于以下四个使用场景。
场景一
编辑场景,在页面 A打开B页面, 在B页面操作数据,关闭B同时刷新页面 A 的数据;
用户通过一些筛选条件过滤出一些主题,且翻到了某一页,这时对某个主题进行编辑,编辑页是一个非常复杂的页面,不适合弹窗展示,因此打开了一个新的页面。
用户点击编辑,新打开一个页面,注意是window.open打开一个新的tab,而非在原来页面上更换url。
用户点击“保存”,更新了数据。
这时,合理的交互可以是:关闭新打开的编辑页,回到列表页,同时保持列表页的筛选条件和页码不变,并局部刷新列表的数据。


场景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内是不同的组件。

模型一:不同页面间的通信-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;
模式二适用于场景四。