如何编写一个前端框架之七-客户端路由(译)

本系列一共七章,Github 地址请查阅这里,原文地址请查阅这里javascript

前端路由

这是编写前端框架系列的最后一章。本章,我将会讨论前端路由和后端路由的不一样以及为何他们应被区别对待。html

网页上的路由

网页无非是后端渲染,前端渲染或者二者混合渲染。无论怎样,一个半复杂的网页不得不处理路由。前端

对于后端渲染,路由是由后端处理的。当 URL 路径改变或者请求参数改变的时候会输出一个新的页面,这对于传统网页是完美的解决方案。然而网页程序常常须要保持当前用户的状态,这在海量的服务端渲染的页面之间是很难维护的。java

客户端框架经过预读取程序和在存储的页面间切换并保持状态来解决这些问题。前端路由的实现与服务器端的路由很是类似。惟一的区别是它直接从前端而不是后端获取资源。本篇文章中,我将会解释为何这二者须要稍微处理得有些不一样。react

由后端启发的路由

许多前端路由库都是由后端启发的。git

他们只是在 url 改变的时候运行适当的路由处理程序,从而启动和渲染所需的组件。前端和服务端的结构是相似的,惟一的区别便是处理函数所作的事情。github

为了演示其类似性,你能够在以下的代码中发现服务端 Express 框架,前端代码 page.js 路由和 React 相同的路由代码片断。shell

// Express
app.get('/login', sendLoginPage)
app.get('/app/:user/:account', sendApp)
复制代码
// Page.js
page('/login', renderLoginPage)
page('/app/:user/:account', renderApp)
复制代码
<!-- React -->
<Router>
  <Route path="/login" component={Login}/>
  <Route path="/app/:user/:account" component={App}/>
</Router>
复制代码

React 隐藏了一些 JSX 背后的逻辑,可是它们作的都是一样的事情,在引入动态参数以前,它们都工做得很完美。express

在上面的例子中,一个用户可能有多个帐号而且当前帐户能够随意更改。若是在 App 页面改变帐户名,对应的处理程序为新的帐户名重启或者重发相同的 App 组件 - 然而其实只须要更改现存组件里面的一些数据便可。编程

对于虚拟 DOM 解决方案这只是小菜一碟,由于它们会查找 DOM 的差别,而后只更新须要的部分 - 可是对于传统框架,这意味着更多没必要要的工做。

处理动态参数

当参数改变的时候从新渲染整个页面是我想要避免的。为了解决这个问题,我先从动态参数中分离出路由。

在 NX 中,路由会决定显示哪一个组件或视图,而后进入到 URL 路径名。动态参数控制当前页面显示的数据,他们老是在查询参数里面。

这意味着 /app/:user/:account 将会转换为 /app?user=userId&account=accountId 。他略显冗长,但更加清晰,而且它容许我把客户端路由分为页面路由和参数路由。前者在 app 壳中导航,然后者在数据壳中进行导航。

app 壳

你或许会熟悉 app 壳模型,它在谷歌的 PWA 程序中获得推广。

app 壳是一个用来驱动用户界面的最小化的 HTML,CSS 和 JavaScript。

在 NX 中,路径路由负责在 app 壳中导航。一个简单的路由结构以下所示。

<router-comp>
	<h2 route="login"/>Login page</h2>
	<h2 route="app"/>The app</h2>
</router-comp>
复制代码

这和以前的例子相似 - 特别是和 React 例子 - 可是这里有一个主要的区别。它没有处理 useraccount 参数。相反,它只是在空的app shell中导航。

这让它成为一个很是简单的树遍历问题。路由树被遍历 - 基于 URL 路由 - 而后它以它的方式显示组件。

以上图表解释了当前视图是如何切换为 /settings/profile 地址的。你能够找到以下相应的代码。

nx.components.router()
  .register('router-comp')
复制代码
<a iref="home">Home</a>
<a iref="settings">Settings</a>
<router-comp>
  <h2 route="home" default-route>Home page</h2>
  <div route="settings">
    <h2>Settings page</h2>
    <a iref="./profile">Profile</a>
    <a iref="./privacy">Privacy</a>
    <router-comp>
      <h3 route="profile" default-route>Profile settings</h3>
      <h3 route="privacy">Privacy settings</h3>
    </router-comp>
  </div>
</router-comp>
复制代码

这个示例展现了拥有默认和相对路由的嵌套路由结构。如你所见,只用 HTML 配置至关的简单,而且它和大多数的文件系统运行原理相似。你能够在它里面使用绝对路径 home 和相对路径 ./privacy 连接来导航。路由片断运行效果以下图所示。

