跨页面 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,那么是不是可以间接的传递?是的,解决方案就是先新开一个标签页,但不是直接打开目标页面,而是打开一个中转页面。

在这个中转页面,可以通过一些方式获取到要跳转页面的 pathstate 状态,然后再转到最终的目标页面。

获取 pathstate 有多种方式,一般传递数据量不大的情况,可通过 query string 方式传递至中转页,对于数据量大的情况,则可采用 window.postMessage() 的方式。

对于 query string 方式, pathstate 需要先进行百分号编码以及整体 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 后又自动 forward
      history.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 传参就不太适合了。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Vue中的history模式可以使用HTML5的History API来管理浏览器历史记录,并且可以使用Vue Router的push和replace方法来进行页面跳转。其中,push方法可以将新的页面添加到历史记录中,而replace方法可以替换当前页面的历史记录。具体实现可以通过以下步骤: 1. 在Vue Router中配置路由,同时设置mode为history模式。 ``` const router = new VueRouter({ mode: 'history', routes: [...] }) ``` 2. 在页面中使用router-link组件来进行页面跳转,例如: ``` <router-link to="/home">Home</router-link> ``` 3. 在Vue组件中使用router的push或replace方法来进行页面跳转,例如: ``` this.$router.push('/home') ``` 这样就可以实现Vue中的history模式,使用HTML5的History API来管理浏览器历史记录,并且使用Vue Router的push和replace方法进行页面跳转。 ### 回答2: Vue的`history.pushState`是一种用于修改浏览器历史记录的方法。它是Vue Router提供的一种导航方式。 通过调用`this.$router.push()`方法,我们可以使用`history.pushState`方法实现页面的导航操作。它允许我们在不刷新页面的情况下更改URL,并且会在浏览器的历史记录中添加新的条目。 `history.pushState`方法的参数是一个状态对象、标题(目前在大多数浏览器中忽略)、以及可选的URL。通过传递不同的URL,我们可以实现不同页面之间的切换。 使用`pushState`方法时,Vue Router会将该导航操作记录下来,以便在浏览器的前进后退按钮或通过`this.$router.back()`、`this.$router.go()`等方法进行页面导航时,能够正确恢复到之前的导航状态。 例如,我们可以在Vue组件的方法中使用`this.$router.push()`来触发页面导航操作,然后显示其他组件或者切换URL路径,同时也会修改浏览器的历史记录。 总而言之,Vue的`history.pushState`方法是实现路由导航的一种技术手段,它可以在不刷新页面的情况下改变URL,并且能够记录导航状态,使得页面导航操作更加流畅和友好。 ### 回答3: vue-history pushstate是Vue.js中的一个路由组件,它用于在浏览器历史记录中添加一个新的状态。 在Vue.js中,我们经常使用Vue Router来管理前端路由。Vue Router使用了HTML5的history模式,而不是传统的hash模式。 通过使用pushState方法,我们可以在浏览器历史记录中添加一个新的状态,这样我们就可以通过浏览器的前进和后退按钮来在不同的路由之间导航。 如下是使用history.pushState方法的示例: ```javascript router.push({ path: '/example' }); ``` 这将在浏览器历史记录中添加一个新的路由状态,并且会自动导航到'/example'路径。 可以通过在浏览器的开发者工具中查看Network面板,或者查看浏览器地址栏的变化来验证pushState的效果。 需要注意的是,当使用pushState方法时,我们应该捕获popstate事件来处理浏览器的前进和后退按钮操作,以确保正确地更新路由状态。 通过使用Vue Router的history模式和pushState方法,我们可以更好地管理前端路由并提供更好的用户体验。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值