vue篇之路由详解

一、vue路由传参的几种方式

父组件:<router-link :to="`/path/${id}`"><router-link>
  
//获取参数
子组件:this.$route.params.id

2.编程式导航 $router.push

(1)冒号的的形式传递传参(页面刷新数据不会丢失)

父组件:

this.$router.push({
    path: `/describe/${id}`
})

对应路由配置如下:

{
    path: '/describe/:id',
    name: 'Describe',
    component: Describe
}
# path中添加/:id来对应 $router.push 中path携带的参数

子组件:获取参数

this.$route.params.id

(2)params传参

父组件:通过路由属性中的name来确定匹配的路由,通过params来传递参数

this.$router.push({
    name: 'Describe', 
    params: { 
        id: id
    }
})

对应路由配置如下:

{
    path: '/describe',
    name: 'Describe',
    component: Describe
}
# 这里可以添加:/id 也可以不添加,添加数据会在url后面显示,页面刷新数据不会丢失,不添加数据就不会显示,页面刷新数据会丢失。不过重要的参数本就不应该显示在地址栏上面!用params传参不是好的实践,不安全!

子组件:获取参数

this.$route.params.id

(3)query传参也就是?形式(路径会显示传递的参数,刷新页面数据不会丢失)

父组件:使用path来匹配路由,然后通过query来传递参数

this.$router.push({
    path: "/describe", //路由配置中的name
    query:{
        id: id
    }
});

对应路由配置如下:

{
    path: '/describe',
    name: 'Describe',
    component: Describe
}
# 注意这里不能使用:/id来传递参数了,因为父组件中,已经使用query来携带参数了。

子组件:获取参数

this.$route.query.id
## important!!! ##
在子组件中 获取参数的时候是$route.params 而不是 $router;
和name配对的是params,和path配对的是query

二、Vue-router 中hash路由和history路由

1.hash模式

hash:即地址栏 URL 中的 # 符号。比如这个 URL:www.abc.com/#/hello,hash 的值为 #/hello。

特点: hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。

当页面中的 hash 发生变化时,会触发hashchange事件,因此我们可以监听这个事件,来判断路由是否发生了变化。

window.addEventListener(
    'hashchange',
    function (event) {
        const oldURL = event.oldURL; // 上一个URL
        const newURL = event.newURL; // 当前的URL
        console.log(newURL, oldURL);
    },
    false
);

2.history模式

history:利用了 HTML5 History Interface 中新增的 pushState() 和 replaceState() 方法。(需要特定浏览器支持)这两个方法应用于浏览器的历史记录栈,在当前已有的 back、forward、go 的基础之上,它们提供了对历史记录进行修改的功能。只是当它们执行修改时,虽然改变了当前的 URL,但浏览器不会立即向后端发送请求。

特点:监听 url 中的路径变化,需要客户端和服务端共同的支持。

在 history 路由中,我们一定会使用window.history中的方法,常见的操作有:

  • back():后退到上一个路由;
  • forward():前进到下一个路由,如果有的话;
  • go(number):进入到任意一个路由,正数为前进,负数为后退;
  • pushState(obj, title, url):前进到指定的 URL,不刷新页面;
  • replaceState(obj, title, url):用 url 替换当前的路由,不刷新页面;

调用这几种方式时,都会只是修改了当前页面的 URL,页面的内容没有任何的变化。但前 3 个方法只是路由历史记录的前进或者后退,无法跳转到指定的 URL;而pushState和replaceState可以跳转到指定的 URL。(只修改页面的 URL,而不发送请求的5种方法)

router.push、 router.replace 和 router.go 跟 window.history.pushState、 window.history.replaceState 和 window.history.go好像, 实际上它们确实是效仿 window.history API

3.应用场景

(1)调用 history.pushState() 相比于直接修改 hash,存在以下优势:

  • pushState() 设置的新 URL 可以是与当前 URL 同源的任意 URL;而 hash 只可修改 # 后面的部分,因此只能设置与当前 URL 同文档的 URL;

  • pushState() 设置的新 URL 可以与当前 URL 一模一样,这样也会把记录添加到栈中;而 hash 设置的新值必须与原来不一样才会触发动作将记录添加到栈中;

  • pushState() 通过 stateObject 参数可以添加任意类型的数据到记录中;而 hash 只可添加短字符串;

  • pushState() 可额外设置 title 属性供后续使用。

