转载

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

当创建一个会部署到浏览器环境中的应用程序时,必须解决我称之为 应用程序/视图状态同步 的问题:您需要控制前端视觉结果(DOM 的形状),还需要处理应用程序数据。DOM 与数据之间的交互会随着应用程序的增长而变得越来越复杂,所以自行管理该交互会变成一种容易出错的做法。理想情况下,您会将这些工作转交给第三方框架来负责。您希望投入尽可能少的精力来解决应用程序/视图状态同步问题。

在您可选择的几个 JavaScript 框架中,新一代 Vue.js 框架非常关注如何用极少的外部特性来解决应用程序/视图状态同步问题。Vue.js 是一个面向对象、数据驱动的 DOM 管理系统 — 或者更简洁地说,就是一个对象-DOM 绑定系统。Vue.js 负责在浏览器 DOM 中创建应用程序的数据清单。

这个由两部分组成的教程系列将介绍 Vue.js 的基本知识,然后介绍 Vue 的高级用法。在第 1 部分中,将会使用 Vue、 NPM 和 webpack 为一个模块化应用程序构建一个小巧但功能全面的 UI,以及一个符合生产级的构建版本和依赖关系管道。在第 2 部分 中,会通过 IBM Bluemix 平台即服务将该应用程序部署在云中。完成本教程的学习后,您就可以轻松地在自己的项目中应用 Vue。请参见部分,获取第 1 部分的完整示例代码。

Vue.js 的实践性介绍

尽管具有极低的复杂性,但 Vue.js 支持复杂的、大规模的需求。该框架为您提供了足够的工具来构建您所需的任何功能,无需堆叠其他大量特性。根据我的经验,与其他框架相比,Vue 在复杂性和特性频谱中找到了简单性与功能的平衡点:

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

首先,我将向您介绍 Vue.js 的基础知识,以便您能了解 Vue 的工作原理和如何最佳地使用它。然后,在此基础上,您将使用更高级的 Vue 技术,以及现代的、提供生产级环境支持的工具来构建应用程序和管道。

简单的值绑定

为新项目创建一个名为 recruiteranking 的目录。然后 下载 最新的 Vue 包,将其保存在一个单独的位置,在包含该 Vue 包的 recruiteranking 中创建一个 index.html 文件,如清单 1 所示。

清单 1. 基本的 index.html 文件

