Manually implement hash – based front-end routing

Recently, I have been learning vue-Router, and I have also considered some implementation principles of vue-Router. In the last post about learning front-end and back-end routing, I summarized front-end and back-end routing, and introduced the implementation methods of front-end routing and the principle of hash routing. Today I implemented a simple front-end routing on top of this using native code.

Function of 1.

1.1 URL changes Implement component switching

Description: Component switchover is performed by changing the URL in hash mode. Because the url change is implemented with hash, it does not send a request to the server.

1.2 “Component” rendering similar to router-view tags

Description: Similar to the router-view tag in vue-router, the “component” will be rendered in this tag after switching the URL. Of course, the components here are not strictly VUE components, but just simulated ones.

1.3 Registering routes

Description: Register the route binding component through the route registration function register, and associate the URL with the “component”.

1.4 Error URL 404 output

Description: If the entered URL is an unregistered route, 404NotFound is displayed.

2. Implementation method

2.1 Definition of router Class

Router class changeComponent() creates a router class based on routes. This is an object used to store all registered routes. The key of the object represents the registered route path. This function will be described later.

2.2 Router class methods

After defining the basic attributes of the Router class, the basic methods of encoding the router are followed. In the current demo, there are three basic routing methods, namely init(), refresh(), and register.

Initialization methodinit()

In fact, as mentioned in the previous article, the core of hash routing is to listen for the onHashchange event, get the change of the current hash value, and then perform component rendering or refreshing based on this value. With the current demo, a core part of initialization is to bind the refresh() method to the onHashchange event. The code is shown below:

Routing registeredregister()

Just like with vur-Router, if you want to switch components based on urls, you need to register the route first, so the register() function receives an object containing two properties, Path and Component. When this object is received, add path to the route collection this.routes as the key name and the component switch function changeComponent() as the key value. In this case, the refresh function () simply calls this.routes[url] to switch the component. The code is shown below:

Refresh the renderingrefresh()

This function is called on the hashchange event to retrieve the hash value of location.hash. This value is used to determine whether this path exists in the route set. If this. Routes [URL] binding method is called for component switching rendering, if not, 404NotFound is displayed. The code is shown below:

2.3 Component switching function

As mentioned earlier, component switching functions are used as key values in the routing registry, which are then provided to refresh() function calls for component switching and rendering. This method takes a String parameter, namely the component name, and then queries the component array based on the component name. If the component exists in the array, it obtains the template of the component and renders it in router-View. If the component does not exist, the popup displays “No component”. The code is shown below:

2.4 Analog Components

In fact, all the native JS used in this paper are not vue, so the “component” switch is also a form of simulation. The specific implementation is to create a new template.js JS file, which stores the template strings of each component. It creates an array of objects containing the names of each component and their template strings. Finally, the component switch function can look for components in this simulated array. Get the template string for the “component” and render it. The code is shown below:

3. Implementation details

The previous section described the general implementation of front-end routing, so the next section will talk about some problems encountered in the process of implementing front-end routing and the analysis and thinking of these problems.

3.1 init()“This refers to the problem

The onHashchange callback is the same as refresh(), which is used to refresh the URL when the hash value of the url changes.

/ / the full version
// Complete event listener, refresh when hash value changes and load event completes
router.prototype.init = function () {
    addEventListener('load'.this.refresh.bind(this));
    addEventListener('hashchange'.this.refresh.bind(this));
}

// This version is not bound
router.prototype.init = function () {
    addEventListener('load'.this.refresh);
    addEventListener('hashchange'.this.refresh);
}
Copy the code

So the problem is, in the first implementation, I directly bound the this.refresh method to the hashchange event, but after testing it, I found that no matter how MUCH I changed the hash value, the component switch would not be complete. After various log debugging, I finally found that, When the hash value changes and the callback function refresh is triggered, this in the refresh function points to window.

It’s the browser that calls refresh when the event is triggered, because it’s the browser that listens for the event, and it’s the refresh function that the browser calls when the event is triggered, which is equivalent to window.refresh. Therefore, this points to the browser, and we want this to point to our routing instance, because we registered the routing information in the routing instance, and the routing instance only stores the set routes of all routing information.

In this case, we need to bind a function to the event, so we need to return a function after binding this, so we will use bind for this binding.

3.2 Binding of callback methods during Route registration

It can be seen from the above that we need to bind a component switching method for the currently registered route during route registration, and the specific binding code is as follows:

@param {String} obj. Path Path to register * @param {function} obj.component component name, Bind the component-switching method to routes[obj.path] */ based on the component name

