跨页面 history state 传递
背景
最近,在开发过程中遇到了一个 history state 相关的问题。当新开标签页打开新的页面时,是无法传递 history state 的。
问题样例
比如说,下面这个跳转链接通过新开标签页打开,虽然提供了 state 属性,但是由于 history state 不能跨标签页传递,所以在新开的页面获取不到这个 state 的,设置也是白设置。
import React from 'react';
import { Link } from 'react-router-dom';function Test() {return (<Link to={{ pathname: '/path/to', state: { a: 1, b: 2 } }} target="_blank">跳转链接</Link>);
}
解决方案
既然不能直接跨页面传递 history state,那么是不是可以间接的传递?是的,解决方案就是先新开一个标签页,但不是直接打开目标页面,而是打开一个中转页面。
在这个中转页面,可以通过一些方式获取到要跳转页面的 path 和 state 状态,然后再转到最终的目标页面。
获取 path 和 state 有多种方式,一般传递数据量不大的情况,可通过 query string 方式传递至中转页,对于数据量大的情况,则可采用 window.postMessage() 的方式。
对于 query string 方式, path 和 state 需要先进行百分号编码以及整体 JSON 序列化,在中转页获取时需要进行相应的解码及解析。
对于 window.postMessage() 方式,可以直接传递对象,不用对数据进行预处理,但使用稍微麻烦点,需要先打开中转页,然后监听中转页相关事件再传递数据。
跳转至中转页后,将会调用 history.replace() 方法转至目标页面,这样目标页面就能够获取到 history state。
代码实现
中转页代码实现:
/*** 跨页面 history state 传递中转页(解决跨页面不能传递 history state 的问题)* 使用方法 1(适合传递数据量少的情况):* window.open('/path/to/transfer?data={JSON字符串}')* 生成 JSON 字符串样例代码:encodeURIComponent(JSON.stringify({path: '/path/to', state: {a:1, b: 'bb'}}))** 使用方法 2(使用前提:调用页与中转页同源):* const win = window.open('/path/to/transfer');* if (win) {* win.addEventListener('load', function () {* win && win.postMessage({ type: HISTORY_STATE_TRANSFER_MESSAGE_TYPE, payload: { path: '/path/to', state: { a: 1, b: 'bb' } } }, '*');* });* }** 使用方法 3(适合非同源的情况):* const win = window.open('/path/to/transfer');* function receiveMessage(event: MessageEvent) {* const data = event.data;* if (win && data.type === HISTORY_STATE_TRANSFER_READY_MESSAGE_TYPE) {* win.postMessage({ type: HISTORY_STATE_TRANSFER_MESSAGE_TYPE, payload: { path: '/path/to', state: { a: 1, b: 'bb' } } }, '*');* window.removeEventListener('message', receiveMessage, false);* }* }* win && window.addEventListener('message', receiveMessage, false);*/
import React, { useEffect } from 'react';
import { Spin } from 'antd';
import queryString from 'query-string';
import { RouteComponentProps, useHistory } from 'react-router-dom';// history state 传递消息类型
export const HISTORY_STATE_TRANSFER_MESSAGE_TYPE = 'CrossPageHistoryStateTransfer';
// history state 传递准备消息类型
export const HISTORY_STATE_TRANSFER_READY_MESSAGE_TYPE = 'CrossPageHistoryStateTransferReady';interface IProps extends RouteComponentProps {}const CrossPageHistoryStateTransfer = (props: IProps) => {const history = useHistory();const { data } = queryString.parse(props.location.search, {parseNumbers: true,});let parsedData: { path?: string; state?: any } | null = null;if (typeof data === 'string') {try {parsedData = JSON.parse(decodeURIComponent(data));} catch (err) {console.log(err);}}useEffect(() => {// URL 方式获取数据if (parsedData && typeof parsedData?.path === 'string') {// 用 replace 跳转,不用 push 是为了避免 back 后又自动 forwardhistory.replace(parsedData.path, parsedData.state);}// postMessage 方式获取数据function receiveMessage(event: MessageEvent<{ type: string; payload: { path?: string; state?: any } }>) {const data = event.data;if (data.type === HISTORY_STATE_TRANSFER_MESSAGE_TYPE && typeof data.payload.path === 'string') {history.replace(data.payload.path, data.payload.state);}}window.addEventListener('message', receiveMessage, false);window.opener && window.opener.postMessage({ type: HISTORY_STATE_TRANSFER_READY_MESSAGE_TYPE }, '*');return () => {window.removeEventListener('message', receiveMessage, false);};}, []);return <Spin spinning={true}></Spin>;
};export default CrossPageHistoryStateTransfer;
中转页调用代码样例:
其中 /path/to/transfer 路径指代中转页路径,/path/to/target 路径指代跳转的目标路径。
方式一(适合传递数据量少的情况):
const json = encodeURIComponent(JSON.stringify({path: '/path/to/target',state: { a: 1, b: 2, c: 3 },})
);
window.open(`/path/to/transfer?data=${json}`);
方式二(使用前提:调用页与中转页同源):
const win = window.open('/path/to/transfer');
if (win) {win.addEventListener('load', function () {win && win.postMessage({ type: HISTORY_STATE_TRANSFER_MESSAGE_TYPE, payload: { path: '/path/to/target', state: { a: 1, b: 'bb' } } }, '*');});
}
方式三(适合非同源的情况):
const win = window.open('/path/to/transfer');
function receiveMessage(event: MessageEvent) {const data = event.data;if (win && data.type === HISTORY_STATE_TRANSFER_READY_MESSAGE_TYPE) {win.postMessage({ type: HISTORY_STATE_TRANSFER_MESSAGE_TYPE, payload: { path: '/path/to/target', state: { a: 1, b: 'bb' } } }, '*');window.removeEventListener('message', receiveMessage, false);}
}
win && window.addEventListener('message', receiveMessage, false);
答疑
有同学可能会问为什么不直接用 URL 传参,非要用 history state?
因为如果是简单的场景,比如说只需要一个 id 字段时,这种情况只需要用 URL 传参即可。但对于有些情况,比如说参数非常多,或者参数中包含了 URL 的保留字需要百分号编码等等,这些情况用 URL 传参就不太适合了。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
