Recat的路由与项目实践

87 阅读4分钟

前端路由演变史

随着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上应用最广泛的第三方路由库,提供了可定制化的路由组件化设计,还支持多种路由优化实践,包括懒加载、代码分割、缓存页面状态等。