<html>     <head>          <meta charset="utf-8">         <script type="text/javascript" src="vue.js"></script>     </head>     <body>   <div id="test">   <p>User: {{ username }}</p>   </div>           <script> test = new Vue({  // 1 — Instantiate a vue instance   el: '#test',   data: {     username: "Luke Skywalker"   } });         </script>     </body> </html>

Vue 中的大部分操作都是通过 Vue 实例完成的,这正是我将 Vue 描述为面向对象的原因。

在中,请注意您在 <script> 标记中创建 Vue 实例的位置(标为注释 1)。Vue 中的大部分操作都是通过 Vue 实例完成的,这正是我将 Vue 描述为面向对象的原因。这个 Vue 实例仅有两个字段,二者都是 Vue 类的关键属性。 el 属性告诉 Vue 它将绑定到哪个 DOM 元素;此属性可以是任何解析为单个元素(或实际元素)的查询。如果您熟悉 Backbone,可以认为此字段类似于该框架中的 el 属性。

第二个属性是 data 字段。此属性是 Vue 对象上的一个神奇的字段,它表示实例状态,可自动与 DOM 交互。 data 属性也是一个包含 username 字段的对象。 username 字段是我创建的一个字段。 data 属性可包含任何类型、任何名称的字段,包括数组和复杂的嵌套对象。现在来看看 HTML 的主体, <div> 中具有 id="test" 的段略元素包含 {{ username }} 标志。此标志是一个模板标志,与 mustache 标志非常相似(它们对于 ${} 格式的 JavaServer Pages Expression Language 标志的意图类似)。

Vue 将 test <div>test 实例相关联,并将 username 令牌替换为实例上的 username 字段的值。所以,如果加载该页面,它会显示 User:Luke Skywalker

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

现在,在浏览器中打开一个 JavaScript 控制台。(如果使用的是 Chrome,可以按下 F12 打开开发人员控制台。)因为您为 Vue 实例提供了全局范围内的一个引用,所以您可以在控制台中访问 test 实例。可以通过更改 test Vue 实例上的 data 对象的值来更改页面上显示的用户名。如何获得 data 字段的访问权?您不会获得该权限。 data 字段可在实例上直接设置: test.username

所以,如果您将 test.username = "Han Solo"; 输入控制台中,就会在 UI 中看到该变化的反映,其中 User:Han Solo 取代了 User:Luke Skywalker :

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

方法

您刚执行的更改通过探出 Vue 实例的状态并直接修改它而破坏了封装。Vue 提供了一种更好的替代方法。清单 2 向 test 对象添加了一个 methods 属性。

清单 2. methods 属性

test = new Vue({   el: '#test',   data: {     username: "Luke Skywalker"   },   methods: {     changeName: function(name){       this.username=name;     }   } });

test Vue 实例现在有一个 methods 字段,其中有一个 changeName 方法。这个简单的方法接受一个参数,然后使用该参数设置您之前看到的 username 数据成员。现在,在 JavaScript 控制台中,您可以 直接 访问 changeName 方法,像 test 实例上的方法一样。打开 JavaScript 控制台并输入 test.changeName("Chewy");

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

输入绑定

现在,进一步完善这个简单示例,看看 Vue 如何支持绑定到用户输入字段。再次访问 test <div> 并添加一个 input 字段,将该字段的 v-model 属性设置为 username

<div id="test">   <p>User: {{ username }}</p>   <input v-model="username">   </div>

Vue 从 Angular 借鉴了术语 指令v-model 属性也被视为指令。通过这次简单添加,UI 现在有一个绑定到 Vue 模型的表单输入字段。该绑定是 双向的 :如果 Vue 数据模型发生更改,表单字段也会发生更改,反之亦然。

v-model 指令使创建与表单输入的双向绑定变得非常简单。您可以在控制台中运行 test.changeName("Yoda") 来测试绑定的两个方向。您会看到,文本显示和表单输入值都会发生变化来反映更新。

事件处理

我们要介绍的 Vue 的最后一个基本特性是用户事件处理。您通过指令处理事件。在本例中,格式为 v-on:events ,其中 events 是您想要监听的事件,比如 click

在该标记中,添加一个使用事件处理的按钮:

<div id="test">   <p>User: {{ username }}</p>   <input v-model="username">   <button v-on:click="defaultUser">Default User</button>   </div>

(使用 Vue 速记语法,您可以将事件指令缩短为 @ 。所以您可以将该按钮处理函数重写为 <button @click="defaultUser">Default User</button> 。)

清单 3 包含对 JavaScript 的相应更改:添加一个 defaultUser 方法来将 username 设置回 Luke Skywalker

清单 3. 向 Vue 实例添加一个单击处理函数

test = new Vue({   el: '#test',   data: {     username: "Luke Skywalker"   },   methods: {     changeName: function(name){       this.username=name;     },     defaultUser: function(){       this.changeName("Luke Skywalker");     }   } });

单击该按钮,并观察绑定到 username 属性的所有元素是否都被更新为默认值,通过此操作验证 v-on:click 处理函数是否有效。

现在,您已经了解了 Vue 中的状态、 data 属性、行为和 methods 属性。将上述这些与 el 属性、事件处理和绑定结合使用,它们是 API 的核心元素。在继续阅读本文的时候,您会发现这些简单的基础元素为您提供了大量强大的功能。

演示应用程序和技术堆栈

后端

本文的重点是演示应用程序的前端。我通过 Spark Framework 设置的一个简单的演示后端提供了简单的端点来显示前端 JavaScript。本文没有涉及到数据库:所有数据都在内存中。您在第 2 部分 中学习生产部署时,将会了解后端的内部原理。

您已准备好开始构建一个更复杂的演示应用程序:一个 RESTful 单页应用程序。(您需要继续在 recruiteranking 项目的文件中操作,所以不要删除它们。)Vue 是我们要介绍的主角,但要将各部分组合起来,还需要使用其他技术作为配角。具体来讲,将会使用 webpack 作为构建系统。webpack 是一个强大的、高度可配置的系统;我们仅使用它的少部分功能。我们还将使用 Foundation 实现响应式 CSS 布局,但 Foundation 仍然高度透明。此外还会使用 NPM。

使用此技术栈,构建一个应用程序来帮助对您接触过的招聘公司进行排名。该应用程序在顶部有一个徽标和菜单栏,还有一个包含招聘公司的主表格。表格的行按 1 到 5 排名,可扩展这些行来编辑公司的细节。

您需要使用 Node 和 NPM 来安装 webpack,然后包含依赖项。按照以下 说明 安装二者。

构建流程的目的是:除了 Node 和 NPM 外, 安装任何全局依赖项。此方法可保持工作区干净整洁。所有依赖项都在项目中明确列出,可通过一个命令部署在任何环境中。

要确认您的系统已准备好,可以打开命令行并运行 npm -v 。如果获得了一个版本作为响应,那么您的 NPM 安装已准备就绪。

使用 NPM 管理依赖项

如果之前没有为项目创建过目录,那么请创建一个:

mkdir recruiteranking

更改到 recruiteranking 并运行以下命令来获得一个默认的 package.json 文件:

npm init

稍后您会在 package.json 中定义开发依赖项,还会借助 webpack 的强大功能来指定部署依赖项,比如 jQuery。但继续后面的操作之前,需要在 Git 中设置跟踪,以便为更改建立一个保护网:

git init git add git commit -m "a single step"

或者不采用 "a single step" ,而是将它称为 "initial commit" 或适合您的风格的任何叫法。

现在,在一个前端 IDE 中打开 package.json。(我最喜欢的是 Sublime。)将您的依赖项添加到 package.json,然后,可将它用作客户端依赖的每个工具的记录列表(与 Apache Maven 对 Java™ 应用程序的做法相同)。请记住,您希望通过 NPM 来管理其他所有依赖项,而且没有隐藏的依赖项;然后,可以运行一个 NPM 命令来安装您需要的所有包。清单 4 给出了更新的依赖项列表。

清单 4. package.json 依赖项列表

{   "name": "recruiteranking",   "version": "1.0.0",   "description": "",   "main": "index.js",   "scripts": {   },   "author": "",   "license": "ISC",   "dependencies": {     "jquery": "2.1.1",     "foundation-sites": "5.5.3",     "webpack": "~1.12.6",     "webpack-dev-server": "~1.14.0",     "style-loader": "~0.6",     "script-loader": "~0.5",     "css-loader": "~0.23.0",     "node-sass-loader": "~0.1.7",     "sass-loader": "~3.1.2",     "vue": "1.0.10",     "vue-loader": "7.2.0"   } }

现在开始,我只会介绍要执行的更改,而不是介绍整个列表。

在中,重点在于添加的 dependencies 属性,它包含多个包:

  • jQuery v.2.1.1,这是一个著名的、广受欢迎的 JavaScript 库。NPM 和 webpack 使得 jQuery 可用于您的 JavaScript 和所有其他库。(请注意,在 NPM 3 和更高版本中,必须以这种方式明确规定对等依赖项。)
  • Foundation 是您的响应式框架。
  • 两个 webpack 依赖项:webpack 本身和它的开发服务器(您很快会看到它的实际应用)。
  • *-loader 依赖项。这些都由 webpack 使用。webpack 可使用和组合各种各样的文件类型,高效地将它们打包在一起,将它们作为 JavaScript 文件包含在您的最终项目中。所以 “加载器” 就像是 webpack 的术语,您可以通过它们为 webpack 提供与 JavaScript、CSS 和 Vue 通信的能力。
  • Vue.js。

在命令行上转到项目的根目录(package.json 文件位于这里)并键入 npm install 。此命令将会添加 node_modules 目录,并将指定的包下载到该目录。等待(可能时间有点长)所有包下载完成。

配置 webpack

现在,您需要依赖的所有工具都已准备就绪,可以使用 webpack 将它们集中在一起。

webpack.config.js 文件

在项目根目录中,添加一个 webpack.config.js 文件。此文件将会配置 webpack;它是真正的 JavaScript 而不是 JSON,为您提供了更高的灵活性。清单 5 显示了入门 webpack 配置。

清单 5. webpack.config.js

var path = require("path");  module.exports = {     entry: {       main: "./app/js/main.js"     },     output: {         path: __dirname,         filename: "bundle.js"     },     module: {         loaders: [             { test: //.css$/, loader: "style!css" },             { test: //.scss$/, loaders: ["style", "css", "sass"] },             { test: //.vue$/, loader: 'vue' }         ]     },      sassLoader: {     includePaths: [path.resolve(__dirname, "./node_modules/foundation-sites/scss/")]     } };

我将快速略过这个配置文件,仅在每个需要注意的地方稍作介绍。

webpack.config.js:包含模块

首先,要定义一个包含 path ,该模块提供了处理文件系统的功能。

接下来是对 module.exports 的调用,该名称在 CommonJS 中的意思是 “这是我的包中包含的内容”。webpack 构建项目时依赖于这个模块。如果您熟悉 Maven,可以将 module.exports 视为与 Maven 项目对象模型 (POM) 等效的 Webpack 模块。

webpack.config.js: entry

多个入口点、输出和代码块

webpack 支持多个入口点,并将应用程序输出分解为多个 很方便,也可以方便地支持项目的增量加载。

一种常见的用例是定义一个与应用程序 bundle 独立的供应商 bundle。然后,应用程序可在必要时加载经常改变的应用程序代码,保持用户浏览器缓存中的供应商代码不变。

这里不需要使用这些特性,但您可以在 此处 找到它们的深入介绍。

entry 字段告诉 webpack 从何处开始该项目,被用作模块包含的根目录。可以在中看到,/app/js/main.js 定义了项目的所有运行时依赖项。

webpack.config.js: output

output 告诉 webpack 将最终结果放在何处。您的 index.html 在包含该构建版本时引用了这个文件。

webpack.config.js:加载器

module 配置您的 webpack 的加载器。加载器负责读取原始资产并将它们转换为最终的 bundle。加载器是可插拔和可自定义的。 module 上的详细信息会告诉加载器它们应该处理哪些文件类型(例如样式加载器处理 .css)。 test 字段表示 “如果该文件通过此测试,则应用此加载器。” module 之后是 sassLoader.includePaths ,它引导 Sass 加载器在 foundation-sites 依赖项在 NPM 内添加的 foundation 目录中查找 — 必须这么做,才能正确解析您应用程序中的 foundation SCSS includes。

您的最终构建资产是一个 JavaScript 文件。CSS/SCSS 加载器创建一个由 JavaScript 捆绑和注入的最终产品。不会输出任何最终的 CSS 文件。

现在,您已经拥有了生产级依赖项管理功能和一种生产级构建设置。您已准备好使用二者来构建 UI 了。

模块化的应用程序

将包含以下内容的入口文件 main.js 添加到 /app/js/ 中(相对于您的项目根目录的路径):

$ = jQuery = require('jquery'); foundation = require('foundation-sites'); Vue = require("vue"); require('../css/app.scss');

在这个初始 main.js 文件中,您需要 jQuery 和 Foundation 包,以及您自己的本地 app.scss 文件,该文件是您的样式的入口点。请注意,您为 jQuery 提供了两个全局标识符 $jQuery ,使用它们作为 Foundation JavaScript 中一些松散的引用的解决方案。这些标识符非常标准,不会导致任何问题。您还需要将 Vue 全局化,我们稍后将解决这个问题。

将包含以下内容的 app.scss 文件添加到 /app/css/app.scss 上:

@import "settings"; @import "foundation";

您在此文件中包含了 Foundation Sass 和您自己的所有全局样式。

启用本地测试和部署

要对全新的 JavaScript/Vue/Foundation 装备执行的第一个操作是切换您部署和测试该应用程序的方式。webpack 提供了一个基于 Node/Express 的开发服务器来托管该应用程序,这使得编码工作变得很轻松。该开发服务器还支持代理,这便于稍后与后端进行交互。要运行开发服务器,必须对您的 package.json 文件进行更改。因为您的所有依赖项都是本地托管的 NPM 包,所以需要对 webpack 运行 NPM。因此,在 package.json 中,要将此条目添加到 scripts 对象中来启用开发服务器:

"scripts": {     "server": "webpack-dev-server"   }

现在打开命令行并运行 npm run-scripts server ,以便让该应用程序在 localhost 上的 8080 端口上运行。 run-scripts 是一个运行指定脚本的 NPM 工具,会加载 package.json 中定义的所有本地依赖项。现在,只需一行代码即可加载所有依赖项,打包它们,然后在浏览器中测试它们。更好的地方是, webpack-dev-server 会实时地热部署您的代码更改。开发服务器将构建版本捆绑到内存中并从内存部署;它未将任何输出文件放在文件系统中,这使得代码更改时能快速地重新部署。

修改 index.html 和 main.js

现在,在您的根目录中创建的 index.html 文件中(参见),将包含的 JavaScript 文件的名称从 vue.js 更改为 bundle.js

<script type="text/javascript" src="bundle.js"></script>

在浏览器中,打开 http://localhost:8080/index.html。您可以看到该应用程序已加载,且像之前一样正常运行,例外的是该按钮和输入现在已样式化 — 因为 Foundation 已成功加载,而且它的 CSS 已注入到页面中。外观变得更加美观了。该页面也是响应式的;调整浏览器窗口,您会看到输入字段也相应地进行了调整。

现在是时候消除全局 Vue 变量并将您的 JavaScript 转移到 main.js 中了。剪切 index.html 中的 <script> 标记中的代码并将它们粘贴到 main.js 中。将 bundle.js include 转移到 index.html 的末尾处;需要在解析标记后调用它,以便代码可以在浏览器渲染和 DOM 激活后引用 DOM。现在您的 index.html 文件类似于清单 6。

清单 6. 更新后的 index.htm

<html>     <head>         <meta charset="utf-8">     </head>     <body>   <div id="test">   <p>User: {{ username }}</p>   <input v-model="username">   <button v-on:click="defaultUser">Default User</button>   </div>   <script src="http://localhost:8080/webpack-dev-server.js"></script>   <script type="text/javascript" src="bundle.js"></script>     </body> </html>

您的 main.js 文件现在类似于清单 7。

清单 7. 更新后的 main.js

$ = jQuery = require('jquery'); foundation = require('foundation-sites');  var Vue = require("vue");  require('../css/app.scss');  test = new Vue({   el: '#test',   data: {   username: "Luke Skywalker"   },   methods: {   changeName: function(name){     this.username=name;   },   defaultUser: function(){     this.changeName("Luke Skywalker");   }   } });

将该页面重新加载到浏览器中,它会像之前一样正常运行。请注意中的另一点:您使用 var 限定了 Vue 引用的范围,所以现在不会将该变量泄漏到全局命名空间中。

Vue 组件

您将会看到如何将应用程序功能组合到组件中。组件是促进应用程序设计中的重用、删除重复和模块化的好方法。

您的依赖项管理和构建系统都已就绪,您已装入了所需的包,这些包正在正常运行,而且脚本代码已组合到一个外部 JavaScript 文件中。现在看看如何将应用程序功能组合到组件中。组件是促进应用程序设计中的重用、删除重复和模块化的好方法。

Vue 应用程序有一个主要的父 Vue 实例,它包含并呈现所有其他实例。(可以拥有多个 Vue 实例,但这里不需要这么做。)您首先将登录特性改进为一个名为 user 的组件。

这是 user 组件的 index.html 的新的主体内容:

<div id="app">         <user></user>     </div>

名为 app<div> 仍是一个 Vue 实例,但您将自定义 <user> 标记创建为一个 Vue 组件。现在访问 main.js。保持 import 不变,删除剩余代码。为 user 组件添加一个组件定义,然后添加 App Vue 的声明。清单 8 显示了组件声明。

清单 8. 以编程方式定义一个 user 组件

Vue.component('user', {   template: '<div>User: {{ username }}</div>',   data: function(){     return {       username: "Luke Skywalker"     }   } });

清单 8 使用了 Vue.component 方法,您可以使用它在一个名称(第一个参数)下注册一个组件,并跟随着一个 组件参数对象(第二个参数)。

重要事项

在 Vue 组件中,使用一个函数返回数据字段,以避免跨实例的数据共享。

请注意,HTML 标记已转移到 template 属性中。(如果您熟悉 Dojo,就会注意到它与 Dojo 的小部件系统的模板属性具有类似的工作原理。)接下来是一个 data 属性 — 一个 返回 数据对象的函数, 而不是 直接返回数据对象。采用该方法的原因是,您创建了一个可在多个位置重用的组件,就像一种类型。您不希望所有类型实例共享 data 字段,如果它是一个直接对象,就会发生这种情况。

接下来将以下行(实例化 App 实例)添加到 main.js 中:

var App = new Vue({   el: "#app",   data: {},   methods: {} });

App Vue 很简单 — 它只获取该元素。

如果重新加载该页面,就会看到各项功能都正常运行。 App 实例已成功将 user 组件注入到其标记中,而且呈现了 User:Luke Skywalker

现在,我们将在组件化上更进一步,将 User 组件转移到它自己的 .vue 文件中。从项目的根目录,创建一个 /app/vue 文件夹并添加一个 user.vue 文件。将清单 9 中的代码添加到该文件中。

清单 9. 在单独的文件 user.vue 中定义的 User 组件

<style> </style>  <template>   <p>User: {{ username }}</p> </template>  <script> module.exports = {   data:function () {   return {     username: "Luke Skywalker"   }   } } </script>

中的 3 个标记( <style><template><script> )表示任何组件的 3 个元素:CSS、标记和代码。(针对 Sublime 用户的提示:单击 View > syntax > HTML 告诉 Sublime 显示正确的突出显示。)

目前为止,尚未对该组件使用任何自定义 CSS,所以 <style> 标记是空的。 <template> 元素包含您熟悉的标记, <script> 标记包含您熟悉的 data 元素,但以不同方式包装它。因为您位于一个捆绑为 CommonJS 组件的文件中,所以需要使用 module.exports 并像中那样定义您的组件。)请注意,您没有在这里声明组件名称。

返回到 main.js,如何使用该组件?您必须做两件事:包含该组件并在您的 App Vue 中引用它,如清单 10 所示。

清单 10. 在 App Vue 中使用模块化的 User 组件

var User = require("../vue/user.vue");  var App = new Vue({   el: "#app",   data: {},   methods: {},   components: {   user: User   } });

在中,您请求 user.vue 文件并为它提供一个局部变量引用。接下来,在 App 实例声明中,添加一个 components 属性。 components 属性的职责是将您的 User Vue 类型映射到 <user> 标记。该组件的键是在 Vue 标记中很有用的标记名称,组件值是这些标记引用的 Vue 组件。

现在,如果重新加载,就会再次看到 User:Luke Skywalker ,证明应用程序仍能运行,并按原样分解为可加载的组件。所有各部分都已准备就绪。您已准备好采取下一步操作,开始将这些部分集成到一个真实的应用程序中。

首先,使用 Foundation 添加一个菜单栏,并将您的 user 组件放入其中。清单 11 显示了已经更新了菜单栏代码的 index.html。

清单 11. 包含用户名顶栏的 index.html

<html>  <head>     <meta charset="utf-8"> </head>  <body>     <div id="app">         <div class="sticky">             <nav class="top-bar" data-topbar role="navigation" id="top-bar">                 <ul class="title-area">                     <li class="name">                         <h1>             <a href="#">Recruiteranking</a>           </h1>                     </li>                 </ul>                 <section class="top-bar-section">                         <ul class="right">                             <li class="active">                               <user></user>                             </li>                         </ul>                     </section>             </nav>         </div>     </div>     <script src="http://localhost:8080/webpack-dev-server.js"></script>     <script type="text/javascript" src="bundle.js"></script> </body>  </html>

现在,当重新加载时,该应用程序在顶栏的左侧显示了 Recruiteranking ,在右侧显示了 User:Luke Skywalker

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

组件分层结构

接下来,需要构建一个更好的数据流系统。首先让 username 字段摆脱 user 组件的控制,并将它提供给 App 。在这里,您会看到 Vue 组件分层结构的实际强大功能。

转到 user.vue 并执行清单 12 中所示的更改 — 添加 props 成员。

清单 12. 将 props 成员添加到用户中

<script> module.exports = {   props: ["username"],   data:function () {     return {       username: "Luke Skywalker"     }   } } </script>

props 成员会告诉该组件哪些属性可供其父组件访问。所以在这里,应该表明 username 可供父 Vue 实例访问。

接下来,将一个 data 字段添加到 App Vue 实例中。这个 data 字段可以是一个直接对象,而不是函数,因为您不会重用此实例。在 data 字段中,放入一个包含 username 字段的 User 对象。您放入了一个 User 对象,因为接下来您可能希望添加已登录用户的其他信息,比如 ID。 App 的新 data 字段为:

data: {     user: { username: "Luke Skywalker" }   }

这里要注意两点。首先,父 Vue 实例可与子组件交互。其次,Vue 可将 App 上的 data 字段作为复杂对象而无缝地处理。

现在,要将这些部分衔接在一起,可返回到 index.html 字段,使用 v-bind 指令告诉 user 组件在何处查找它的 username 数据。这是将父对象绑定到 User 组件的标记:

<user v-bind:username="user.username"></user>

Vue 会毫无怨言地导航 user 上的句点运算符,以便获得 username 属性。

v-bind 速写语法

您可以对 v-bind 使用速写语法,该语法类似于针对事件处理的速写语法。Vue 将冒号解释为 v-bind 指令。所以将父数据绑定到 User 组件的标记可重写为 <user :username="user.username"></user>

user 组件的 username 字段现在可以简单而又优雅地绑定到父 user.username 字段。默认情况下,数据绑定是 单向 流动的,从父字段流向子字段。Vue 支持双向通信,甚至拥有一个组件间事件系统。就现在而言,您只需要了解基本的父子通信,但在需要的时候知道您可以实现双向通信也很不错。

登录

如何才能让用户进行登录?回想目前完成的所有工作,您可能会对这么快的进度感到惊讶。

首先,通过将 user.vue 文件中的 username 字段设置为 null ,删除默认用户 Luke Skywalker:

module.exports = {   user: null };

user 组件现在仅关心数据的显示,而不是数据本身。

此刻,如果您重新加载并查看控制台,就会看到一个错误: [Vue warn]:Error when evaluating expression user.username.Turn on debug mode to see stack trace. 出现该错误是因为在 index.html 的 HTML 中,您引用了 user.username ,而且您的 user 对象为 null 。您只希望在一个人登录时显示用户名。可以使用 v-if 指令实现此有条件渲染:

<li class="active" v-if="user">   <user v-bind:username="user.username"></user> </li>

v-if 指令表示 “仅在 v-if 条件为 true 时渲染此元素”。在这种情况下,仅在用户数据对象不为 null 时渲染此列表项 — 这正是您想要的结果。现在用户名仅在用户登录时显示。

要在用户为 null 时显示一个 Log In 按钮,可在顶栏中添加一个 else 代码块:

<li class="active" v-if="user">   <user v-bind:username="user.username"></user> </li> <li class="active" v-else>   <a href="#" v-on:click="showLogin">Log In</a> </li>

请注意,登录操作上有一个指向 showLogin()v-on:click 处理函数。将该处理函数添加到您的 App Vue 中,如清单 13 所示。

清单 13. App Vue 上的 showLogin() 方法

App = new Vue({   el: "#app", data: {     user: null,     loggingIn: false   }, methods: {     showLogin: function(){       this.loggingIn=true;     }   } //...

logIn() 方法中执行的所有任务只是将 App.data 中的 loggingIn 标志设置为 true 。您可以使用该标志显示一个登录窗格。(Vue 也支持动画过渡。)您可以注意到,您没有直接执行任何 DOM 操作;设置该标志并让标记中的 Vue 指令来完成工作。

在 index.html 中,在紧挨 top-bar 标记后的地方添加一个登录表单,如清单 14 所示。

清单 14. 将一个登录表单添加到 index.html 中

<div id="app">   <div class="sticky">     <nav class="top-bar" data-topbar role="navigation" id="top-bar">       ...     </nav>   </div>   <div class="small-12 columns" id="sign-in-on" v-show="loggingIn">     <div class="row">         <div class="large-12 columns">             <div class="signup-panel">                 <form id="signup-form" data-abide="ajax">                     <div class="row collapse">                         <div class="small-10  columns">                             <input type="text" placeholder="Username" name="username" required>                         </div>                     </div>                     <div class="row collapse">                         <div class="small-10 columns ">                             <input placeholder="Password" name="password" required type="password">                         </div>                     </div>                     <button type="submit" v-on:click="login">Log In</button>                 </form>             </div>         </div>     </div>   </div> </div>

登录页面会在 main.loggingIn 设置为 true 时显示。您可以注意到,您是使用 v-show 指令实现此结果的。 v-show 不同于 v-if ,因为 v-show 虽然渲染了此内容,但隐藏它,而 v-if 完全不会渲染该内容。在本例中,您选择了隐藏和显示 (hide-and-reveal),而不是按需渲染。您的 Log In 按钮也已附加到方法 login (同样使用 v-on:click )。该方法为您执行登录操作。

与后端相集成:开发

现在需要与后端进行通信了。出于本教程的目的,我提供了一个可在本地运行的后端(请参见)。如果您在工作站上同时开发前端和后端(换句话说,您是一位 “全堆栈” 开发人员),那么您可以在端口 4567 上运行开发后端,代理会请求它,就像我在这里使用演示应用程序所做的一样。执行 java -jar rr-backend-1.0.jar 来运行所下载的后端服务器。该服务器启动并开始通过嵌入式 Jetty(通过 Spark Framework)处理请求。

您需要采用某种方法来告诉客户端开发服务器将 API 请求转发给后端。webpack 在开发服务器代理中提供了该功能。将清单 15 中的代码添加到您的 webpack.config.js 文件的根级对象中。

清单 15. 将开发服务器代理添加到 webpack.config.js

module.exports = {    ...     devServer: {         proxy: {             '/api/*': {                 target: 'http://localhost:4567',                 secure: false             }         }     } }

现在,保持后端应用程序一直运行,对 /api/* 路径的任何请求都会转到该服务器,并在端口 4567 上进行监听。

保持后端服务器一直运行,在浏览器中访问 http://localhost:8080/api/test 来验证服务器是否通过开发服务器推送回了它的响应。如果响应是 TEST OK ,则一切正常。

要继续采用模块化设计,可以将您的登录表单转变为组件。清单 16 显示了该组件。

清单 16. login 组件

<style> </style> <template>   <form action="{{ action }}" method="{{ method }}" v-on:submit.stop.prevent="login">     <div class="row collapse">         <div class="small-10  columns">       <input type="text" placeholder="Username" name="username" v-model="payload.username" required>         </div>     </div>     <div class="row collapse">         <div class="small-10 columns ">       <input placeholder="Password" name="password" v-model="payload.password" required type="password">         </div>     </div>     <button type="submit">Log In</button>   </form> </template> <script> module.exports = {   props: {     'action': {         type: String,         required: true     },     'method': {         type: String,         default: "post"     }   },   data: function() {     return {     payload: {       username: null,       password: null     }     }   },   methods: {     login: function() {         this.$http.post(this.action, this.payload, function (data, status, request) {          this.$dispatch('onLoginSuccess', data);         }).error(function (data, status, request) {       console.error(status);         });     }   } } </script>

您应该对 login 组件最为熟悉,但有两个值得注意的新内容。您可以注意到,您有一个更加详细的 props 字段,您使用它从表单中调入 methodactionprops 字段显示 Vue 为 requiredtypedefault 参数提供了一定的支持。 props 字段甚至包含一个验证器,以防您需要它。

可以使用 v-on:submit.stop.prevent="login" 监听模板表单的提交,在这里的 v-on 指令上,可以看到两个修饰符: stopprevent 。这些修饰符的用途是预防默认的浏览器操作和阻止事件传播。根据您的需要,可以像此处一样一起使用这些修饰符,也可以单独使用它们。

调用 login 方法时,发送了一个包含来自 data 对象的 payload 字段的 post ,它通过 v-bind 绑定到输入字段。 $http 对象来自 vue-resource 插件,我们稍后将安装该插件。但是首先请注意,您对一个字符串和从 post 返回的数据执行了 #dispatch 。通过使用 $dispatch (Vue 组件事件系统的一部分),子组件可在组件树中向父组件发送事件。您将在 App Vue 中处理此事件。

转到 main.js 并添加 events 字段:

events: {       onLoginSuccess: function(data) {         this.user = data;         this.loggingIn = false;       }     }

events.onLoginSuccess 捕获该事件。后端测试服务是一个存根,它返回添加了 UUID 的用户对象。您在 App.user 上设置 user 对象,并将 loggingIn 设置为 false ,这会隐藏登录表单。

在这些更改生效之前,需要包含 vue-resource 插件。您可以使用直接 XHR 或 jQuery 或您想要的 Ajax 支持,但 vue-resource 插件会让各项功能变得干净而又简单,就像您在中的 login 方法中的 this.$http 调用中看到的一样。要添加 vue-resource 支持,可以转到 package.json 并将 "vue-resource":"0.5.1" 添加到依赖项。接下来,进入 main.js 并将此行添加到 imports 中:

Vue.use(require('vue-resource'));

可以注意到,您调用了 Vue.use() 来将模块作为插件添加到 Vue 运行时中。

有两种选择来向表单添加验证(Foundation 和 Vue 都包含验证器支持),但我们跳过了验证,前进到与招聘公司相关的功能。您想要一个可供用户单击来查看详细信息的表格,而且还希望登录的用户能够编辑、删除和创建招聘公司条目。登录需求也就是应用程序的授权部分。(很容易通过推断创建用户角色或访问控制列表来作为授权的基础,但为了简便起见,这里只使用了登录检查。)

将 CRUD 操作映射到 RESTful 语义,如下所示: PUT 表示创建, GET 表示读取, POST 表示更新, DELETE 表示删除。所以您的服务器中用于 recruiter 的 API 类似于:

GET /api/recruiter POST /api/recruiter PUT /api/recruiter DELETE /api/recruiter

这里唯一有争议的地方是 POSTPUT 动词的含义,但这是一个很小的问题,不会让人夜不能寐。

警告

测试服务器是全内存型的,没有验证或授权检查,因此,如果重新启动该服务器,就会丢失更改。

首先创建 grid 组件来显示招聘公司列表,使用来自 Vue 的 表格示例 作为起点。此组件将您目前学到的许多功能融合在一起,还引入了一些新功能。

通过将清单 17 中的代码放在 /app/js/vue/grid.vue 文件中来创建 grid 组件。

清单 17. grid 组件:/app/js/vue/grid.vue

<style> /* CSS truncated for brevity */ </style> <template>     <table>         <thead>             <tr>                 <th v-for="key in columns" @click="sortBy(key)" :class="{active: sortKey == key}">                     {{key | capitalize}}                     <span class="arrow" :class="sortOrders[key] > 0 ? 'asc' : 'dsc'">           </span>                 </th>             </tr>         </thead>         <tbody>             <tr v-for="entry in data | orderBy sortKey sortOrders[sortKey]">                 <td v-for="key in columns" v-on:click="rowClick(entry)">                     {{entry[key]}}                 </td>             </tr>         </tbody>     </table> </template> <script> module.exports = {     props: {         data: Array,         columns: Array     },     data: function() {         var sortOrders = {}         this.columns.forEach(function(key) {             sortOrders[key] = 1         })         return {             sortKey: '',             sortOrders: sortOrders         }     },     methods: {         sortBy: function(key) {             this.sortKey = key             this.sortOrders[key] = this.sortOrders[key] * -1         },         rowClick: function(row){           this.$dispatch('onRecruiterDetail', row);         }     } } </script>

可以注意到,这个组件包含我为了保持简单性而在清单中省略的样式。您可以在可下载的应用程序中看到该样式 — 我们为您提供了表格外观的简单 CSS。

可以在中的 <template> 中注意到,您有一个基本的表格布局。头标记 <th> 包含一些新内容:

  • v-for 指令(表示 Vue 的迭代器支持):您可以通过它根据数组来发出指定的标记。在本例中,您使用了 v-for="key in columns" ,它类似于一个正式的 JavaScript for 循环,表示 “对于 columns 集合中的每一项,渲染该标记,将 key 变量公开为当前元素的引用。”
  • 标头上的单击处理函数:该函数调用了 sortBy 方法,并通过 @click="sortBy(key)" 传入当前键。
  • 一个 class 指令: :class="{active: sortKey == key}" 。请记住,单独一个冒号是 v-bind 语法的一种速记,所以您看到的是与 Vue 实例的 data 成员的绑定 — 但在本例中,表示 class 属性。Vue 为 classstyle 属性提供了一些特殊的处理方法。每个属性接受一个逗号分隔的名称列表,每个名称指向一个解析为布尔值的表达式。在本例中,如果 sortKey == key ,那么这个 <th> 元素就会收到 active 类: <th class="active"> 。此表达式暗示 Vue 组件实例有一个类似于 key 变量的 sortKey 数据字段。
  • 一个用作 <th> 的主体的基本标志,包含一个您不熟悉的特性 — 过滤器{{key | capitalize}} 。该过滤器名为 capitalize ,它通过竖线字符添加到 key 的基值中。 capitalize 是一个内置的过滤器,其用途从字面上就可以看出。(借助 Vue,您还可以定义自己的自定义过滤器,但这里不会这么做。)

中下一个有趣的地方是表主体的 <td> 元素上的 v-for 指令: v-for="entry in data | orderBy sortKey sortOrders[sortKey]" 。此循环在 data 属性上执行并使用 orderBy 函数修改结果,它传入两个参数: sortKeysortOrders[sortKey]orderBy 使用参数来确定排序算法。第一个参数定义了要使用有序对象上的哪个字段,第二个参数告诉该算法按升序 ( true ) 还是降序 ( false ) 进行排序。

该组件的 JavaScript 部分负责支持标记的各个部分。 props 字段提供了 data (表格主体所迭代的数组)和 columns (标头所迭代的数组)。父组件设置 data 和 columns 数组;父组件负责将应用程序数据提供给子组件进行渲染。

data 字段证明您可以在返回 data 对象之前执行任意 JavaScript。在中, data 代码准备 sortKeysortOrders 字段。

对于 columns 数组中的每个键, sortOrders 被设置为 1 ,该值在表格主体上的 orderBy 操作中解析为 true (升序)。

该组件有两个方法: sortByrowClicksortBy 方法设置活动的软键,然后通过乘以 -1 来对该键的 sortOrders 值求反: this.sortOrders[key] = this.sortOrders[key] * -1 。最后, rowClick 方法接受传入的项(当前被迭代的对象)并发出一个 onRowClicked 事件供父 Vue 处理(它向上遍历父组件链并继续调用任何 onRowClicked 处理函数,直到返回 false 或父组件链结束。)

要遍历该表格,只需将它包含在 main.js 中并提供 columnsdata 属性。在 main.js 中,可以添加 var grid = require("../vue/grid.vue"); 来包含该组件。请记得在 components 属性中注册该 grid 组件:

components: {         user: user,         login: login,         grid: grid     }

另外,将 gridDatacolumns 字段添加到 data 属性中,并注意在这里设置表格的列标题:

gridColumns: ['name', 'rank'],       gridData: []

现在,您需要通过 ready 方法填充 gridData 。您目前应该尚未看到过这个方法。 ready 是一个生命周期回调,它在渲染并完全准备好 Vue 实例后执行。清单 18 显示了 App.ready 方法。

清单 18. 使用 ready() 回调将数据加载到 App Vue

ready: function() {         this.$http.get("/api/recruiter").then(function(response) {             this.gridData = response.data;         }, function(response) {             console.error(response.status);         });     }

ready 方法中,再次使用 vue-resource $http() 方法,像对 login 所做的一样。这一次,要将一个 GET 请求发送到 /api/recruiter URL(还记得您的代理根据 /api/ 路由将此请求转发到服务器吗?)使用 .then 提供一个处理函数。response 参数包含您处理响应所需的所有信息。在本例中,会将 data 字段设置为您的 gridData 。当 main Vue 准备好后,会从服务器填充您的表格。

最后一步是将 grid 组件引入到布局中,这很容易实现。在 index.html 中,将以下标记添加到 HTML 主体,放在紧挨顶栏代码的下方:

<div class="row panel" id="main">               <div class="large-6 columns">                   <grid :data="gridData" :columns="gridColumns"></grid>                 </div>

在前面的代码中,将您需要的字段绑定到所提供的数据: data 绑定到 gridDatacolumns 绑定到 gridColumns 。现在,如果重新加载该页面,您就会看到招聘公司表格,其中填充了一些初始行:

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

现在您希望能够单击一行来查看细节。完整的示例应用程序(参见)在 /js/app/vue/recruiter-detail.vue 文件中包含一个 recruiter-detail 组件。该组件不包含任何新的和不同的内容,所以我不会深入介绍它。将 recruiter-detail 作为组件添加到 main.js 中,然后将它包含在标记中,如清单 19 所示。

清单 19. 将 recruiter-detail 组件添加到 index.html 标记中

<div class="row panel" id="main">               <div class="large-6 columns">                   <grid :data="gridData" :columns="gridColumns"></grid>                 </div>                 <div class="large-6 columns" v-show="detail">                   <detail :payload="detail" :editable="user" action="/api/recruiter">                   </detail>                 </div>         </div>

这里值得注意的部分是 :payload 绑定,它被解析为 main Vue 上的 detail 。回想一下 grid 组件在单击一行时发出了一个事件,该事件被 main 上的 onRecuiterDetail 事件处理函数处理:

onRecruiterDetail: function(recruiter) {             this.detail = recruiter;         }

onRecuiterDetail 方法在 recruiter-detail 中设置表单的内容,它还会显示视图(通过 <div> 上的 v-show 指令)。

现在如果您单击一行,就会获得招聘公司的详细信息:

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

请注意,在将 detail 组件包含在 HTML 中时,您将 editable 字段绑定到了 main Vue 上的 user 对象上。因此,仅在用户登录后,编辑功能才能生效。如果您已经登录,就会看到详细信息部分看到一些按钮。请注意,无论是登录后打开详细信息,还是详细信息已经可见,然后您再登录,这里的所有功能都是相同的:

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

使用 Vue.js 和 Bluemix 创建模块化的单页应用程序,第 1 部分: 开发和测试前端

Save 和 Delete 按钮发出的事件都由 main Vue 处理 — 所有业务逻辑都不会在 detail 组件中发生。这些事件包括 onRecruiterSaveonRecruiterDelete 。它们都使用 vue-resource 支持向 API 发出恰当的 REST 调用,使用所提供的 recruiter 实例来填充数据。因为 detail 组件中的 recruiter 实例是从表格数组中获得的,所以表格会立即发生更改,以反映用户对细节表单中的值的更改。

您需要做的最后一项操作是,工具栏上需要一个创建招聘公司的按钮:

<li class="active" v-if="user">                             <a href="#" v-on:click="createRecruiter">New Recruiter</a>                         </li>

同样地,仅在用户登录时才能启用该特性。 createRecruiter 函数很简单:

createRecruiter: function() {             this.detail = {};         }

通过在 detail 字段上设置一个空对象, createRecruiter 可以显示招聘公司细节窗格。如果 id 字段已存在,该表格会被当作更新操作还处理,否则会被当作创建操作来处理。

现在,您已经拥有了完整的 CRUD 支持,包括一个可排序的表格以及基本的身份验证和授权。

结束语

您可以使用 Vue.js 的核心特性来构建您能想到的任何东西,同时保持项目干净而且容易管理。Vue 的广泛功能具有极低的开销。这些品质造就了 Vue 受欢迎程度的快速增长,而且社区创建的功能在不断增加。您可以利用适合 Vue 的工具来最大限度地改进您的开发体验。在本系列的第 2 部分 中,您会看到使用 IBM Bluemix 将 recruiter-ranking 应用程序部署到云中的两种方法。

下载

描述 名字 大小
Recruiteranking 前端代码 recruiteranking.zip 75KB
独立的后端 WAR rr-backend-war.zip 321KB
原文  http://www.ibm.com/developerworks/cn/web/wa-develop-vue1-bluemix/index.html?ca=drs-
正文到此结束
Loading...