阅读本文需要 5 分钟,编写本文耗时 1 小时
经过一周的努力,我们已经完成了前端框架的工程化的基础部分,完成了一个能跑的前端框架工程。接下来我们会将需求实现转移到项目交付本身相关的内容上来。
SPA
SPA 就是单页面 Web 应用,顾名思义就是只有一个页面,所有的用户访问都在这个页面中进行,只有一开始的时候加载了需要的 javascript 和 css 之后。(不考虑按需加载优化等情况的时候)用户的操作都将在当前页面中完成,有前端实现的 javascript 控制整个页面的交互逻辑。
有个比较容易分辨是否是 SPA 的方法,是当你在页面中发生“页面跳转”的时候,页面是否重新刷新,重新加载了 js。如果是按需引入添加的 js 请求,是在你现有的基础上而外发起的请求,原来已加载的请求不会刷新。
为什么现在基本上都选择使用 SPA
除了部分比较传统的网站,有强烈 SEO 优化需求的项目之外,现在前端首选几乎都是 SPA。因为它除了首次加载页面耗时之外,其他的用户交互体验都比多页应用要快,部分内容的更改,不需要整个页面的刷新。前端的渲染也不用占用服务器资源。所以从用户体验和成本上来说,SPA 都是 Web 2.0 之后较好的选择。
react-router
React Router 是 React 的一个功能齐全的客户端和服务器端路由库,它是一个用于构建用户界面的 JavaScript库。React Router 可以运行在 React 运行的任何地方;在web上的实现是 react-router-dom。
react-router@6 api
react-router@6 与 react-router@5 存在一定的差异,包含导出组件和导出 api 的使用,这在掘金上有很多的文章介绍,这里就不做过多的展开,我只罗列了本次文章中我们会使用到的几个简单的 api。
导出 | 作用 | 说明 |
<Routes> |
一组路由 | 所有子路由都用基础的 Router 来表示,必须写 |
<Route> |
基础路由 | Route 是可以嵌套的 |
<Link> |
导航组件 | 在实际页面中跳转使用 |
<Outlet/> |
自适应渲染组件,你可以把它当作之前的 children | 根据实际路由 url 自动选择组件 |
useLocation |
返回当前的location 对象 | - |
react-router@6 更新最让我惊喜的是 Outlet
组件,他还有一个配套的 useLocation
hooks 供用户使用,用它来实现 keepalive 功能,比之前的实现要优雅简单的多,明天我们的主题就是手写一个 keepalive 组件。
在项目中配置路由
安装依赖
cd examples/app pnpm i react-router react-router-dom 复制代码
记得安装两个依赖 react-router
和 react-router-dom
,使用的时候,我们只从 react-router-dom
中使用它导出的 API。 react-router
是 react-router-dom
的核心包,所以它是必须安装的。
在项目中使用
examples/app/src/index.ts
首先我们先改一下我们页面的渲染入口组件。
const App = () => { return (); } const root = ReactDOM.createRoot(document.getElementById('malita')); - root.render(React.createElement(Hello)); + root.render(React.createElement(App)); 复制代码
编写我们的 app 组件
const App = () => { return ( <HashRouter> <Routes> <Route path='/' element={<Layout />}> <Route path="/" element={<Hello />} /> <Route path="/users" element={<Users />} /> <Route path="/me" element={<Me />} /> </Route> </Routes> </HashRouter> ); } 复制代码
仔细看上述的实现,我们使用 Route 嵌套了 Route 组件,这将会导致在渲染内层 Route 组件是会用外层的 Route 包裹,说起来有一点绕,简单的说就是常常被提到的 layout 功能。
实现 layout 页面,layout 就是多个页面的公共部分的提取,这包括公共部分页面也包括公共逻辑和数据流。
比如,你可以编写一个仅仅处理逻辑的 layout 页面。
import { Outlet, useLocation } from 'react-router-dom'; const Layout = () => { const { pathname } = useLocation(); console.log(pathname); return (<Outlet />); } 复制代码
下面我们简单的编写一个我们本次教程需要的 layout 吧。 就是返回被嵌套的内部 Route 的渲染,简单理解就是之前的
{children}
。
import { Outlet, useLocation } from 'react-router-dom'; import { Page, Content, Header } from '@alita/flow'; const Layout = () => { const { pathname } = useLocation(); return (<Page> <Header>当前路由: {pathname}</Header> <Content> <Outlet /> </Content> </Page>) } 复制代码
简单的编写一下三个页面的组件,值得注意的是,我们使用 Link
组件来做页面跳转,他的功能就相当于元素上添加一个 onClick
事件,响应的是 history.push(to);
。
const Hello = () => { const [text, setText] = React.useState('Hello Malita!'); return ( <> <p onClick={() => { setText('Hi!') }}> {text} </p> <Link to='/users'>Users</Link> </>); }; const Users = () => { return ( <> <p> Users </p> <Link to='/me'>Me</Link> </>); }; const Me = () => { return (<><p> Me </p> <Link to='/'>go Home</Link></>); }; 复制代码
上面的代码对于对 React Router 比较熟悉的朋友应该看一眼就清楚了,对于 React Router 不熟悉的朋友感兴趣的话可以再过一下官网的新手教程。
因为在 malita 的设计中,我们会弱化掉这个概念,后续会沿用 umi 中的“文件即路由”的思路,毕竟这是我最喜欢的一个特性,所以你没有掌握 react-router 的知识,也可以正常继续后续的开发工作。
效果展示
仔细观察上面的操作,我们在进行页面切换的时候,页面并没有发生刷新。
感谢阅读,今天的内容比较简单,仅仅作为对 react-router 的一个简单应用,同时作为明天 keepalive 实现的一个前置预告。因为路由部分是我们实现 keepalive 的基础。如果你对 react 状态保持感兴趣,在 react 官方支持这个功能之前,有强烈需求的,可以关注一下明天的文章内容。