You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 15:57:05