public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
<style>
#root{
border:1px solid red;
}
</style>
</head>
<body>
<div id="root"></div>
<ul>
<li><a href="#/a">/a</a></li>
<li><a href="#/b">/b</a></li>
</ul>
<script>
window.addEventListener('hashchange',()=>{
//把最前面的那个# 删除
let pathname = window.location.hash.slice(1);
root.innerHTML = pathname;
});
</script>
</body>
</html>
history.pushState()
和history.replaceState()
,和1个事件window.onpopstate
replaceState
是替换浏览器历史堆栈的当前历史记录为设定的urlreplaceState
不会改动浏览器历史堆栈的当前指针history.forward
、history.back
、和history.go
触发,因为这些操作有一个共性,即修改了历史堆栈的当前指针onpopstate
事件History
栈,执行pushState
函数可压入设定的url
至栈顶,同时修改当前指针back
和forward
操作时,history栈大小并不会改变(history.length不变),仅仅移动当前指针的位置<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>hash路由</title>
</head>
<body>
<div id="root"></div>
<ul>
<li><a onclick="go('/a')">/a</a></li>
<li><a onclick="go('/b')">/b</a></li>
<li><a onclick="go('/c')">/c</a></li>
<li><a onclick="forward()">前进</a></li>
<li><a onclick="back()">后退</a></li>
</ul>
<script>
function render() {
root.innerHTML = window.location.pathname;
}
//只有当你前进后退的时候会触发,pushState不会触发
window.onpopstate = render;
let historyObj = window.history;
let oldPushState = historyObj.pushState;
historyObj.pushState = function (state, title, url) {
oldPushState.apply(history, arguments);
render();
}
function go(path) {
historyObj.pushState({}, null, path);
}
function forward() {
historyObj.go(1);
//historyObj.forward();
}
function back(path) {
historyObj.go(-1);
//historyObj.back();
}
</script>
</body>
</html>
HashRouter
是一个使用 URL 的哈希部分(# 之后的部分)来实现客户端路由的路由器。它的主要优势是可以在不需要服务器端配置的情况下支持浏览器的历史记录功能。这在一些特定场景下(例如 GitHub Pages)非常实用BrowserRouter
是一个使用 HTML5 历史记录 API(pushState、replaceState 和 popstate 事件)的路由器。它可以帮助你创建更美观的 URL(没有哈希部分)以及更好地支持服务器端渲染的单页面应用(SPA)Routes
组件是一个重要的组成部分,它负责定义和组织路由规则Route
组件用于定义应用程序的路由规则。Route 组件需要指定一个 URL 路径(通过 path 属性)和与该路径关联的组件(通过 element 属性)。当用户访问与某个 Route 定义的路径相匹配的 URL 时,React Router 会渲染与该路径关联的组件,如果您希望为嵌套路由提供支持,可以在 Route 的 element 属性中使用 Outlet 组件。当子路由匹配时,Outlet 组件将被替换为对应的子路由组件npm i react-router-dom --save
import React from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter, BrowserRouter, Routes, Route } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</BrowserRouter>);
src\components\Home.js
import React from 'react';
function Home(props) {
console.log(props);
return (
<div>Home</div>
)
}
export default Home;
src\components\User.js
import React from 'react';
function User() {
return (
<div>User</div>
)
}
export default User;
src\components\Profile.js
import React from 'react';
function Profile() {
return (
<div>Profile</div>
)
}
export default Profile;
src\react-router-dom\index.js
import React from 'react'
import { Router } from '../react-router';
import { createHashHistory, createBrowserHistory } from "@remix-run/router";
export { Routes, Route } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
src\react-router\index.js
import { Route, Router, Routes } from "./lib/components";
export {
Route,
Routes,
Router
}
src\react-router\lib\components.js
import React from 'react'
import { LocationContext, NavigationContext } from "./context";
import { useRoutes } from "./hooks";
export function Router({ children, location, navigator }) {
const navigationContext = { navigator };
const locationContext = { location };
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider children={children} value={locationContext} />
</NavigationContext.Provider>
);
}
export function Route() {
}
export function Routes({ children }) {
let routes = createRoutesFromChildren(children)
return useRoutes(routes);
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, (element) => {
let route = {
element: element.props.element,
path: element.props.path
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
src\react-router\lib\context.js
import * as React from "react";
export const NavigationContext = React.createContext(null);
export const LocationContext = React.createContext(null);
export const RouteContext = React.createContext({
outlet: null,
matches: []
});
src\react-router\lib\hooks.js
import React from 'react'
import { LocationContext } from "./context";
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();
let pathname = location.pathname || "/";
for (let i = 0; i < routes.length; i++) {
let route = routes[i];
if (route.path === pathname) {
return (
route.element
)
}
}
}
src\react-router-dom\index.js
import React from 'react'
import { Router } from '../react-router';
+import { createHashHistory, createBrowserHistory } from "../router";
export { Routes, Route } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
src\router\index.js
export * from "./history";
src\router\history.js
export const Action = {
Pop: "POP",
Push: "PUSH"
}
const PopStateEventType = "popstate";
let action = Action.Pop;
export function createHashHistory() {
if (!window.location.hash) {
window.location.hash = '/';
}
function createHashLocation(window, globalHistory) {
let pathname = window.location.hash.substr(1);
let state = globalHistory.state || {};
return { pathname, state: state.usr };
}
function createHashHref(to) {
let url = window.location.href;
let hashIndex = url.indexOf("#");
let href = hashIndex === -1 ? url : url.slice(0, hashIndex);
return href + "#" + to;
}
return getUrlBasedHistory(createHashLocation, createHashHref);
}
export function createBrowserHistory() {
function createBrowserLocation(window, globalHistory) {
let { pathname } = window.location;
let state = globalHistory.state || {};
return { pathname, state: state.usr };
}
function createBrowserHref(to) {
return to;
}
return getUrlBasedHistory(createBrowserLocation, createBrowserHref);
}
export function getUrlBasedHistory(getLocation, createHref) {
let globalHistory = window.history;
let listener = null;
let index = getIndex();
if (index == null) {
index = 0;
globalHistory.replaceState({
...globalHistory.state,
idx: index
}, "");
}
function getIndex() {
let state = globalHistory.state || {
idx: null
};
return state.idx;
}
function handlePop() {
action = Action.Pop;
let nextIndex = getIndex();
index = nextIndex;
if (listener) {
listener({
location: history.location
});
}
}
function push(to, state) {
action = Action.Push;
index = getIndex() + 1;
const url = createHref(to);
globalHistory.pushState({ usr: state, idx: index }, "", url);
if (listener) {
listener({
location: history.location
});
}
}
let history = {
get action() {
return action;
},
get location() {
return getLocation(window, globalHistory);
},
listen(fn) {
window.addEventListener(PopStateEventType, handlePop);
listener = fn;
return () => {
window.removeEventListener(PopStateEventType, handlePop);
listener = null;
};
},
push,
go(n) {
return globalHistory.go(n);
}
};
return history;
}
let pathToRegExp = require('path-to-regexp');
let regxp = pathToRegExp('/home',[],{end:true});
console.log(regxp);// /^\/home\/?$/i
console.log(regxp.test('/home'));
console.log(regxp.test('/home/2'));
let pathToRegExp = require('path-to-regexp');
let regExp = pathToRegExp('/home',[],{end:false});
console.log(regExp);// /^\/home\/?(?=\/|$)/i
console.log(regExp.test('/home'));
console.log(regExp.test('/home/'));
console.log(regExp.test('/home//'));
console.log(regExp.test('/home/2'));
let params = [];
let regExp = pathToRegExp('/user/:id',params,{end:true});
console.log(regExp,params);
/**
/^\/user\/(?:([^\/]+?))\/?$/i
[ { name: 'id', optional: false, offset: 7 } ]
**/
表达式 | 含义 |
---|---|
() | 表示捕获分组,()会把每个分组里的匹配的值保存起来,使用$n(n是一个数字,表示第n个捕获组的内容) |
(?:) | 表示非捕获分组,和捕获分组唯一的区别在于,非捕获分组匹配的值不会保存起来 |
(? |
表示命名捕获分组,反向引用一个命名分组的语法是 \k |
//分组获取
console.log('1ab'.match(/1[a-z]([b-c])/));
//分组不捕获
console.log('1ab'.match(/1[a-z](?:[a-z])/));
//?<x> 表示命名捕获分组
console.log('11-22'.replace(/(?<x>\d{2})-(?<y>\d{2})/,"$<y>-$<x>"));
表达式 | 含义 |
---|---|
(?=pattern) | 正向肯定查找(前瞻),后面必须跟着什么 |
(?!pattern) | 正向否定查找(前瞻),后面不能跟着什么 |
(?<=pattern) | 反向肯定条件查找(后顾),不捕获 |
(?<!pattern) | 反向否定条件查找(后顾) |
//会消耗掉字符的
//console.log('1a'.match(/\d[a-z][a-z]/));
//?= 正向肯定查找 不消费字符 正向前瞻
//console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向肯定前瞻
console.log('1a'.match(/\d(?=[a-z])[a-z]/));
//正向否定前瞻
console.log('1a'.match(/\d(?![A-Z])[a-z]/));
//反向肯定前瞻
console.log('1a'.match(/(?<=[a-z])\d[a-z]/));
//反向否定前瞻
console.log('1a'.match(/(?<![A-Z])\d[a-z]/));
let array = ['1ab'];
array.index = 0;
array.input = '1ab';
array.groups = undefined;
console.log(array);
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { HashRouter, BrowserRouter, Routes, Route } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
+import Post from './components/Post';
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<Routes>
+ <Route path="/post/:id" element={<Post />} />
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
</Routes>
</BrowserRouter>);
src\components\Post.js
import React from 'react';
function Post() {
return (
<div>Post</div>
)
}
export default Post;
src\react-router\lib\hooks.js
import React from 'react'
import { LocationContext } from "./context";
import { matchRoutes } from "../../router";
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();
let pathname = location.pathname || "/";
+ let match = matchRoutes(routes, { pathname });
+ console.log(match);
+ return match.route.element
}
src\router\index.js
export * from "./history";
export { matchRoutes } from "./utils";
src\router\utils.js
export function matchRoutes(routes, location) {
let { pathname } = location;
let match = null;
for (let i = 0; i < routes.length; ++i) {
match = matchPath(routes[i].path, pathname);
if (match) {
match.route = routes[i];
return match;
}
}
}
export function matchPath(pattern, pathname) {
let [matcher, paramNames] = compilePath(pattern,true);
let match = pathname.match(matcher);
if (!match) return null;
let captureGroups = match.slice(1);
let params = paramNames.reduce((memo, paramName, index) => {
memo[paramName] = captureGroups[index];
return memo;
}, {});
return {
params
};
}
function compilePath(path,end) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/\/*\*?$/, "")
.replace(/^\/*/, "/")
.replace(/\/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return "/([^\\/]+)";
});
if (end) {
regexpSource += "$";
}
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import { HashRouter, BrowserRouter, Routes, Route, Link } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import Post from './components/Post';
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
+ <ul>
+ <li><Link to="/">首页</Link></li>
+ <li><Link to="/user" >用户管理</Link></li>
+ <li><Link to="/profile" >个人中心</Link></li>
+ </ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} />
<Route path="/profile" element={<Profile />} />
<Route path="/post/:id" element={<Post />} />
</Routes>
</BrowserRouter>);
src\react-router-dom\index.js
import React from 'react'
+import { Router, useNavigate } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../router";
export { Routes, Route } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
+export const Link = function (props, ref) {
+ const { to, state, ...rest } = props;
+ let navigate = useNavigate();
+ function handleClick(event) {
+ event.preventDefault();
+ navigate(to, state);
+ }
+ return (
+ <a
+ {...rest}
+ onClick={handleClick}
+ />
+ );
+};
src\react-router\lib\hooks.js
import React from 'react'
+import { LocationContext, NavigationContext } from "./context";
import { matchRoutes } from "../../router";
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();
let pathname = location.pathname || "/";
let match = matchRoutes(routes, { pathname });
console.log(match);
return match.route.element
}
+export function useNavigate() {
+ let { navigator } = React.useContext(NavigationContext);
+ let navigate = React.useCallback((to, state) => {
+ navigator.push(to, state);
+ }, [navigator]);
+ return navigate;
+}
src\react-router\index.js
import { Route, Router, Routes } from "./lib/components";
+import { useNavigate } from "./lib/hooks";
export {
Route,
Routes,
Router,
+ useNavigate
}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import { HashRouter, BrowserRouter, Routes, Route, Link } from 'react-router-dom';
import Home from './components/Home';
import User from './components/User';
+import UserAdd from './components/UserAdd';
+import UserList from './components/UserList';
+import UserDetail from './components/UserDetail';
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<ul>
<li><Link to="/">首页</Link></li>
<li><Link to="/user" >用户管理</Link></li>
<li><Link to="/profile" >个人中心</Link></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
+ <Route path="/user" element={<User />} >
+ <Route path="add" element={<UserAdd />} />
+ <Route path="list" element={<UserList />} />
+ <Route path="detail/:id" element={<UserDetail />} />
+ </Route>
</Routes>
</BrowserRouter>);
src\components\User.js
import React from 'react';
+import { Link, Outlet } from 'react-router-dom';
function User() {
return (
+ <div>
+ <ul>
+ <li><Link to="/user/list">用户列表</Link></li>
+ <li><Link to="/user/add">添加用户</Link></li>
+ </ul>
+ <Outlet />
+ </div>
)
}
export default User;
src\utils.js
export const UserAPI = {
list() {
let usersStr = localStorage.getItem('users');
let users = usersStr ? JSON.parse(usersStr) : [];
return users;
},
add(user) {
let users = UserAPI.list();
users.push(user);
localStorage.setItem('users', JSON.stringify(users));
},
find(id) {
let users = UserAPI.list();
return users.find((user) => user.id === id);
}
}
src\components\UserList.js
import React from 'react'
import { Link } from 'react-router-dom';
import { UserAPI } from '../utils';
export default function User() {
const [users, setUsers] = React.useState([]);
React.useEffect(() => {
let users = UserAPI.list();
setUsers(users);
}, []);
return (
<ul>
{
users.map((user, index) => (
<li key={index}>
<Link to={`/user/detail/${user.id}`} state={user}>{user.name}</Link>
</li>
))
}
</ul>
)
}
src\components\UserAdd.js
import React from 'react'
import { useNavigate } from 'react-router-dom';
import { UserAPI } from '../utils';
export default function UserAdd() {
const navigate = useNavigate();
const nameRef = React.useRef();
const handleSubmit = (event) => {
event.preventDefault();
let name = nameRef.current.value;
UserAPI.add({ id: Date.now() + "", name });
navigate('/user/list');
}
return (
<form onSubmit={handleSubmit}>
<input type="text" ref={nameRef} />
<button type="submit">添加</button>
</form>
)
}
src\components\UserDetail.js
import React from 'react'
import { useLocation, useParams } from 'react-router-dom';
import { UserAPI } from '../utils';
export default function UserDetail(props) {
const location = useLocation();
const { id } = useParams();
const [user, setUser] = React.useState({});
React.useEffect(() => {
let user = location.state;
if (!user) {
if (id) {
user = UserAPI.find(id);
}
}
if (user) setUser(user);
}, []);
return (
<div>
{user.id}:{user.name}
</div>
)
}
[
{
"path": "/user",
"element": "User",
"children": [
{
"path": "add",
"element": "UserList"
}
]
}
]
function flattenRoutes(routes, branches = []) {
routes.forEach(route => {
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches);
}
branches.push({ name: route.name });
});
return branches;
}
let routes = [
{
name: 'A',
children: [
{
name: 'B',
children: [
{
name: 'C'
}
]
}
]
}
]
console.log(flattenRoutes(routes));
[
{
path: "/",
routesMeta: [
{
relativePath: "/",
childrenIndex: 0,
route: {
path: "/",
element: {
$$typeof: Symbol(react.element),
type: Home,
props: {}
}
}
}
],
score: 4
},
{
path: "/profile",
routesMeta: [
{
relativePath: "/profile",
childrenIndex: 1,
route: {
path: "/profile",
element: {
$$typeof: Symbol(react.element),
type: Profile,
props: {}
}
}
}
],
score: 13
},
{
path: "/user/add",
routesMeta: [
{
relativePath: "user",
childrenIndex: 2,
route: {
path: "user",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
children: [
{
path: "add",
element: {
$$typeof: Symbol(react.element),
type: UserAdd,
props: {}
}
},
{
path: "list",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
}
},
{
path: "detail/:id",
element: {
$$typeof: Symbol(react.element),
type: UserDetail,
props: {}
}
}
]
}
},
{
relativePath: "add",
childrenIndex: 0,
route: {
path: "add",
element: {
$$typeof: Symbol(react.element),
type: UserAdd,
props: {}
}
}
}
],
score: 24
},
{
path: "/user/list",
routesMeta: [
{
relativePath: "user",
childrenIndex: 2,
route: {
path: "user",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
children: [
{
path: "add",
element: {
$$typeof: Symbol(react.element),
type: UserAdd,
props: {}
}
},
{
path: "list",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
}
},
{
path: "detail/:id",
element: {
$$typeof: Symbol(react.element),
type: UserDetail,
props: {}
}
}
]
}
},
{
relativePath: "list",
childrenIndex: 1,
route: {
path: "list",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
}
}
}
],
score: 24
},
{
path: "/user/detail/:id",
routesMeta: [
{
relativePath: "user",
childrenIndex: 2,
route: {
path: "user",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
children: [
{
path: "add",
element: {
$$typeof: Symbol(react.element),
type: UserAdd,
props: {}
}
},
{
path: "list",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
}
},
{
path: "detail/:id",
element: {
$$typeof: Symbol(react.element),
type: UserDetail,
props: {}
}
}
]
}
},
{
relativePath: "detail/:id",
childrenIndex: 2,
route: {
path: "detail/:id",
element: {
$$typeof: Symbol(react.element),
type: UserDetail,
props: {}
}
}
}
],
score: 28
},
{
path: "/user",
routesMeta: [
{
relativePath: "user",
childrenIndex: 2,
route: {
path: "user",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
children: [
{
path: "add",
element: {
$$typeof: Symbol(react.element),
type: UserAdd,
props: {}
}
},
{
path: "list",
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
}
},
{
path: "detail/:id",
element: {
$$typeof: Symbol(react.element),
type: UserDetail,
props: {}
}
}
]
}
}
],
score: 13
},
{
path: "/profile",
routesMeta: [
{
relativePath: "/profile",
childrenIndex: 3,
route: {
path: "/profile",
element: {
$$typeof: Symbol(react.element),
type: Protected,
props: {
component: Profile
}
}
}
}
],
score: 13
},
{
path: "/login",
routesMeta: [
{
relativePath: "/login",
childrenIndex: 4,
route: {
path: "/login",
element: {
$$typeof: Symbol(react.element),
type: Login,
props: {}
}
}
}
],
score: 13
},
{
path: "/*",
routesMeta: [
{
relativePath: "*",
childrenIndex: 5,
route: {
path: "*",
element: {
$$typeof: Symbol(react.element),
type: NotFound,
props: {}
}
}
}
],
score: 1
}
]
const branches=[
{path:'/user/*/add',routesMeta:[user*Meta,addMeta]},
{path:'/user/*/list',routesMeta:[user*Meta,listMeta]},
{path:'/user/*/detail',routesMeta:[user*Meta,detailMeta]},
{path:'/user/*',routesMeta:[user*Meta]},
]
[
{
"pathname": "/user",
"route": {
"element": {
"type": User
},
"path": "/user"
}
},
{
"pathname": "/user/list",
"route": {
"element": {
"type": UserList
},
"path": "list"
}
}
]
let element = {
$$typeof: Symbol(react.element),
type: {
$$typeof: Symbol(react.provider)
},
props: {
value: {
outlet: {
$$typeof: Symbol(react.element),
type: {
$$typeof: Symbol(react.provider)
},
props: {
value: {
outlet: null,
matches: [
{
params: {},
pathname: "/user",
route: {
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
path: "/user"
}
},
{
params: {},
pathname: "/user/list",
route: {
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
path: "list"
}
}
]
}
}
},
matches: [
{
params: {},
pathname: "/user",
route: {
element: {
$$typeof: Symbol(react.element),
type: User,
props: {}
},
path: "/user"
}
}
]
}
}
}
src\react-router-dom\index.js
import React from 'react'
import { Router, useNavigate } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../router";
+export { Routes, Route, Outlet, useLocation, useParams, useNavigate } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
window.browserHistory = history;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
window.hashHistory = history;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export const Link = function (props) {
const { to, state, ...rest } = props;
let navigate = useNavigate();
function handleClick(event) {
event.preventDefault();
navigate(to, state);
}
return (
<a
{...rest}
onClick={handleClick}
/>
);
};
src\react-router\index.js
+import { Route, Router, Routes, Outlet } from "./lib/components";
+import { useNavigate, useLocation, useParams } from "./lib/hooks";
export {
Route,
Routes,
Router,
useNavigate,
+ Outlet,
+ useLocation,
+ useParams
}
src\react-router\lib\components.js
import React from 'react'
import { LocationContext, NavigationContext } from "./context";
+import { useRoutes, useOutlet } from "./hooks";
+export function Outlet() {
+ return useOutlet();
+}
export function Router({ children, location, navigator }) {
const navigationContext = { navigator };
const locationContext = { location };
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider children={children} value={locationContext} />
</NavigationContext.Provider>
);
}
export function Route() {
}
export function Routes({ children }) {
let routes = createRoutesFromChildren(children)
return useRoutes(routes);
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, (element) => {
let route = {
element: element.props.element,
path: element.props.path
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
src\react-router\lib\hooks.js
import React from 'react'
import { matchRoutes } from "../../router";
+import { LocationContext, NavigationContext, RouteContext } from "./context";
export function useLocation() {
return React.useContext(LocationContext).location;
}
export function useRoutes(routes) {
let location = useLocation();
let pathname = location.pathname || "/";
+ let matches = matchRoutes(routes, { pathname });
+ if (matches)
+ return renderMatches(matches);
}
+// 导出一个名为 renderMatches 的函数,参数为 renderedMatches
+export function renderMatches(renderedMatches) {
+ // 使用 reduceRight 方法从右到左遍历 renderedMatches 数组,累计器初始值为 null
+ return renderedMatches.reduceRight((outlet, match, index) => {
+ // 获取当前元素之前的子数组,包含当前元素
+ let matches = renderedMatches.slice(0, index + 1);
+ // 返回一个 RouteContext.Provider 组件,将累计值和子数组作为值传递给组件
+ return (
+ <RouteContext.Provider value={{ outlet, matches }}>
+ {match.route.element} //渲染当前 match 对象的 route.element
+ </RouteContext.Provider>
+ );
+ }, null);
+}
export function useNavigate() {
let { navigator } = React.useContext(NavigationContext);
let navigate = React.useCallback((to, state) => {
navigator.push(to, state);
}, [navigator]);
return navigate;
}
+export function useOutlet() {
+ let { outlet } = React.useContext(RouteContext);
+ return outlet;
+}
+export function useParams() {
+ let { matches } = React.useContext(RouteContext);
+ let routeMatch = matches[matches.length - 1];
+ return routeMatch ? routeMatch.params : {};
+}
src\router\utils.js
+export const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
export function matchRoutes(routes, location) {
let { pathname } = location;
+ let branches = flattenRoutes(routes);
+ let matches = null;
+ for (let i = 0; matches == null && i < branches.length; ++i) {
+ matches = matchRouteBranch(branches[i], pathname);
+ }
+ return matches;
}
+// 定义一个函数,将嵌套的路由数组扁平化
+function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
+ // 定义一个内部函数,处理单个路由对象
+ let flattenRoute = (route, index) => {
+ // 定义一个元数据对象,存储路由相关信息
+ let meta = {
+ relativePath: route.path,
+ childrenIndex: index,
+ route
+ };
+ // 使用 joinPaths 函数将父路径与当前相对路径组合,生成完整路径
+ let path = joinPaths([parentPath, meta.relativePath]);
+ // 将当前路由元数据添加到父级元数据数组中
+ let routesMeta = parentsMeta.concat(meta);
+ // 如果当前路由对象有子路由,则递归处理
+ if (route.children && route.children.length > 0) {
+ flattenRoutes(route.children, branches, routesMeta, path);
+ }
+ // 将路径和元数据对象添加到结果数组中
+ branches.push({ path, routesMeta });
+ };
+ // 遍历路由数组,调用内部函数处理每个路由对象
+ routes.forEach((route, index) => {
+ flattenRoute(route, index);
+ });
+ // 返回扁平化后的路由数组
+ return branches;
+}
+/**
+ * 定义matchRouteBranch函数,用于匹配路由分支和路径名
+ * @param {*} branch 分支
+ * @param {*} pathname 路径名
+ * @returns
+ */
+function matchRouteBranch(branch, pathname) {
+ // 从branch中解构routesMeta
+ let { routesMeta } = branch;
+ // 初始化匹配参数、匹配路径名、匹配数组
+ let matchedParams = {};
+ let matchedPathname = "/";
+ let matches = [];
+ // 遍历routesMeta
+ // 遍历routesMeta
+ for (let i = 0; i < routesMeta.length; ++i) {
+ // 获取当前元素的meta
+ let meta = routesMeta[i];
+ // 判断是否为最后一个元素
+ let end = i === routesMeta.length - 1;
+ // 获取剩余路径名
+ let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
+ // 获取当前路径匹配结果
+ let match = matchPath({ path: meta.relativePath, end }, remainingPathname);
+ // 若没有匹配结果,返回null
+ if (!match) return null;
+ // 将当前匹配参数合并到matchedParams
+ Object.assign(matchedParams, match.params);
+ // 获取当前路由
+ let route = meta.route;
+ // 将匹配结果添加到matches数组
+ matches.push({
+ params: matchedParams,
+ pathname: joinPaths([matchedPathname, match.pathname]),
+ route
+ });
+ // 更新匹配路径名
+ matchedPathname = joinPaths([matchedPathname, match.pathname]);
+ }
+ // 返回匹配结果数组
+ return matches;
+}
+export function matchPath({ path, end }, pathname) {
+ let [matcher, paramNames] = compilePath(path, end);
let match = pathname.match(matcher);
if (!match) return null;
+ let matchedPathname = match[0];
let captureGroups = match.slice(1);
let params = paramNames.reduce((memo, paramName, index) => {
memo[paramName] = captureGroups[index];
return memo;
}, {});
return {
params,
+ pathname: matchedPathname
};
}
+function compilePath(path, end = true) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/\/*\*?$/, "")
.replace(/^\/*/, "/")
.replace(/\/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return "/([^\\/]+)";
});
+ if (end) {
+ regexpSource += "$";
+ }
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
public\index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
<title>React App</title>
+ <style>
+ .active {
+ color: red;
+ }
+ </style>
</head>
<body>
<div id="root"></div>
</body>
</html>
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, Routes, Route, NavLink } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
+const activeStyle = { backgroundColor: 'green' };
+const activeClassName = 'active';
+const activeNavProps = {
+ style: ({ isActive }) => isActive ? activeStyle : {},
+ className: ({ isActive }) => isActive ? activeClassName : ''
+}
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<ul>
+ <li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
+ <li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
+ <li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
</Routes>
</BrowserRouter>);
src\react-router-dom\index.js
import React from 'react'
+import { Router, useNavigate, useLocation } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../router";
export { Routes, Route, Outlet, useLocation, useParams, useNavigate } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export const Link = function (props, ref) {
const { to, state, ...rest } = props;
let navigate = useNavigate();
function handleClick(event) {
event.preventDefault();
navigate(to, state);
}
return (
<a
{...rest}
onClick={handleClick}
/>
);
};
/**
* @param {*} className 类名 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} end 是否结束
* @param {*} style 行内样式 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} to 点击导航跳转的路径
* @param {*} children 子组件
*/
+export function NavLink({ className: classNameProp = '', end = false, style: styleProp = {}, to, children, ...rest }) {
+ let location = useLocation();
+ let path = { pathname: to };
+ let locationPathname = location.pathname;//当前的路径
+ let toPathname = path.pathname;//当前导航想要跳转的路径
+ //如果路径一样,或者 不结束,并且当前的路径是以to开头的,并且下一个字符/,也就是路径路径分隔符
+ let isActive = locationPathname === toPathname || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === '/')
+ let className;
+ if (typeof classNameProp === 'function') {
+ className = classNameProp({
+ isActive
+ });
+ }
+ let style;
+ if (typeof styleProp === 'function') {
+ style = styleProp({
+ isActive
+ });
+ }
+ return (
+ <Link {...rest} to={to} className={className} style={style}>{children}</Link>
+ )
+}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, Routes, Route, NavLink, Navigate } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
+import Profile from './components/Profile';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
const activeStyle = { backgroundColor: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<ul>
<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
<Route path="/profile" element={<Profile />} />
+ <Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>);
src\components\Home.js
import React from 'react';
+import { useNavigate } from '../react-router-dom';
function Home(props) {
+ let navigate = useNavigate();
+ function navigateTo() {
+ navigate('/profile');
+ };
console.log(props);
return (
+ <div>
+ <p>Home</p>
+ <button onClick={navigateTo}>跳转到/profile</button>
+ </div>
)
}
export default Home;
src\react-router\index.js
+import { Route, Router, Routes, Outlet, Navigate } from "./lib/components";
import { useNavigate, useLocation, useParams } from "./lib/hooks";
export {
Route,
Routes,
Router,
Outlet,
+ Navigate,
useNavigate,
useLocation,
useParams
}
src\react-router\lib\components.js
import React from 'react'
import { LocationContext, NavigationContext } from "./context";
+import { useRoutes, useOutlet, useNavigate } from "./hooks";
export function Router({ children, location, navigator }) {
const navigationContext = { navigator };
const locationContext = { location };
return (
<NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider children={children} value={locationContext} />
</NavigationContext.Provider>
);
}
export function Route() {
}
export function Routes({ children }) {
const routes = createRoutesFromChildren(children);
return useRoutes(routes);
}
export function createRoutesFromChildren(children) {
let routes = [];
React.Children.forEach(children, (element) => {
let route = {
element: element.props.element,
path: element.props.path
};
if (element.props.children) {
route.children = createRoutesFromChildren(element.props.children);
}
routes.push(route);
});
return routes;
}
export function Outlet() {
return useOutlet();
}
+export function Navigate({ to, state }) {
+ let navigate = useNavigate();
+ React.useEffect(() => {
+ navigate(to, state)
+ });
+ return null;
+}
src\react-router-dom\index.js
import React from 'react'
import { Router, useNavigate, useLocation } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../router";
+export { Routes, Route, Outlet, useLocation, useParams, useNavigate, Navigate } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export const Link = function (props, ref) {
const { to, state, ...rest } = props;
let navigate = useNavigate();
function handleClick(event) {
event.preventDefault();
navigate(to, state);
}
return (
<a
{...rest}
onClick={handleClick}
/>
);
};
/**
* @param {*} className 类名 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} end 是否结束
* @param {*} style 行内样式 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} to 点击导航跳转的路径
* @param {*} children 子组件
*/
export function NavLink({ className: classNameProp = '', end = false, style: styleProp = {}, to, children, ...rest }) {
let location = useLocation();
let path = { pathname: to };
let locationPathname = location.pathname;//当前的路径
let toPathname = path.pathname;//当前导航想要跳转的路径
//如果路径一样,或者 不结束,并且当前的路径是以to开头的,并且下一个字符/,也就是路径路径分隔符
let isActive = locationPathname === toPathname || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === '/')
let className;
if (typeof classNameProp === 'function') {
className = classNameProp({
isActive
});
}
let style;
if (typeof styleProp === 'function') {
style = styleProp({
isActive
});
}
return (
<Link {...rest} to={to} className={className} style={style}>{children}</Link>
)
}
src\router\utils.js
export const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
//import { stringifyWithWhitelist } from "stringify-with-whitelist";
export function matchRoutes(routes, location) {
let { pathname } = location;
let branches = flattenRoutes(routes);
//console.log(stringifyWithWhitelist(branches, ['*.path,', '*.routesMeta.*.relativePath', '*.routesMeta.*.route.element.type', '*.routesMeta.*.route.path', '*.routesMeta.*.childrenIndex']));
let matches = null;
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
// 定义一个函数,将嵌套的路由数组扁平化
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
// 定义一个内部函数,处理单个路由对象
let flattenRoute = (route, index) => {
// 定义一个元数据对象,存储路由相关信息
let meta = {
relativePath: route.path,
childrenIndex: index,
route
};
// 使用 joinPaths 函数将父路径与当前相对路径组合,生成完整路径
let path = joinPaths([parentPath, meta.relativePath]);
// 将当前路由元数据添加到父级元数据数组中
let routesMeta = parentsMeta.concat(meta);
// 如果当前路由对象有子路由,则递归处理
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
// 将路径和元数据对象添加到结果数组中
branches.push({ path, routesMeta });
};
// 遍历路由数组,调用内部函数处理每个路由对象
routes.forEach((route, index) => {
flattenRoute(route, index);
});
// 返回扁平化后的路由数组
return branches;
}
/**
* 定义matchRouteBranch函数,用于匹配路由分支和路径名
* @param {*} branch 分支
* @param {*} pathname 路径名
* @returns
*/
function matchRouteBranch(branch, pathname) {
// 从branch中解构routesMeta
let { routesMeta } = branch;
// 初始化匹配参数、匹配路径名、匹配数组
let matchedParams = {};
let matchedPathname = "/";
let matches = [];
// 遍历routesMeta
// 遍历routesMeta
for (let i = 0; i < routesMeta.length; ++i) {
// 获取当前元素的meta
let meta = routesMeta[i];
// 判断是否为最后一个元素
let end = i === routesMeta.length - 1;
// 获取剩余路径名
let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
// 获取当前路径匹配结果
let match = matchPath({ path: meta.relativePath, end }, remainingPathname);
// 若没有匹配结果,返回null
if (!match) return null;
// 将当前匹配参数合并到matchedParams
Object.assign(matchedParams, match.params);
// 获取当前路由
let route = meta.route;
// 将匹配结果添加到matches数组
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
route
});
// 更新匹配路径名
matchedPathname = joinPaths([matchedPathname, match.pathname]);
}
// 返回匹配结果数组
return matches;
}
export function matchPath({ path, end }, pathname) {
let [matcher, paramNames] = compilePath(path, end);
let match = pathname.match(matcher);
if (!match) return null;
let matchedPathname = match[0];
let captureGroups = match.slice(1);
let params = paramNames.reduce((memo, paramName, index) => {
memo[paramName] = captureGroups[index];
return memo;
}, {});
return {
params,
pathname: matchedPathname
};
}
function compilePath(path, end = true) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/\/*\*?$/, "")
.replace(/^\/*/, "/")
.replace(/\/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return "/([^\\/]+)";
});
+ if (path === "*") {
+ paramNames.push("*");
+ regexpSource += "(.*)$";
+ } if (end) {
regexpSource += "$";
}
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter, Routes, Route, NavLink, Navigate } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
+import Protected from './components/Protected';
+import Login from './components/Login';
const activeStyle = { backgroundColor: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<ul>
<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
</ul>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/user" element={<User />} >
<Route path="add" element={<UserAdd />} />
<Route path="list" element={<UserList />} />
<Route path="detail/:id" element={<UserDetail />} />
</Route>
+ <Route path="/profile" element={<Protected component={Profile} path="/profile" />} />
+ <Route path="/login" element={<Login />} />
<Route path="*" element={<Navigate to="/" />} />
</Routes>
</BrowserRouter>);
src\components\Protected.js
import React from 'react';
import { Navigate } from '../react-router-dom';
function Protected(props) {
let { component: RouteComponent, path } = props;
return localStorage.getItem('login') ? <RouteComponent /> :
<Navigate to='/login' state={{ from: path }} />
}
export default Protected;
src\components\Login.js
import React from 'react';
import { useNavigate, useLocation } from '../react-router-dom';
function Login() {
let navigation = useNavigate();
let location = useLocation();
const login = () => {
localStorage.setItem('login', 'true');
let to = '/';
if (location.state) {
to = location.state.from || '/';
}
navigation(to);
}
return (
<button onClick={login}>登录</button>
)
}
export default Login;
src\index.js
import React from 'react';
import ReactDOM from 'react-dom/client';
+import { BrowserRouter, Routes, Route, NavLink, Navigate, useRoutes } from './react-router-dom';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import UserAdd from './components/UserAdd';
import UserList from './components/UserList';
import UserDetail from './components/UserDetail';
import Protected from './components/Protected';
import Login from './components/Login';
+import routesConfig from './routesConfig';
+const LazyPost = React.lazy(() => import('./components/Post'));
+function App() {
+ let [routes, setRoutes] = React.useState(routesConfig);
+ const addRoute = () => {
+ setRoutes([...routes, {
+ path: '/post', element: (
+ <React.Suspense fallback={<div>loading...</div>}>
+ <LazyPost />
+ </React.Suspense>
+ )
+ }]);
+ }
+ return (
+ <div>
+ {useRoutes(routes)}
+ <button onClick={addRoute}>addRoute</button>
+ </div>
+ )
+}
const activeStyle = { backgroundColor: 'green' };
const activeClassName = 'active';
const activeNavProps = {
style: ({ isActive }) => isActive ? activeStyle : {},
className: ({ isActive }) => isActive ? activeClassName : ''
}
ReactDOM.createRoot(
document.getElementById('root')
).render(
<BrowserRouter >
<ul>
<li><NavLink end={true} to="/" {...activeNavProps}>首页</NavLink></li>
<li><NavLink to="/user/list" {...activeNavProps}>用户管理</NavLink></li>
<li><NavLink to="/profile" {...activeNavProps}>个人中心</NavLink></li>
+ <li><NavLink to="/post" {...activeNavProps}>post</NavLink></li>
</ul>
+ <App />
</BrowserRouter>);
src\routesConfig.js
import React from 'react';
import Home from './components/Home';
import User from './components/User';
import Profile from './components/Profile';
import UserAdd from './components/UserAdd';
import UserDetail from './components/UserDetail';
import UserList from './components/UserList';
import NotFound from './components/NotFound';
import Login from './components/Login';
import Protected from './components/Protected';
const routes = [
{ path: '/', element: <Home /> },
{ path: '/profile', element: <Profile /> },
{
path: 'user',
element: <User />,
children: [
{ path: 'add', element: <UserAdd /> },
{ path: 'list', element: <UserList /> },
{ path: 'detail/:id', element: <UserDetail /> }
]
},
{ path: '/profile', element: <Protected component={Profile} /> },
{ path: '/login', element: <Login /> },
{ path: '*', element: <NotFound /> }
];
export default routes;
src\components\NotFound.js
import React from 'react';
function NotFound(props) {
return (
<div>NotFound</div>
)
}
export default NotFound;
src\react-router-dom\index.js
import React from 'react'
import { Router, useNavigate, useLocation } from '../react-router';
import { createHashHistory, createBrowserHistory } from "../router";
+export { Routes, Route, Outlet, useLocation, useParams, useNavigate, Navigate, useRoutes } from '../react-router';
export function BrowserRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createBrowserHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export function HashRouter({ children }) {
let historyRef = React.useRef();
if (historyRef.current == null) {
historyRef.current = createHashHistory();
}
let history = historyRef.current;
let [state, setState] = React.useState({
action: history.action,
location: history.location
});
React.useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigationType={state.action}
navigator={history} />;
}
export const Link = function (props, ref) {
const { to, state, ...rest } = props;
let navigate = useNavigate();
function handleClick(event) {
event.preventDefault();
navigate(to, state);
}
return (
<a
{...rest}
onClick={handleClick}
/>
);
};
/**
* @param {*} className 类名 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} end 是否结束
* @param {*} style 行内样式 可以是固定的字符串,也可以是一个函数,函数的参数是isActive
* @param {*} to 点击导航跳转的路径
* @param {*} children 子组件
*/
export function NavLink({ className: classNameProp = '', end = false, style: styleProp = {}, to, children, ...rest }) {
let location = useLocation();
let path = { pathname: to };
let locationPathname = location.pathname;//当前的路径
let toPathname = path.pathname;//当前导航想要跳转的路径
//如果路径一样,或者 不结束,并且当前的路径是以to开头的,并且下一个字符/,也就是路径路径分隔符
let isActive = locationPathname === toPathname || (!end && locationPathname.startsWith(toPathname) && locationPathname.charAt(toPathname.length) === '/')
let className;
if (typeof classNameProp === 'function') {
className = classNameProp({
isActive
});
}
let style;
if (typeof styleProp === 'function') {
style = styleProp({
isActive
});
}
return (
<Link {...rest} to={to} className={className} style={style}>{children}</Link>
)
}
src\react-router\index.js
import { Route, Router, Routes, Outlet, Navigate } from "./lib/components";
+import { useNavigate, useLocation, useParams, useRoutes } from "./lib/hooks";
export {
Route,
Routes,
Router,
Outlet,
Navigate,
useNavigate,
useLocation,
useParams,
+ useRoutes
}
src\router\utils.js
export const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
+//如果路径中有*就减少2
+const splatPenalty = -2;
+const indexRouteValue = 2;
+const paramRegexp = /^:\w+$/;
+const dynamicSegmentValue = 3;
+const emptySegmentValue = 1;
+const staticSegmentValue = 10;
+const isSplat = s => s === '*';//console.log(computeScore('/user/*', 1));
+function computeScore(path, index) {
+ // /user/add
+ let segments = path.split('/');//['','user','*']//3
+ let initialScore = segments.length;//3
+ if (segments.some(isSplat)) {
+ initialScore += splatPenalty;//1
+ }
+ if (typeof index !== 'undefined') {
+ initialScore += indexRouteValue;//3
+ }
+ //['','user','*']=>['','user']
+ return segments.filter(s => !isSplat(s)).reduce((score, segment) => {
+ let currentScope = 0;
+ //如果这个片断是路径参数的话 :id
+ if (paramRegexp.test(segment)) {
+ currentScope += dynamicSegmentValue;
+ } else {
+ if (segment === '') {
+ currentScope += emptySegmentValue;
+ } else {
+ currentScope += staticSegmentValue;
+ }
+ }
+ score += currentScope;
+ return score;
+ }, initialScore);
+}
+function rankRouteBranches(branches) {
+ branches.sort((a, b) => a.score !== b.score ? b.score - a.score : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
+}
+function compareIndexes(a, b) {
+ let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
+ return siblings ? a[a.length - 1] - b[b.length - 1] : 0;
+}
export function matchRoutes(routes, location) {
let { pathname } = location;
let branches = flattenRoutes(routes);
rankRouteBranches(branches);
let matches = null;
for (let i = 0; matches == null && i < branches.length; ++i) {
matches = matchRouteBranch(branches[i], pathname);
}
return matches;
}
// 定义一个函数,将嵌套的路由数组扁平化
function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
// 定义一个内部函数,处理单个路由对象
let flattenRoute = (route, index) => {
// 定义一个元数据对象,存储路由相关信息
let meta = {
relativePath: route.path,
childrenIndex: index,
route
};
// 使用 joinPaths 函数将父路径与当前相对路径组合,生成完整路径
let path = joinPaths([parentPath, meta.relativePath]);
// 将当前路由元数据添加到父级元数据数组中
let routesMeta = parentsMeta.concat(meta);
// 如果当前路由对象有子路由,则递归处理
if (route.children && route.children.length > 0) {
flattenRoutes(route.children, branches, routesMeta, path);
}
// 将路径和元数据对象添加到结果数组中
+ branches.push({ path, routesMeta, score: computeScore(path, route.index) });
};
// 遍历路由数组,调用内部函数处理每个路由对象
routes.forEach((route, index) => {
flattenRoute(route, index);
});
// 返回扁平化后的路由数组
return branches;
}
/**
* 定义matchRouteBranch函数,用于匹配路由分支和路径名
* @param {*} branch 分支
* @param {*} pathname 路径名
* @returns
*/
function matchRouteBranch(branch, pathname) {
// 从branch中解构routesMeta
let { routesMeta } = branch;
// 初始化匹配参数、匹配路径名、匹配数组
let matchedParams = {};
let matchedPathname = "/";
let matches = [];
// 遍历routesMeta
// 遍历routesMeta
for (let i = 0; i < routesMeta.length; ++i) {
// 获取当前元素的meta
let meta = routesMeta[i];
// 判断是否为最后一个元素
let end = i === routesMeta.length - 1;
// 获取剩余路径名
let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
// 获取当前路径匹配结果
let match = matchPath({ path: meta.relativePath, end }, remainingPathname);
// 若没有匹配结果,返回null
if (!match) return null;
// 将当前匹配参数合并到matchedParams
Object.assign(matchedParams, match.params);
// 获取当前路由
let route = meta.route;
// 将匹配结果添加到matches数组
matches.push({
params: matchedParams,
pathname: joinPaths([matchedPathname, match.pathname]),
route
});
// 更新匹配路径名
matchedPathname = joinPaths([matchedPathname, match.pathname]);
}
// 返回匹配结果数组
return matches;
}
export function matchPath({ path, end }, pathname) {
let [matcher, paramNames] = compilePath(path, end);
let match = pathname.match(matcher);
if (!match) return null;
let matchedPathname = match[0];
let captureGroups = match.slice(1);
let params = paramNames.reduce((memo, paramName, index) => {
memo[paramName] = captureGroups[index];
return memo;
}, {});
return {
params,
pathname: matchedPathname
};
}
function compilePath(path, end = true) {
let paramNames = [];
let regexpSource = "^" + path
.replace(/\/*\*?$/, "")
.replace(/^\/*/, "/")
.replace(/\/:(\w+)/g, (_, paramName) => {
paramNames.push(paramName);
return "/([^\\/]+)";
});
if (path === "*") {
paramNames.push("*");
regexpSource += "(.*)$";
} if (end) {
regexpSource += "$";
}
let matcher = new RegExp(regexpSource);
return [matcher, paramNames];
}