Vue3嵌套页面与 iframe 页面通信
概述
本文介绍了如何使用 useNestedIframe
和 useIframePage
这两个 Vue 3 Hooks,来实现嵌套页面与 iframe 页面之间的通信和缓存管理。useNestedIframe
用于嵌套页面,管理与 iframe 的通信,useIframePage
则用于 iframe 页面,接收父页面发送的消息并管理缓存。
使用场景
父页面与 iframe 通信:父页面需要发送消息给 iframe,并接收其反馈。缓存数据的管理:在 iframe 内部需要缓存数据并在页面之间切换时恢复这些数据。
useNestedIframe
使用说明
useNestedIframe
Hook 主要用于嵌套页面,负责管理与 iframe 的通信、监听 iframe 消息、发送初始化信息以及清除缓存数据。
参数说明
- iframeRef:iframe 的引用,用于定位 iframe 实例。
- iframeSrc:iframe 的源地址,默认为空字符串。
- config:配置对象,包含以下属性:
- parentId:父页面标识符。
- enableCache:是否启用缓存。
- onReceiveMessage:回调函数,处理从 iframe 收到的消息。
返回值
- sendMessageToIframe:向 iframe 发送消息的函数。
- isIframeLoaded:指示 iframe 是否加载完成的布尔值。
- clearCacheData:清除 iframe 缓存数据的函数。
示例代码
vue
import { useNestedIframe } from '@/hooks/useIframe';
import { ref } from 'vue';
const iframeRef = ref(null);
const iframeSrc = ref('https://example.com/iframe');
const { sendMessageToIframe, clearCacheData, isIframeLoaded } = useNestedIframe({
iframeRef,
iframeSrc,
config: { parentId: 'tianyi', enableCache: true },
onReceiveMessage: (event) => {
console.log('父页面收到来自 iframe 的消息:', event.data);
// 处理收到的消息
},
});
// 示例:向 iframe 发送消息
sendMessageToIframe({ type: 'custom', content: 'Hello, iframe!' });
// 示例:清除缓存数据
clearCacheData();
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
useIframePage
使用说明
useIframePage
Hook 主要用于 iframe 页面,负责接收父页面发送的消息、管理缓存数据并与父页面通信。
参数说明
- onReceiveMessage:收到父页面消息时的回调函数。
- cacheData:需要缓存的数据,默认为空对象。
返回值
- sendMessageToParent:向父页面发送消息的函数。
示例代码
vue
import { useIframePage } from '@/hooks/useIframe';
import { ref } from 'vue';
const cacheData = ref({ key: 'initialValue' });
const { sendMessageToParent } = useIframePage({
onReceiveMessage: (data) => {
console.log(`父级页面传入的数据:${data}`);
},
cacheData,
});
// 示例:向父页面发送消息
sendMessageToParent({ type: 'ready' });
1
2
3
4
5
6
7
8
9
10
11
12
13
14
2
3
4
5
6
7
8
9
10
11
12
13
14
功能描述
消息通信
useNestedIframe
和 useIframePage
通过 window.postMessage
实现消息通信。父页面可以使用 sendMessageToIframe
发送消息给 iframe,iframe 可以使用 sendMessageToParent
向父页面发送消息。
iframe 加载管理
useNestedIframe
提供了 isIframeLoaded
属性,可以用于监听 iframe 加载的状态。
缓存数据管理
useIframePage
提供了缓存管理功能,当 config.enableCache
为 true
时,cacheData
会被保存到 localStorage
,并在重新加载时恢复。
安全性
两个 Hook 都通过 allowedOrigins
对消息来源进行校验,以保证只接收来自可信来源的消息。
注意事项
- 允许的来源校验:allowedOrigins 用于限定消息的来源,确保通信安全。
- 缓存启用:确保在需要持久化数据时启用缓存功能,以减少数据丢失风险。
- iframe 加载状态:在向 iframe 发送消息之前,应确认其加载完成。
适用场景
- 父页面需要控制嵌套 iframe 的行为,例如设置参数或清空缓存。
- iframe 页面需要与父页面同步状态,或在重新加载时恢复数据。
完整代码
vue
import { ref, onMounted, onUnmounted, computed, watch } from 'vue';
/**
* 嵌套页面使用的 hook,负责管理与 iframe 的通信和缓存
*
* @param {Object} options 配置项
* @param {Object} options.iframeRef iframe 的引用,默认为 null
* @param {String} options.iframeSrc iframe 的源地址,默认为空字符串
* @param {Object} options.config 缓存配置,包括父页面标识符和缓存启用标志
* @returns {Object} 返回一个对象,包含以下属性:
* - sendMessageToIframe: 向 iframe 发送消息的函数
*/
export function useNestedIframe({ iframeRef = null, iframeSrc = ref(''), config = { parentId: '', enableCache: false }, } = {}, onReceiveMessage) {
const targetOrigin = ref('');
// 设置允许的来源
const allowedOrigins = computed(() => [targetOrigin.value]);
const isIframeLoaded = ref(false);
// 计算 targetOrigin,当 iframeSrc 变化时更新
watch(iframeSrc, (newSrc) => {
try {
const url = new URL(newSrc);
targetOrigin.value = url.origin;
} catch (error) {
console.warn('Invalid iframeSrc:', newSrc);
targetOrigin.value = '';
}
}, { immediate: true });
// 向 iframe 发送消息
const sendMessageToIframe = (message) => {
if (iframeRef?.value?.contentWindow) {
const messageWithConfig = {
...message,
config
};
iframeRef.value.contentWindow.postMessage(messageWithConfig, targetOrigin.value);
} else {
console.warn('iframeRef 未定义或 iframe 尚未加载');
}
};
// 处理收到的消息
const handleMessage = (event) => {
const { origin, data } = event;
const isAllowedOrigin = !allowedOrigins.value.length || allowedOrigins.value.includes(origin);
console.log(allowedOrigins.value, origin, 'isAllowedOriginisAllowedOrigin')
if (!isAllowedOrigin) {
console.warn(`收到来自未授权源 ${origin} 的消息,已忽略`);
return;
}
console.log(`收到ifram消息:${data}`)
if (data.type === 'ready') {
console.log('收到 iframe ready 消息,发送初始化配置');
sendMessageToIframe({ type: 'init' });
}
onReceiveMessage?.(event);
};
// 清除缓存数据
const clearCacheData = () => {
sendMessageToIframe({ type: 'clear-cache' });
};
// 处理 iframe 加载完成事件
const handleIframeLoad = () => {
isIframeLoaded.value = true;
console.log('iframe 页面已加载');
};
// 监听消息事件
onMounted(() => {
window.addEventListener('message', handleMessage);
if (iframeRef?.value) {
iframeRef.value.addEventListener('load', handleIframeLoad);
}
});
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('message', handleMessage);
if (iframeRef?.value) {
iframeRef.value.removeEventListener('load', handleIframeLoad);
}
});
return {
sendMessageToIframe,
isIframeLoaded,
clearCacheData
};
}
/**
* iframe 页面使用的 hook,负责接收父页面发送的消息和管理缓存
*
* @param {Object} options 配置项
* @param {Function} options.onReceiveMessage 收到消息时的回调函数,默认为 null
* @returns {Object} 返回一个对象,包含以下属性:
* - sendMessageToParent: 向父页面发送消息的函数
*/
export function useIframePage({ onReceiveMessage = null, cacheData = ref({}) } = {}) {
const config = ref({ parentId: '', enableCache: false });
const cacheKey = computed(() => `${config.value.parentId}-iframeData`);
const targetOrigin = ref('');
// 设置允许的来源
const allowedOrigins = computed(() => [targetOrigin.value]);
// 向父窗口发送消息
const sendMessageToParent = (message) => {
console.log(allowedOrigins.value)
if (window.parent) {
window.parent.postMessage(message, targetOrigin.value); // 发送到父页面
} else {
console.warn('没有父窗口可发送消息');
}
};
// 监听 cacheData 的变化并保存到本地
watch(cacheData, (newData) => {
if (config.value.enableCache) {
try {
localStorage.setItem(cacheKey.value, JSON.stringify(newData));
console.log('缓存数据已更新');
} catch (error) {
console.warn('无法保存缓存数据:', error);
}
}
}, { deep: true });
// 从缓存中恢复数据
const restoreCachedData = () => {
if (config.value.enableCache) {
const cachedData = localStorage.getItem(cacheKey.value);
if (cachedData) {
try {
cacheData.value = JSON.parse(cachedData);
console.log('缓存数据已恢复');
} catch (error) {
console.warn('缓存数据解析失败:', error);
}
}
}
};
// 清除缓存数据
const clearCacheData = () => {
localStorage.removeItem(cacheKey.value);
console.log('缓存数据已清除');
};
// 处理收到的消息
const handleMessage = (event) => {
const { origin, data } = event;
const isAllowedOrigin = !allowedOrigins.value.length || allowedOrigins.value.includes(origin);
console.log(allowedOrigins.value, origin, 'isAllowedOriginisAllowedOrigin')
if (!isAllowedOrigin) {
console.warn(`收到来自未授权源 ${origin} 的消息,已忽略`);
return;
}
console.log(`收到父级${config.value.parentId}消息:${data}`)
if (data.config) {
config.value = data.config;
}
if (data.type === 'init') {
console.log('收到初始化消息,恢复缓存数据');
restoreCachedData();
}
if (data.type === 'clear-cache') {
console.log('收到清除缓存消息');
clearCacheData();
}
onReceiveMessage?.(event);
};
const setTargetOrigin = () => {
const urlParams = new URLSearchParams(window.location.search);
const originFromParams = urlParams.get('parentOrigin');
if (originFromParams) {
targetOrigin.value = decodeURIComponent(originFromParams);
return
}
// 尝试从 document.referrer 获取
try {
const referrerUrl = new URL(document.referrer);
targetOrigin.value = referrerUrl.origin;
} catch (error) {
console.warn('无法获取父页面的 origin');
return '*'; // 或者根据需求设置默认值
}
};
// 监听消息事件
onMounted(() => {
window.addEventListener('message', handleMessage);
setTargetOrigin()
sendMessageToParent({ type: 'ready' });
});
// 组件卸载时移除事件监听
onUnmounted(() => {
window.removeEventListener('message', handleMessage);
});
return {
sendMessageToParent,
};
}
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
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
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212