Vue.js渲染函数变更动态组件时未重新渲染问题排查
自定义Vue路由插件渲染不更新问题
免责声明: 本问题并非与vue-router直接相关,但通过vue-router演示最为直观。这是vue-router解决的核心问题,我需要同类问题的解决方案(无需vue-router),请勿进行XY问题误导。
我正尝试实现一个极简版vue-router替代插件,包含RouterLink、RouterView组件及路由表。点击RouterLink时,插件会通过路由表查找对应组件并交由RouterView渲染。
当前实现中,RouterView通过ref关联待渲染组件,插件中间件会更新该ref。问题在于:尽管RouterView的render()已被调用,且组件ref已更新为新值,但浏览器中渲染的内容始终未变化,仿佛Vue运行时判定无需重新渲染。请问我哪里出错了?该如何解决?
示例代码
import { type App, type Component, defineComponent, h, type PropType, shallowRef, ref } from 'vue'; import HomeView from './HomeView.vue'; import AboutView from './AboutView.vue'; const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: AboutView, } ]; const currentComponent = shallowRef<Component>(() => h('div', h('h1', 'Default'))); export function createRouter() { return (app: App) => { app.component('router-link', RouterLink); app.component('router-view', RouterView); }; } function matchTo(to: string) { for(const route of routes) { if(route.path === to) { return route; } } } function setTo(to: string) { const route = matchTo(to); console.log('setComponentTo', to, route); if(!route) return; currentComponent.value = route.component; } export const RouterLink = defineComponent({ name: 'RouterLink', props: { to: { type: String as PropType<string>, required: true, }, }, setup(props, { slots }) { return () => { return h( 'a', { onClick: () => setTo(props.to), }, slots.default && slots.default() ); }; }, }); export const RouterView = defineComponent({ name: 'RouterView', setup() { const view = currentComponent.value ? h(currentComponent.value) : []; return () => { console.log('View render', (currentComponent.value as any).__file); return h('div', [view]); }; } });
解决方案
错误原因
RouterView的setup函数中,view变量是在组件初始化时一次性计算生成的,后续currentComponent更新时,view并不会重新生成新的虚拟DOM节点。虽然render函数会被触发调用,但返回的始终是初始化时创建的旧view,所以页面内容不会更新。
修改后的代码
import { type App, type Component, defineComponent, h, type PropType, shallowRef, ref } from 'vue'; import HomeView from './HomeView.vue'; import AboutView from './AboutView.vue'; const routes = [ { path: '/', name: 'home', component: HomeView }, { path: '/about', name: 'about', component: AboutView, } ]; const currentComponent = shallowRef<Component>(() => h('div', h('h1', 'Default'))); export function createRouter() { return (app: App) => { app.component('router-link', RouterLink); app.component('router-view', RouterView); }; } function matchTo(to: string) { for(const route of routes) { if(route.path === to) { return route; } } } function setTo(to: string) { const route = matchTo(to); console.log('setComponentTo', to, route); if(!route) return; currentComponent.value = route.component; } export const RouterLink = defineComponent({ name: 'RouterLink', props: { to: { type: String as PropType<string>, required: true, }, }, setup(props, { slots }) { return () => { return h( 'a', { onClick: () => setTo(props.to), }, slots.default && slots.default() ); }; }, }); export const RouterView = defineComponent({ name: 'RouterView', setup() { return () => { console.log('View render', (currentComponent.value as any).__file); // 每次render时根据最新的currentComponent生成虚拟DOM const view = currentComponent.value ? h(currentComponent.value) : []; return h('div', [view]); }; } });
修改说明
- 将
view的生成逻辑从setup函数内移到render函数中,这样每次render执行时,都会根据最新的currentComponent.value生成新的虚拟DOM节点。 - 确保当
currentComponent的ref更新时,RouterView的render函数返回的内容是最新的组件实例,从而触发Vue的重新渲染,更新页面内容。
内容的提问来源于stack exchange,提问作者spinkus
相关产品推荐
相关产品推荐

