react路由

357 阅读6分钟

路由

  • yarn add react-router-dom

使用HASH路由,HashRouter

  • 页面中所有内容都用他包起来
  • 并且内容只有一个根节点
使用Route
  • 设置路由规则的,基于不同规则渲染不同组件
  • 行间属性 path="地址" 匹配地址
  • 行间属性 component={组件} 显示组件
  • 行间属性 render={()=>{}}地址匹配到后执行这个render函数,函数返回什么就渲染什么,这样我们在函数中可以做路由权限校验
  • 行间属性 exact 精准匹配
Switch
  • 只显示一个组件
Redirect
  • 重定向,行间加上to代表重定向的地址
  • 也可以加上path,代表匹配地址的重定向
二级路由
  • 在哪用就在哪里写,不过不用HashRouter了,直接Switch就可以
跳转
  • 分为Link和NavLink两种
NavLink
  • 行间属性to 代表点击跳转的位置,
  • to直接写字符串就是地址,写个对象,那对象内的pathname是地址,search就是路由传参
  • 会渲染成a标签
  • NavLink优势,会拿页面路由地址和NavLink中的to来进行匹配,如果匹配上就给谁添加一个active样式类名,我们可以针对这个类名写样式
Link
  • 用法与NavLink一样,只是不会默认加active样式类名了

受路由管控组件与非受控路由管控组件

受路由管控组件
  • 受:根据路由Route规则校验后进行渲染的组件,render函数处理的不算
  • 默认属性(props)中有三项值的,history、location、match
history
  • go、goBack(-1)、goForward(相当于go(1))、push都是实现路由跳转的
location
  • 存储了当前路由跳转时候传递的一些信息
  • search是问号传参的信息
  • pathname是地址
  • state 是隐式传参的信息,如果刷新就会丢失,为undefined
match
  • 存储路由规则解析出来的信息
  • params
非受路由管控的组件
  • 非受:不是根据路由进行渲染的组件
  • 属性中是没有history、match、location这些东西的
  • 如果想使用的话,可以根据withRouter高阶函数处理,使用的时候在export default的时候执行withRouter(组件名),那么props里面就有了

页面跳转的两大类方式

根据Link和NavLink
  • 基于标签进行跳转
  • 行间传递to,可以传字符串也可以传对象
  • 对象模式:pathname是地址search是问号传参,是显示传参state是隐式传参,路由内部接收到,但是不会在url中显示
  • 问题隐式传递页面刷新就没有了,所以说隐式传参只在路由跳转的那一会儿有用
  • 问号传参&隐式传参的信息都可以基于location获取到
根据this.props.history里面的方法,也称为编程式导航
  • 例如this.props.history.push('/home/add')
  • push跳转也可以写成对象格式,语法与Link里面的to一样
路径参数
  • 将参数信息当做URL路径的一部分,类似与vue中的动态路由
  • 例如<Route path="/home/:id/:name" component={CustomList}></Route>
  • props里面的match里面的params可以拿到id与name

使用 react-router 的好处

  • 常用的两种路由方式是 hash 和 history
  • 举例说明,直接刷新页面,他的 js 会重新加载,使用 hash 和 history 后,页面没有刷新,所以,他的静态资源不会刷新,也就没有了那么长等待时间,用户体验会好一点

尝试一下

  • 常见的跳转页面的方式,
    • 网址框输入 url,回车 会导致刷新页面,静态资源重新请求
    • location.href 会导致刷新页面,静态资源重新请求
    • location.hash 更改 hash,静态资源不会重新请求,没有刷新页面
    • history.go 或 history.pushState 等方法,静态资源不会重新请求,没有刷新页面

History.pushState()

  • 作用:在不刷新页面的情况下,实现页面切换
  • 三个参数
  • window.history.pushState(state, title, url)
  • state 是 object 类型,跳转后会将 state 存储,可以通过 history.state 拿到,不传可以为 null,页面刷新不丢失,页面关闭丢失
  • title 是 string 类型,作用为页面 title,可以为 null,目前浏览器不认可这个值,写了也没用
  • url 是 string 类型,代表要跳转的路径
  • 注意:hashchange 事件无法监听到他切换
    • popstate 事件也无法监测到

popstate 事件

  • 用来监听 history 部分事件
  • 为什么说是部分事件,因为 history.pushState 和 replace 事件监听不到,
  • 但是 go,back,和浏览器自带回退前进都可以监听到(前提是 history 触发过了),否则监听不到
location.hash = "#/1";
history.go(-1); //监听不到

history.pushState(null, null, "#/1");
//监听不到
history.go(-1); //监听到了

路由受控组件和非路由受控组件

  • 个人觉得判断是否是路由受控组件的标准就是在他的 props 里面是否有父级传递过来的 history,location,和 match 集合
  • 常见的几种路由受控组件:Route 匹配到的组件,withRouter 处理过的组件
  • 将非受控组件变为受控组件的方法,withRouter(组件)

