Detailed Explanation of Frontend Routing Principles and Implementation

Detailed Explanation of Frontend Routing Principles and Implementation

Problem Description

Frontend routing is one of the core technologies of modern Single-Page Applications (SPA). It allows switching page content based on URL changes without refreshing the entire page. Interviewers often examine your understanding of frontend routing implementation principles, including the differences between Hash routing and History routing modes, implementation details, and application scenarios.

Breakdown of Key Knowledge Points

1. Why is Frontend Routing Needed?

  • Traditional Multi-Page Applications (MPA): Every page jump requires requesting a new page from the server, causing a full refresh and resulting in a less smooth experience.
  • Single-Page Applications (SPA): HTML, CSS, and JS are loaded only during the initial load. Subsequent page content switches are handled dynamically via frontend routing, achieving page jumps without refresh, thereby enhancing user experience.

2. Two Modes of Frontend Routing

2.1 Hash Mode
  • Principle: Utilizes the characteristic that changes to the part of the URL after the # (the hash) do not trigger a page refresh.

  • URL Example: http://example.com/#/home, http://example.com/#/about

  • Listening Event: Monitors hash changes via window.onhashchange.

  • Implementation Steps:

    1. Define a route table: Map hash paths to corresponding components or functions.
    2. Parse the current hash during initialization and render the corresponding content.
    3. Listen for hash change events and dynamically update the page.
  • Code Example:

    class HashRouter {
      constructor() {
        this.routes = {}; // Store route mappings
        window.addEventListener('hashchange', () => this.handleRouteChange());
      }
    
      // Register a route
      addRoute(path, callback) {
        this.routes[path] = callback;
      }
    
      // Handle route changes
      handleRouteChange() {
        const hash = window.location.hash.slice(1) || '/'; // Remove #, default to root path
        const callback = this.routes[hash];
        if (callback) callback();
      }
    
      // Manual navigation
      navigate(path) {
        window.location.hash = path;
      }
    }
    
    // Usage Example
    const router = new HashRouter();
    router.addRoute('/home', () => { document.getElementById('content').innerHTML = 'Home Page'; });
    router.addRoute('/about', () => { document.getElementById('content').innerHTML = 'About Page'; });
    
2.2 History Mode
  • Principle: Uses the HTML5 History API (pushState, replaceState) to modify the URL path, and listens for browser forward/back navigation via the popstate event.

  • URL Example: http://example.com/home (No #, more aesthetically pleasing).

  • Key APIs:

    • history.pushState(state, title, url): Adds a history entry without triggering a page refresh.
    • history.replaceState(): Replaces the current history entry.
    • window.onpopstate: Listens for browser forward/back operations.
  • Important Notes:

    • Requires server support: Since users might directly access subpaths (e.g., /home), the server needs to be configured to redirect to the main page; otherwise, a 404 error may be returned.
    • Only pushState and replaceState do not trigger the popstate event; the page must be updated manually.
  • Code Example:

    class HistoryRouter {
      constructor() {
        this.routes = {};
        // Bind click events (intercept default navigation of anchor tags)
        document.addEventListener('click', (e) => {
          if (e.target.tagName === 'A') {
            e.preventDefault();
            this.navigate(e.target.getAttribute('href'));
          }
        });
        window.addEventListener('popstate', () => this.handleRouteChange());
      }
    
      addRoute(path, callback) {
        this.routes[path] = callback;
      }
    
      // Manual navigation (using pushState)
      navigate(path) {
        history.pushState(null, '', path);
        this.handleRouteChange();
      }
    
      handleRouteChange() {
        const path = window.location.pathname; // Get current path
        const callback = this.routes[path];
        if (callback) callback();
      }
    }
    

3. Comparison of the Two Modes

Feature Hash Mode History Mode
URL Aesthetics Contains #, less aesthetic No #, more like a real path
Browser Compatibility Supported by all browsers Requires IE10+ (History API)
Server Configuration No special configuration needed Requires redirect configuration to avoid 404
Listening Method onhashchange onpopstate (only listens to forward/back navigation)

4. Advanced: Dynamic Routing and Parameter Passing

  • Dynamic Paths (e.g., /user/:id):
    • Implementation Idea: Convert paths to regular expressions and extract parameters during matching.
    • Example: Path /user/123 can be parsed as { path: '/user/:id', params: { id: '123' } }.
  • Query Parameters (e.g., ?name=foo):
    • Parse window.location.search via URLSearchParams.

5. Optimizations in Practical Applications

  • Route Lazy Loading: Combine with dynamic import() to load components on-demand, improving initial page load speed.
  • Route Guards: Implement access control through hook functions (e.g., beforeEnter).

Summary

The core of frontend routing is monitoring URL changes and mapping them to corresponding views. Hash mode is simple and has good compatibility, while History mode is more aesthetically pleasing but requires server support. Modern frameworks (like Vue Router, React Router) are built upon these two modes, adding advanced features such as dynamic routing and nested routing. Understanding their underlying principles helps in better utilizing and debugging routing-related functionalities.