(2)pushState 和 replaceState 两个方法跟 location.href 和 location.replace 两个方法区别

  • location.href 和 location.replace 切换时要向服务器发送请求,而 pushState 和 replace 仅修改 url,除非主动发起请求;

  • 仅切换 url 而不发送请求的特性,可以在前端渲染中使用,例如首页是服务端渲染,二级页面采用前端渲染;

  • 可以添加路由切换的动画;

  • 在浏览器中使用类似抖音的这种场景时,用户滑动切换视频时,可以静默修改对应的 URL,当用户刷新页面时,还能停留在当前视频。

4.总结

(1) hash 模式下,仅 hash 符号之前的内容会被包含在请求中,如 http://www.abc.com,因此对于后端来说,即使没有做到对路由的全覆盖,也不会返回 404 错误。

(2)history 模式下,前端的 URL 必须和实际向后端发起请求的 URL 一致,如 http://www.abc.com/book/id 如果后端缺少对 /book/id 的路由处理,将返回 404 错误。Vue-Router 官网里如此描述:“不过这种模式要玩好,还需要后台配置支持……所以呢,你要在服务端增加一个覆盖所有情况的候选资源:如果 URL 匹配不到任何静态资源,则应该返回同一个 index.html 页面,这个页面就是你 app 依赖的页面。”

(3)结合自身例子,对于一般的 Vue + Vue-Router + Webpack + XXX 形式的 Web 开发场景,用 history 模式即可,只需在后端(Apache 或 Nginx)进行简单的路由配置,同时搭配前端路由的 404 页面支持。

三、Vue 路由 导航守卫

1.全局守卫

(1)全局前置守卫 router.beforeEach

router.beforeEach((to,from,next)=>{
    // ...
})
回调函数中的参数,to:进入到哪个路由去,from:从哪个路由离开,next:函数,决定是否展示你要看到的路由页面。
  • next(): 进行管道中的下一个钩子。如果全部钩子执行完了,则导航的状态就是 confirmed (确认的)。

  • next(false): 中断当前的导航。如果浏览器的 URL 改变了 (可能是用户手动或者浏览器后退按钮),那么 URL 地址会重置到 from 路由对应的地址。

  • next('/') 或者 next({ path: '/' }): 跳转到一个不同的地址。当前的导航被中断,然后进行一个新的导航。你可以向 next 传递任意位置对象,且允许设置诸如 replace: true、name: 'home' 之类的选项以及任何用在 router-link 的 to prop 或 router.push 中的选项。

  • next(error): (2.4.0+) 如果传入 next 的参数是一个 Error 实例,则导航会被终止且该错误会被传递给 router.onError() 注册过的回调。

举例说明:

router.beforeEach((to,from,next)=>{
    if(to.path == '/login' || to.path == '/register'){
        next();
    }else{
        alert('您还没有登录,请先登录');
        next('/login');
    }
})
// 判断to.path当前将要进入的路径是否为登录或注册,如果是就执行next(),展示当前界面。如果不是,就弹出alert,然后移至登录界面。
// 这样就可实现,用户在未登录状态下,展示的一直是登录界面。

(2)全局解析守卫 router.beforeResolve

和 router.beforeEach 类似,区别是在导航被确认之前,同时在所有组件内守卫和异步路由组件被解析之后,解析守卫就被调用。

(3)全局后置钩子 router.afterEach

router.afterEach((to,from)=>{
    // ...
})
只有两个参数,to:进入到哪个路由去,from:从哪个路由离开

举例说明:

router.afterEach((to,from)=>{
    alert("after each");
})
// 每次切换路由时,都会弹出alert,点击确定后,展示当前页面。

2.路由独享的守卫

可以在路由配置上直接定义 beforeEnter 守卫,用法与全局守卫一致。只是,将其写进其中一个路由对象中,只在这个路由下起作用。

const router = new VueRouter({
    routes: [
        {
            path: '/foo',
            component: Foo,
            beforeEnter: (to, from, next) => {
                // ...
            }
        }
    ]
})

3.组件内的守卫

(1)beforeRouteEnter

beforeRouteEnter(to, from, next) {
    // 在渲染该组件的对应路由被 confirm 前调用
    // 不能获取组件实例 'this'
    // 因为当守卫执行前,组件实例还没被创建
}

beforeRouteEnter 守卫 不能 访问 this,因为守卫在导航确认前被调用,因此即将登场的新组件还没被创建。

不过,可以通过传一个回调给 next来访问组件实例。在导航被确认的时候执行回调,并且把组件实例作为回调方法的参数。

