Skip to content

Commit beec69c

Browse files
committed
Feat: routing first step with URL parsing
1 parent c7b0618 commit beec69c

3 files changed

Lines changed: 121 additions & 0 deletions

File tree

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@
22
Re-implementation of React
33

44
>The point of using a framework is to abstract away the manipulation of the DOM, so we want to avoid accessing the DOM. Any piece of information that’s relevant to the application should be part of the state.
5+

docs/routing.md

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,96 @@ The concept of routing is crucial for web application navigation. Traditionally,
33
SPAs provide a dynamic and interactive user experience by loading a single HTML page initially and updating its content dynamically as users interact. Routing in SPAs refers to how the application manages different views or "pages" based on the URL or user interactions, all without full page reloads. This client-side navigation leads to faster load times, smoother transitions, and a more engaging user experience.
44

55
## URL hash fragment
6+
In a traditional web application, each page typically corresponds to a distinct URL, and navigating between pages triggers a request to the server, resulting in the loading of a new page. However, in the context of SPAs, only a single HTML page is initially loaded and subsequent interactions dynamically update content. To make this work, there are two questions that we need to address:
7+
8+
1. How can we make sure the server always returns the same page (the index.html) for any URL?
9+
2. How can the framework be aware of the URL changes and update the view accordingly?
10+
11+
https://example.com/page#section2, #section2 is the hash fragment that points to a specific section of the page on the example.com website. The hash fragment of a URL, also known as the fragment identifier, appears after the '#' symbol in a URL:
12+
13+
https: // example.com : 8080 /something/ ?query=abc123 #/fooBarBaz
14+
15+
____⎦ ⎣__________⎦ ⎣__⎦ ⎣________⎦ ⎣____________⎦ ⎣________
16+
protocol domain port path parameters hash
17+
18+
19+
> The key characteristic of the hash fragment is that it is not sent to the server as part of the HTTP request. Instead, it is handled entirely client-side by the web browser.
20+
21+
How can your framework be aware of the URL changes and update the view accordingly? The answer lies in the popstate event.
22+
23+
The "**popstate**" event is part of the History API, which allows web applications to manage the browser’s history stack. This event is triggered when the active history entry changes, which generally occurs when the user navigates the session history by clicking the back or forward buttons, or when history.back(), history.forward(), or history.go() methods are called programmatically. However, the event is not triggered by:
24+
25+
- Direct URL changes (e.g. typing a new URL in the address bar or using window.location to navigate).
26+
- Navigating to a different domain or reloading the page.
27+
28+
```js
29+
window.addEventListener('popstate', (event) => {
30+
console.log(`The URL has changed to: ${window.location.href}`)
31+
console.log('Current state:', event.state)
32+
})
33+
```
34+
35+
Now, you can push a new state to the history stack using the pushState() method, which adds a new entry to the history stack and changes the URL of the current page without triggering a full page reload
36+
37+
`history.pushState({ page: 1 }, '', '?page=1')`
38+
39+
40+
SPAs manage view with `History API`
41+
42+
```js
43+
history.pushState(state, unused, url) — Adds a new entry to the history stack.
44+
- state: A JavaScript object associated with the new history entry.
45+
- unused: This parameter exists for historical reasons, and cannot be omitted; the MDN docs recommend to pass an empty string.
46+
- url: A string representing the new URL (must be same-origin).
47+
48+
history.back() — Navigates to the previous entry in the history stack.
49+
history.forward() — Navigates to the next entry in the history stack.
50+
```
51+
52+
To navigate between the pages of your SPA, you can use the pushState() method to change the hash fragment of the URL and update the view accordingly.
53+
54+
## Example
55+
```js
56+
const routes = [
57+
{
58+
path: '/user/:id',
59+
component: User
60+
},
61+
{
62+
path: '/user/:id/config',
63+
component: UserConfig
64+
},
65+
// REDIRECT
66+
{
67+
path: '/profile',
68+
redirect: '/user'
69+
},
70+
{
71+
path: '/user',
72+
component: User
73+
},
74+
// CATCH ROUTES
75+
{
76+
path: '*',
77+
component: NotFound>
78+
}
79+
]
80+
81+
history.pushState({}, '', '#/home') // navigate to home
82+
history.pushState({}, '', '#/about') // same...
83+
history.pushState({}, '', '#/contact')
84+
```
85+
86+
Clicking on a link with a hash fragment will trigger a full page reload by default. We don’t want this to happen in an SPA, so you need to prevent the default behavior
87+
88+
```js
89+
<a href="#/about" onclick="navigateToAbout">About</a>
90+
91+
function navigateTo(event) {
92+
event.preventDefault();
93+
history.pushState({}, '', event.target.href)
94+
}
95+
```
96+
97+
But, at this point, we’ve only updated the URL—​you still need to update the view based on the new URL. This is where the **popstate** event comes into play.
698

src/routing/api.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
const CATCH_ALL_ROUTES = '*';
2+
3+
// type RouteMatcher = {
4+
// route: Route;
5+
// isRedirect: boolean;
6+
7+
// checkMatch: (path: string) => boolean;
8+
// extractParams: (path: string) => { [x: string]: string };
9+
// extractQuery: (path: string) => { [x: string]: string };
10+
// };
11+
12+
export function makeRouteMatcher(route) {
13+
return routeHasParams(route)
14+
? makeMatcherWithParams(route)
15+
: makeMatcherWithoutParams(route);
16+
}
17+
18+
function routeHasParams({ path }) {
19+
return path.includes(':');
20+
}
21+
22+
function makeRouteWithoutParamsRegex({ path }) {
23+
if (path === CATCH_ALL_ROUTES) {
24+
return new RegExp('^.*$');
25+
}
26+
27+
return new RegExp(`${path}`);
28+
}

0 commit comments

Comments
 (0)