react-router-dom原理浅析

341 阅读2分钟

前言

在介绍router之前,先描述一下什么是观察者模式。通俗得讲就是先注册(或者说是缓存)事件回调,然后当触发事件的时候,调用缓存的回调函数。比如history.js和redux.js其实就是这种模式。

前端路由用一句话表达就是:在不刷新浏览器页面的情况下,改变url的同时,更新视图。

react-router-dom本质上依赖history.js库,首先利用history.listen注册url变化事件的回调(也就是对应react 的 setState),然后当通过history.pushState来不刷新页面改变路由的时候,将自定义的最新的location作为参数,调用缓存的回调函数,来达到上面描述的目的。

BrowserRouter

import { createBrowserHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
    history = createHistory(this.props);
    render() {
        return <Router history={this.history} children={this.props.children}  />
    }
}

实例化history对象,传给Router组件。

**HashRouter
**

import { createHashHistory as createHistory } from "history";

class BrowserRouter extends React.Component {
    history = createHistory(this.props);
    render() {
        return <Router history={this.history} children={this.props.children}  />
    }
}

实例化history对象,传给Router组件。

Router

class Router extends React.Component {
    constructor() {
        this.props.history.listen((location) => {
            this.setState({ location });
        })
    }
}

Router组件只做了一件事情:通过history.listen,添加url变化的回调函数。回调函数中调用setState重置
state.location,进而重新执行render函数。这样就达到了路由变化,视图(render)执行的目的。

histort.createHashHistory

function createHashHistory() {
    const listeners = []; // 缓存回调函数

    window.addEventListener('popState', () => applyTx()); 当手动点击前进后退时,也触发回调函数

    function applyTx() {
        const location = getIndexAndLocation(); // 获取最新的location
        // 注入最新的location,调用注册的回调。
        // 其实就是调用Router组件注册的location => setState({ location })
        listeners.forEach((callback) => { callback(location) })
    }


    function getIndexAndLocation() {
        // 假设此时url为:host/path/#/home?id=1
        // 则会将真正的hash作为pathname返回:{search: "?id=1", pathname: "/home"}
        // 这样route组件才能将真正的hash跟props.pathname比较
        return parsePath(window.lcoation.hash.substr(1));
    }

    function replace(to) {
        let nextLocation = getNextLocation(to); // 根据to来自定义最新的location        let url = getHistoryStateAndUrl(nextLocation); 根据nextLocation获取需要跳转的地址
        window.history.replaceState(state, '', url); // 无刷新更新url
        
        applyTx(); // url更新之后,循环listeners,调用事先注册的回调函数
    }
    
    return {
        replace, // 触发url更新函数
        listen(callback) { listeners.push(callback) } // 监听函数        ...others // 其它一些模块,就不展开说了
    }
    
}

history.createBrowserHistory

createBrowerHistory跟createHashHistory的逻辑基本一样,只是在将路径解析成自定义的location的时候
略有不同,也就是getIndexAndLocation函数。hashHistroy是将真正的hash解析成pathname字段,而browerHistory
是直接将最新的location导出就好了。

function getIndexAndLocation() {
    let { pathname, search, hash } = window.location;

    return { pathname, search, hash }
}

Route

class Route extends React.Component {
    render() {
        const match = matchPath(location.pathname, this.props)
        return (
            match ? children : null
        )
    }
}

简单理解,route组件只做了一件事:每次render的时候,比较props.pathname和context注入的location.href是否匹配,
如果匹配则渲染children,否则返回null

总结

history监听popstate/hashchange事件,同时包装了replaceState等事件。Router注册回调,回调中执行setState,这样就达到了路由变化,
回调事件执行,render重新渲染的目的。然后route.render中对href和props.pathname做匹配判断,来决定
是否渲染children。