/ / the full version
router.prototype.register = function (obj) {
    this.routes[obj.path] = function(){changeComponent(obj.component)};
}

/ /).
router.prototype.register = function (obj) {
    this.routes[obj.path] = changeComponent(obj.component);
}
Copy the code

I’m sure those of you who have a solid foundation can see what would go wrong if I wrote it the way I did in the first version. Yes, the component switch is executed immediately without actually binding this method to our routing collection. This is contrary to our original intention, but the solution is also very simple, we just need to wrap a function on the outer layer.

4. To summarize

In fact, most students will find this front-end routing demo is really quite simple if they read it completely. Then I can summarize the work I have done as follows:

  • createrouterClass, the realization of initialization, route registration, refresh three methods.
  • throughhashchangeEvent listener at urlhashRefresh when values change
  • Through a simple simulated “component” array to achieve a simple component by the name of the component switch rendering method.

Of course, this is actually only the most basic front-end routing, which is far from vue-Router nested routing, named routing, navigation guard and other advanced uses. Next, I hope to understand the principle of front-end routing in history mode, and also carry out a basic implementation to deepen my understanding.

5. The code

The code for hash-router. HTML is as follows:

<! -- hash-router.html -->
<! -- Author: Liu Wei C -->

      
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="Width = device - width, initial - scale = 1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <script src=".. /js/template.js"></script>
    <script src=".. /js/hash-router.js"></script>
    <title>Hash route simulation implementation</title>
</head>

<body>
    <div id="content">
        <! -- Click a tag to hash the route -->
        <a href="#/shopping">The shopping cart</a>
        <a href="#/food">Food information</a>
        <a href="#/review">Review the information</a>

        <! -- Switch components are rendered here -->
        <div id="router-view"></div>
    </div>
    <script type="text/javascript">
        @params {string} name Component name @params {template} Component template */
        let components = [{
                name: 'shopping'.component: templateShopping
            },
            {
                name: 'review'.component: templateReview
            },
            {
                name: 'food'.component: templateFood
            },
        ];

        // Create a route set
        let routers = new router();
        // Register a route for this route collection
        routers.register({
            path: '/shopping'.component: 'shopping'
        });
        routers.register({
            path: '/food'.component: 'food'
        });
        routers.register({
            path: '/review'.component: 'review'
        });
    </script>
</body>

</html>
Copy the code

The code for hash-router.js is as follows:

/** * create router class * routes: a route collection object with a path key and a callback function * currentUrl: the currentUrl */
function router() {
    this.routes = {};
    this.currentUrl = "";
    this.init();
    
}

// Complete event listener, refresh when hash value changes and load event completes
router.prototype.init = function () {
    addEventListener('load'.this.refresh.bind(this));
    addEventListener('hashchange'.this.refresh.bind(this));
}

//refresh to display components based on the callback function that gets this.rotes[currentUrl] from the currentUrl
router.prototype.refresh = function () {
    this.currentUrl = location.hash.slice(1);
    // When loaded, router-view does not need to render any components
    if(location.hash=="") {document.getElementById("router-view").innerHTML = "";
        return;
    }   
    if (this.routes[this.currentUrl]) {
        this.routes[this.currentUrl]();
    } else {
        document.getElementById("content").innerHTML = "404 Not Found"; }}@param {String} obj. Path Path to register * @param {function} obj.component component name, Bind the component-switching method to routes[obj.path] */ based on the component name
router.prototype.register = function (obj) {
    this.routes[obj.path] = function(){changeComponent(obj.component)};
}

* @param {String} name Component name */
function changeComponent(name) {
    for (let i = 0; i < components.length; i++) {
        if (components[i].name == name) {
            document.getElementById("router-view").innerHTML = components[i].component;
            break;
        }
        if (i == (components.length - 1)) {
            alert("There is no such component"); }}}Copy the code

Template.js looks like this:

//template.js
let templateShopping=` < li > braised pork X1 < / li > < li > green pepper shredded meat X2 < / li > < li > tomato scrambled eggs X3 < / li > `;

let templateReview=` < p > green pepper shredded meat < / p > < li > good < / li > < li > good inexpensive < / li > < p > the yuxiang pork < / p > < li > too sweet < / li > < li > general < / li > `;

let templateFood=` < p > dishes name: tomato scrambled eggs < / p > < p > unit price: RMB 20 < / p > < p > dishes: braised pork < / p > < p > unit price: RMB 30 < / p > `;
Copy the code