Code前端首页关于Code前端联系我们

一、Vue Router场景下,mounted啥时候会执行?

terry 19小时前 阅读数 15 #Vue

不少刚接触Vue Router的同学会碰到这样的困惑:明明在组件里写了mounted钩子,可路由切换后它有时候不执行,有时候又执行了,这到底咋回事?想让mounted在路由变化时按自己想法触发,得从Vue Router的工作逻辑、组件生命周期这些角度慢慢捋,今天就把“Vue Router里mounted的执行规律、不触发的原因、解决办法”这些问题拆明白。

先回忆下Vue组件的基础生命周期:mounted是组件**完成挂载**后触发的,也就是模板编译成DOM、插入页面之后,这时候能拿到真实DOM,做DOM操作、请求数据都很顺手。

但结合Vue Router后,mounted啥时候执行,得看路由切换时组件会不会被“复用”,举个例子:假设做了个用户详情页,路由是/user/:id,组件叫User,从/user/1跳到/user/2时,Vue Router默认会复用User组件的实例——因为这俩路由匹配的是同一个组件(User),只是路由参数(id)变了,这种情况下,组件没被销毁再重建,所以mounted这类“挂载阶段”的生命周期钩子,只会在组件第一次挂载时执行,后续参数变化时不会再触发。

还有个常见情况是缓存组件,如果你的路由组件被包裹(比如把包在里),组件会被缓存起来,第一次进入组件时,mounted会执行;之后离开再回来,组件从缓存里拿出来复用,这时候触发的是activated钩子,mounted就不再执行了,这是Vue为了性能做的优化,但也容易让新手误以为mounted“失效”了。

路由切换时mounted不触发,原因在哪?

搞清楚mounted不触发的场景,才能针对性解决,常见原因分两类:

组件复用(动态路由参数变化)

Vue Router的“动态路由匹配”(比如/user/:id)设计,是为了复用组件实例来提升性能——毕竟销毁再重建组件挺费资源的,这时候路由参数变了,但组件还是同一个,所以mounted这类“初始化”钩子不会重复执行,举个🌰:User组件里mounted写了请求用户信息的逻辑,第一次进/user/1时,mounted触发,请求到用户1的数据;但切到/user/2时,组件没销毁,mounted不执行,这时候页面还显示用户1的信息,就会出问题。

keep-alive缓存导致

当路由组件被包裹时,Vue会缓存组件实例,避免重复渲染,这时候组件的生命周期会变成:第一次进入执行mounted→ 离开执行deactivated→ 再进入执行activated,所以后续进入时,mounted不会再触发,取而代之的是activated,比如做 tabs 切换的页面,用keep-alive缓存tab内容,就会遇到这种情况。

想让mounted在路由变化时执行,咋处理?

不同原因对应不同解法,咱分情况说:

应对“组件复用(动态路由参数变化)”的办法

  • 方法1:监听$route变化
    组件里用watch监听$route,当路由参数(比如params、query)变化时,执行原本mounted里的逻辑,举个代码例子:

    export default {
      data() { return { user: {} } },
      mounted() {
        this.fetchUser(this.$route.params.id); // 首次进入执行
      },
      watch: {
        $route(to) {
          this.fetchUser(to.params.id); // 路由参数变化时,重新请求数据
        }
      },
      methods: {
        fetchUser(id) {
          // 调用接口获取用户信息
        }
      }
    }

    这种方法性能友好,因为不用销毁组件,只需要响应路由变化,适合大部分场景。

  • 方法2:给加key,强制组件重建
    在路由出口()上绑定一个唯一的key,比如$route.fullPath(包含路径和参数的完整路径),这样每次路由变化时,key也会变,Vue会认为这是一个新的组件实例,旧组件销毁、新组件创建,mounted就会重新执行,代码长这样:

    <template>
      <div>
        <keep-alive>
          <router-view :key="$route.fullPath"></router-view>
        </keep-alive>
      </div>
    </template>

    但要注意:这种方法会增加性能开销(组件频繁销毁重建),所以只适合简单场景,或者路由参数变化频繁但组件逻辑不复杂的情况。

