锚点是通过在界面中增加一些特征(比如 id),然后在 URL 地址后面加上 #id 就可以访问到指定页面的指定位置,这样可以让我们快速跳转到页面的某个位置,但是在 react-router 中这种方法遇到了问题,因为 react-router 会把 # 当做是 hash 来处理。导致即使跳转到指定页面后,# 后面的锚点也不生效。针对这个问题,在 react-router 的一个 issue 中大家也展开了激烈的讨论。以下是我看过以后整理的几种解决办法。

只有某些页面需要

当只有某些页面需要使用锚点的时候,可以在访问到该页面后使用 react 生命周期中 componentDidMount 阶段来判断 # 后面的字符串,然后使用 dom 操作取到这个字符串所属的 dom,然后滚动到该位置。具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
componentDidMount() {
// Decode entities in the URL
// Sometimes a URL like #/foo#bar will be encoded as #/foo%23bar
window.location.hash = window.decodeURIComponent(window.location.hash);
const scrollToAnchor = () => {
const hashParts = window.location.hash.split('#');
if (hashParts.length > 2) {
const hash = hashParts.slice(-1)[0];
document.querySelector(`#${hash}`).scrollIntoView();
}
};
scrollToAnchor();
window.onhashchange = scrollToAnchor;
}

说不准哪些页面会使用

以上方法适用于具有生命周期的 react component,而且是在组件的生命周期中实现这个功能,若需要所有页面都实现这个功能,明显我们不可能在所有 component 中都增加这个方法。这就产生另外一个方案,就是在 Router 的 onUpdate 函数中实现该功能。onUpdate 函数在路由跳转后会被调用一次,如下所示:

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
import React from 'react';
import { render } from 'react-dom';
import { Router, Route, browserHistory } from 'react-router';

const routes = (
// your routes
);

function hashLinkScroll() {
const { hash } = window.location;
if (hash !== '') {
// Push onto callback queue so it runs after the DOM is updated,
// this is required when navigating from a different page so that
// the element is rendered on the page before trying to getElementById.
setTimeout(() => {
const id = hash.replace('#', '');
const element = document.getElementById(id);
if (element) element.scrollIntoView();
}, 0);
}
}

render(
<Router
history={browserHistory}
routes={routes}
onUpdate={hashLinkScroll}
/>,
document.getElementById('root')
)

当地址改变后,hashLinkScroll 会被调用,函数内部实现原理与上面的原理是一样的,拿到 # 后面的 id 并找到指定 dom,然后滚动到该 dom 的位置。

总结

两种方案各有优劣,可以根据自己的情况来选择使用。