Vue.js 构建大型应用


构建大型应用

新: 使用脚手架工具 vue-cli 可以快速地构建项目:单文件 Vue 组件,热加载,保存时检查代码,单元测试等。

Vue.js 的设计思想是专注与灵活——它只是一个界面库,不强制使用哪个架构。它能很好地与已有项目整合,不过对于经验欠缺的开发者,从头开始构建大型应用可能是一个挑战。
Vue.js 生态系统提供了一系列的工具与库,用于构建大型单页应用。这些部分会感觉开始更像一个『框架』,但是它们本质上只是一套推荐的技术栈而已 - 你依然可以对各个部分进行选择和替换。

模块化

对于大型项目,为了更好地管理代码使用模块构建系统非常必要。推荐代码使用 CommonJS 或 ES6 模块,然后使用 Webpack 或 Browserify 打包。
Webpack 和 Browserify 不只是模块打包器。两者都提供了源码转换 API,通过它可以用其它预处理器转换源码。例如,借助 babel-loader 或 babelify 代码可以使用 ES2015/2016 语法。
如果你之前没有用过它们,我强烈推荐你阅读一些教程,了解模块打包器,然后使用最新的 ECMAScript 特性写 JavaScript。

单文件组件

在典型的 Vue.js 项目中,我们会把界面拆分为多个小组件,每个组件在同一地方封装它的 CSS 样式,模板和 JavaScript 定义,这么做比较好。如上所述,使用 Webpack 或 Browserify 以及合适的源码转换器,我们可以这样写组件:

vue.jsg构建大型应用

如果你喜欢预处理器,甚至可以这么做:

vue.js构建大型应用

你可以使用 Webpack + vue-loader : https://github.com/vuejs/vue-loader 或 Browserify + vueify 构建这些单文件 Vue 组件。推荐使用 Webpack,因为它的加载器 API 提供更好的文件依赖追踪/缓存以及一些 Browserify 没有的转换功能。 最快的构建方式是使用官方出品的脚手架工具 vue-cli。你也可以在 GitHub 上查看官方的构建模板:

  • Webpack + vue-loader: https://github.com/vuejs-templates/webpack
  • Browserify + vueify : https://github.com/vuejs-templates/browserify

路由

对于单页应用,推荐使用官方库 vue-router : https://github.com/vuejs/vue-router 。详细请查看它的文档: http://vuejs.github.io/vue-router/。 如果你只需要非常简单的路由逻辑,可以这么做,监听 hashchange 事件并使用动态组件:

示例:

<div id="app">
  <component :is="currentView"></component>
</div>
Vue.component('home', { /* ... */ })
Vue.component('page1', { /* ... */ })
var app = new Vue({
  el: '#app',
  data: {
    currentView: 'home'
  }
})
// 在路由处理器中切换页面
app.currentView = 'page1'

利用这种机制也可以非常容易地配合其它路由库,如 Page.js : https://github.com/visionmedia/page.js 或 Director : https://github.com/flatiron/director

与服务器通信

Vue 实例的原始数据 $data 能直接用 JSON.stringify()序列化。社区贡献了一个插件 vue-resource,提供一种容易的方式与 RESTful APIs 配合。也可以使用任何自己喜欢的 Ajax 库,如 $.ajaxSuperAgent。Vue.js 也能很好地与无后端服务配合,如 Firebase 和 Parse。

状态管理

在大型应用中,状态管理常常变得复杂,因为状态分散在许多组件内。常常忽略 Vue.js 应用的来源是原生的数据对象—— Vue 实例代理访问它。因此,如果一个状态要被多个实例共享,应避免复制它:

var sourceOfTruth = {}

var vmA = new Vue({
  data: sourceOfTruth
})

var vmB = new Vue({
  data: sourceOfTruth
})

现在每当 sourceOfTruth 被修改后,vmAvmB 将自动更新它们的视图。扩展这个思路,我们可以实现 store 模式

