The introduction
In our daily use of React development projects, we will have to deal with react-rouer. However, due to the design and usage of react routes, many students who are new to react will be very uncomfortable when they come into contact with react- Router, and do not know its matching mechanism and principle.
React-router: react-hooks react-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks: React-router: react-hooks
Initialize the project
We need to do some preparatory work, initialization of the project is a necessary step, we pass
npx create-react-app my-react-router
The scaffold tool initializes a project, and for testing purposes, removes unnecessary files from it, leaving a cleaner workspace.
Create a folder named React-router-dom to store our custom Router components.
Create the views folder, first define three components and import them into app.js for later testing of our page jump. Here, I have taken Home, Category and Profile components as examples.
Context api
The react-router has the following basic uses:
<Router>
<div>
<Route path="/">
<Home />
</Route>
<Route path="/news">
<News />
</Route>
</div>
</Router>
Copy the code
When we use Router, as long as the component wrapped by Route, we can obtain the routing information of the reorganized component through the props property, and these information is usually passed through the parent Router. This is why you wrap a Route with a Router.
When routes are deeply nested and routing information becomes more complex, new apis, namely createContext and useContext, need to be introduced to solve the problem of data sharing between nested routes and status updates, which can implement data transfer across components.
Therefore, we create a Context file in the react-router-dom folder to manage routing information.
Import React from 'React' // Create routing context const RouterContext = React. CreateContext ({}); export default RouterContext;Copy the code
The usage of createContext and useContext will not be described here. For those who are not familiar with the usage of createContext and useContext, here is an article for your reference.
The Router components
Above we have seen the basic usage of the React-Router, namely router and Route components.
We might as well build these two components first.
In the react-router-dom folder, create the index.js entry file, hashrouter. js and route. js component files, and export them in the entry file, and import app.js.
The basic contents of app.js components are as follows:
import { HashRouter as Router , Route } from './react-router-dom'
function App () {
return (
<Router>
<div className="App">
<div className="">
<Route path={'/home'} component={Home} />
<Route path={'/category'} component={Category} />
<Route path={'/profile'} component={Profile} />
</div>
</div>
</Router>
);
}
Copy the code
The router.js component, which provides routing information to its children, has the following infrastructure:
import React from 'react';
import RouterContext from './Context';
const HashRouter = (props) => {
return <RouterContext.Provider value={{a : 1}}>
{ props.children }
</RouterContext.Provider>
}
export default HashRouter;
Copy the code
Where props. Children is the content of the subcomponent wrapped around the router.js component.
- Initializing a Route
If the initialization does not have a hash value, the route will be automatically directed to the #/ root route by default when initializing the route. We need to do this in useEffect
UseEffect (() = > {/ / initializes the routing information window. The location. The hash = window. The location. The hash | | '/'. }, [])Copy the code
- build
location
information
Routing information has a very important property location, which contains pathName, hash and other parameters, so we need to build such a state through useState;
/ / the location state const [location, setLocation] = useState ({pathname: window. The location. The hash. Slice (1) | | '/'})Copy the code
- monitoring
hash
change
Given the state of the location, we need to change it at hashChange by adding the onHashChange event to useEffect to monitor hash changes and update the value of the location.
// Hash change handler const onHashChange = () => {setLocation((location) => ({... location, pathname: Window. The location. The hash. Slice (1) | | '/')}}) useEffect (() = > {/ / initializes the routing information window. The location. The hash = window. The location. The hash | | '/'. // Monitor hash changes window.addEventListener('hashchange', onHashChange, false); }, [])Copy the code
- pass
location
attribute
The parent Component can already update the location value based on the hash state, so the Route child needs to match the corresponding Component based on the pathName state, so the parent Component needs to pass the location information through the context value.
// Pass to child component const value={location} return <RouterContext.Provider value={value}> {props. Children} </RouterContext.Provider>Copy the code
The Route component
The parent component has passed the path to the Route child through the context, and the Route child can use useContext to consume the location provided by the parent to match the path and determine the rendering of the component.
Const {location: {pathName}} = useContext(RouterContext); // Get path and Component attributes const {path, Component: Component} = props; If (pathName === path) return <Component/>; // Mismatch return null return null;Copy the code
Now that a basic match is complete, you can manually type the hash value into the address bar to see the matching component.
If the path is home/1, it cannot match home. This is a problem. We need to change the matching rule to regular match.
This is done using a third-party library called path-to-regexp, which can be described and used here
Let reg = pathToRegexp(path, [], {end: false}); let reg = pathToRegexp(path, [], {end: false}); // Check whether the path matches and return the corresponding component let result = pathname.match(reg); if ( result ) return <Component/>;Copy the code
End :false indicates an inaccurate match.
Ok, this will be more reliable ~
When we access home/1, we will match the components of both home and home/1. When we want to match only one component, we need to add exact.
The value of the end parameter is derived from the exact parameter of the props (false by default).
// Get the path and Component attributes const {path, Component: Component, exact = false} = props; Let reg = pathToRegexp(path, [], {end: exact});Copy the code
Link component
The result of our current implementation is that we can manually input the hash value and jump to the corresponding component, which is obviously very inconvenient. What we hope is that we can click to complete this function, and we need to provide a Link component.
Create a Link component in the react-router-dom directory and export it in index.js.
We need to click the jump, so we need to have a jump method push, which should be provided by the parent Router component, so we add a history property to the Router component with a push method in it.
// rouer.js // Pass to child const value = {location, history: {push(to){window.location.hash = to; }}}Copy the code
Then use it in the Link component
Const Link = (props) => {// Get push method const {history: {push}} = useContext(RouterContext); Const {to} = props; / / jump return < a onClick = {() = > push (to)} > {props. Children} < / a >}Copy the code
Redirect components
Sometimes we want users to Redirect from one route to another. For example, if an invalid route fails to match, we want users to Redirect to our home page /home.
The app.js component is slightly modified:
<Route path={'/home'} exact={true} component={Home}/>
<Route path={'/category'} component={Category}/>
<Route path={'/profile'} component={Profile}/>
<Redirect to={'/home'} />
Copy the code
Create a redirect. js file in our react-router-dom folder and export it in index.js.
The Redirect component is similar to Link except that it doesn’t do hot river rendering and jumps directly after loading.
Const Redirect = (props) => {// Get push method const {history: {push}} = useContext(RouterContext); Const {to} = props; UseEffect (() => {push(to); }, [push, to]); // do not render return null; }Copy the code
This will redirect you to the home page by typing on a path that doesn’t exist
However, clicking on the Category or Profile route will also flash and redirect to the home page.
That’s because we didn’t do anything about it. We matched the category and profile routes, but it didn’t stop us from executing the Redirect component. We would have redirected to the home page anyway. Swtich components are required for wrapping.
Switch beyond constant-like components
What the Swtich component is supposed to do is that once we have matched the path, we don’t need to continue matching, and solve the redirection problem above.
Create swtich.js in the react-router-dom folder and export it in index.js.
The implementation idea is to iterate through the sub-components of the Switch and determine whether the current path matches the route of the sub-components.
Const Switch = (props) => {// Get pathName const {location: {pathName}} = useContext(RouterContext); // const children = props. Children; For (let I = 0; i < children.length; I ++) {// let child = children[I]; / / the path of the child components (redirect no path attribute) let path = child. Props. The path | | '. // Route reg let reg = pathToRegexp(path, [], {end: false}); If (reg.test(pathname)) return child; } // return null; }Copy the code
Now just wrap the Switch on our Route to solve the above problem!
Now, we’re more than halfway through basic routing. Cool!
Component Props
Currently we have to rely on Link or Redirect components to Redirect, but we don’t want that to happen. We want each component to have access to the Router component’s history and location properties to make it easier for sub-components to use.
The implementation is very simple. We just need to modify our Route a little bit and pass it to the child components as props when rendering the Component.
Const {location, history} = useContext(RouterContext); const componentProps = { location, history, match: {} } if ( result ) return <Component {... componentProps}/>;Copy the code
This way, the child component can get the history, location and other parameters, which means it can jump to the component using the props. History. Push method.
Parameters of the routing
The route with parameters such as /detail/:id is very common in actual production. We need to dynamically obtain the value of id. Fortunately, path-to-regexp provides a method to resolve the parameters of the route, so we just need to copy it.
Modify the route.js component,
Const Route = (props) => {// Get pathName const {location: {pathName}} = useContext(RouterContext); // Get the path and Component attributes const {path, Component: Component, exact = false} = props; // let keys = []; let reg = pathToRegexp(path, keys, { end: exact }); // Check whether the path matches and return the corresponding component let result = pathname.match(reg); let [url, ...values] = result || []; Const {location, history} = useContext(RouterContext); const componentProps = { location, history, match: { path: pathname, params: keys.reduce((obj, current, idx) => { obj[current['name']] = values[idx] return obj; }, {}), url } } if ( result ) return <Component {... componentProps}/>; // Mismatch return null return null; }Copy the code
Run keys to query the matched name attribute value, and run values to query the value corresponding to the matched name attribute value. Then, the matched name attribute value is added to the match object and passed to the sub-component. Then, the sub-component can query the value in the match of props.
conclusion
So far, we have implemented a simple version of the React-Router. Are you glad?
The react-Router implementation is very clever and not very complicated. Of course, these basic functions are only the tip of the iceberg. There are more powerful functions waiting for you to implement.
Code has been uploaded to Github, the need for small partners to consult!