vue 基础进阶 教程(3):组件嵌套、组件之间的通信、路由机制、slot传值

本文详细介绍了 Vue.js 中的组件嵌套、组件间通信、路由机制及 slot 传值等内容,帮助读者掌握 Vue.js 的核心功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

vue 基础-->进阶 教程(3):组件嵌套、组件之间的通信、路由机制、slot传值

 

前面的nodejs教程并没有停止更新,因为node项目需要用vue来实现界面部分,所以先插入一个vue教程,以免不会的同学不能很好的完成项目。

本教程,将从零开始,教给大家vue的基础、高级操作、组件封装等,再配合前面的Nodejs后台,实现一个完整的项目。

 

组件嵌套


 

在组件中使用components定义子组件,可以定义多个,定义好以后,就可以在组件的模板中使用子组件了。看下面示例:具体解释在注释中

 

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 

 

 

 

 

父子组件之间的通信

使用 v-bind:  (或直接用冒号) 传递数据,使用props接收数据,先看如下代码

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

解释一下数据的传递流程(以msg2为例):在父组件中的数据中有个msg2数据,在模板中调用子组件的时候 以名为 m2 传递给了子组件 ,然后在子组件中就可以通过props接收到,这样在子组件中就可以使用变量 this.m2访问到 父组件的 msg2对应的值了。(如果传的值是对象,则传递引用指针,如果传递的是基本类型,则直接将值传递过去)

运行:

当点击子组件的h3标签,触发子组件的showMsg方法,在方法中,我们通过 this.m2.text改变了父级的msg2.text的值。通过  this.$parent.方法名    可以直接访问父组件里的方法。

点击bbb后

 

 


 

非父子组件之间的通信

上面我们使用props只能解决父子组件之间的通信,当两个兄弟关系的组件,就不能这样传递了

兄弟节点之间需要使用 事件的触发方法 $emit去实现

先看代码:具体解释在注释中

代码中,我们声明了一个独立的空Vue公用实例,用来触发通讯的事件。在a组件中使用 $emit触发事件,在 c组件中使用on监听事件,就可以实现数据的传递了。

复制代码
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>
   <div id="box">
       <com-a></com-a> <!-- 调用com-a组件 -->
       <com-c></com-c><!-- 调用com-c组件 -->
   </div>
<script>
   var Event=new Vue();  // 声明一个独立的空Vue公用实例,用来触发通讯的事件

    var a={
        template:'<div><span>我是a组件,我的数据内容是{{msga}}</span><br><input type="button" value="我要发送数据" @click="send"></div>',
        data(){
            return{
             msga:'我是a数据,我要发送给兄弟组件'
            }
        },
        methods:{
            send(){
                Event.$emit('a-msg',this.msga)  //触发前面 Event 公用示例的方法,那么别的地方就可以想办法监听接收这个事件。参数(事件名,传输的值)
            }
        }
    };
    var c={
        template:"<div><h3>这是C组件</h3><span>我从A里面接受的数据:{{a}}</span></div>",
        data(){
            return{
                a:''
            }
        },
        mounted(){    //这里的mouted表示当组件和页面挂载完成的时候,需要执行的函数
            var _this = this;  //因为在Event.on内部的this是指向 Event实例的,所以这里,先使用 _this将this存起来,后面就可以使用了。
            Event.$on('a-msg',function (a) {  //使用on监听事件 a-msg,这样当a组件中使用 emit主动触发了 Event实例的a-msg事件之后,这里就可以接收到
                alert('触发了接收');
                _this.a = a;
            })
        }
    };
    new Vue({
        el:'#box',
        components:{
            'com-a':a,
            'com-c':c
        }
    })


</script>

</body>
</html>
复制代码

 

 当点击发送数据按钮,数据就传输给了c组件。


路由


 

 路由可以让我们在页面中加载不同的模板内容,

使用vue的路由,需要借助vue-router.js

    <script src="https://unpkg.com/vue/dist/vue.js"></script>
    <script src="https://unpkg.com/vue-router/dist/vue-router.js"></script>

 

我们使用路由配置一个tab切换,点击首页和列表分别显示不同内容,并且点击的时候,url中的路径会发生对应的改变。

 

