The previous article mainly introduced the basic usage of react, this time will introduce a react routing component-react-router.
In Web application development, the routing system is an integral part. When the browser's current URL changes, the routing system responds with a response that ensures that the user interface is synchronized to the URL. With the advent of the single-page application era, the front-end routing system for the service has also appeared successively. There are some independent third-party routing systems, such as director, and the code base is relatively lightweight. Of course, the mainstream front-end framework also has its own routes, such as Backbone, Ember, Angular, React, and so on. What optimizations do the react-router have for react compared to other routing systems? How does it take advantage of the React UI state machine feature? And how do you use this declarative feature of JSX in routing?
A simple example
Now, we use a simple blog system example to explain the question we have just encountered, it contains a view of the article archive, article details, login, exit and permissions check several functions, the system's complete code is hosted in JS Bin (Note that the example code used in the corresponding ES6 syntax), You can click on the link to view it. In addition, this instance is all written based on the latest React-router 1.0. Here's a look at the application examples of React-router:
import React from ‘react‘;
import { render, findDOMNode } from ‘react-dom‘;
import { Router, Route, Link, IndexRoute, Redirect } from ‘react-router‘;
import { createHistory, createHashHistory, useBasename } from ‘history‘;
//Use here to add a root path
const history = useBasename(createHashHistory)({
queryKey: ‘_key‘,
basename: ‘/blog-app‘,
};
React.render((
<Router history={history}>
<Route path="/" component={BlogApp}>
<IndexRoute component={SignIn}/>
<Route path="signIn" component={SignIn}/>
<Route path="signOut" component={SignOut}/>
<Redirect from="/archives" to="/archives/posts"/>
<Route onEnter={requireAuth} path="archives" component={Archives}>
<Route path="posts" components={{
original: Original,
reproduce: Reproduce,
> >
</Route>
<Route path="article/:id" component={Article}/>
<Route path="about" component={About}/>
</Route>
</Router>
), document.getElementById(‘example‘));
If you've never been in touch with React-router before, instead just using the Backbone route or director you just mentioned, you'll be amazed at how this declarative notation is written. However, it is reasonable to think of it, after all, is only services and React class library, the introduction of its characteristics is understandable. Take a closer look and you'll find:
-
Router and route are the same react component, its history object is the core of the whole routing system, it burst a lot of properties and methods used in the routing system;
-
The path property of the route indicates the route component, which can be an absolute or relative path, and the relative path can be inherited;
-
Redirect is a redirect component that has a from and to two attributes;
-
The OnEnter Hook of the Route will be used to intercept the object before it is rendered, such as verifying permissions;
-
In the Route, you can use component to specify a single component, or to specify multiple component collections through components;
-
Param is/:paramdelivered in a way that is consistent with express and Ruby on Rails and is compliant with RESTFUL specifications;
Let's see what happens if you use Director to declare this routing system:
import React from ‘react‘;
import { render } from ‘react-dom‘;
import { Router } from ‘director‘;
const App = React.createClass({
getInitialState() {
Return {
App: null
}
}
componentDidMount() {
const router = Router({
‘/signIn‘: {
On () {
this.setState({ app: (<BlogApp><SignIn/></BlogApp>) })
}
}
‘/signOut‘: {
Structure similar to signin
}
‘/archives‘: {
‘/posts‘: {
On () {
this.setState({ app: (<BlogApp><Archives original={Original} reproduct={Reproduct}/></BlogApp>) })
}
}
}
‘/article‘: {
‘/:id‘: {
On (ID) {
this.setState({ app: (<BlogApp><Article id={id}/></BlogApp>) })
}
}
}
};
}
Render () {
return <div>{React.cloneElement(this.state.app)}</div>;
}
}
render(<App/>, document.getElementById(‘example‘));
From the elegance, readability, and maintainability of the code, the absolute React-router is better here. Analysis of the above code, each route of the rendering logic is relatively independent, so it is necessary to write a lot of duplicate code, although the React can be used to manage the setState of the route returned components, the Render method to do a certain encapsulation, but the result is to maintain a state, in This step in React-router is not needed at all. In addition, this kind of imperative writing and React code together is also slightly abrupt. The declarative notation in React-router is clearly understandable in component inheritance, and more in line with react style. This declarative is used for both default routes, redirects, and so on. I'm sure you've given up on this. Use a routing system outside of React-router in React!
Next, go back to the react-router example and look at the code inside the routing component:
const SignIn = React.createClass({
handleSubmit(e) {
e.preventDefault();
const email = findDOMNode(this.refs.name).value;
const pass = findDOMNode(this.refs.pass).value;
//The login effect is simulated here by modifying localstorage
if (pass !== ‘password‘) {
Return;
}
localStorage.setItem(‘login‘, ‘true‘);
const location = this.props.location;
if (location.state && location.state.nextPathname) {
this.props.history.replaceState(null, location.state.nextPathname);
} else {
//The replacestate method is used here to jump, but there will be no more records in the browser history, because the current record is replaced
this.props.history.replaceState(null, ‘/about‘);
}
}
Render () {
if (hasLogin()) {
Return < p > you have logged into the system! Click here to exit;
}
Return (
<form onSubmit={this.handleSubmit}>
<label><input ref="name"/></label><br/>
<label><input ref="pass"/></label> (password)<br/>
< button type = "submit" > login < / button >
</form>
);
}
};
const SignOut = React.createClass({
componentDidMount() {
localStorage.setItem(‘login‘, ‘false‘);
}
Render () {
Return < p > has exited! </p>;
}
}
The above code indicates the login and exit functions of the blog system. The login succeeds, the default jumps to the/aboutpath, and if Nextpathname is stored in the state object, jump to that path. It is important to note that the components declared in each route (such as SignIn) are passed in before renderingprops, specifically in the routingcontext.js of the source code, including:
-
The history object, which provides a number of useful methods that can be used in the routing system, such as those that have just been used tohistory.replaceStatereplace the current URL, and that the replaced URL will be deleted in the browser. The first parameter of a function is the state object, and the second is the path;
-
The Location object, which can be simply thought of as the object representation of the URL, is to mention that the statelocation.statehere has the same meaning as the state object in the HTML5 history.pushstate API. Each URL corresponds to a state object, and you can store the data in the object, but the data does not appear in the URL. In fact, the data is sessionstorage in existence;
In fact, the two objects just mentioned exist in the context of the routing component, and you can also get both objects in the component's child component through the React context API. For example, inside the SignIn component contains a Signinchild component, you can get to the history object inside the component, andthis.context.historythen call its API to jump and so on.
Next, let's take a look at the code inside the Archives component:
const Archives = React.createClass({
Render () {
Return (
<div>
Original: < br / > {this. Props. Original}
Reprint: < br / > {this. Props. Reproduction}
</div>
);
}
};
const Original = React.createClass({
Render () {
Return (
<div className="archives">
<ul>
{blogData.slice(0, 4).map((item, index) => {
Return (
<li key={index}>
<Link to={`/article/${index}`} query={{type: ‘Original‘}} state={{title: item.title}}>
{item.title}
</Link>
</li>
)
}}
</ul>
</div>
);
}
};
const Reproduce = React.createClass({
//Similar to original
}
The above code shows the article archive as well as the original and reproduced list. Now look back at the code in the Routing Declarations section:
<Redirect from="/archives" to="/archives/posts"/>
<Route onEnter={requireAuth} path="archives" component={Archives}>
<Route path="posts" components={{
original: Original,
reproduce: Reproduce,
}}/>
</Route> function requireAuth(nextState, replaceState) { if (!hasLogin()) {
replaceState({ nextPathname: nextState.location.pathname }, ‘/signIn‘);
}
}
There are three points worth noting in the code above:
-
A Redirect component is used, which redirects/archivesto the/archives/postsnext;
-
OnEnter hooks are used to determine whether a user is logged in or not, and if not logged in, usereplaceStatemethod redirection, which acts<Redirect/>like a component and does not leave a pre-redirect history in the browser;
-
If multiple components of a route are declared using components, the component can be obtained within the componentthis.props.original(in this case);
Here, our blog routing system is basically finished, I hope you can react-router the most basic API and its internal basic principles have a certain understanding. To summarize the characteristics and advantages of react-router as a react routing system:
-
Combined with JSX's declarative syntax, it elegantly implements the declaration of route nesting and routing callback components, including redirect components, default routes, etc., thanks to its internal matching algorithm, which can accurately match the components that need to be rendered in the component tree via a URL (exactly the Location object). This will definitely win the performance of the director and other routes in the React;
-
There is no need to maintain state alone to represent the current route, which is also unavoidable to use a route such as director;
-
In addition to the routing component, you can also route and redirect through the history objectpushStateorreplaceStatemethod, such as in the Flux store want to do a jump operation can be done by this method;
//Approximate to < link to = {path} state = {null} / >
history.pushState(null, path);
//Approximate to < redirect from = {CurrentPath} to = {nextpath} / >
history.replaceState(null, nextPath);
Of course, there are other features that are not introduced here, such as loading routing components on demand in large applications, server rendering, and consolidating redux/relay frameworks, which are difficult to accomplish with other routing systems. The next section focuses on the rationale behind the example.
Principle Analysis
In this section, we will explain the basic principles of routing, React-router state machine features, what happens in the system by the user clicking on the Link component, and how front-end routing handles the browser's forward and backward functions.
Fundamentals of Routing
Whether it's a traditional back-end MVC-led application or in the most popular single-page applications today, the responsibility of routing is important, but the principle is not complicated, which is to ensure that views and URLs are synchronized, and views can be seen as a representation of resources. When a user is working on a page, the app switches between several interactions, and routing can record some important states, such as whether a user is logged on in a blog system, which article is visited, and the page on the article archive list. These changes will also be recorded in the browser's history, the user can switch the status through the browser's forward, Back button, the same can be shared URLs to friends. In short, the user can change the URL by manually entering or interacting with the page, and then sending a request to the server via a synchronous or asynchronous way to get the resource (of course, the resource may also be present locally), and then redraw the UI after success, as shown in the following principle:
State machine Characteristics of React-router
We see that many of the features in React-router are consistent with react, such as its declarative components, component nesting, and, of course, the state machine features of react, because it is built based on react and is used for it. Think back. In React, we compare components to a function, state/props as a function parameter, and trigger function execution when they change, helping us to redraw the UI. So what will it look like in react-router? In React-router, we can think of the router component as a function, the location as a parameter, and the result of returning the same UI, as shown in the comparison:
Indicates that the UI interface returned is always the same as long as the URL is consistent. Maybe you're wondering what it's like to be behind this simple state machine? What happened to the system after clicking on Link? What does the system do after clicking on the browser's forward and back buttons? Then, look at:
The next two sections will explain in detail.
What happens when you click the Link to the system?
The Link component is eventually rendered as an HTML tag<a>, and its to, query, and hash properties are grouped together and rendered as an HREF attribute. Although link is rendered as a hyperlink, the default behavior of the browser is blocked by using a script on the internal implementation, and then thehistory.pushStatemethod is called (note that the history in this article refers to the object created by the Create*history method inside the history package, whichwindow.historymeans The original history object of the browser, because some APIs are the same, do not confuse). The underlying Pushstate method in the history package supports passing in two parameters, state and path, in the body of the function to transfer these two parameters to the Createlocation method, the structure of the return location is as follows:
Location = {
Pathname, / / current path, i.e. to attribute in link
search, // search
hash, // hash
State, / / state object
Action, / / location type, push when clicking link, pop when browser moves forward and backward, and replace when calling replacestate method
Key, / / used to operate the sessionstorage access state object
}
The above location object is passed as a parameter to the Transitionto method, and then the URL of thewindow.location.hashwindow.history.pushState()app is invoked or modified, depending on how you created the History object.history.listenthe event listener that is registered in is also triggered.
Next, look at how the UI is modified inside the routing system. After the new location object is obtained, the method inside the systemmatchRouteswill match a subset of the Route component tree that matches the current position object, and thenextStatespecific matching algorithm is not explained here, the interested students can click to view, state is structured as follows:
nextState = {
Location, / / the current location object
Routes, / / a subset of the route tree matching the location object. It is an array
Params, / / the param passed in, that is, the parameter in the URL
Components, / / components corresponding to each element in routes, also an array
}
The method is called in thecomponentWillMountlife cycle method of the Router componenthistory.listen(listener). Listener will execute after the successful execution of the Matchroutes method described abovelistener(nextState), Nextstate object The specific meaning of each property has been commented in the above code, the next implementationthis.setState(nextState)can be implemented to re-render the Router component. As a simple example, when the URL (which should be exactly location.pathname) is/archives/postsapplied, the matching results are as follows:
The corresponding rendering results are as follows:
<BlogApp>
<Archives original={Original} reproduce={Reproduce}/>
</BlogApp>
Here, the system has been completed when the user clicks on a hyperlink rendered by the link component to the full page refresh process.
What happens when I click the Forward and Back buttons of my browser?
You can simply compare the history of a Web browser to a stack that only has a stack operation, and when the user's browser goes to a page, the document is saved to the stack, and when you click the back or Forward button, you move the pointer to one of the corresponding documents in the historical stack. In a traditional browser, documents are requested from the server. However, modern browsers generally support two ways of dynamically generating and loading pages.
Location.hash and Hashchange Events
This is also relatively simple and compatibility is also a good way, see the following points in detail:
-
Usehashchangeevents to listenwindow.location.hashfor changes
-
Hash changes the browser updates the URL and generates a record in the history stack
-
The routing system will save all routing information to thelocation.hash
-
Event listener registered inside React-routerwindow.addEventListener(‘hashchange‘, listener, false)
-
Listener inside can get the location object corresponding to the current URL through hash fragment
-
The next process is consistent with Click <Link/>
Of course, you will think that not only in the forward and backward will triggerhashchangethe event, it should be said that each route operation will have a hash change. Indeed, to solve this problem, the routing system handles the problem internally by judging whether Currentlocation and nextlocation are equal. However, from its implementation principle, because the routing operation hash changes and repeated callstransitonTo(location)this step is really unavoidable, this is what I draw in the meaning of the dashed line.
This method adds a # number to the URL of the browser, but for compatibility reasons (ie8+), the routing system internally takes this approach (corresponding to the Createhashhistory method in the history package) as the default method for creating the History object.
History.pushstate and Popstate Events
The new HTML5 specification also proposes a relatively complex but more robust way to solve this problem, see the following points:
-
As mentioned above, you canwindow.history.pushState(state, title, path)change the URL of the browser by means of the method (more detailed API for the history object can be viewed here), in fact the method also stores the state object in the history stack.
-
Triggers an event when the browser is forward and backwardpopstate, then registerswindow.addEventListener(‘popstate‘, listener, false), and the corresponding state object can be taken out of the event object
-
The state object can store some of the simple information needed to restore the page, as mentioned above, which is stored in the location object as an attribute, so that you can pass in the component tolocation.stateget the
-
The object is stored inside the react-router in Sessionstorage, which is the saveState operation in
-
The next operation is consistent with the first approach
Using this method (corresponding to the Createhistory method in the history package) requires the server to do a routed configuration to redirect all requests to the portal file location, you can refer to this example, otherwise the user will be reported 404 error when refreshing the page.
In fact, the state object mentioned above is not only available in the second routing method. React-router internal made the Polyfill, unified the API. When you create a route using the first method, you will find a similar query in the URL_key=s1gvrm, which_keyis provided for react-router internal reading of the state object in Sessionstorage.
React-router Related Resources
About React (ii)