应对“keep-alive缓存”的办法

  • 方法1:用activated钩子替代mounted
    如果组件被keep-alive缓存,后续进入时触发的是activated,所以可以把“每次进入组件都要执行”的逻辑(比如请求数据、初始化插件)放到activated里,原本mounted里的逻辑如果是“首次进入才执行”,可以保留;如果是“每次进入都要执行”,就移到activated,举个例子:

    export default {
      mounted() {
        // 这里写“只有第一次进入”才需要的逻辑,比如初始化第三方插件的基础配置
        this.initPlugin();
      },
      activated() {
        // 这里写“每次进入(包括从缓存中激活)”都要执行的逻辑,比如刷新数据
        this.fetchData();
      },
      methods: {
        initPlugin() { /* 初始化插件 */ },
        fetchData() { /* 请求数据 */ }
      }
    }

    如果有“离开组件时清理资源”的需求,原本destroyed里的逻辑要移到deactivated里,因为keep-alive缓存时destroyed不会触发。

  • 方法2:排除keep-alive缓存
    如果某个路由组件不需要缓存,可以在上用exclude属性,指定组件名(组件的name选项)。

    <template>
      <div>
        <keep-alive exclude="User">
          <router-view></router-view>
        </keep-alive>
      </div>
    </template>

    这样User组件就不会被缓存,路由切换时mounted能正常触发。

实战中mounted结合Vue Router的常见场景

理解原理后,得落地到实际开发场景里,举几个常见例子:

场景1:动态路由参数变化时,重新请求数据

比如电商项目的商品详情页,路由是/product/:id,首次进入时,mounted里请求商品1的数据;从商品1切到商品2时,组件复用,mounted不执行,这时候用watch $route来重新请求:

<template>
  <div>
    <h1>{{ product.title }}</h1>
    <p>{{ product.desc }}</p>
  </div>
</template>
<script>
export default {
  name: 'Product',
  data() { return { product: {} } },
  mounted() {
    this.getProduct(this.$route.params.id);
  },
  watch: {
    $route(to) {
      this.getProduct(to.params.id);
    }
  },
  methods: {
    getProduct(id) {
      // 调用接口:axios.get(`/api/product/${id}`)
      // 然后把数据赋值给this.product
    }
  }
}
</script>

场景2:路由切换时,初始化依赖DOM的第三方插件

比如在文章详情页用富文本编辑器(如Quill),需要在mounted里初始化编辑器(因为要拿到DOM元素),但如果路由切换时组件复用,mounted不执行,编辑器就不会更新,这时候要结合watch $route,在路由变化时先销毁旧编辑器,再初始化新的:

export default {
  mounted() {
    this.initQuill(); // 首次初始化编辑器
  },
  watch: {
    $route() {
      this.quill.destroy(); // 销毁旧编辑器实例
      this.initQuill(); // 重新初始化
    }
  },
  methods: {
    initQuill() {
      this.quill = new Quill(this.$refs.editor, { /* 配置 */ });
    }
  }
}

场景3:结合keep-alive实现“缓存+强制刷新”

比如App首页用keep-alive缓存,提升切换tab的流畅度,但用户点“刷新”按钮时,要强制重新加载数据,这时候可以:

  • 正常情况下,用activated获取数据(缓存时触发);
  • 点击刷新时,通过修改的key,让组件销毁重建,触发mounted。

代码思路:

<!-- 父组件:控制router-view的key -->
<template>
  <div>
    <button @click="refresh">刷新首页</button>
    <keep-alive>
      <router-view :key="pageKey"></router-view>
    </keep-alive>
  </div>
</template>
<script>
export default {
  data() { return { pageKey: 0 } },
  methods: {
    refresh() {
      this.pageKey += 1; // key变化,强制组件重建
    }
  }
}
</script>
<!-- 首页组件Home -->
<template>
  <div>{{ list.length }}</div>
</template>
<script>
export default {
  data() { return { list: [] } },
  mounted() {
    this.fetchList(); // 组件重建时触发,重新请求数据
  },
  methods: {
    fetchList() { /* 调接口 */ }
  }
}
</script>

容易混淆的点:mounted和路由导航守卫的区别

不少同学会把mounted和路由的导航守卫(比如beforeRouteEnter)搞混,这里简单对比下:

  • 路由导航守卫(以beforeRouteEnter为例):是路由层面的钩子,在“进入路由前”触发,这时候组件实例还没创建(所以this拿不到),适合做“路由进入前的权限判断、预加载数据”等操作。
  • mounted:是组件层面的钩子,在“组件挂载到DOM后”触发,这时候this能拿到组件实例和DOM,适合做“依赖DOM的操作(比如初始化插件)、请求数据并渲染到页面”等操作。

举个🌰:如果想在进入商品详情页前,先判断用户有没有权限,用beforeRouteEnter;如果想在商品详情页DOM渲染后,初始化轮播图,用mounted。

Vue Router里mounted的执行规律,核心是理解“组件复用”和“keep-alive缓存”这两个机制对生命周期的影响,遇到mounted不触发的问题,先判断是哪种场景,再选对应的解法:参数变化用watch $route或加key;keep-alive场景用activated或exclude,把这些逻辑理顺后,就能灵活控制mounted在路由切换时的行为,避免页面逻辑“不听话”的情况~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门