最新公告
  • 欢迎您光临网站无忧模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入浅出react-router原理

    正文概述 掘金(叁牛)   2021-04-07   601

    引言

    路由即根据不同的路径渲染不同的组件 实现方式有两种:

    HashRouter: 利用hash实现路由的切换
    BrowserRouter:利用h5 api 实现路由的切换
    

    自己手写实现react-router,彻底掌握react-router原理,我将以这样的顺序进行。

    • 路由的两种实现方式
    • 实现react-router-dom
    • 实现Router和Route
    • 实现hashHistory
    • 实现browserHistory
    • 正则表达式的应用
    • 实现matchPath
    • 实现Switch
    • 实现Redirect、Link
    • 实现嵌套路由
    • 受保护路由
    • 实现NavLink
    • 实现WithRouter、Prompt
    • 实现react-router(路由)中的hooks

    props路由参数: history:历史对象 location: 路径对象 match: 匹配参数

    深入浅出react-router原理

    hashRouter的实现方式

    function createHashHistory() {
        let action;
        const listeners = [];
        const historyStack = [];//历史栈
        let historyIndex = -1;
        let state;
        function listen(listener){
            listeners.push(listener);
            return () => {
                const index = listeners.indexOf(listener);
                listeners.splice(index, 1);
            }
        }
        
        const hashChange = () => {
            let pathname = window.location.hash.slice(1);
            Object.assign(history, { action, location: { pathanme, state }})
        }
        if(!action || action === 'PUSH') {
            historyStack[++historyIndex] = history.location;
        }else if(action === 'REPLACE'){
            historyStack[historyIndex] = history.location;
        }
        listeners.forEach(listener => listener(history.location));
    }
    window.addEventListener('hashChange', hashChange);
    
    function push(pathname, nextState) {
    /* history.push('user', {name: 'tom'}) ; history.push({ pathname: '/user', state: {name: "用户管理"} })*/
        action = 'PUSH';
        if(typeof pathname === 'object') {
            state = pathname.state;
            pathname = pathname.pathname;
        }else {
            state = nextState;
        }
        window.location.hash = pathname;
    }
    
    function replace(pathname, nextState) {
        if(typeof pathname === 'object') {
            pathname = pathname.pathname;
            state = pathname.state;
        }else {
            state = nextState;
        }
        window.location.hash = pathname;
    }
    
    /* go通过历史栈实现 */
    
    function go(n) {
        action = 'POP';
        historyIndex += n;
        const nextLocation = historyStack[historyIndex];
        state = nextLocation.state;
        window.location.hash = nextLocation.pathname;
    }
    
    function goBack(-1) {
        go(-1);
    }
    
    function goForward() {
        go(1);
    }
    /* hashHistory中的history没有兼容性问题 */
    const history = {
        action: 'POP',
        location: { pathname: "/", state: undefined },
        go,
        goBack,
        goForward,
        push,
        replace,
        listen
    }
    action = 'PUSH';
    if(window.location.hash) {
        hashChange();
    }else {
        window.location.hash = '/';
    }
    return history;
    

    browserHistory的实现方式

    利用H5 API实现路由的切换,HTML5规范给我们提供了一个history接口,HISTORY给我们提供了两个方法一个事件: history.pushState()和history.replaceState()、window.onpopstate。

    
    history.pushState(stateObject, title, url),包括三个参数
    第一个参数用于存储该url对应的状态对象,该对象可在onpopstate事件中获取,也可在history对象中获取
    第二个参数是标题,目前浏览器并未实现
    第三个参数则是设定的url
    pushState函数向浏览器的历史堆栈压入一个url为设定值的记录,并改变历史堆栈的当前指针至栈顶
    
    history.replaceState(stateObject, title, url):
    该接口与pushState参数相同,含义也相同
    唯一的区别在于replaceState是替换浏览器历史堆栈的当前历史记录为设定的url
    需要注意的是replaceState不会改动浏览器历史堆栈的当前指针
    
    
    history.onpopstate,该事件是window的属性
    该事件会在调用浏览器的前进、后退以及执行history.forward、history.back、和history.go触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针
    在不改变document的前提下,一旦当前指针改变则会触发onpopstate事件
    
    function createBrowserHistory() {
        const globalHistory = window.history;
        const listeners = [];
        let action, state, message;
        
        function go(n) {
            globalHistory.go(n);
        }
        
        function goForward() {
            go(1);
        }
        function goBack(n) {
            go(-1);
        }
        function listen(listener) {
            listeners.push(listener);
            return () => {
                let index = listeners.indexOf(listener);
                listeners.splice(index, 1);
            }
        }
        function setState(newState) {
            Object.assign(history, newState);
            listeners.forEach(listener => listener(history.location));
        }
        function push(pathname, nextState) {
            action = 'PUSH';
            if(typeof pathname === 'object') {
                state = pathname.state;
                pathname = pathname.pathname;
            }else { state = nextState }
            if(messgage) {
            let showMessage = message({ pathname });
            let allow = window.confirm(showMessage);
            if(!allow) return;
        }
        globalHistory.pushState(state, null, pathname);
        let location = { state, pathname };
        setState({ action, location });
        }
        /* 当回退或者前进的时候执行该函数,浏览器自带的,默认支持 */
        window.onpopstate = (event) => {
            setState({ action: 'POP', location: { pathname: window.location.pathname, state: globalHistory.state }})
        }
        function block(newMessage) {
            message = newMessage;
            return () => message = null;
        }
        const history = {
            action: 'POP';
            location: { pathname: window.location.pathname, state: globalHistory.state },
            go,
            goForward,
            goBack,
            push,
            listen,
            block
        }
        return history;
    }
    

    实现matchPath

    import pathToRegexp from 'path-to-regexp';
    
    const cache = {};
    
    function compilePath(path, options = {}) {
        let cacheKey = path + JSON.stringify(options);
        if (cache[cacheKey]) return cache[cacheKey];
    
        const keys = [];//处理路径参数
        const regexp = pathToRegexp(path, keys, options);
        let result = { keys, regexp };
        cache[cacheKey] = result;
        return { keys, regexp };
    }
    /* 
    pathname: 浏览器当前真实的路径名
    options: Route组件的props  path Component exact
    path Route的路径
    exact 是否精确匹配
    strict 是否严格匹配
    sensitive 是否大小写敏感
    */
    /* 根据路径匹配到match */
    function matchPath(pathname, options = {}) {
        let { path = '/', exact = false, strict = false, sentive = false } = options;
        let { keys, regexp } = compilePath(path, { end: exact, strict, sentive });
        const match = regexp.exec(pathname);
        if (!match) return null;
        const [url, ...values] = match;
        const isExact = pathname === url;
        /* 要求精确但是并不精确 */
        if (exact && !isExact) return null;
        return {
            path,//来自Route里的path路径
            url,//来自浏览器地址中的url
            isExact,//是否精确匹配
            params: keys.reduce((memo, key, index) => {
                memo[key.name] = values[index];
                return memo;
            }, {})
        }
    }
    
    export default matchPath;
    
    • 自定义event事件:使用customEvent构造函数
    window.addEventListener('eventName', (e) => {
        console.log('111', e);
    })
    let event = new CustomEvent('eventName', {
        detail: {
        message: 'Hello World'
        }
    })
    
    window.dispatchEvent(event);
    

    深入浅出react-router原理

    正则表达式的使用

    • path-to-regext 把路径转成一个正则,跟真实的路径做匹配

    正则表达式可视化工具: jex.im/regulex/#!f…

    • 嵌套路由不需要在源码中有额外的代码,Route已经有支持了
    剩下的内容见: github.com/China-forre…

    下载网 » 深入浅出react-router原理

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元