5

系列文章的目录在 ? 这里

(由于 我比较懒 最近一段时间在忙其他事,系列文章拖了好久终于又更新了。。。)

什么是 vue-router ?

vue-router 官方文档

vue-router 是针对 Vue.js 开发的前端路由工具,可以很方便的开发单页应用。

单页应用

单页应用的概念其实很早就出现了,它是指在同一个页面内包含了应用的所有功能,一个页面就是一个应用,整个应用只有一个页面,是在 Web 场景下提出的一种开发方式。单页应用的特性在文末讨论,这里先说用法。

怎么在 Weex 里引入 vue-router

vue-router 是以 Vue.js 插件的形式存在的,使用前必须要引入 Vue.js。因为 WeexSDK (>= 0.9.5)中已经包含了 Vue.js Runtime,所以不需要再引入一遍 Vue.js ,只需引入 vue-router 并注册即可:

// import Vue from 'vue'
import VueRouter from 'vue-router'

Vue.use(VueRouter)

使用 vue-router 的例子

定义根视图

通过在模板中添加 <router-view> 的方式定义路由出口,路由匹配到的组件将会替换这个标签。

<!-- App.vue -->

<template>
  <router-view></router-view>
</template>

在模板中也可以包含其他标签, <router-view> 也可以是其他标签的子标签,和其他标签的用法是一样的;在 vue-router 的内部实现中,router-view 的实现就是一个普通的函数式组件。

配置路由规则

在向应用中注册 router 之前,需要先创建路由实例,并且配置路由规则。

// router.js

import VueRouter from 'vue-router'
import HomeView from 'path/to/HomeView.vue'
import AboutView from 'path/to/AboutView.vue'

Vue.use(VueRouter)

export default new VueRouter({
  routes: [
    { path: '/home',  component: HomeView  },
    { path: '/about', component: AboutView }
  ]
})

上述代码中创建了 VueRouter 的实例,并且传入了 routes 配置,当路径是 home 时,页面就会跳转到 HomeView 组件,HomeView.vue 就会渲染到 App.vue 中 <router-view> 标签的位置。同理,当路径是 about 时,页面就会跳转到 AboutView 组件。

给应用注入路由功能

想要在应用中注入路由功能,还有给入口组件添加 router 属性,使应用和路由建立联系。

import App from 'path/to/App.vue'
import router from 'path/to/router.js'

App.el = '#root'
App.router = router

new Vue(App)

注意事项

前边提到过,单页应用是在 Web 场景下提出的一种开发方式,它的具体实现依赖了 Web 平台的功能,如 Histroy API 和 URL Hash 等特性。然而 Weex 的运行环境不只是浏览器,通常是以移动端原生环境为主,这些特性在 Weex 中并不完全适用,参考《Weex 和 Web 平台的差异》

针对这些平台差异,有以下两点需要注意:

路由模式

在 vue-router 的配置项中,有一个 mode 参数可以用来指定 vue-router 的运行模式。

  • hash: 使用 URL hash 值来作路由。默认模式。

  • history: 依赖 HTML5 History API 和服务器配置。查看 HTML5 History 模式

  • abstract: 支持所有 JavaScript 运行环境,如 Node.js 服务器端。

根据平台差异可以看出,在 Weex 环境中只支持使用 abstract 模式。 不过,vue-router 自身会对环境做校验,如果发现没有浏览器的 API,vue-router 会自动强制进入 abstract 模式,所以 在使用 vue-router 时只要不写 mode 配置即可,默认会在浏览器环境中使用 hash 模式,在移动端原生环境中使用 abstract 模式。 (当然,你也可以明确指定在所有情况下都使用 abstract 模式)

编程式导航

vue-router 支持使用 <router-link> 创建导航链接,不过在其中使用了基于 DOM 事件的一些特性,这些特性在 Weex 原生环境中并不能很好的工作。所以 在 Weex 中,你必须使用编程式导航来编写页面跳转逻辑。 用法参考 Weex 官方文档

更多功能

除了上述最基本的特性以外,vue-router 还有很多很强大的功能,具体使用方法建议参考其官方文档,这里不再赘述。

实际项目中的 vue-router

weex-hackernews 项目中使用了 vue-router 做路由管理。

图片描述

路由配置

其中表头的五个一级菜单(Top 、New 、Show 、Ask 、Job),就对应了五个不同的路由路径,也对应了五个列表组件。除此之外,还有列表页、评论页、用户信息页也都对应了单独的路由路径。具体配置在 src/router.js 中,如下所示:

export default new Router({
  routes: [
    { path: '/top', component: createStoriesView('top') },
    { path: '/new', component: createStoriesView('new') },
    { path: '/show', component: createStoriesView('show') },
    { path: '/ask', component: createStoriesView('ask') },
    { path: '/job', component: createStoriesView('job') },
    { path: '/article/:url(.*)?', component: ArticleView },
    { path: '/item/:id(\\d+)', component: CommentView },
    { path: '/user/:id', component: UserView },
    { path: '/', redirect: '/top' }
  ]
})

其中 article 、item 和 user 三个配置项中用到了动态路由匹配,把所有同类路由都映射到了同一个组件上,组件结构相同,但是参数是不同的(url 不同或者 id 不同)。

