Front-end routes such as the React -router work in the same way and can switch between different pages without refreshing. The essence of routing is that when the URL of a page changes, the page display results can be changed according to the URL change, but the page is not refreshed. Single page (SPA) application can be realized through front-end routing. This paper firstly introduces the change of front-end routing principle in detail from the principle of front-end routing. React – Router4.0 implements front-end routing using the react- Router4.0 source code.
- Implements front-end routing using Hash
- Implement front-end routing through H5 history
- The use of the React – router4.0
- React- Router4.0 source code analysis
The original address, in my blog: github.com/fortheallli…
If it is helpful, your star is the best encouragement for me
1. Implement front-end routing using Hash
1. Principles of Hash
Early front-end routing was implemented with hashes:
Changing the HASH value of the URL will not refresh the page.
Therefore, front-end routing can be implemented using hash to achieve no refresh effect. The hash attribute is in the location object, and in the current page, it can be accessed via:
window.location.hash='edit'
Copy the code
To change the hash value of the current URL. After the above hash assignment, the url of the page changes.
Before: http://localhost:3000 After: http://localhost:3000/#edit
There is a hash value ending in # in the URL, but the page will not refresh even though the hash value of the page changes before and after the assignment, causing the complete URL of the page to change. In addition, there is an event called hashchange that listens for changes to the hash. We can listen for changes to the hash in one of two ways:
window.onhashchange=function(event){
console.log(event);
}
window.addEventListener('hashchange',function(event){
console.log(event);
})
Copy the code
Output a HashChangeEvent when the hash value changes. The value of HashChangeEvent is:
{isTrusted: true, oldURL: "http://localhost:3000/", newURL: "http://localhost:3000/#teg", type: "hashchange"..... }Copy the code
By listening for events and changing the hash page to not refresh, we can perform our ability to show and hide different UI displays in the callback function of listening for events, thus implementing front-end routing.
In addition to changing the hash value of the current page with window.location.hash, you can also change the HASH value of the HTML with the A tag:
<a href="#edit">edit</a>
Copy the code
2. Disadvantages of Hash
Hash is widely used in early front-end routing because of its good compatibility. However, using hash also has many disadvantages.
- Search engines are not friendly to pages with hash
- It is difficult to track user behavior within a page with a hash
2. Implement front-end routing through history
HTML5’s History interface, the History object is a low-level interface, does not inherit from any interface. The History interface allows us to manipulate the browser session History.
(1) Properties and methods of History
History provides several properties and methods.
Attributes of History:
- History.length: Returns the number of records in the session History, including the current session page. Also, if a new Tab is opened, the length value is 1
- History.state: Holds the property object passed by the method that initiates the popState event (more on pushState and replaceState methods later).
Methods the History:
-
History.back(): returns to the previous page in the browser’s session History, similar to the browser’s back button
-
History.forward(): points to the next page in the browser’s session History, similar to the forward button in the browser
-
History.go(): Jumps to a specified page in the browser session History
-
History.pushstate ():pushState pushes the given data onto the browser session History stack. This method takes three parameters, the object, the title, and a list of urls. PushState changes the current page URL, but is not accompanied by a refresh
-
History.replacestate ():replaceState replaces the url of the current session page with the specified data. ReplaceState also changes the URL of the current page, but does not refresh the page.
PushState is similar to repalce in the above methods:
That is, they all change the URL displayed on the current page, but they don’t refresh the page.
Difference:
PushState pushes the browser’s session History stack, increasing history. length by 1, while replaceState replaces the current session History and does not increase history. length.
(2) the BOM object history
History is an important property in the browser’s BOM object model. History fully inherits the History interface and therefore has all the properties and methods in History.
Here we’ll focus on the history.length property and the history.pushState and history.replaceState methods.
- history.pushState(stateObj,title,url) or history.replaceState(stateObj,title,url)
PushState and replaceState accept three parameters: the state object, the title title, and the changed URL.
window.history.pushState({foo:’bar’}, “page 2”, “bar.html”);
At this point, the current URL becomes:
Before executing the above methods: http://localhost:3000 after executing the above methods: http://localhost:3000/bar.html
If we print window.history.state:
console.log(window.history.state); // {foo:’bar’}
Window.history. state is the first object argument to our pushState.
-
The history.replacestate () method does not change the length of hitroy
console.log(window.history.length); window.history.replaceState({foo:'bar'}, "page 2", "bar.html"); console.log(window.history.length); Copy the code
The output window.history.length is equal to the output window.history.length.
In addition.
Each time history.back() or the browser’s back button is triggered, a popState event is triggered. This event occurs when moving backwards or forward:
window.onpopstate=function(event){
}
Copy the code
Note: the history.pushState and history.replaceState methods do not fire the popState event.
If history is used as the basis for routing, history.pushState and history.replaceState can be used to change the URL address without refreshing, and if the page is pushed back or forward, The PopState event is raised.
Advantages of implementing routing based on hisory:
- Be search engine friendly
- Convenient statistics of user behaviors
Disadvantages:
- Less compatible than Hash
- The backend must be configured accordingly; otherwise, a 404 error occurs when you access the sub-page directly
Use of React- Router4.0
Now that we know how to implement front-end routing, we introduce React- Router4.0. The React-Router4.0 code base contains the following separate packages, depending on the usage scenario:
- React-router: The core code of react-Router4.0
- React-router-dom: builds web applications, including core packages in DOM object scenarios
- React-router-native: applies to building react-native applications
- React-router-config: configures static routes
- React-router-redux: combines redux to configure routes. This option is deprecated and not recommended.
In React-Router4.0, we follow the design philosophy of Just Component:
The apis provided are presented in the form of components.
Apis such as BrowserRouter, Router, Link, Switch, and so on are used as components.
1. React-router-dom component API
BrowserRouter, HashRouter, Link, router, etc. Use the react-router-dom package in React-Router4.0.
(1) <BrowserRouter>
After wrapping the entire App system with the <BrowserRouter> component, it is through the HISTORY of HTML5 to realize the front-end routing without refreshing conditions.
The <BrowserRouter> component has the following properties:
-
The basename: string attribute is a subdirectory that adds a value named basename to the current URL.
<BrowserRouter basename="test"/> Copy the code
If the basename property is set, the basename property is
http://localhost:3000 and http://localhost:3000/test said is the same address, rendering the same content.
-
GetUserConfirmation: func this property is used to confirm the navigation functionality. Window.confirm is used by default
-
ForceRefresh: bool Default false, indicates that the page will not be refreshed when the route is changed. If the current browser does not support history, then when forceRefresh is set to true, the entire page will be refreshed every time the URL is changed.
-
keyLength: In the React-Router, each URL has a location corresponding to it, and each URL’s location has a different key value. The default value of this attribute is generally used, so setting it is not meaningful.
-
Children: node The children property must be a ReactNode node, representing a unique render element.
The counterpart to <BrowserRouter> is <HashRouter>, which uses the hash attribute in the URL to ensure that the page is rendered simultaneously without a refresh.
(2) <Route>
The <Route> component is very important. What <Route> does is to match the address in the corresponding location and render the corresponding component. Let’s look at the attributes in <Route>.
First look at how to perform the match, determining the attributes of the <Route> address match:
-
Path: When the URL in location changes, it is matched with the path property in Route. Path determines the rendering effect associated with the Route or URL.
-
Exact: If there is exact, the url is matched only when the URL is exactly the same as the path. If there is no exact attribute, the URL will match the exact address.
For example, when exact is not set:
<Route path='/home' component={Home}/>
<Route path='/home/first' component={First}/>
Copy the code
The url is: http://localhost:3000/home/first, it will not only match to path = ‘/ home/first when the components first, at the same time also will match to the path =’ home ‘when the Router.
If exact is set:
<Route path='/home' component={Home}/>
Copy the code
Only http://localhost:3000/home/first does not match Home components, only the url address completely the same as the path, only http://localhost:3000/home can match Home component of success.
- Strict: Different from exact, strict is only a supplement to exact. The strict property strictly limits the slash “/”.
For example, when strict is not set:
<Route path='/home/' component={Home}/>
Copy the code
At http://localhost:3000/home and http://localhost:3000/home/ can be matched to the components of Home. Matches are looser with slashes. If strict is set:
<Route path='/home/' component={Home}/>
Copy the code
So strict matching slash at this time whether there is any, http://localhost:3000/home will not be able to match to Home components.
When the Route component matches a URL, it continues rendering. What attributes determine which component or style to render? Route’s Component, Render and children determine what to render.
- Component: This property takes a React component and renders it when the URL matches
- Render: func This property takes a function that returns a React Element and, when the URL matches, renders the returned Element
- Children: Similar to Render, accepts a function that returns a React Element, but the children content is always rendered regardless of whether the URL matches the path of the current Route.
And the methods or components that these three attributes accept take location, match, and history as parameters. If the component, then the component props will have location, match, and history passed from Link.
(3) <Link>
<Route> defines the matching and rendering rules, while <Link> determines how to change the URL within the page to match the corresponding <Route>. <Link> is similar to the A tag in HTML. In addition, when <Link> changes the URL, it can pass some attributes to the matching Route for the corresponding component to use when rendering.
- to: The value of the string to attribute can be a string, just like the href of the A tag in HTML. Even if the value of the to attribute is a string, clicking the Link tag to jump to the Route of the corresponding path will also change the history, location, The match objects are passed to the props of the component to which the Route corresponds.
For example:
<Link to='/home'>Home</Link>
Copy the code
As shown above, when to accepts a string, it jumps to the Route matched by the URL ‘/home’ and renders its associated component that accepts three objects history, location, and match. These three objects are described in more detail in the next section.
- The value of the to attribute can also be an object, which can contain several attributes: PathName, Seacth, Hash, and state. The first three parameters are related to how to change the URL, and the last state parameter is an object parameter to pass to the corresponding changed URL.
For example:
<Link to={{pathname:'/home',search:'? sort=name',hash:'#edit',state:{a:1}}}>Home</Link>Copy the code
In the previous example, to was an object, and clicking on the Link tag changed the url to: ‘/home? Sort = name# edit ‘. But only components whose path is ‘/home’ are matched with the corresponding Route, ‘/home? Sort = name# edit ‘. The parameters after ‘/home’ are not used as matching criteria. They are only passed as parameters to the matched component, and state={a:1} is also passed as parameters to the newly rendered component.
(4) The history object passed to the component props in the React-router
With <BrowserRouter>, <Route>, and <Link> introduced, you can build a simple React-Router application using these three component apis. Here we said that whenever you click the Link TAB to jump to, or use the React-router method in JS to jump from the currently rendered component to the new component. When the new component is rendered, it receives an argument passed from the old component.
As mentioned earlier, Route will render the new component after matching the corresponding changed URL. In this new component, there are three object properties of history, location and Match. among them, the hisotry object property is the most critical.
The following examples are also used to illustrate:
<Link to={{pathname:'/home',search:'? sort=name',hash:'#edit',state:{a:1}}}>Home</Link> <Route exact path='/home' component={Home}/>Copy the code
We use <BrowserRouter>, which makes use of the window.history object. When we click the Link TAB to jump to it, we render the new component Home, and we can print the history in the props component:
ƒ go(n) goBack: ƒ go(n) goBack: ƒ goBack() goForward: ƒ goForward() length: 12 listen: ƒ Listen (listener) location: {pathName: "/home", search: "? Sort =name", hash: "#edit", state: {... ƒ push(path, state) replace: ƒ replace(path, state)}, key: "uxs9r5"Copy the code
From the attribute details above:
-
Push :f This method is used to change the URL in JS. Previously, you could change the URL in the Link component in the form of an HTML tag. The push method maps to the pushState method in window.history.
-
The replace: f method is also used to change urls in JS, and the replace method maps to the replaceState method in window.history.
-
Block: f is also useful, such as giving the user a text prompt when leaving the current page, using history.block(” Are you sure you want to leave the current page?” “).
-
go / goBack / goForward
In the component props, the go, goBack, and goForward methods of history correspond to window.history.go, window.history.back, and window.history.
- Action: “PUSH” | | “POP” action about this property is very big, if it is through the Link tag or by enclosing in js props. The PUSH method to change the current url, then the action in the new component is “PUSH”, or “POP”.
The action property is very useful. For example, when we do a page turn animation, the forward animation is SlideIn and the back animation is SlideOut. We can determine which animation to use based on the action in the component:
function newComponent (props)=>{ return ( <ReactCSSTransitionGroup transitionAppear={true} transitionAppearTimeout={600} transitionEnterTimeout={600} transitionLeaveTimeout={200} transitionName={props.history.action==='PUSH'? 'SlideIn':'SlideOut'} > <Component {... props}/> </ReactCSSTransitionGroup> ) }Copy the code
-
The location:object property of the new component records the parameters passed from the original component. From the above example, we can see that the value of location is:
hash: "#edit" key: "uxs9r5" pathname: "/home" search: "? sort=name" state: {a:1}Copy the code
Except for key, which is used as the only representation, the other attributes are parameters we passed in from the previous Link tag.
4. Source code analysis of React- Router4.0
In section 3, we introduce the general usage of the React-Router. Read the source code for the React-Router4.0.
BrowserRouter, Router, Route, Link
1、React-router中的history
As we saw in the previous section, clicking on the Link tag passes a History object to the props of the newly rendered component. This object is rich in methods such as action, goBack, Go, location, push, and replace.
The React-Router builds a History class that builds a more attribute-rich instance of window.history. The History class is instantiated with action, goBack, Location, and so on.
The React-router separates the new History class build method into a Node package named History.
npm install history -s
Copy the code
Let’s take a look at the implementation of the History class.
const createBrowserHistory = (props = {}) => { const globalHistory = window.history; . Const {forceRefresh = false, getUserConfirmation = getConfirmation, keyLength = 6, basename = '', const {forceRefresh = false, getUserConfirmation = getConfirmation, keyLength = 6, basename = '', } = props; const history = { length: globalHistory.length, action: "POP", location: initialLocation, createHref, push, replace, go, goBack, goForward, block, listen }; ---- (1) const basename = props.basename; const canUseHistory = supportsHistory(); ---- (2) const createKey = () => math.random ().toString(36).substr(2, keyLength); ---- (3) const transitionManager = createTransitionManager(); ---- (4) const setState = nextState => {object.assign (history, nextState); history.length = globalHistory.length; transitionManager.notifyListeners(history.location, history.action); }; ---- (5) const handlePopState = event => {handlePop(getDOMLocation(event.state)); }; const handlePop = location => { if (forceNextPop) { forceNextPop = false; setState(); } else { const action = "POP"; transitionManager.confirmTransitionTo( location, action, getUserConfirmation, ok => { if (ok) { setState({ action, location }); } else { revertPop(location); }}); }}; ------ (6) const initialLocation = getDOMLocation(getHistoryState()); let allKeys = [initialLocation.key]; ------ (7) // Corresponding to pop, similar push and replace methods const push... replace ... ------(8) return history ------(9)}Copy the code
-
(1) Specify the properties in the History object returned by the new build method History.
-
The supportsHistory method in (2) determines the compatibility of the current browser with Window. history as follows:
export const supportsHistory = () => { const ua = window.navigator.userAgent; if ( (ua.indexOf("Android 2.") ! = = 1 | | ua. IndexOf (" Android 4.0 "). == -1) && ua.indexOf("Mobile Safari") ! == -1 && ua.indexOf("Chrome") === -1 && ua.indexOf("Windows Phone") === -1 ) return false; return window.history && "pushState" in window.history; };Copy the code
Windows. History is definitely supported on Chrome, Mobile Safari and Windows Phone, but not on Android 2. X and Android 4.0
-
The unique identifier key used in (3) to create a specified number of bits associated with each URL record in history. The default keyLength is 6 bits
-
(4) createTransitionManager (), return an integration object containing a listener function about the history address or object change, the code is as follows:
const createTransitionManager = () => { const setPrompt = nextPrompt => { }; const confirmTransitionTo = ( location, action, getUserConfirmation, callback ) => { if (typeof getUserConfirmation === "function") { getUserConfirmation(result, callback); } else { callback(true); }}}; let listeners = []; const appendListener = fn => { let isActive = true; const listener = (... args) => { if (isActive) fn(... args); }; listeners.push(listener); return () => { isActive = false; listeners = listeners.filter(item => item ! == listener); }; }; const notifyListeners = (... args) => { listeners.forEach(listener => listener(... args)); }; return { setPrompt, confirmTransitionTo, appendListener, notifyListeners };Copy the code
};
The setPrompt function, which is used to set the text prompt that will appear when the URL jumps, and the confirmTransaction function, which uses the location, action, and callback parameters in the history object that is currently generated, in the callback method, Change the location and Action objects passed in as required.
Notice that we have an array of LISTENERS that hold an array of URL-related listener events. We add events to this array through the appendListener method, and use the notifyListeners method to loop through all of the listeners.
-
The setState method updates the properties of the history object and triggers notifyListeners whenever the HISTORY URL or history action is changed. Pass in the current history.location and history.action. Traverse and execute an array of events listening for URL changes.
-
(6) The getDOMLocation method generates the location property object of new history based on the current value of window.state. AllKeys is the key that always keeps the history URL associated with the change of URL, stored globally. AllKeys keeps a live update when performing “POP” or “PUSH” or “Repalce” url changes.
-
(7) The handlePop method is used to handle the “POP” event. We know that clicking back in window.history will trigger the “POP” event.
-
(8) contains the push and replace methods similar to the POP method. The push method also does the same thing by calling the action “push” (” replace “) and changing the value of the allKeys array. The only difference is that actio’s “PUSH” method, PUSH, adds to the allKeys array, whereas action’s “REPLACE” method REPLACE replaces the current element.
-
Return the newly generated history object.
2. Link component in react-router
The most difficult thing to understand is how the React-Router reconstructs a history factory function. In section 1, we looked at the history generator createBrowserHistory in detail, and it’s easy to look at the Link component.
First of all, the Link component is similar to the A tag in HTML, the purpose is very simple, isto actively trigger the method to change the URL, the method to actively change the URL, from the introduction of the history of the above known as push and replace methods, so the source of the Link component is:
class Link extends React.Component {
handleClick = event => {
...
const { history } = this.context.router;
const { replace, to } = this.props;
if (replace) {
history.replace(replace);
} else {
history.push(to);
}
}
};
render(){
const { replace, to, innerRef, ...props } = this.props;
<a {...props} onClick={this.handleClick}/>
}
}
Copy the code
Take the history from the React Context API global object, and if replace is true in the property passed to the Link component, execute history.replace(to). To is an object that contains the pathname. If the replace property passed to the Link component is false, the history.push(to) method is executed.
React-router Route component
The Route component is also very simple. It accepts the main property path in props. Route does only one thing:
When the URL changes, the path property is compared to the changed URL, and if the match is successful, the component assigned by the Component’s Componet or Children property is rendered.
The specific source code is as follows:
class Route extends React.Component {
....
constructor(){
}
render() {
const { match } = this.state;
const { children, component, render } = this.props;
const { history, route, staticContext } = this.context.router;
const location = this.props.location || route.location;
const props = { match, location, history, staticContext };
if (component) return match ? React.createElement(component, props) : null;
if (render) return match ? render(props) : null;
if (typeof children === "function") return children(props);
if (children && !isEmptyChildren(children))
return React.Children.only(children);
return null;
}
}
Copy the code
If the path of the current Route is matched, the React component to which it points will be rendered according to the precedence order of the component, render, and children properties.
4. The router component in react-router
The Router component is the underlying component of BrowserRouter, HashRouter, and other components. This component defines the match function that contains the matching rule, and uses the listener method in the new history to listen for URL changes, so that when the URL changes, the isMatch results of different path components on the Router are changed.
class Router extends React.Component { componentWillMount() { const { children, // call the history.listen method, This.unlisten = history.listen(() => {this.setState({match: match); this.computeMatch(history.location.pathname) }); }); } componentWillUnmount() { this.unlisten(); } render() { } }Copy the code
The listener method is called before component creation to monitor url changes and update isMatch results in real time.
5, summary
In this paper, we introduce two common front-end routing methods, and then introduce the basic component API and usage of the React-Router component. In detail, we introduce the newly constructed history object of the React-Router component. Finally, I read the source code of react-Router with the API of React-Router.