这个简单的结构能够被滥用来建立强大的模式。一个例子是并行路由,指的是同一时间遍历多个路由树。侧边菜单栏和 NX docs page 的内容都是这样工做的。它有两个并行的内嵌路由,同时改变侧边导航和页面的内容。

数据壳

和 app 壳不一样,'data shell' 并非一个炒做的术语。事实上,它只供我使用,它指的是用动态参数池来驱动数据流。它不是更改当前页面,它只更新页面的数据。改变当前页面一般会改变参数池,但改变参数池的参数不会致使页面刷新。

一般状况下,数据壳是由一组原始值-还有当前页面所组成,它表示当前程序的状态。所以,它能够用来保存加载和分享状态。为了达到这个目的,它必须在 URL,localStorage 或者浏览器历史中体现-这使它实质上是全局的。

NX 众多组件之中的控制组件能够经过声明性配置链接到参数池,它会决定参数如何和组件的状态,URL,浏览器历史和网页存储进行交互。

nx.components.control({
  template: require('./view.html'),
  params: {
    name: { history: true, url: true, default: 'World' }
  }
}).register('greeting-comp')
复制代码
<p>Name: <input type="text" name="name" bind/></p>
<p>Hello @{name}</p>
复制代码

以上示例代码建立了一个组件,使得 name 属性与 URL 和浏览器历史保持同步。你能够在以下看到效果。

多亏了基于 ES6 代理的透明反射,同步是无缝的。你能够书写 vanilla JavaScript,全部的东西都会在后台按需双向绑定。下图给出了一个这方面的深刻概述。

简单的声明式的语法鼓励开发者在编程以前花几分钟时间设计页面的网页整合。并非全部的参数都应该进入 URL 或者当页面改变的时候添加一个新的历史记录项。有大量的不一样状况,每种状况都得进行适当配置。

  • 一个简单的文本过滤器应该是一个 url 参数,由于它应该能够与其余用户共享。
  • 一个帐户 id 应该是一个 urlhistory 参数,由于目前的帐户应该是可分享,而且改变它是足以用来添加一个新的历史记录项。
  • 一个视觉选项应该是一个可持久的参数(保存在本地存储中),由于它应该为每一个用户持久化,而且不该该被共享。

这只是一些可能的设置。只须要最少的努力,你就能够真正地让这些参数完美地适用于你的使用场景。

总结

路径路由和参数路由是互相独立的,他们被设计成能够很好地一块儿协做。路径路由导航到 app 壳中的预期页面,参数路由接收而且并管理状态和数据外壳。

不一样页面中的参数池也许会不同,因此在 JavaScript 和 HTML 中都有一个显式的 API 来改变当前页面和参数。

<a iref="newPage" $iref-params="{ newParam: 'value' }"></a>

comp.$route({
  to: 'newPage',
  params: { newParam: 'value' }
})
复制代码

在这个基础上,NX 自动在激活的连接上面添加 active 样式类名,这样你可使用配置参数里面的 options 配置全部的常见路由功能好比参数继承和路由事件。

打开 routing docs 来查看更多的功能。

前端路由示例

以下示例演示了参数路由与反应性数据流的结合。这是一个彻底能够工做的 NX app。只要把以下代码拷贝进一个空的 HTML 文件中, 而后在现代浏览器中打开它来试用。

<script src="https://www.nx-framework.com/downloads/nx-beta.2.0.0.js"></script>

<script>
nx.components.app({
  params: {
    title: { history: true, url: true, default: 'Gladiator' }
  }
}).use(setup).register('movie-plotter')

function setup (comp, state) {
  comp.$observe(() => {
    fetch('http://www.omdbapi.com/?r=json&t=' + state.title)
      .then(response => response.json())
      .then(data => state.plot = data.Plot || 'No plot found')
  })
}
</script>

<movie-plotter>
  <h2>Movie plotter</h2>
  <p>Title: <input type="text" name="title" bind /></p>
  <p>Plot: @{plot}</p>
</movie-plotter>
复制代码

状态中的 title 属性会自动与 URL 和 浏览器历史保持同步。传入函数中的 comp.$observe 会被监听,当标题改变的时候,它会自动抓取对应的电影情节。这会建立一个强大的完美地和浏览器结合的反应式数据流。

这个 app 没有展现路径路由。想看更多的完整示例请查看 intro appNX Hacker News clone 或者 path routingparameter routing 文档页面。都有可编辑示例。

打个广告 ^.^

今日头条招人啦!发送简历到 likun.liyuk@bytedance.com,便可走快速内推通道,长期有效!国际化PGC部门的JD以下:c.xiumi.us/board/v5/2H…,也可内推其余部门!

本系列一共七章,Github 地址请查阅这里,原文地址请查阅这里

相关文章
相关标签/搜索