React router 中的实现
react-router的基础——history.js
history是一个第三方js库,借鉴HTML5 history对象的理念,在其基础上又扩展了一些功能,用来管理历史记录,可以兼容不同的浏览器和不同的环境,根据不同的环境提供了三种不同的API。
HashHistory:针对老版本的浏览器,主要通过Hash实现。 BrowserHistory:针对较高版本的浏览器,主要通过H5的History实现 MemoryHistory:主要通过内存中的历史记录实现,主要用于Node环境。
History支持发布/订阅功能,当history发生改变的时候,可以自动触发订阅的函数。 history库可以说是对原生history对象的扩展,使功能更加丰富
// 内部的抽象实现
function createHistory(options={}) {
...
return {
listenBefore, // 内部的hook机制,可以在location发生变化前执行某些行为,AOP的实现
listen, // location发生改变时触发回调
transitionTo, // 执行location的改变
push, // 改变location
replace,
go,
goBack,
goForward,
createKey, // 创建location的key,用于唯一标示该location,是随机生成的
createPath,
createHref,
createLocation, // 创建location
}
}
重点在于createHistory的实现
设计思想:
- 对History进行了一次封装,使能够识别将url的变化与componet渲染进行匹配
- 根据BrowserRouter等不同的API针对H5的history的重构
- 结构的构建,同时对history属性进行注册。
- 在Router的componentWillMount中注册history的事件回调。
- 在Redirect中进行路径的计算,调用
history.push/history.replace
等更新history信息。 - Route中根据计算的匹配结果,进行页面首次渲染/页面更新渲染处理。
为了维护state的状态,可以将其存储在sessionStorage里面:
// createBrowserHistory/createHashHistory中state的存储
function saveState(key, state) {
...
window.sessionStorage.setItem(createKey(key), JSON.stringify(state));
}
function readState(key) {
...
json = window.sessionStorage.getItem(createKey(key));
return JSON.parse(json);
}
// createMemoryHistory仅仅在内存中,所以操作比较简单
const storage = createStateStorage(entries); // storage = {entry.key: entry.state}
function saveState(key, state) {
storage[key] = state
}
function readState(key) {
return storage[key]
}
react-router-dom 组件结构
react-router-dom 提供了BrowserRouter、Route、Link等api,可以通过 dom 操作触发事件控制路由。
- Link组件,会渲染一个a标签;BrowserRouter和HashRouter组件,前者使用pushState和popState事件构建路由,后者使用hash和 hashchange事件构建路由。
render() {
const { replace, to, innerRef, ...props } = this.props; // eslint-disable-line no-unused-vars
invariant(
this.context.router,
"You should not use <Link> outside a <Router>"
);
invariant(to !== undefined, 'You must specify the "to" property');
const { history } = this.context.router;
const location =
typeof to === "string"
? createLocation(to, null, null, history.location)
: to;
const href = history.createHref(location);
return (
<a {...props} onClick={this.handleClick} href={href} ref={innerRef} />
);
}
- react-router-dom在react-router的基础上扩展了可操作dom的api。
- Swtich 和 Route 都是从react-router中导入了相应的组件并重新导出,没做什么特殊处理。 Route 实现
render() {
const { match } = this.state; // 布尔值,表示 location 是否匹配当前 Route 的 path
const { children, component, render } = this.props; // Route 提供的三种可选的渲染方式
const { history, route, staticContext } = this.context.router; // Router 传入的 context
const location = this.props.location || route.location;
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null; // Component 创建
if (render) return match ? render(props) : null; // render 创建
if (typeof children === "function") return children(props); // 回调 children 创建
if (children && !isEmptyChildren(children)) // 普通 children 创建
return React.Children.only(children);
return null;
}
switch 实现
render() {
const { route } = this.context.router;
const { children } = this.props;
const location = this.props.location || route.location;
let match, child;
React.Children.forEach(children, element => {
if (match == null && React.isValidElement(element)) {
const {
path: pathProp,
exact,
strict,
sensitive,
from
} = element.props;
const path = pathProp || from;
child = element;
match = matchPath(
location.pathname,
{ path, exact, strict, sensitive },
route.match
);
}
});
return match
? React.cloneElement(child, { location, computedMatch: match })
: null;
}
可直接 npm 安装 react-router-dom,使用其api。
参考链接: