Hone logo
Hone
Problems

Implement a Custom Router View Component in Vue.js with TypeScript

This challenge asks you to build a fundamental part of a routing system in a Vue.js application. You will create a reusable component that acts as a placeholder for dynamically rendered content based on the current route. This is crucial for building single-page applications (SPAs) where different views or pages are loaded without full page reloads.

Problem Description

Your task is to create a TypeScript-based Vue.js component named RouterView. This component should:

  1. Display the current route's component: It needs to dynamically render the component associated with the current route.
  2. Respond to route changes: When the route changes, the RouterView component should update to display the new route's component.
  3. Handle nested routes (optional but recommended): For a more robust solution, consider how your RouterView would handle nested routing, typically by rendering another RouterView within the nested route's component.

Key Requirements:

  • The RouterView component should be a functional component or a regular component that accepts no props.
  • It needs to access the current route information (e.g., the component to render) from a hypothetical routing system. For this challenge, you can assume a global router object or a reactive store that holds the current route.
  • The rendered component should be mounted and unmounted correctly as the route changes.

Expected Behavior:

When a route changes, the RouterView should:

  1. Unmount the currently rendered component.
  2. Mount the component corresponding to the new route.
  3. If the new component has specific props or data it needs from the route, these should be passed correctly. (For simplicity in this challenge, assume the component itself handles its data based on route parameters, or no route-specific data is needed).

Edge Cases to Consider:

  • Initial Load: What component is rendered when the application first loads and a route is matched?
  • No Matching Route: What happens if no route matches the current URL? (For this challenge, you can choose to render nothing or a default "not found" component).
  • Component Lifecycle: Ensure component lifecycle hooks (like mounted, unmounted) are called correctly for the dynamically rendered components.

Examples

For this challenge, we'll simulate a basic routing system and its interaction with RouterView.

Simulated Routing System:

Imagine a simple reactive system that emits route changes.

// Assume this is provided by a routing library or your own implementation
interface RouteRecord {
  path: string;
  component: any; // Vue component definition
  children?: RouteRecord[];
}

interface CurrentRoute {
  path: string;
  component: any; // The component to render
  params: Record<string, string>;
}

class MockRouter {
  private routes: RouteRecord[];
  private _currentRoute: CurrentRoute | null = null;

  constructor(routes: RouteRecord[]) {
    this.routes = routes;
    // Initialize with the initial route
    this.updateRoute(window.location.pathname || '/');
  }

  get currentRoute(): CurrentRoute | null {
    return this._currentRoute;
  }

  // Simulates a route change
  navigate(path: string): void {
    this.updateRoute(path);
  }

  private updateRoute(path: string): void {
    // Basic route matching logic (can be more complex)
    const matchedRoute = this.matchRoute(this.routes, path);

    if (matchedRoute) {
      this._currentRoute = {
        path: matchedRoute.path,
        component: matchedRoute.component,
        params: {}, // Simplified: no param extraction for this example
      };
      // In a real app, this would trigger a re-render
      console.log(`Route changed to: ${path}, Component: ${this._currentRoute.component.__name || 'Anonymous'}`);
    } else {
      this._currentRoute = null;
      console.log(`Route changed to: ${path}, No matching route.`);
    }
    // This would typically be handled by a reactive observer
    // For this exercise, assume the RouterView "knows" about route changes
  }

  private matchRoute(routes: RouteRecord[], path: string): RouteRecord | undefined {
    for (const route of routes) {
      if (route.path === path) {
        return route;
      }
      if (route.children) {
        const matchedChild = this.matchRoute(route.children, path);
        if (matchedChild) {
          return matchedChild;
        }
      }
    }
    return undefined;
  }
}

// Example Usage:
// const router = new MockRouter([...]);
// router.navigate('/about');

Example 1: Basic Route Rendering

Input:

  • A RouterView component.
  • A mock router configured with two routes:
    • /home -> HomeComponent
    • /about -> AboutComponent
  • The current route is /home.

Expected Output (Vue Component Structure):

The RouterView should render the HomeComponent.

Explanation:

The RouterView checks the currentRoute from the mock router. It finds that the current route's component is HomeComponent and dynamically renders it within its own template.

Example 2: Route Change

Input:

  • The same setup as Example 1.
  • The current route is initially /home, rendering HomeComponent.
  • The router navigates to /about.

Expected Output (Vue Component Structure):

The RouterView should first unmount HomeComponent and then render AboutComponent.

Explanation:

The RouterView detects the route change. It calls the unmount lifecycle hook on HomeComponent, then retrieves the new component (AboutComponent) from the router and mounts it.

Example 3: Nested Routes (Conceptual)

Input:

  • A RouterView component.
  • A mock router configured with nested routes:
    • /users
      • children:
        • /users/list -> UserListComponent
        • /users/:id -> UserProfileComponent
  • The current route is /users/list.

Expected Output (Vue Component Structure):

The main RouterView renders a UsersLayoutComponent (or similar). UsersLayoutComponent itself contains another <router-view> which then renders UserListComponent.

Explanation:

This demonstrates how nesting works. The top-level RouterView renders a component that is responsible for a layout. That layout component then contains its own <router-view> to handle rendering specific child views within its context. For this challenge, you can focus on the primary RouterView's logic.

Constraints

  • The implementation of RouterView must be in TypeScript.
  • You should leverage Vue 3's Composition API or Options API. Composition API is generally preferred for more dynamic component logic.
  • Assume the existence of a mechanism to access the current route's component (e.g., a global reactive object, a provide/inject pattern, or a simple global variable for this exercise). You do not need to implement the entire routing matching logic or history management yourself. Focus on the RouterView component.
  • The dynamically rendered components should be treated as opaque VNodes or Vue component definitions that the RouterView can mount.
  • Performance is important; avoid unnecessary re-renders of the RouterView itself if the route component hasn't changed.

Notes

  • Think about how Vue.js handles dynamic component rendering. The <component :is="currentComponent"> syntax in Vue templates is a key tool here.
  • Consider how to access the route information. If using Vue 3, provide and inject can be a good pattern for passing down router instances or reactive route objects. For simplicity in this exercise, you might access a globally available reactive route object directly within RouterView's setup.
  • The core of this challenge is managing the lifecycle of dynamically mounted components.
  • You will likely need to use Vue's built-in defineComponent or reactive APIs.
  • To simulate route changes for testing your RouterView, you can create a simple mock function that updates a reactive variable representing the currentRoute.
Loading editor...
typescript