Route

  • 我们配置路由的时候,根据他的 path 来渲染组件
  • 他会将 path 匹配到的 route 全部渲染,匹配规则是包含关系,如/a 包含/
  • exact=true 就是绝对相等,才会渲染

Switch

  • 因为 Route 默认会将 path 匹配到的组件全部展示,但是往常我们只需要一个页面匹配一个组件,公有的写在外面即可
  • 作用:在所有 Route 外包一层 Switch,即可只渲染匹配到的第一个

Link

  • 作用:跳转
  • 为什么我们有 a 标签了,还要用 Link 呢?
    • 因为 a 标签会刷新页面
    • 那如果使用 history.pushState 来刷新页面,不是也能达到想要的效果吗?
      • 如果 pathname 改变是会走渲染逻辑的,但是如果是问号传参的更改,history.pushState 并不会刷新视图,
      • 这个时候我们可以使用 props 里的 history.push 或者是 Link 来实现
  • 参数:to
    • 可以是一个对象,也可以是一个字符串

Redirect

  • 作用:重定向
  • 参数:
    • from 选填,string,不填的时候默认是匹配到所有路径,填上就是只匹配对应路径才跳转

    • to 要跳转的页面 string

  • 应用场景:用户进到我们没有处理的页面,让他跳到首页
  • 他用来 history.replace 来实现的,因为使用 history.back 会回退到上上一个

两种路由方法

  • HashRouter
  • BrowserRouter
    • 区别:一个是 hash,一个是 history
    • 个人感觉到的区别:
    • hash 带个#,有些丑
    • hash 是相对于 pathname 的,而 history 是相对于根路径的(所以使用 history 可能会导致一种情况,页面刷新后,找不到页面了)

粗略实现下两种路由

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./index.css">
    <script src="./index.js"></script>
    <style>
        #root {
            width: 100vw;
            height: 100vh;
        }
    </style>
    <title>hash</title>
</head>

<body>
    <div id="root">
        <a href="/myHash/index.html">原地转圈(刷新页面请求资源)</a>
    </div>
</body>

</html>
<script>
    const locationClick = (e) => {
        e.preventDefault();
        location.href = "/myHash/index.html";
    }
    const RootDom = document.querySelector('#root');
    const mapList = {
        type: 'history',
        append: function () {
            this.domList.forEach(item => {
                RootDom.appendChild(item);
            })
        },
        pond: {},
        domList: [],
        remove: function () {
            let {
                domList
            } = this;
            if (domList.length === 0) return;
            domList.forEach(item => {
                RootDom.removeChild(item);
            });
            this.domList = [];
        },
        addPond: function (params) {
            const {
                path,
                component,
                exact
            } = params;
            this.pond[path] = {
                component: component && component(),
                exact: exact || false
            }
        },
        init: function (pathname) {
            const pond = this.pond;
            const pathList = Object.keys(pond);
            this.remove();
            pathList.forEach(item => {
                const reg = new RegExp(`^${item}`);
                if (reg.test(pathname)) {
                    const dom = pond[item]['component'];
                    if (dom) {
                        this.domList.push(dom);
                    }
                }
            });
            this.append();
        }
    };
    const HashRouter = () => {
        mapList['type'] = 'hash';
    };
    const BrowserRouter = () => {
        mapList['type'] = 'history';
    };
    const historyClick = (e) => {
        e.preventDefault();
        const href = e.target.history;
        history.pushState(null, null, href);
        mapList.init(href);
    }
    //Link标签
    const Link = (params) => {
        let {
            chidren,
            href
        } = params;
        let A = document.createElement('a');
        if (mapList['type'] === 'hash') {
            href = '#' + href;
            A.setAttribute('href', href);
        } else {
            A.onclick = historyClick;
            A.history = href;
        }
        
        A.innerHTML = chidren;
        RootDom.appendChild(A);
    };
    /*
    path 路径 string
    component 渲染 function
    */
    const Route = (params) => {
        mapList.addPond(params);
    };
    HashRouter();
    Link({
        href: '/',
        chidren: '我是/'
    });
    Link({
        href: '/1',
        chidren: '我是/1'
    });
    Route({
        path: '/',
        component: () => {
            let Box = document.createElement('div');
            Box.innerHTML = '我是/';
            return Box;
        },
    });
    Route({
        path: '/1',
        component: () => {
            let Box = document.createElement('div');
            Box.innerHTML = '我是/1';
            return Box;
        },
    });
    const change = () => {
        let pathname = '';
        if (mapList['type'] === 'hash') {
            pathname = location.hash.slice(1);
        } else {
            pathname = location.pathname;
        }
        mapList.init(pathname);
    }
    change();
    window.addEventListener('hashchange', change, false);
    window.addEventListener('popstate', change, false);
</script>