avatar
fireworks99
keep hungry keep foolish

Vue Router

路由的两种方式

  1. Hash:location.hash = 'about',形如file:///Users/.../202303311041.html#foo
  2. HTML5 History API:
    • history.pushState({}, '', "about") (可返回)
    • history.replaceState({}, '', about);不可返回
    • history.go(num)

1.The simplest router

(hashchange + <component :is>)

<!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">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>
<body>

    <div id="app">
        <a href="#foo">foo</a>
        <a href="#bar">bar</a>
        <component :is="cmp"></component>
    </div>

    <script>
        const app = new Vue({
            el: "#app",
            components: {
                Foo: {
                    template: "<div>foo</div>"
                },
                Bar: {
                    template: "<div>bar</div>"
                }
            },
            data() {
                return {
                    cmp: "bar"
                };
            }
        });

        window.addEventListener('hashchange', () => {
            // app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
            app.cmp = window.location.hash.slice(1);
        })
    </script>
</body>
</html>

2.Extracting a route table

(hashchange + <component :is>)

<!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">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>

    <div id="app">
        <a href="#foo">foo</a>
        <a href="#bar">bar</a>
        <a href="#fire">fire</a>
        <component :is="curCmp"></component>
    </div>

    <script>

        const Foo = { template: `<div>foo</div>` };
        const Bar = { template: `<div>bar</div>` };
        const Fire = { template: `<div>fire</div>` };
        const NotFound = { template: `<div>not found</div>` };

        const routeTable = {
            foo: Foo,
            bar: Bar,
            fire: Fire
        };

        const app = new Vue({
            el: "#app",
            components: {
                Foo, Bar, Fire, NotFound
            },
            data() {
                return {
                    cmp: "bar"
                }
            },
            computed: {
                curCmp() {
                    return routeTable[this.cmp] || NotFound;
                }
            }
        });

        window.addEventListener('hashchange', () => {
            // app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
            app.cmp = window.location.hash.slice(1);
        })
    </script>
</body>

</html>

(hashchange + render)

<!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">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
</head>

<body>

    <div id="app">
        <!-- <a href="#foo">foo</a>
        <a href="#bar">bar</a>
        <a href="#fire">fire</a>
        <component :is="curCmp"></component> -->
    </div>

    <script>

        const Foo = { template: `<div>foo</div>` };
        const Bar = { template: `<div>bar</div>` };
        const Fire = { template: `<div>fire</div>` };
        const NotFound = { template: `<div>not found</div>` };

        const routeTable = {
            foo: Foo,
            bar: Bar,
            fire: Fire
        };

        const app = new Vue({
            el: "#app",
            components: {
                Foo, Bar, Fire, NotFound
            },
            data() {
                return {
                    cmp: "bar"
                }
            },
            render(h) {
                return h('div', [
                    h('a', { attrs: {href: "#foo"} }, "foo"),
                    h('a', { attrs: {href: "#bar"} }, "bar"),
                    h('a', { attrs: {href: "#fire"} }, "fire"),
                    h(routeTable[this.cmp] || NotFound)
                ]);
            }
        });

        window.addEventListener('hashchange', () => {
            // app.cmp = window.location.hash === "#foo" ? "foo" : "bar";
            app.cmp = window.location.hash.slice(1);
        })
    </script>
</body>

</html>

3.URL matching

(with ‘path-to-regexp’)

前面的例子仅仅是在顶级页面切换,情境简单。

路由形参:’/user/:username’

路由实参:’/user/123’

路由解析:{ username: ‘123’ }

第三方库path-to-regexp的使用

<!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">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="../JS/path-to-regexp.js"></script>
</head>