代码如下:代码解释在注释中。

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 

 

二级路由:具体解释在注释中

注:

二级路由使用children来配置 ,内部配置和一级路由一样

tab切换中的当前选项标签会自动添加 router-link-active类,我们只需要设置该类的样式,就可以实现当前选中的样式了。

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 

 

 


 url参数传递


 在配置路由的时候,路径名前面加 冒号,就表示此路径为参数

然后在模板中就可以使用 $route.params去访问到这个值了

 

完整示例代码如下:

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 点击  用户 -->  小红 ,然后可以看到我们url中的 "15",就传递到了模板中显示出来了。

 

 

 

补充讲一个概念:槽/slot      用来接收标调用时传入的内部内容

直接上代码,解释在注释中

按 Ctrl+C 复制代码
按 Ctrl+C 复制代码

 显示结果我们可以看到,原来 slot所在的地方被传入的p和ul标签替代了

 

上面是一个slot的情况,当如果我们要多个slot分开传值,我们就需要给slot指定名字,并且给传入的标签指定slot

示例如下:具体的解释在注释中

复制代码
<!DOCTYPE html>
<html>
<head lang="en">
    <meta charset="UTF-8">
    <title></title>
    <style>
        *{
            margin: 0;
            padding: 0;
        }
        body{
            line-height: 30px;
            padding: 10px;
        }
    </style>
    <script src="https://unpkg.com/vue/dist/vue.js"></script>
</head>
<body>

<div id="box">
    <who-h>
        <p slot="slot01">插入slot的值1(p)</p>  <!--  在组件调用的时候,标签内部的内容,会被放到template中定义的槽slot标签中,使用slot属性去指定插入哪个slot,如果不指定slot名,就插入没有名字的slot中 -->
        <ul slot="slot02">
            <li>插入slot的值2(ul)</li>
        </ul>
        <span>插入slot的值3(span)</span>
    </who-h>
</div>

<template id="temp01">  <!-- template 用来定义模板,需要指定一个id -->
    <div>               <!--  模板中的内容,只能包裹在唯一一个标签中  (最外层不能有多个标签) -->
        <h3>{{msg}}</h3>
        <slot></slot>  <!-- 用来接收标调用时传入的内部内容-->
        <slot name="slot01"></slot>
        <slot name="slot02"></slot>
    </div>

</template>

<script>

    var item = Vue.extend({
        data:function(){
            return {
                msg:"示例的值"
            }
        },
        template:'#temp01'
    });

    var vm = new Vue({
        el:"#box",
        data:{},
        components:{  //可以配置多个内部组件
            "who-h":item    //组件命名的时候,最好都用引号引起来(当然,单个单词的时候不用也不报错)
        }
    })
    
</script>

</body>
</html>
复制代码

 

 结果如下,三个传入的值 分别插入到了对应的位置

 

 

 

 

今天就讲到这里,vue就讲这么多,下次更新的时候,就是vue+nodejs的项目了。

