# 构建大型应用
Vue.js 的设计思想是专注与灵活——它只是一个界面库,不强制使用哪个架构。它能很好地与已有项目整合,不过对于经验欠缺的开发者,从头开始构建大型应用可能是一个挑战。
Vue.js 生态系统提供了一系列的工具与库,用于构建单页应用。但是它们只是推荐而已。
## 模块化
对于大型项目,为了更好地管理代码使用模块构建系统非常必要。推荐代码使用 CommonJS 或 ES6 模块,然后使用 [Webpack](http://webpack.github.io/) 或 [Browserify](http://browserify.org/) 打包。
Webpack 和 Browserify 不只是模块打包器。两者都提供了源码转换 API,通过它可以用其它预处理器转换源码。例如,借助 [babel-loader](https://github.com/babel/babel-loader) 或 [babelify](https://github.com/babel/babelify) 代码可以使用 ES2015/2016 语法。
如果你之前没有用过它们,我强烈推荐你阅读一些教程,了解模块打包器,然后使用最新的 ECMAScript 特性写 JavaScript。
## 单文件组件
在典型的 Vue.js 项目中,我们会把界面拆分为多个小组件,每个组件在同一地方封装它的 CSS 样式,模板和 JavaScript 定义,这么做比较好。如上所述,使用 Webpack 或 Browserify 以及合适的源码转换器,我们可以这样写组件:
![](https://box.kancloud.cn/2016-01-03_5688e1a911b7b.png)
如果你喜欢预处理器,甚至可以这么做:
![](https://box.kancloud.cn/2016-01-03_5688e1a934631.png)
你可以使用 Webpack + [vue-loader](https://github.com/vuejs/vue-loader) 或 Browserify + [vueify](https://github.com/vuejs/vueify) 构建这些单文件 Vue 组件。推荐使用 Webpack,因为它的加载器 API 提供更好的文件依赖追踪/缓存以及一些 Browserify 没有的转换功能。
在 GitHub 上有一些构建示例:
* [Webpack + vue-loader](https://github.com/vuejs/vue-loader-example)
* [Browserify + vueify](https://github.com/vuejs/vueify-example)
## 路由
对于单页应用,推荐使用[官方库 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](https://github.com/vuejs/vue-resource),提供一种容易的方式与 RESTful APIs 配合。也可以使用任何自己喜欢的 Ajax 库,如 `$.ajax` 或 [SuperAgent](https://github.com/visionmedia/superagent)。Vue.js 也能很好地与无后端服务配合,如 Firebase 和 Parse。
## 状态管理
在大型应用中,状态管理常常变得复杂,因为状态分散在许多组件内。常常忽略 Vue.js 应用的来源是原生的数据对象—— Vue 实例代理访问它。因此,如果一个状态要被多个实例共享,应避免复制它:
```
var sourceOfTruth = {}
var vmA = new Vue({
data: sourceOfTruth
})
var vmB = new Vue({
data: sourceOfTruth
})
```
现在每当 `sourceOfTruth` 被修改后,`vmA` 与 `vmB` 将自动更新它们的视图。扩展这个思路,我们可以实现 **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 的状态。集中管理状态更易于理解状态将怎样变化。组件仍然可以拥有和管理它的私有状态。
![状态管理](https://box.kancloud.cn/2016-01-03_5688e1a951e81.png)
有一点要注意,不要在 action 中替换原始的状态对象——为了观察到变化,组件和 store 需要共享这个对象。
如果我们约定,组件不可以直接修改 store 的状态,而应当派发事件,通知 store 执行 action,那么我们基本上实现了 [Flux](https://facebook.github.io/flux/) 架构。此约定的好处是,我们能记录 store 所有的状态变化,并且在此之上实现高级的调试帮助函数,如修改日志,快照,历史回滚等。
Flux 架构常用于 React 应用中。借助于响应系统,Flux 的核心思想能非常容易地用 Vue.js 实现。注意我们这里的演示只是为了介绍概念,简单的情况完全不需要这么做,应根据应用的需求调整这个模式。
## 单元测试
任何支持模块构建系统的单元测试工具都可以。推荐使用 [Karma](http://karma-runner.github.io/0.12/index.html)。它有许多插件,支持 [Webpack](https://github.com/webpack/karma-webpack) 和 [Browserify](https://github.com/Nikku/karma-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!')
})
})
```
因为 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](https://github.com/hughsk/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 作为后端。它当然不是大应用,但是它综合演示了本页讨论的概念。
- 教程
- 起步
- 概述
- Vue 实例
- 数据绑定语法
- 计算属性
- Class 与 Style 绑定
- 条件渲染
- 列表渲染
- 方法与事件处理器
- 表单控件绑定
- 过渡
- 组件
- 深入响应式原理
- 自定义指令
- 自定义过滤器
- 混合
- 插件
- 构建大型应用
- 对比其它框架
- API
- 示例
- Markdown 编辑器 Example
- GitHub 提交 Example
- Firebase + 验证 Example
- 表格组件 Example
- 树状视图 Example
- SVG 图形 Example
- 模态组件 Example
- Elastic Header Example
- 自定义指令 Example
- TodoMVC Example
- HackerNews 克隆 Example