关于keep-alive与router-view一起使用,子组件渲染两次

环境

  • Vue3、vue-router4、vite

问题

父组件使用局部<router-view>,用于渲染子组件,并且使用<keep-alive>。同时需要局部页面刷新,所以加了一个if的判断语句、参照官网写法,结果会发现,路由跳转时、子组件渲染了两次,并且是在父组件挂载前就执行了,在子组件的onMounted钩子打印当前路由信息为上一个路由信息

<router-view v-slot="{ Component, route }" v-if="isRouterAlive">
  <transition>
    <keep-alive>
      <suspense>
        <template #default>
          <component
            :is="Component"
            :key="route.meta.usePathKey ? route.path : undefined"
          />
        </template>
        <template #fallback> Loading... </template>
      </suspense>
    </keep-alive>
  </transition>
</router-view>

分析一:

  • 在vue-router的github上查找相关问题,发现vue-router和keep-alive一起使用,是有可能出现子组件渲染两次的问题,并且这个问题只有vue3才出现,vue2暂时为出现这个问题。自己摸索了很久,结果发现是官网bug的时候就感觉很无措。
  • 但我发现keep-alive缓存也失效了,考虑是不是因为不同路由(动态路由)渲染同一个子组件的问题,导致keep-alive失效
    通过了解keep-alive的工作原理,发现并非是动态路由引起的,因为keep-alive是缓存路由名称来缓存匹配的组件,如果路由名称不变,是不会更新dom的,并且缓存之后的组件,不在执行onMounted钩子,只有activated,deactivated钩子函数,我在这两函数打印发现能够执行,那就证明keep-alive有起作用
  • 为了验证并不是keep-alive的问题,写了个demo测试

目录

layout.vue

___fatherOne.vue
_______childOne.vue
_______childchild.vue

___fatherTwo.vue
_______childTwo.vue
_______childchild.vue

路由

import { createRouter, createWebHistory    } from 'vue-router'

const routes = [
  {
    path:'/',
    name: '首页',
    component:() => import('../views/layout.vue'),
    children:[
      {
        name: 'fartherone',
        path: '/',
        component: () =>
            import('../views/fatherOne.vue')
      },
      {
        name: 'farthertwo',
        path: '/fartherTwo/:id',
        component: () =>
            import('../views/fatherTwo.vue'),
      },
    ]
  }
]

const router = createRouter({
  history: createWebHistory(),
  routes
})

export default router

fatherOne.vue

<template>
  <h1>
    fatherOne
  </h1>
  <child-one></child-one>
  <button @click="goToFathTWO">goToFathTWO</button>
</template>

<script>
import childOne from './childOne.vue';
import { onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router'
export default {
  components: { childOne },
  setup(){
    const router = useRouter()
    const goToFathTWO = () =>{
      router.push('/fartherTwo')
    }
    onMounted(()=>{
      const route = useRoute()
      console.log('fatherOne',route.name);
    })

    return{
      goToFathTWO,
    }
  },
}
</script>

<style>

</style>

fatherTwo.vue

<template>
  <h1>
    fatherTwo
  </h1>
  <child-two></child-two>
  <button @click="goToFathOne">goToFathOne</button>

  
</template>

<script>
import childTwo from './childTwo.vue';
import childTwoVue from './childTwo.vue';
import { onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router'
export default {
  components: { childTwo },
  setup(){
    const router = useRouter()
    const goToFathOne = () =>{
      router.push('/')
    }

    onMounted(()=>{
      const route = useRoute()
      console.log('fatherTWO',route.name);
    })
    return{
      goToFathOne,
    }
  },
}
</script>

<style>

</style>

childOne.vue

<template>
  <h1>
    childOne
  </h1>
  <childchildVue></childchildVue>
</template>

<script>
import { onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router'
import childchildVue from './childchild.vue';
export default {
  components: { childchildVue },
  setup(){
    const route = useRoute
    // console.log('route--childONe',route.name);
    
    onMounted(()=>{
      console.log('route--childONe',route.name);
    })
  }
}
</script>

<style>
</style>

childTwo.vue

<template>
  <h1>
    childTwo
  </h1>
  <childchildVue></childchildVue>
</template>

<script>
import { onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router'
import childchildVue from './childchild.vue';
export default {
  components: { childchildVue },
  setup(){
    const route = useRoute
    // console.log('route--childTwo',route.name);
    onMounted(()=>{
      console.log('route--childTwo',route.name);
    })
    
  }
}
</script>

<style>

</style>

childchild.vue

<template>
  <h1>
    childchild
  </h1>
</template>
<script>
import { onMounted } from 'vue';
import { useRoute,useRouter } from 'vue-router'
export default {
  setup(){
    const route = useRoute
    // console.log('route--childchild',route.name);
    onMounted(()=>{
      console.log('route--childchil',route.name);
    })
  }
}
</script>
<style>
</style>

输出结果
首次进入
在这里插入图片描述点击按钮goToFathTWO
在这里插入图片描述点击按钮btn1-2,无输出
在这里插入图片描述那大概不是router-view与keep-alive一起使用所引起的问题

分析二

排除router-view与keep-alive的藕合问题(可能存在)

那就锁定 在v-if和key值绑定

先去除:key,

  • 确实减少了渲染次数,但是还是存在一个问题,keep-alive缓存失效,
    上网查了keep-alive的缓存失效的问题,
    • 组件没有name属性
    • 路由的name属性跟组件的name属性不一致
    • 动态绑定key

以上问题我都有遵循,但还是没有缓存

分析三

只剩下if,
去除if之后,keep-alive就能够缓存了
这时候我才恍惚,因为if是对dom的动态创建和销毁,把if判断在<router-view>,就会使router-view的路由跳转的时候就会创建和销毁,定然keep-alive会失效,每次跳转都会重新渲染,再加上我为了解决不同路由同组件,内容没更新的问题再动态绑定key值导致,再一次重新渲染刷新,就一共渲染了两次

补充

使用v-if来局部刷新页面
是目前网上不叫推荐的一种做法,能够提高用户体验感,减缓空白页出现

const reload = () => {
    isRouterAlive.value = false;
    nextTick(function () {
        isRouterAlive.value = true;
    });
};

但会使keep-alive失效

所以采用一下方法可以让数据更新

动态路由

如何参数是从路径获取、

使用官方 onBeforteRouteUpdate

组合式api onBeforteRouteUpdate(to,from)

onBeforteRouteUpdate((to,from) => {
	
)
watch监听

监听路由变化

watch(
	() => route,
	(newVal,oldVal) =>{}
)
computed
const params = computed(
	() = > return route.params
)
动态绑定key
 <component
    :is="Component"
    :key="route.meta.usePathKey ? route.path : undefined"
  />
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值