beforeRouteEnter (to, from, next) {
    next(vm => {
        // 通过 'vm' 访问组件实例
    })
}

举例

<script>
    export default {
        data(){
            return{
                name:"Arya"
            }
        },
        beforeRouteEnter:(to,from,next)=>{
           console.log("hello " + this.name); // hello undefined
        }
    }
</script>
<script>
    export default {
        data(){
            return{
                name:"Arya"
            }
        },
        beforeRouteEnter:(to,from,next)=>{
            next(vm=>{
                 console.log("hello " + vm.name); // hello Arya
            })
        }
    }
</script>
## 注意 ## beforeRouteEnter 是支持给 next 传递回调的唯一守卫。对于 beforeRouteUpdate 和 beforeRouteLeave 来说,this 已经可用了,所以不支持传递回调,因为没有必要了。

(2)beforeRouteUpdate

beforeRouteUpdate(to, from, next) {
    // just use `this`
    this.name = to.params.name
    next()
    // 在当前路由改变,但是该组件被复用时调用
    // 举例来说,对于一个带有动态参数的路径 /foo/:id,在 /foo/1 和 /foo/2 之间跳转的时候,
    // 由于会渲染同样的 Foo 组件,因此组件实例会被复用。而这个钩子就会在这个情况下被调用。
    // 可以访问组件实例 'this'
}

(3)beforeRouteLeave

beforeRouteLeave(to, from, next) {
    const answer = window.confirm('Do you really want to leave? you have unsaved changes!')
    if (answer) {
        next() // 确认离开
    } else {
        next(false) //取消执行
    }
    // 导航离开该组件的对应路由时调用
    // 可以访问组件实例 'this'
}

点击其他组件时,判断是否确认离开。确认执行next();取消执行next(false),留在当前页面。

3.完整的导航解析流程

(1)导航被触发。

(2)在失活的组件里调用 beforeRouteLeave 守卫。

(3)调用全局的 beforeEach 守卫。

(4)在重用的组件里调用 beforeRouteUpdate 守卫。

(5)在路由配置里调用 beforeEnter。

(6)解析异步路由组件。

(7)在被激活的组件里调用 beforeRouteEnter。

(8)调用全局的 beforeResolve 守卫。

(9)导航被确认。

(10)调用全局的 afterEach 钩子。

(11)触发 DOM 更新(mounted)。

(12)调用 beforeRouteEnter 守卫中传给 next的回调函数,创建好的组件实例会作为回调函数的参数传入。

4.钩子函数执行后输出的顺序截图

顺序截图

四、不知道的 keep-alive

在开发Vue项目的时候,大部分组件是没必要多次渲染的,所以Vue提供了一个内置组件keep-alive来缓存组件内部状态,避免重新渲染。

<transition> 相似,<keep-alive> 是一个抽象组件:它自身不会渲染一个 DOM 元素,也不会出现在组件的父组件链中。

  • 缓存动态组件:

< keep-alive > 包裹动态组件时,会缓存不活动的组件实例,而不是销毁它们,此种方式并无太大的实用意义。

<!-- 基本 -->
<keep-alive>
    <component :is="view"></component>
</keep-alive>

<!-- 多个条件判断的子组件 -->
<keep-alive>
    <comp-a v-if="a > 1"></comp-a>
    <comp-b v-else></comp-b>
</keep-alive>
  • 缓存路由组件:

使用 keep-alive 可以将所有路径匹配到的路由组件都缓存起来,包括路由组件里面的组件,keep-alive 大多数使用场景就是这种。

<keep-alive>
    <router-view></router-view>
</keep-alive>

在被keep-alive包含的组件/路由中,会多出两个生命周期的钩子:activated 与 deactivated。

activated在组件第一次渲染时会被调用,之后在每次缓存组件被激活时调用。

  • activated调用时机:

第一次进入缓存路由/组件,在mounted后面,beforeRouteEnter守卫传给 next 的回调函数之前调用:

beforeMount => 如果你是从别的路由/组件进来(组件销毁destroyed/或离开缓存deactivated) =>
mounted => activated 进入缓存组件 => 执行 beforeRouteEnter回调

因为组件被缓存了,再次进入缓存路由/组件时,不会触发这些钩子:

beforeCreate created beforeMount mounted 都不会触发。

所以之后的调用时机是:

组件销毁destroyed/或离开缓存deactivated => activated 进入当前缓存组件 => 执行 beforeRouteEnter回调
// 组件缓存或销毁,嵌套组件的销毁和缓存也在这里触发

  • deactivated:组件被停用(离开路由)时调用