var store = {
  state: {
    message: 'Hello!'
  },
  actionA: function () {
    this.state.message = 'action A triggered'
  },
  actionB: function () {
    this.state.message = 'action B triggered'
  }
}

var vmA = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

var vmB = new Vue({
  data: {
    privateState: {},
    sharedState: store.state
  }
})

我们把所有的 action 放在 store 内,action 修改 store 的状态。集中管理状态更易于理解状态将怎样变化。组件仍然可以拥有和管理它的私有状态。

vue.js构建大型应用

有一点要注意,不要在 action 中替换原始的状态对象——为了观察到变化,组件和 store 需要共享这个对象。

如果我们约定,组件不可以直接修改 store 的状态,而应当派发事件,通知 store 执行 action,那么我们基本上实现了 Flux : https://facebook.github.io/flux/ 架构。此约定的好处是,我们能记录 store 所有的状态变化,并且在此之上实现高级的调试帮助函数,如修改日志,快照,历史回滚等。

Flux 架构常用于 React 应用中,但它的核心理念也可以适用于 Vue.js 应用。比如 Vuex : https://github.com/vuejs/vuex/ 就是一个借鉴于 Flux,但是专门为 Vue.js 所设计的状态管理方案。React 生态圈中最流行的 Flux 实现 Redux 也可以通过简单的绑定 : https://github.com/egoist/revue 和 Vue 一起使用。

单元测试

任何支持模块构建系统的单元测试工具都可以。推荐使用 Karma。它有许多插件,支持 Webpack 和 Browserify。用法见它们的文档。 代码测试的最佳实践是导出组件模块的选项/函数。例如:

// my-component.js
module.exports = {
  template: '<span>{{msg}}</span>',
  data: function () {
    return {
      msg: 'hello!'
    }
  }
  created: function () {
    console.log('my-component created!')
  }
}

在入口模块中使用这个模块:

// main.js
var Vue = require('vue')
var app = new Vue({
  el: '#app',
  data: { /* ... */ },
  components: {
    'my-component': require('./my-component')
  }
})

测试这个模块:

// Jasmine 2.0 测试
describe('my-component', function () {
  // require source module
  var myComponent = require('../src/my-component')
  it('should have a created hook', function () {
    expect(typeof myComponent.created).toBe('function')
  })
  it('should set correct default data', function () {
    expect(typeof myComponent.data).toBe('function')
    var defaultData = myComponent.data()
    expect(defaultData.msg).toBe('hello!')
  })
})

Karma 的示例配置:Webpack : https://github.com/vuejs/vue-loader-example/blob/master/build/karma.conf.js , Browserify : https://github.com/vuejs/vueify-example/blob/master/karma.conf.js。 因为 Vue.js 指令是异步更新,如果想在修改数据之后修改 DOM ,应当在 Vue.nextTick 的回调中操作。

生产发布

为了更小的文件体积,Vue.js 的压缩版本删除所有的警告,但是在使用 Browserify 或 Webpack 等工具构建 Vue.js 应用时,压缩需要一些配置。

Webpack

使用插件 DefinePlugin : http://webpack.github.io/docs/list-of-plugins.html#defineplugin 将当前环境指定为生产环境,警告将在 UglifyJS 压缩代码过程中被删除。配置示例:

var webpack = require('webpack')

module.exports = {
  // ...
  plugins: [
    // ...
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]
}

Browserify

将 NODE_ENV 设置为 "production",然后运行打包命令。Vue 会自动应用 envify 并让警告块不能运行。例如:

NODE_ENV=production browserify -e main.js | uglifyjs -c -m > build.js

应用示例

Vue.js Hackernews Clone : https://github.com/vuejs/vue-hackernews 这个应用示例使用 Webpack + vue-loader 组织代码,使用 vue-router 作为路由器,HackerNews 官方的 Firebase API 作为后端。它当然不是大应用,但是它综合演示了本页讨论的概念。