单页应用路由原理
补充知识:window.location
```
http://172.16.49.154:3000/search?q=URL#search
console.log(location.href); // http://172.16.49.154:3000/search?q=URL#search
console.log(location.protocol); // http:
console.log(location.host); // 172.16.49.154:3000
console.log(location.hostname); // 172.16.49.154
console.log(location.port); // 3000
console.log(location.pathname); // search
console.log(location.search); // ?q=UR
console.log(location.hash); // #search
console.log(location.origin); // http://172.16.49.154:3000
```
1. **Location.reload()**
重新加载来自当前 URL的资源。他有一个特殊的可选参数,类型为 Boolean,该参数为true时会导致该方法引发的刷新一定会从服务器上加载数据。如果是 false或没有制定这个参数,浏览器可能从缓存当中加载页面。
2. **Location.replace()**
用给定的URL替换掉当前的资源。与 assign() 方法不同的是用 replace()替换的新页面不会被保存在会话的历史 History中,这意味着用户将不能用后退按钮转到该页面。
补充知识:window.history 和 History
window.history是一个只读属性,用来获取History 对象的引用,History 对象提供了操作浏览器会话历史(浏览器地址栏中访问的页面,以及当前页面中通过框架加载的页面)的接口。
-
History.length
会话历史中元素的数目,包括当前加载的页 -
History.state
返回一个表示历史堆栈顶部的状态的值。
-
History的 back / forward / go 方法
back()和forward()表示前往上一页或者下一页, 用户可点击浏览器左上角的返回按钮和前进按钮模拟此方法. 等价于 history.go(-1)或者history.go(1)
go()通过当前页面的相对位置从浏览器历史记录( 会话记录 )加载页面,当整数参数超出界限时,那么这个方法没有任何效果也不会报错
-
History.pushState / repalceState
pushState() 和 repalceState 按指定的名称和URL(如果提供该参数)将数据push进会话历史栈或者更新历史栈上最新的入口,页面不会跳转。
// pushState() 需要三个参数: 一个状态对象, 一个标题 (目前被忽略), 和 (可选的) 一个URL history.pushState({foo: "bar",};, "page 2", "bar.html"); -
window.popstate事件
// window.addEventListener('popstate', (event) => { ... });
history.js库
以创建history路由进行分析:(方便区分,分为window.history 和 history库)
-
辅助函数
import { createLocation // 创建history.location格式的对象 // 类似 {key: '',state: '',pathname: '',search: '',hash: ''} // createLocation(path, state, key, currentLocation) // 入参path可以为string类型或者包含 {pathname: '',search: '',hash: ''}的对象 // state为一个对象,初始为window.history的state,push,replace等方法可以传入一个state,history把它用来当做传入下一个页面的参数来读取 } from './LocationUtils'; import { addLeadingSlash, // 路径最前面加斜杠 stripTrailingSlash, // 路径最后面去斜杠 hasBasename, // 创建history时,是否传入了basename属性 stripBasename, // 去掉basename createPath // 创建完成路径 pathname + search + hash } from './PathUtils'; import createTransitionManager from './createTransitionManager'; import { canUseDOM, // 是否可以使用DOM API getConfirmation, // 跳转前确认的弹框 supportsHistory, // 是否支持H5的History API supportsPopStateOnHashChange, // 是否支持onPopstate事件 isExtraneousPopstateEvent // 源码注释:Ignore extraneous popstate events in WebKit. } from './DOMUtils'; -
createBrowserHistory
// transitionManager 为一个发布订阅模式, // 包含appendListene(添加监听),notifyListeners(触发所有监听), // setPrompt(设置一个Prompt,用于跳转前的弹框), // comfirmTransitionTo(确认跳转函数)四个方法 function createBrowserHistory(props = {}) { // ... const history = { // globalHistory.length = window.history.length length: globalHistory.length, action: 'POP', // history内部管理的一个变量,导航的action location: initialLocation, createHref, // 生成完整href地址的方法 // push(path,state)方法接受path(string或者对象)和 state为参数 // 随机创建一个key,调用createLocation生成要跳转路由的location // 调用createHref生成 href // 调用 transitionManager.confirmTransitionTo 主要用于跳转前的确认 // 如果确认跳转,window.history.pushState({ key, state }, null, href) // history对象merge上要跳转路由的 key,state,和window.history.length // 然后出发所有监听事件(传入**location,action**) push, // replace与push类似 replace, go, // history.go() goBack, // history.go(-1) goForward, // history.go(1) block, // 重要! listen方法使用 transitionManager.appendListene方法向订阅数组里推入一个监听函数,返回一个卸载监听的方法(这种设计模式,在react中非常常见), // 同时调用checkDOMListeners方法,此方法会在订阅数据添加上 **第一个** 监听事件后,同时发起window.addEventListener('popstate', handlePopState); // 同时会在卸载完全部监听后,**订阅数组为空时**,移除监听 window.removeEventListener('popstate', handlePopState); listen }; return history; }
react-router
对外暴露了Router,Router,Switch,matchPath,withRouter等API
-
Router
class Router extends React.Component { static computeRootMatch(pathname) { // 静态方法 return { path: "/", url: "/", params: {}, isExact: pathname === "/" }; } constructor(props) { super(props); this.state = { location: props.history.location }; // 这是一种hack的写法,从新建实例的时候就开始对location的监听 this._isMounted = false; this._pendingLocation = null; if (!props.staticContext) { this.unlisten = props.history.listen(location => { // 如果组件以加载到真实的DOM,直接设置location的state if (this._isMounted) { this.setState({ location }); } else { // 如果没有,先用一个变量进行保存,等到componentDidMount后进行赋值 this._pendingLocation = location; } }); } } componentDidMount() { this._isMounted = true; if (this._pendingLocation) { this.setState({ location: this._pendingLocation }); } } componentWillUnmount() { // 卸载监听 if (this.unlisten) this.unlisten(); } render() { return ( // 使用了React新的Context API,在最外层提供了history,location等供全局使用 <RouterContext.Provider children={this.props.children || null} // 子代属性 value={{ // 使用的 路由类型 history: this.props.history, // history.js库封装的location,然后内部进行管理 location: this.state.location, // 返回类似{ path: "/", url: "/", params: {}, isExact: pathname === "/" }的匹配对象; match: Router.computeRootMatch(this.state.location.pathname), // 自定义传入的context staticContext: this.props.staticContext }} /> ); } } -
Route
<RouterContext.Consumer> {context => { // Router组件传入this.props.location 或者 context 的 location const location = this.props.location || context.location; const match = this.props.computedMatch // Router组件传入computedMatch ? this.props.computedMatch // <Switch> already computed the match for us : this.props.path // Router组件传入path // matchPath 函数返回类似{ path: "/", url: "/", params: {}, isExact: pathname === "/" }的匹配对象 ? matchPath(location.pathname, this.props) : context.match; const props = { ...context, location, match }; let { children, component, render } = this.props; ...... // 最后为每个Router提供一个新的Provider,包含history,location,match等 return ( <RouterContext.Provider value={props}> {children && !isEmptyChildren(children) // children不为空则渲染children ? children : props.match // ? component // Router组件传入类组件 使用createElement 创建组件 ? React.createElement(component, props) : render // Route组件传入函数组件 传入props执行 ? render(props) : null : null} // 没有匹配到返回null </RouterContext.Provider> ); }} </RouterContext.Consumer> -
withRouter
<Router ...> <Route ... /> <Route ... /> <Route ... /> // 这些Route中能从props中获取到 history location match等, // 但是Route里面的子组件是没有的, // 如果想获得这些属性,可以通过props往下层传递, // 或者直接通过withRouter,它是利用RouterContext.Consumer获得 </Router> -
generatePath
根据路径(例如:'/user/:id/:name')和 params(例如:{id: 10001, name: 'bob'})生成完整路径(/user/10001/bob)的函数,就是填充url的字符串
react-router-dom
-
导出内容
export * from "react-router"; // 对react-router的API直接导出 // 导出 BrowserRouter HashRouter Link NavLink export { default as BrowserRouter } from "./BrowserRouter"; export { default as HashRouter } from "./HashRouter"; export { default as Link } from "./Link"; export { default as NavLink } from "./NavLink"; -
BrowserRouter
class BrowserRouter extends React.Component { history = createHistory(this.props); // history库暴露的createHistory方法 render() { // Router 组件传入生成的history对象 return <Router history={this.history} children={this.props.children} />; } } export default BrowserRouter; // HashRouter 与其一样 创建history对象时使用了hash -
Link 和 NavLink
Link封装的 a 标签,NavLink封装的Link组件
总结
react-router提供核心路由功能,react-router-dom用作浏览器端路由,依赖react-router,如果 我们写web应用,直接引入react-router-dom即可,此外还包含react-router-native,可以用于react-native应用
