React-Route源码讲解(一)

本文详细探讨了history库的重要性,整体结构,以及关键API如BrowserHistory的分析。通过实现mini-history包,解释了listen、push和back方法的工作原理,为理解React-Route奠定了基础。

一、文章思路

这篇文章主要是讲解history库并在最后会带着大家一起实现一个小型history库。

二、为啥要讲history?

因为history库提供着最核心的动作API,我们经常使用的this.props.history.push等相应API都是这个库提供的。

三、history库整体分析

这个库分为2部分,一个docs、一个package;docs目录主要是对package目录的描述,package目录就是history库的核心代码。

四、docs分析

4.1、getting-started.md

这个文件主要是讲了这个包的产物分类,应用,属性。其中会遇到写API,如果想具体了解API,它就会指引你跳到api-reference.md文件。其中我觉得最重要的话就是:

根据环境,它们将创建的路由分为3种,一种是“browser history”,一种是“hash history”,一种是“memory history”。

4.2、api-reference.md

这个文件主要是对三种路由包含的API的一些讲解和使用

五、API分析(BrowserHistory为例)

5.1、history对象

 const history = {
        action (){},
        location (){},
        createHref (){},
        push (){},
        replace (){},
        go (){},
        back (){},
        forward (){},
        listen (){},
        block (){}
    } 

这个就是history对象(源码里是ts)

5.2、action

 function action (value){
       const actionType = ['PUSH', 'POP', 'REPLACE'];
       return actionType.filter(item => item == value)[0];
   } 

action主要是操作标识,何种方式操作历史堆栈

5.3、location

 function getIndexAndLocation (){
       let { pathname, search, hash } = window.location;
       let state = window.history.state || {};
       return [state.idx, { 
           pathname, 
           search, 
           hash, 
           state: state.usr || null,
       }]
   }
   let [index, location] = getIndexAndLocation();
   const history = {
       get location (){
           return location;
       }
   } 

location,字如其名,描述当前路由的位置,里面保存着当前路由的相关信息,比如url, search(查询字符串)、state(自己维护的state)等等。

5.4、createHref

 function createPath ({ pathname = '/', search = '', hash = '' }){
        if (search && search !== '?')
        pathname += search.charAt(0) == '?' search : '?' + search;
        return pathname;
    }
    function createHref (to){
        return typeof to == 'string' ? to : createPath(to);
    }
    const history = {
        createHref
    } 

createHref,创建链接,说白了就是根据传参来得到url。

5.5、listen

 function createEvents (){
        let handlers = [];
        return {
            get length (){
                return handlers.length;
            }
            push (fn){
                handlers.push(fn);
                return function (){
                    handlers = handlers.filter(handler => handler !== fn)
                };
            }
            call (arg){
                handlers.forEach( fn => fn && fn(arg) );
            }
        }
    }
    let listeners = createEvents();
    const history = {
        listen (fn){
            return listeners.push(fn);
        }
    }; 

上面的代码就是创建一个对象obj,当调用 history.listen(fn)的时候,实际上就是执行obj.push,push这个动作会把传入的函数参数放入到自由变量handlers里面,当人为的改变当前历史堆栈后,就会触发listeners.call, 从而实现路由监听。这里需要解释一下什么是人为的改变当前历史堆栈? 就是手动this.props.history.xx() 或者 手动改变浏览器上方的前进或者后退按钮。

5.6、push

 // 存放着listen里的函数参数
    let listeners = createEvents();
    
    // 创建新的history的location信息
    function getNextLocation (to, state){
        return {
            pathname: window.location.pathname,
            search: '',
            hash: '',
            to,
            state,
            key: Math.random()
        }
    }

    // 获取路由状态和url
    function getHistoryStateAndUrl (nextLocation, index){
        return [
            {
                usr: nextLocation.state,
                key: nextLocation.key,
                idx: index
            }, 
            nextLocation.pathname + nextLocation.search
        ]
    }

    function push (to, state){
        let nextAction = 'PUSH';
        let nextLocation = getNextLocation(to, state);
        let index = 0;
        let [historyState, url] = getHistoryStateAndUrl(nextLocation, index + 1);
        window.history.pushState(historyState, '', url);
        listeners.call({ 'PUSH', {  pathname, search, hash, state, key } })
    }
    
    const history = {
        push
    } 

当我们调用 history.push('/usr', { name: '小明' }), 实际上就是在调用 window.history.pushState(state, ‘’, url), 中间的其他步骤只是在转化state,最后呢,就是触发listen函数。

六、mini-history包的实现

  • 我们需要创建一个history对象
  • 这个对象我们只需实现listen、push、back方法,其他方法与push方法都差不多

6.1、mini-listen的实现

简要思路:就是找一个全局变量来存放listen函数,并在push、back函数里去主动触发它

 // 声明监听函数集合(全局)
    let listenerArr = [];
    const createBrowserHistory = () => {
        function listen (fn){
            listenerArr.push(fn);
        }
        return {
            listen
        }
    } 

6.2、mini-push的实现

简要思路:声明一个全局变量来记录当前路由,push的时候将路由的url等相关信息全都赋值到全局变量里,随后执行监听函数

 // 当前路由位置信息
    let currentRouteLocation = {};
    // 声明监听函数集合
    let listenerArr = [];
    
    const createBrowserHistory = () => {
        function push (to, state){
            currentRouteLocation.pathname = to;
            currentRouteLocation.state = state;
            listenerArr.forEach(fn => fn && fn());
        }
        return {
            push
        }
    } 

现在我们来测试一下:

 const history = createBrowerHistory();
    history.listen(() => console.log('当前路由的信息:', currentRouteLocation) );
    history.push('/usr', { name: '小明' }) 

输出信息如下:

history1.jpg

好了,走到这说明我们的监听函数和push函数都已经成功了。

6.3、mini-back的实现

简要思路:声明一个全局变量historyStack, 当我们手动调用push方法时,我们也要向 historyStack里push路由信息,因为路由信息里有唯一顺序号,所以当我们调用back时,根据顺序号就能找到上一个路由。

 // 用于存放历史堆栈
    let historyStack = [];
    // 同样记录这是第几个路由
    let index = 0;
    // 当前路由信息
    let currentRouteLocation = {};
    
    const createBrowerHistory = () => {
    
        // push 方法需要做出小改动
        function push (to, state){
            currentRouteLocation.pathname = to;
            currentRouteLocation.state = state;
            // back方法会根据index来正确的回到上一层路由
            currentRouteLocation.index = index;
            // 需要在历史堆栈里添加一下
            history.push({ ...currentRouteLocation });
            listenerArr.forEach(fn => fn && fn());
            index++;
        }
    
        function back (){
            index -= 2;
            const current = historyStack.filter(item => item.index == index);
            currentRouteLocation = { ...current[0] };
            listenerArr.map( fn => fn && fn() );
        }
        return {
            back,
            push
        }
    } 

七、总结

这篇文章我们梳理了history的工作流程,其中如果有没讲到的地方或者是错误的地方欢迎大家指出,那么下一篇我将会讲解React-Route系列的最后一篇dom篇,敬请期待哈哈哈哈。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值