v6
来源
react-router vue-router
- SPA 出现后, 前端才开始自己接管路由
- 现在,客户端接管了路由。
- router 是啥
- 路由的变化,是不是就是意味着界面(部分内容)的变化
- 界面的变化,意味着数据的变化,组件的变化。
- keywords: context, components, path(/user/edit)
- 路由的核心
- 根据/监听 path/url 的变化,根据 path 和 components 之间的对应关系,触发组件进行unmount 和 mount,同时使用 context 注入上下文
- navigation
- path
- 根据/监听 path/url 的变化,根据 path 和 components 之间的对应关系,触发组件进行unmount 和 mount,同时使用 context 注入上下文
react-router
提供一些核心的 API, 如:Router, Route 这些,但是不提供和 DOM 相关的
react-router-dom
提供 BrowserRouter, HashRouter, Link 这些 API,可以通过 DOM 操作触发事件,控制路由
history
模拟浏览器的 history 的一个库, V6 的版本这个库已经内置的,并且导出成 navigation
react-router-dom v6
- 已经没有 component 了,用 element 代替
Routes
一组路由,代替原来的 Switch
Route
基础路由
Link
Outlet
类似于 Vue 中的 router-view
useRoutes、useParams、useNavigate(代替以前的 useHistory)、useLocation
import { BrowserRouter, Outlet, Route, Routes, useLocation, useNavigate, useParams, useRoutes } from 'react-router-dom';
import { lazy,Suspense } from 'react';
import { Link } from 'react-router-dom';
//写法1
function App_old() {
return <BrowserRouter>
<Routes>
<Route path="/list" element={<div>新闻列表--pages</div>} />
<Route path="/about" element={<div>关于我们--pages</div>} />
<Route path="/hot" element={<div>热点新闻--pages</div>} />
</Routes>
</BrowserRouter>
}
//写法2
const NavMenu = ({ to, children }) => (
<div className=' border-x border-gray-500 border-solid px-10 py-1 whitespace-nowrap'>
<Link to={to} >{children}</Link>
</div>
)
const Menu = () => (
<div>
<header className=' w-full h-14 flex flex-row justify-start items-center bg-blue-200 '>
<NavMenu to="./" >首页</NavMenu>
<NavMenu to="./list" >新闻列表</NavMenu>
<NavMenu to="./about" >关于我们</NavMenu>
<NavMenu to="./hot" >热点新闻</NavMenu>
</header>
<Outlet />
</div>
)
const List = () => {
const nav = useNavigate();
return <div>
this is the news Page
<button onClick={() => nav('/post/1')} >去新闻1</button>
</div>
}
const Post = () => {
const { id } = useParams();
const location = useLocation();
console.log(location)
return <div>
{id}: 新闻信息
</div>
}
const App_old_Outlet = () => {
return <BrowserRouter>
<Routes>
<Route path='/' element={<Menu />}>
<Route path='/list' element={<List />} />
<Route path='/about' element={<div>关于我们--pages</div>} />
<Route path="/hot" element={<div>热点新闻--pages</div>} />
</Route>
</Routes>
</BrowserRouter>
}
//写法3
let routes = [
{
path: '/', element: <Menu />,
children: [
{ path: '/list', element: <List />},
{ path: '/post/:id', element: <Post />},
{ path: '/about', element: <div>关于我们--pages--json</div>},
{ path: '/hot', element: <div>
<Suspense fallback={<div>loading</div>}>
<DynamicNews />
</Suspense>
</div>},
]
}
]
const Routing = () => useRoutes(routes)
const App = () => {
return <BrowserRouter>
<Routing />
</BrowserRouter>
}
手写router实现
import { createBrowserHistory, createHashHistory } from "history";
import { useLayoutEffect, useState } from "react";
import { useMemo } from "react";
import { useRef } from "react";
import { useContext } from "react";
import React, { createContext } from "react";
// 创建上下文
const NavigationContext = createContext({});
const LocationContext = createContext({});
export function HashRouter({ children }) {
let historyRef = useRef();
if(historyRef.current == null) {
historyRef.current = createHashHistory();
};
let history = historyRef.current;
let [ state, setState ] = useState({
action: history.action,
location: history.location
})
use
// 我们需要监听 history 的变化, 用useLayoutEffect
// 当 history 变化的时候,(浏览器输入、获取a标签跳转,api跳转)
// 派发更新,渲染整个 router 树
useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigator={history}
navigationType={state.action}
/>
}
export function BrowserRouter({ children }) {
let historyRef = useRef();
if(historyRef.current == null) {
historyRef.current = createBrowserHistory();
};
let history = historyRef.current;
let [ state, setState ] = useState({
action: history.action,
location: history.location
})
// 我们需要监听 history 的变化, 用useLayoutEffect
// 当 history 变化的时候,(浏览器输入、获取a标签跳转,api跳转)
// 派发更新,渲染整个 router 树
useLayoutEffect(() => history.listen(setState), [history]);
return <Router
children={children}
location={state.location}
navigator={history}
navigationType={state.action}
/>
}
// Provider
function Router({ children, location: locationProp, navigator}) {
const navigationContext = useMemo(() => ({ navigator }), [navigator]);
const locationContext = useMemo(() => ({ location: locationProp }), [locationProp])
return <NavigationContext.Provider value={navigationContext}>
<LocationContext.Provider
value={locationContext}
children={children}
>
</LocationContext.Provider>
</NavigationContext.Provider>
}
function useLocation() {
return useContext(LocationContext).location;
}
function useNavigate() {
return useContext(NavigationContext).navigator;
}
// const Routing = () => useRoutes(routes)
// Routing 就是 我跟你 routes 参数的内容,
// 结合当前浏览器上的 url, 返回具体是哪个 element
function useRoutes(routes) {
let location = useLocation(); // 当前路径
let currentPath = location.pathname || '/';
console.log(currentPath)
for(let i = 0; i < routes.length; i++) {
let { path, element } = routes[i];
let match = currentPath.match(new RegExp(`^${path}`));
if(match) {
return element;
}
}
return null;
}
// Routes 这个东西,
// 我就是要把所有的 Route 组件,创建成一棵树
export const Routes = ({ children }) =>
useRoutes(createRoutesFromChildren(children));
export const Route = () => {}
// 我就是要把 <Route /> 的嵌套,转成一棵树。
export const createRoutesFromChildren = (children) => {
let routes = [];
React.Children.forEach(children, (node) => {
let route = {
element: node.props.element,
path: node.props.path,
};
if(node.props.children) {
route.children = createRoutesFromChildren(node.props.children)
}
routes.push(route);
});
console.log(routes)
return routes;
}