<think>我们正在讨论Vue3中如何使用keep-alive缓存路由组件。根据引用内容,我们可以总结以下几点:1.在Vue3中,使用keep-alive包裹router-view组件可以实现路由组件的缓存。2.可以通过include属性指定需要缓存的组件名(注意:组件必须设置name选项)。3.结合路由守卫(如beforeRouteLeave)可以动态设置是否需要缓存(通过设置路由元信息meta.keepAlive)。4.注意:include和keepAlive(通过meta.keepAlive控制)不能混合使用,通常选择其中一种方式。5.缓存多级路由时,需要注意keep-alive只能缓存直接子组件,对于多级路由需要特殊处理(如将多级路由转化为一级路由或使用嵌套的keep-alive)。根据用户需求,我们需要提供Vue3中使用keep-alive缓存路由组件的最佳实践和示例代码。步骤:1.在路由配置中,为需要缓存的路由设置meta.keepAlive属性(或者使用include,这里我们采用动态include的方式)。2.在App.vue(或放置router-view的地方)使用keep-alive包裹router-view,并通过include绑定一个缓存组件名的数组。3.通过状态管理(如Pinia或Vuex)或直接在组件内管理需要缓存的组件名列表(cachedViews)。4.在路由守卫中,根据条件动态添加或移除缓存组件名(例如,在离开列表页时,将列表页组件名加入缓存;进入详情页时,详情页通常不需要缓存,但返回时列表页需要缓存)。但是,根据引用[3]中的描述,我们可能希望实现多级路由的缓存,但注意keep-alive不能缓存孙子组件,所以需要将多级路由转化为一级路由或者采用其他策略。这里我们采用一个常见的方案:使用标签页(tagsView)管理缓存列表,每个标签页对应一个路由,当标签页存在时,该路由组件被缓存;当标签页关闭时,从缓存中移除。由于用户要求最佳实践和示例代码,我们将提供一个简化版本,不使用状态管理,而是使用一个全局数组来管理缓存组件名(在实际项目中,建议使用状态管理)。示例代码:1.路由配置示例:```javascript//router.jsimport{createRouter,createWebHistory}from&#39;vue-router&#39;;constroutes=[{path:&#39;/&#39;,name:&#39;Home&#39;,component:()=>import(&#39;./views/Home.vue&#39;),meta:{keepAlive:false//首页不需要缓存}},{path:&#39;/list&#39;,name:&#39;List&#39;,component:()=>import(&#39;./views/List.vue&#39;),meta:{keepAlive:true//列表页需要缓存}},{path:&#39;/detail/:id&#39;,name:&#39;Detail&#39;,component:()=>import(&#39;./views/Detail.vue&#39;),meta:{keepAlive:false//详情页不需要缓存}}];constrouter=createRouter({history:createWebHistory(),routes});exportdefaultrouter;```2.在App.vue中使用keep-alive,并动态管理缓存列表:```vue<!--App.vue--><template><divid="app"><!--注意:根标签不能直接是router-view,所以加一个div--><div><router-viewv-slot="{Component}"><transitionname="fade-transform"mode="out-in"><keep-alive:include="cachedViews"><component:is="Component":key="$route.fullPath"/></keep-alive></transition></router-view></div></div></template><script>import{ref,watch}from&#39;vue&#39;;import{useRoute}from&#39;vue-router&#39;;exportdefault{name:&#39;App&#39;,setup(){constroute=useRoute();constcachedViews=ref([]);//监听路由变化,根据meta.keepAlive动态添加缓存watch(route,(to)=>{//如果该路由需要缓存,并且组件名不在缓存列表中,则添加constcomponentName=to.matched[0]?.components?.default?.name;//注意:这里获取的是路由组件的name//注意:路由组件必须设置name,并且要缓存的组件name必须与这里一致if(to.meta.keepAlive&&componentName&&!cachedViews.value.includes(componentName)){cachedViews.value.push(componentName);}},{immediate:true});return{cachedViews};}};</script>```然而,上述代码中获取组件名的方式可能不可靠,因为路由匹配的组件可能不止一个(嵌套路由),且我们通常希望缓存的组件路由对应的组件(即我们给组件设置的name)。另一种做法:在定义路由时,我们要求每个需要缓存的组件必须设置name,并且我们在路由的meta中指定需要缓存的组件名(或者直接使用路由组件的name)。然后在全局状态中维护一个缓存列表(cachedViews),通过路由守卫动态管理。3.使用路由守卫动态管理缓存列表:修改上面的App.vue,使用状态管理(这里为了简化,我们仍然使用ref,但实际项目可能使用Pinia或Vuex)。另外,引用[1]中提到了在路由守卫中设置meta.keepAlive,然后通过meta.keepAlive来控制是否缓存。但是,这种方式与include不能同时使用?实际上,我们可以将meta.keepAlive作为一个标志,在路由守卫中动态修改cachedViews。我们调整策略:-在路由离开时(beforeRouteLeave),如果需要缓存当前路由,则将其组件名加入cachedViews;如果不需要,则移除(但注意,返回时我们可能需要缓存,所以通常是在进入路由时就决定是否缓存,离开时设置)。但根据引用[1]中的示例,他们在beforeRouteLeave中设置to.meta.keepAlive=true,这样在进入目标路由时,目标路由就会缓存。但这样控制的是目标路由的缓存,而不是当前离开的路由。实际上,我们通常希望缓存的是离开的路由(比如从列表页离开去详情页,列表页需要缓存)。所以:-在离开列表页时,将列表页的组件名加入缓存列表(cachedViews)。-当从详情页返回列表页时,列表页已经被缓存,所以状态保留。-当关闭列表页(比如关闭标签页)时,将列表页从缓存列表中移除。因此,我们需要在路由全局前置守卫中处理:```javascript//在router.js中添加全局守卫router.beforeEach((to,from,next)=>{//如果from路由需要缓存,则将其组件名加入缓存列表if(from.meta.keepAlive){//假设我们在全局状态中有一个方法addCache,用于添加缓存//这里需要访问到App.vue中的cachedViews,可以通过全局状态管理,或者通过provide/inject,但通常使用状态管理//为了简化,我们使用一个全局变量(不推荐,仅示例)constfromComponentName=from.matched[0]?.components?.default?.name;if(fromComponentName&&!window.$cachedViews.includes(fromComponentName)){window.$cachedViews.push(fromComponentName);}}next();});```但是,这种方法并不优雅,而且全局变量不易维护。因此,我们使用状态管理(这里使用Pinia)来管理cachedViews。4.使用Pinia管理缓存状态:store.js(使用Pinia):```javascriptimport{defineStore}from&#39;pinia&#39;;exportconstuseCacheStore=defineStore(&#39;cache&#39;,{state:()=>({cachedViews:[]}),actions:{addView(componentName){if(componentName&&!this.cachedViews.includes(componentName)){this.cachedViews.push(componentName);}},removeView(componentName){constindex=this.cachedViews.indexOf(componentName);if(index>=0){this.cachedViews.splice(index,1);}}}});```然后,在路由守卫中使用这个store:```javascript//在router.js中import{useCacheStore}from&#39;./store&#39;;router.beforeEach((to,from,next)=>{constcacheStore=useCacheStore();//离开的路由需要缓存if(from.meta.keepAlive){constfromComponentName=from.matched[0]?.components?.default?.name;if(fromComponentName){cacheStore.addView(fromComponentName);}}next();});```同时,在App.vue中,我们从store中获取cachedViews:```vue<!--App.vue--><template><divid="app"><div><router-viewv-slot="{Component}"><transitionname="fade-transform"mode="out-in"><keep-alive:include="cachedViews"><component:is="Component":key="$route.fullPath"/></keep-alive></transition></router-view></div></div></template><script>import{computed}from&#39;vue&#39;;import{useCacheStore}from&#39;./store&#39;;exportdefault{name:&#39;App&#39;,setup(){constcacheStore=useCacheStore();constcachedViews=computed(()=>cacheStore.cachedViews);return{cachedViews};}};</script>```5.注意点:-每个需要缓存的组件必须设置name,并且该name要与路由守卫中获取的组件名一致(通常我们设置组件的name与路由配置中的name不同,路由配置中的name是路由的名称,而组件的name是组件选项中的name)。-在路由配置中,我们设置meta.keepAlive为true表示该路由组件需要被缓存(在离开时缓存)。-对于多级路由嵌套路由),我们可能需要特殊处理,因为路由守卫中的from.matched[0]可能不是我们想要的(嵌套路由的matched数组有多个记录)。我们可以遍历from.matched,将需要缓存的组件都加入缓存。或者,我们可以在路由的meta中直接指定要缓存的组件名,而不是通过matched[0]获取。改进路由守卫:```javascriptrouter.beforeEach((to,from,next)=>{constcacheStore=useCacheStore();//遍历离开的路由记录(嵌套路由)from.matched.forEach(record=>{//检查当前路由记录是否需要缓存(在meta中设置keepAlive)if(record.meta.keepAlive){constcomponentName=record.components?.default?.name;if(componentName){cacheStore.addView(componentName);}}});next();});```这样,嵌套路由中父路由和子路由都可以被缓存(只要它们的meta.keepAlive为true)。6.清除缓存:当关闭标签页或手动想要清除缓存时,调用store的removeView方法。总结:以上是一个基于Vue3和Pinia的keep-alive缓存路由组件的最佳实践。我们通过路由的meta.keepAlive标记需要缓存的路由组件,在全局路由守卫中动态管理缓存列表(cachedViews),然后在App.vue中用keep-alive的include绑定这个列表。注意:由于keep-alive的include是基于组件名,所以必须确保每个需要缓存的组件都设置了name选项,并且该name是唯一的。最后,提供相关的问题:§§相关问题§§1.在Vue3中,如何动态清除keep-alive的缓存?2.如何处理嵌套路由的缓存?3.keep-alive缓存路由组件时,如何结合过渡动画(transition)使用?</think>### Vue3 中使用 keep-alive 缓存路由组件的最佳实践 在 Vue3 中,`<keep-alive>` 组件用于缓存路由组件状态,提升用户体验,特别是在列表页-详情页等场景中。以下是核心实践要点和代码示例: #### 最佳实践要点 1. **组件命名要求** 所有需要缓存的组件**必须设置 `name` 选项**,且名称需与路由配置一致 ```vue <script> export default { name: &#39;UserList&#39; // 必须声明组件名 } </script> ``` 2. **路由配置** 在路由元信息中标记需要缓存的组件: ```javascript const routes = [ { path: &#39;/list&#39;, name: &#39;UserList&#39;, component: () => import(&#39;./UserList.vue&#39;), meta: { keepAlive: true // 标记需要缓存 } } ]; ``` 3. **缓存策略控制** 使用路由守卫动态管理缓存状态: ```javascript // 在路由文件中 router.beforeEach((to, from, next) => { // 离开列表页时启用缓存 if (from.name === &#39;UserList&#39; && to.name === &#39;UserDetail&#39;) { from.meta.keepAlive = true; } next(); }); ``` #### 完整示例代码 ```vue <!-- App.vue --> <template> <!-- 外层需包裹 div,避免根标签是 router-view --> <div class="app-container"> <!-- 使用 include 精确控制缓存 --> <router-view v-slot="{ Component }"> <keep-alive :include="cachedViews"> <component :is="Component" :key="$route.fullPath" /> </keep-alive> </router-view> </div> </template> <script> import { computed } from &#39;vue&#39;; import { useStore } from &#39;vuex&#39;; export default { setup() { const store = useStore(); // 从 Vuex 获取缓存列表 (需配合状态管理) const cachedViews = computed(() => store.state.cachedViews); return { cachedViews }; } }; </script> ``` ```javascript // 状态管理示例 (Vuex/Pinia) const store = createStore({ state: { cachedViews: [] // 存储需缓存的组件名 }, mutations: { ADD_CACHED_VIEW(state, viewName) { if (!state.cachedViews.includes(viewName)) { state.cachedViews.push(viewName); } }, REMOVE_CACHED_VIEW(state, viewName) { const index = state.cachedViews.indexOf(viewName); if (index > -1) { state.cachedViews.splice(index, 1); } } } }); ``` #### 关键注意事项 1. **层级限制** `keep-alive` **不支持缓存孙子组件**,多级路由需转换为一级路由嵌套使用 `keep-alive`[^3] 2. **动态管理** 使用 `include` 而非 `keep-alive` 属性实现精确缓存控制[^1] 3. **生命周期** 缓存组件会触发 `activated`/`deactivated` 钩子,可在此重置数据 4. **路由** 建议使用 `:key="$route.fullPath"` 避免同一组件不同参数的缓存冲突[^2] #### 多级路由缓存方案 对于三级以上路由,推荐转换为一级路由: ```javascript // 将三级路由提升为一级 const routes = [ { path: &#39;/parent/child&#39;, component: () => import(&#39;./Child.vue&#39;), meta: { keepAlive: true } } ]; ``` 或使用嵌套 `keep-alive`: ```vue <!-- Parent.vue --> <template> <keep-alive> <router-view v-if="$route.meta.keepAlive" /> </keep-alive> <router-view v-if="!$route.meta.keepAlive" /> </template> ``` [^1]: 动态管理缓存组件需使用 `include` 属性而非混合使用 `keep-alive` 属性 [^2]: 通过 `:key="$route.fullPath"` 解决相同组件不同参数的缓存问题 [^3]: 多级路由需特殊处理,可考虑路由扁平化或嵌套 `keep-alive` [^4]: 使用路由守卫动态更新元数据控制缓存行为
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值