前言 什么是spa?单页和多页的区别?
传统的多页应用: 改变url请求对应的html资源
单页: 单页一次请求所有的资源, 首屏加载会比较慢,后续通过路由切换组件,只需要请求数据
优缺点:
- 单页首屏渲染耗时多,不利于SEO,页面切换流畅,前后端分离,用户体验好,
- 多页利于SEO,每次页面切换都要请求资源会对服务器有一定压力,影响用户体验。
单页在经过分片和路由懒加载后性能会好过多页,因此现在的前端工程化项目一般都是使用单页,当项目体量非常庞大的时候再考虑拆分多页。
1. React Router v6
React router官网已经声明v5已经不再维护,那么v6版本相比较v5有哪些变化呢?
作为React生态的工具之一,React Route紧跟React函数组件的步伐,在V6中不再使用 withRouter等class组件的API,所有的API都是以hooks的方式提供。
import { BrowserRouter,Route,Routes,useParams} from ‘react-router-dom’;
安装
yarn add react-router-dom@6
基础使用
function App() {
return (
<BrowserRouter>
<Routes>
<Route path='/'>
<Route index element={<Home/>}/>
<Route path='login' element={<Login/>}/>
<Route path='about/:id' element={<About/>}/>
<Route path='*' element={<NotFound/>}/>
<Route/>
<Route>
</Routes>
</BrowserRouter>
);
}
function Home(){
return <span>Home</span>
}
function Login(){
return <span>Login</span>
}
function About(){
const { id } = useParams();
return <span>About: {id}</span>
}
function NotFound(){
return <span>NotFound</span>
}
动态路由
function App() {
return (
<BrowserRouter>
<Routes path='/'>
<Route path='about/:id' element={<About />} />
<Route />
</Routes>
</BrowserRouter>
);
}
function About() {
// 参数获取
const { id } = useParams();
return <span>About:{id}</span>
}
嵌套路由
function App() {
return (
<BrowserRouter>
<Routes>
<Route path='/' element={<A />}>
<Route index path='home' element={<Home />} />
<Route path='about' element={<About />}>
<Route path=':id' element={<Info />} />
<Route index path='dashboard' element={<Dashboard />} />
</Route>
</Route>
</Routes>
</BrowserRouter>
);
}
function A() {
return <span>
app
<Outlet />
</span>
}
function Home() {
return <span>Home</span>
}
function About() {
return <span>About
<Outlet />
</span>
}
function Info() {
return <span>info</span>
}
function Dashboard() {
return <span>Dashboard</span>
}
使用数据渲染路由
const router = createBrowserRouter([
{
path: '/',
element: <Home />,
children: [
{
path: 'login',
element: <Login />,
index:true
},
{
path: 'about',
element: <About />
}
]
}
])
function App() {
return (
<RouterProvider router={router}/>
);
}
const router = createBrowserRouter(createRoutesFromElements(
<Route path='/' element={<Home/>}>
<Route path='/login' element={<Login/>}/>,
<Route path='/about' element={<About/>}/>,
</Route>
))
function App() {
return (
<RouterProvider router={router}/>
);
}
使用loader加载数据
import { Outlet, createBrowserRouter, RouterProvider, useLoaderData } from 'react-router-dom';
const requestUser = function () {
return Promise.resolve({
id: '001',
name: 'jack',
account: '123',
});
};
async function loader() {
const user = await requestUser();
return user;
}
const router = createBrowserRouter([
{
path: '/',
element: <Home />,
children: [
{
path: 'login',
element: <Login />,
loader,
}
]
}
])
function App() {
return (
<RouterProvider router={router} />
);
}
function Home() {
return <span>Home
<Outlet />
</span>
}
function Login() {
//获取数据
console.log(useLoaderData()); //{id: '001', name: 'jack', account: '123'}
return <span>Login</span>
}
重定向
const requestUser = function () {
return Promise.resolve({
id: '001',
name: 'jack',
account: '123',
});
};
async function loader() {
const user = await requestUser();
if(!user.id){
return redirect('/')
}
return user;
}
link、navLink
navLink和link类似,唯一的区别是navLinkr提供了link状态相关的属性,可以用来做条件渲染和样式的渲染
function App() {
return (
<BrowserRouter>
<Routes>
<Route path='/'>
<Route index element={<Home />} />
<Route path='about' element={<About />} />
</Route>
</Routes>
</BrowserRouter>
);
}
function Home() {
return <span>
Home
<Link to='about'>about</Link>
</span>
}
function About() {
return <span>
About
<Link to='..'>home</Link>
</span>
}
function Home() {
return <span>
Home
<NavLink to='about'>
{({ isActive, isPending }) => {
return isActive ? <span>active </span> : <span>not-active</span>
}}
</NavLink>
</span>
}
function About() {
return <span>
About
<NavLink to='..'>
{({ isActive, isPending }) => {
return isActive ? <span>active </span> : <span>not-active</span>
}}
</NavLink>
</span>
}
使用navigate进行跳转
function Home() {
return <span>
<Navigate to='/about'></Navigate>
</span>
}
function Home() {
const navigate = useNavigate();
return <div>
Home
<button onClick={()=>navigate('about')}>to about</button>
</div>
}
错误处理
<Route
path="/invoices/:id"
// if an exception is thrown here
loader={loadInvoice}
// here
action={updateInvoice}
// or here
element={<Invoice />}
// this will render instead of `element`
errorElement={<ErrorBoundary />}
/>;
function Invoice() {
return <div>Happy {path}</div>;
}
function ErrorBoundary() {
let error = useRouteError();
console.error(error);
// Uncaught ReferenceError: path is not defined
return <div>Dang!</div>;
}
useParams、useLocation useMatch
function About() {
const location = useLocation(); // location对象
const { id } = useParams();
return <span>
About:{id}
</span>
}
2. v5升级v6的一些问题
v5中使用withRouter获取location和history的参数,v6中没有withRouter怎么办呢?
import {
useLocation,
useNavigate,
useParams,
} from "react-router-dom";
function withRouter(Component) {
function ComponentWithRouterProp(props) {
let location = useLocation();
let navigate = useNavigate();
let params = useParams();
return (
<Component
{...props}
router={{ location, navigate, params }}
/>
);
}
return ComponentWithRouterProp;
}
取消了v5中route的children和redner属性,使用element属性接受react元素,自定义的属性可以直接添加到元素上。
<Route path=":userId" element={<Profile animate={true} />} />
取消了switch
取消了正则表达式