在最后还配置了路由重定向,将默认根路由 / 重定向到了 /top,默认加载“最热”文章列表。

在应用中注册路由

若要应用组件和路由之间建立联系,需要给入口组件注入 router 属性。代码在 src/entry.js 中。

new Vue(Vue.util.extend({ router }, App))

router.push('/')

代码在创建 Vue 实例前在 App 上添加了 router 属性,使得应用组件中都能通过 this.$router 获取到路由数据。在创建实例后,手动调用 router.push('/') 跳转到根视图。

同步 Vuex 和 vue-router 的状态

使用 vuex-router-sync 可以很方便地同步 Vuex 和 vue-router 的状态,这个步骤并不是必须的,但是可以简化代码的使用。实际代码在 src/entry.js 中,如下所示:

sync(store, router)

代码的效果就是注册了 store.state.route 这个变量,使得在 Vuex 的 Store 中可以获取到 route 对象,这在一些比较复杂的大型应用中可能会用到。

由于在根组件中已经注入了 router 属性,在所有组件中也都可以通过 this.$router 获取到当前的路由状态。

Weex 适合写单页应用吗 ?

以下是我个人的观点,不代表任何人,也不代表 Weex。

不考虑技术细节,从实际应用的角度聊一下我对“单页应用”的看法。

单页应用也有不少的优势和缺陷:

  • 优势:页面无需刷新;可以实现更好的过渡效果;组件复用;状态共享。

  • 缺陷:初次加载白屏时间长;不利于 SEO;有大量全局状态;技术方法相对复杂,有学习成本。

大部分特性都已经有共识,我也不再展开对比,这里着重讨论一下在 移动端Weex 平台 中使用单页应用的情况。

页面间状态的共享与隔离

单页应用运行在同一个 javascript runtime 里,也就是说页面的状态都是共享的,环境变量也是相同的,这一点其实是有很多隐患。在移动应用中,单页应用不仅耗费内存,也很容易发生内存泄露。

所有的 Weex 页面,无论是基于 Vue 还是 Rax,都共用一个 Weex Runtime,其中的 js 引擎也只初始化一次,除非重新初始化 Weex SDK(应用重启),所有页面对环境的操作痕迹都将保留。在这种情景下,页面间状态的隔离就尤为重要,共享的全局状态很可能会成为内存泄露的元凶。

换句话说,如果某个页面创建了一个临时的全局变量,但是在页面退出后没有清除,这个变量将一直保留在内存中;如果某个页面在全局状态上挂载了某个属性,但是页面退出后没有断开连接,这个属性也会一直保留在全局状态中。

原生应用本身就是多页的场景,页面间状态的隔离比共享更重要一些。

资源加载和缓存

单页应用要把所有页面用到资源(至少是脚本)提前打包在一起,一次性全部发到客户端,首次打开的网络耗时会比较久,容易导致长时间白屏,很难递进的加载资源,无法做到“先加载有用的”。如果有多个单页应用都需要用到某些页面或者某个组件,是很难复用的,也很难加缓存,难不成整个移动应用都写成一个大“单页”的吗? Web 上或许可以考虑,但是原生应用里十分不建议这样做。应用都规模越大,这些缺陷就越明显。

更何况现在 HTTP/2 逐渐普及,有了多路复用,把资源粒度划分的细一些更有利于浏览器的加载。还有 prefetch 和 preload 这种特性可以提升资源加载的效率,Weex 也在考虑支持。浏览器也逐渐开始支持 ES6 module,可以直接通过 <script type="module"> 的方式引入 ES6 模块,代码的管理和加载可能会以 模块 为单元,而不是页面。

从一系列技术趋势来看,现在有很多技术方案都讲究将资源细分,恨不得使用 code split 把代码细分到组件级别(“打包”是一个暂时性的让人又爱又恨的工作)。

Histroy API 和 hash

单页应用在技术上基本上都是基于 Histroy API 或 hash 链接实现,即使不直接依赖,也会模拟这方面的行为(polyfill)。前边也提到过 Weex 和 Web 平台有差异, Histroy API 和 hash 这些都是浏览器中的概念,和 Weex 里不完全对应。

使用 Weex 开发的是原生应用,页面栈的管理使用的也是原生的特性,没有 Histroy API 但是有 navigator 模块 可以实现页面的“前进”和“后退”等操作。其实我觉得原生开发中 Navigator 的概念比 web 上 History API 设计的更完善一些(由于多端行为有差异,Weex 只暴露了部分通用功能),可操作性也更强。至于 hash,移动端不会轻易暴露页面 url,更不用说 hash 了。

一句话总结: Weex 是跨平台的,Histroy API 是针对 Web 平台设计的,未必适合原生开发。

小结

技术本身是客观的,有各自的适用场景。weex-hackernews 项目本身的页面也不多,多个页面之间的确会共用一些数据;而且本来这就是一个范例项目,为了展示能力和用法的,所以我用了 vue-router。在你的应用中是否应该引入 vue-router,需要结合应用自身的特性和需求具体分析。


Hanks10100
1.5k 声望372 粉丝