Disclaimer: This problem is not exactly related to vue-router but it's easiest for me to demonstrate the issue by reference to vue-router. It's a core problem vue-router solves but I just need a solution to the same problem (maybe in a simpler alternative way). Don't "XY" me.
I'm trying to implement a very simple alternative to the vue-router plugin, for reasons ... Like vue-router I have the two components RouterLink
and RouterView
and some routing table. Clicking a RouterLink
causes the plugin to use the lookup table to find a new component to render which is rendered by the ViewRouter
.
The way I've implemented it, ViewRouter
has a ref
to the component it should render which is updated by the plugin middleware. Problem is, even though render()
of the ViewRouter
component is called and the component ref has a new value, the content actually rendered to the browser never changes. It's like Vue runtime decided it doesn't need to re-render. What am I doing wrong? How to solve?
Sample code:
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]);
};
}
});
You lose reactivity in the line with if
. I don't really understand its meaning, but if you pass h(currentComponent.value)
directly to h
, the code starts working. Here is my version of RouterView:
export const RouterView = defineComponent({
name: 'RouterView',
setup() {
return () => {
console.log('View render', (currentComponent.value as any).__file);
return h('div', [h(currentComponent.value)]);
};
}
});
Or the ternary construct must be computed
...