前端路由演变史
随着Web应用的需求越来越复杂,前端的路由也经历了不断地演变。在早期的Web应用中,所有内容都是基于Web页面展开的,任何的更新、操作都需要访问新的页面,网络和带宽都不够强劲的年代也就可以接收此种方式处理。但是,这种做法的一个显著缺点就是用戶端出现了多个页面切换的情况,这个体验非常差。
在后来的Ajax出现之后,前端的路由演进出现了一个重大的突破。此时文档对象模型(DOM)已经足够成熟,可以使用js通过Ajax请求数据并且动态修改页面内容,但还存在一个问题,我们怎么管理和保证每个页面视图的状态和链接。因此,前端路由应运而生。
第一个流行的前端路由解决方案是 Backbone.js。Backbone.js使用锚点(#)的方式作为路由器,目的是为了避免发送到服务器的请求,然而这种方式并不是真正的路由。因为锚点并没有涉及到HTTP的状态码,因此无法实现真正的路由。
随着HTML5的推出,新的API浮现了,包括新的 History API,它让开发者可以操作浏览器的会话历史,包括添加、修改和删除历史记录项,并且可以通过此API保证路由器与服务器的通讯是正确的。
路由的角色和必要性
路由的核心就是根据现有的URL路径和请求参数,从而确定呈现给用户的页面内容和数据。在Spa应用中,路由的职责在于把URL(统一资源定位符)映射到视图层的组件上,使得页面能够响应用户的操作。
React-router源码分析
React-router是React.js上应用最广泛的第三方路由库。它采用了组件化的设计,提供了一些路由组件,例如 Route、Switch 和 Redirect,还可以自定义路由组件。
下面我们来看一下Route的源码实现,Route是一个表示一条匹配路径的路由组件。路由参数通过 props 传递给其子组件,其实现原理就是在组件的 render 函数中判断当前 URL 是否匹配路由表达式,如果匹配将其它参数一起作为 props 传递给其子组件。
function matchPath(pathname, options) {
const { path, exact = false } = options;
const match = new RegExp(`^${path}`).exec(pathname);
if (!match) return null;
if (exact && match[0] !== pathname) return null;
return { path, url: match[0], params: {} };
}
function Route({ path, exact, component: Component, ...rest }) {
const location = useContext(LocationContext);
const match = matchPath(location.pathname, { path, exact });
if (!match) return null;
return <Component {...rest} match={match} location={location} />;
}
**
上面代码主要是通过调用matchPath函数,根据location.pathname是否匹配路由表达式参数,来判断是否返回组件或者是返回null。其中matchPath函数,是一个简单的正则匹配,返回匹配结果(返回匹配路径和传递的参数)或者null。
路由优化
对于大型复杂应用程序,优化路由是至关重要的。一些优化技巧如下:
1. 嵌套路由
嵌套路由指的是,在父子页面中都有各自的路由,并且各自的路由不会影响到对方页面的路由。例如,React-router提供了Route组件的children属性,可以实现路由层叠嵌套,实现复杂的应用路由。
<Route path="/about" component={About}>
<Route path="/about/team" component={Team} />
<Route path="/about/contact" component={Contact} />
</Route>
**
2. 懒加载
懒加载(也称惰性加载)是指当组件被需要时再动态加载,而不是在初始渲染时就把所有组件加载出来。这样可以减轻页面的庞大负担,提高页面的加载速度,同时也降低了组件之间的耦合。React-router提供了React.lazy方法的使用,可以实现懒加载路由组件。
const About = lazy(() => import('./About'));
const Team = lazy(() => import('./Team'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/team" component={Team} />
<Route component={NotFound} />
</Switch>
</Suspense>
</Router>
);
}
**
3. 代码分割
代码分割通过将应用程序代码拆分为更小的代码片段,使得页面的初始化速度更快并且更易于缓存。React-router提供了React.lazy和Suspense组合使用,可以实现在保证可读性的前提下,将代码拆分到几个单独的文件中。
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
const Team = lazy(() => import('./Team'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Switch>
<Route exact path="/" component={Home} />
<Route path="/about" component={About} />
<Route path="/team" component={Team} />
<Route component={NotFound} />
</Switch>
</Suspense>
</Router>
);
}
**
4. 缓存页面状态
通常,在浏览器的前进和后退操作时,页面状态会丢失。因此,在大型应用程序中,为了避免用户在导航时丢失其操作和数据,需要缓存页面的状态。React-router提供了Prompt组件,可以控制用户在离开页面时的提示信息,从而帮助我们实现页面状态的缓存。
import { Prompt } from 'react-router-dom';
function Checkout() {
const [isBlocking, setIsBlocking] = useState(false);
function handleCheckout() {
setIisBlocking(true);
api.checkout();
}
return (
<div>
<button onClick={handleCheckout}>Checkout</button>
<Prompt
when={isBlocking}
message="Are you sure you want to leave without finishing the checkout?"
/>
</div>
);
}
**
总结
前端路由的演变历程越来越成熟,路由在现代Web应用中扮演着核心的角色。React-router是React.js上应用最广泛的第三方路由库,提供了可定制化的路由组件化设计,还支持多种路由优化实践,包括懒加载、代码分割、缓存页面状态等。