<body>

    <div id="app">

    </div>

    <script>

        /**
         * 路由形参:'/user/:username'
         * 路由实参:'/user/123'
         * 路由解析:{ username: '123' }
        **/

        const keys = [];
        const regex = pathToRegexp("/foo/:username", keys);
        console.log(keys);
        /**
         * [
            {
                "name": "username",
                "prefix": "/",
                "delimiter": "/",
                "optional": false,
                "repeat": false,
                "partial": false,
                "pattern": "[^\\/]+?"
            }
           ]

           keys[0].name
        **/


        const result = regex.exec('/foo/123');
        console.log(result);                    //["/foo/123","123"]
        //result[0 + 1]

        //路由解析: 
        // { 
        //     keys[0].name: result[0 + 1],
        //     keys[1].name: result[1 + 1],
        //     ...
        //     keys[n].name: result[n + 1]
        // }

        const res1 = regex.exec('/bar/123');
        console.log(res1);                       //null

        const res2 = regex.exec('foo/123');      //缺少 '/',匹配不到
        console.log(res2);                       //null

    </script>
</body>

</html>

实现一个简单的router

<!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">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <script src="../JS/path-to-regexp.js"></script>
</head>

<body>

    <div id="app">

    </div>

    <script>
        const Foo = {
            props: ['id'],
            template: `<div>foo with id: {{id}}</div>`
        };
        const Bar = { template: `<div>bar</div>` };
        const NotFound = { template: `<div>not found</div>` };

        const routeTable = {
            '/foo/:id': Foo,
            '/bar': Bar
        };

        //打表(预编译)
        const compiledRoutes = []
        Object.keys(routeTable).forEach(key => {
            const dynamicSegments = []
            const regex = pathToRegexp(key, dynamicSegments)
            const component = routeTable[key]
            compiledRoutes.push({
                component,
                regex,
                dynamicSegments
            })
        })

        //查表
        console.log(compiledRoutes.length);         //2
        compiledRoutes.forEach(value => console.log(value));
        /**
         * 
         *  compiledRoutes[0]:
         * 
         *  component: {
                "props": [
                    "id"
                ],
                "template": "<div>foo with id: {{id}}</div>"
            }
            dynamicSegments: [{
                "name": "id",
                "prefix": "/",
                "delimiter": "/",
                "optional": false,
                "repeat": false,
                "partial": false,
                "pattern": "[^\\/]+?"
            }],
            regex: /^\/foo\/((?:[^\/]+?))(?:\/(?=$))?$/i
        **/

        /**
         * 
         *  compiledRoutes[1]:
         * 
         *  component: {
                "template": "<div>bar</div>"
            }
            dynamicSegments: [],
            regex: /^\/bar(?:\/(?=$))?$/i
        **/

        window.addEventListener('hashchange', () => {
            app.url = window.location.hash.slice(1);
        });

        const app = new Vue({
            el: "#app",
            data() {
                return {
                    // url: "bar"
                    url: window.location.hash.slice(1)
                }
            },
            render(h) {
                const path = '/' + this.url;//为了能匹配到

                let componentToRender;
                let props = {};

                //solution
                compiledRoutes.some(route => {
                    const match = route.regex.exec(path)
                    componentToRender = NotFound
                    if (match) {
                        componentToRender = route.component
                        route.dynamicSegments.forEach((segment, index) => {
                            props[segment.name] = match[index + 1]
                            /**
                             * props["id"] = match[1] = "456"
                            **/
                        })
                        return true
                    }
                })
                /**
                 * 
                **/



                return h('div', [
                    h(componentToRender, { props }),
                    h('a', { attrs: { href: '#foo/123' } }, 'foo 123'),
                    ' | ',
                    h('a', { attrs: { href: '#foo/456' } }, 'foo 456'),
                    ' | ',
                    h('a', { attrs: { href: '#bar' } }, 'bar'),
                    ' | ',
                    h('a', { attrs: { href: '#garbage' }}, 'garbage')
                ])

            }
        })


    </script>
</body>

</html>
Site by Baole Zhao | Powered by Hexo | theme PreciousJoy