使用了keep-alive就不会调用beforeDestroy(组件销毁前钩子)和destroyed(组件销毁),因为组件没被销毁,被缓存起来了。如果缓存了组件,要在组件销毁的的时候做一些事情,可以放在这个钩子里。

离开了路由,会依次触发:

> 组件内的离开当前路由钩子 beforeRouteLeave => 路由前置守卫 beforeEach => 全局后置钩子afterEach => deactivated 离开缓存组件 => activated 进入缓存组件(如果你进入的也是缓存路由)
// 如果离开的组件没有缓存的话 beforeDestroy会替换deactivated 
// 如果进入的路由也没有缓存的话  全局后置钩子afterEach=>销毁 destroyed=> beforeCreate等
  • 新增Props

  • include - 字符串或正则表达式。只有名称匹配的组件会被缓存。

  • exclude - 字符串或正则表达式。任何名称匹配的组件都不会被缓存。

  • max - 数字。最多可以缓存多少组件实例。

include 和 exclude prop 允许组件有条件地缓存。二者都可以用逗号分隔字符串、正则表达式或一个数组来表示:

<!-- 逗号分隔字符串 -->
<keep-alive include="a,b">
    <component :is="view"></component>
</keep-alive>

<!-- 正则表达式 (使用 `v-bind`) -->
<keep-alive :include="/a|b/">
    <component :is="view"></component>
</keep-alive>

<!-- 数组 (使用 `v-bind`) -->
<keep-alive :include="['a', 'b']">
    <component :is="view"></component>
</keep-alive>

匹配规则:

(1)首先匹配组件的name选项,如果name选项不可用。

(2)则匹配它的局部注册名称。 (父组件 components 选项的键值)

(3)匿名组件,不可匹配。

(4)exclude的优先级大于include

也就是说:当include和exclude同时存在时,exclude生效,include不生效。

<keep-alive include="a,b" exclude="a">
    <!--只有a不被缓存-->
    <router-view></router-view>
</keep-alive>

当组件被exclude匹配,该组件将不会被缓存,不会调用activated 和 deactivated

max 数字,最多可以缓存多少组件实例。一旦这个数字达到了,在新实例被创建之前,已缓存组件中最久没有被访问的实例会被销毁掉。

<keep-alive :max="10">
    <component :is="view"></component>
</keep-alive>

将路由导航、keep-alive、和组件生命周期钩子结合起来的,触发顺序,假设是从a组件离开,第一次进入b组件:

  • beforeRouteLeave:路由组件的组件离开路由前钩子,可取消路由离开。
  • beforeEach: 路由全局前置守卫,可用于登录验证、全局路由loading等。
  • beforeEnter: 路由独享守卫
  • beforeRouteEnter: 路由组件的组件进入路由前钩子。
  • beforeResolve:路由全局解析守卫
  • afterEach:路由全局后置钩子
  • beforeCreate:组件生命周期,不能访问this。
  • created:组件生命周期,可以访问this,不能访问dom。
  • beforeMount:组件生命周期
  • deactivated: 离开缓存组件a,或者触发a的beforeDestroy和destroyed组件销毁钩子。
  • mounted:访问/操作dom。
  • activated:进入缓存组件,进入a的嵌套子组件(如果有的话)。
  • 执行beforeRouteEnter回调函数next。

五、额外补充

组件的生命周期

  • ajax请求最好放在created里面,因为此时已经可以访问this了,请求到数据就可以直接放在data里面。(这里也碰到过几次,面试官问:ajax请求应该放在哪个生命周期。)

  • 关于dom的操作要放在mounted里面,在mounted前面访问dom会是undefined。

  • 每次进入/离开组件都要做一些事情,用什么钩子:

  • 不缓存:
    进入的时候可以用created和mounted钩子,离开的时候用beforeDestory和destroyed钩子,beforeDestory可以访问this,destroyed不可以访问this。

  • 缓存了组件:
    缓存了组件之后,再次进入组件不会触发beforeCreate、created 、beforeMount、 mounted,如果你想每次进入组件都做一些事情的话,你可以放在activated进入缓存组件的钩子中。
    同理:离开缓存组件的时候,beforeDestroy和destroyed并不会触发,可以使用deactivated离开缓存组件的钩子来代替。

【MARK】 文档以整理记录为主,如有错误,欢迎指出哟,大家一起来学习...

posted @ 2021-04-06 14:50  Red-Plum  阅读(131)  评论(0编辑  收藏  举报