咱們使用React開發項目的時候,基本上是單頁面應用,也就離不開路由。路由看似神祕,當咱們簡單的模擬一下它的核心功能後,發現也就這麼回事兒。本文就詳細的介紹一下react-router-dom
的 HashRouter
的核心實現邏輯。react
本文實現的功能主要包含:git
HashRouter
Route
Link
MenuLink
Switch
Redirect
先不說代碼是怎樣寫的,先上圖,讓你們看一下這個HashRouter
究竟是個什麼東東:
好吧,確定有人會說這些圈圈又是什麼東東呀,客官不要着急,待我慢慢解釋:github
HashRouter
是一個大的容器,它控制着他本身到底渲染成什麼樣子,那麼它是經過什麼控制的呢,看它的名字就能猜出來,那就是window.location.hash
。HashRouter
開始渲染的時候就會拿它本身身上的pathname
屬性跟它肚子裏的Route
的path
進行匹配,匹配上的話,就會渲染Route
的component
對應的組件。Link
是怎樣切換路由的呢,很簡單,就是經過this.props.history.push(path)
來改變HashRouter
中的pathname
屬性,進而驅動Route們
進行從新渲染,再次匹配咱們的路由,最終實現路由的切換。介紹了一下簡單的邏輯,接下來咱們就看一下具體是怎樣實現的吧,以下圖:
react-router
HashRouter
是一個繼承了React.Component
的類,這個類上的state
包括location
,監聽着hash
的變化以驅動Route
組件的從新渲染,另外還有一個history
屬性,能夠切換頁面的路由。Route
、Link
、MenuLink
、Switch
、 Redirect
,其中Route
的是基礎是核心,MenuLink
和某些有特定邏輯的渲染都是在Route
的基礎上實現的。Route
組件上能夠接收三種變量,包括component
、render
、children
,其中render
、children
是都是函數,render
是根據特定的邏輯渲染元素,children
是用來渲染MenuLink
,這兩個函數都接收當前路由的props
,函數的返回值是要渲染的元素。Switch
實現的邏輯是,返回children
中跟hash
匹配到的第一個「孩子」。HashRouter
HashRouter
將window.loacation.hash
跟本身的state
掛鉤,經過改變本身的state
驅動頁面的從新渲染。dom
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class HashRouter extends Component { constructor() { super(); this.state = { location: { pathname: window.location.hash.slice(1) || '/', // 當前頁面的hash值 state: {} //保存的狀態 } }; } // 定義上下文的變量類型 static childContextTypes = { location: PropTypes.object, history: PropTypes.object } // 定義上下文的變量 getChildContext() { return { location: this.state.location, history: { push: (path) => { // 就是更新 window.hash值 if (typeof path === 'object') { let {pathname, state} = path; this.setState({ location: { ...this.state.location, state // {from: '/profile'} } }, () => { window.location.hash = pathname; }) } else { window.location.hash = path; } } } } } render() { return this.props.children; // 渲染頁面元素 } componentDidMount() { window.location.hash = window.location.hash.slice(1) || '/'; // 監聽window的hash的變化,驅動頁面的從新刷新 window.addEventListener('hashchange', () => { this.setState({ location: { ...this.state.location, pathname: window.location.hash.slice(1) || '/' } }); }) } }
Route
Route
的渲染核心邏輯就是將本身的path
和當前頁面的hash
進行匹配,匹配上了就渲染相應的元素,匹配不上就什麼都不渲染。函數
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp' export default class Route extends Component { // 定義上下文context的類型 static contextTypes = { location: PropTypes.object, history: PropTypes.object } render() { // 解構傳入Route的props let {path, component: Component, render, children} = this.props; // 解構上下文的屬性 let {location, history} = this.context; let props = { location, history }; // 將傳入Route的path和當前的hash進行匹配 let keys = []; let regexp = pathToRegexp(path, keys, {end: false}); keys = keys.map(key => key.name); let result = location.pathname.match(regexp); if (result) { // 匹配上了 let [url, ...values] = result; props.match = { path, url, params: keys.reduce((memo, key, index) => { // 獲取匹配到的參數 memo[key] = values[index]; return memo; }, {}) }; if (Component) { // 普通的Route return <Component {...props} />; } else if (render) { // 特定邏輯的渲染 return render(props); } else if (children) { // MenuLink的渲染 return children(props); } else { return null; } } else { // 沒有匹配上 if (children) { // MenuLink的渲染 return children(props); } else { return null; } } } }
Redirect
Redirect
就幹了一件事,就是改變HashRouter
的state
,驅動從新渲染。this
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class Redirect extends Component { // 定義上下文context的Type static contextTypes = { history: PropTypes.object } componentDidMount() { // 跳轉到目標路由 this.context.history.push(this.props.to); } render() { return null; } }
MenuLink
import React, {Component} from 'react'; import Route from "./Route"; import Link from './Link' export default ({to, children}) => { // 若是匹配到了,就給當前組件一個激活狀態的className return <Route path={to} children={props => ( <li className={props.match ? "active" : ""}> <Link to={to}>{children}</Link> </li> ) }/> }
Link
Link
就是渲染成一個a標籤,而後給一個點擊事件,點擊的時候更改HashRouter
的狀態,驅動從新渲染。url
import React, {Component} from 'react'; import PropTypes from 'prop-types'; export default class Link extends Component { static contextTypes = { history: PropTypes.object } render() { return ( <a onClick={() => this.context.history.push(this.props.to)}>{this.props.children}</a> ) } }
Switch
import React, {Component} from 'react'; import PropTypes from 'prop-types'; import pathToRegexp from 'path-to-regexp'; export default class Switch extends Component { static contextTypes = { location: PropTypes.object } render() { let {pathname} = this.context.location; let children = this.props.children; for (let i = 0, l = children.length; i < l; i++) { let child = children[i]; let path = child.props.path; if (pathToRegexp(path, [], {end: false}).test(pathname)) { // 將匹配到的第一個元素返回 return child; } } return null } }
好了,這幾個功能介紹完了,你是否對HashRouter
的原理有所瞭解了呢?本文只是貼出部分代碼,若是有須要請看demo能夠手動體驗一下哦。spa
參考文獻:code