Knockout js 入门






4.88/5 (99投票s)
本文将帮助初学者理解 Knockout 如何与 ASP.NET 协同工作,以及客户端和服务器端的通信和响应式 UI 设计的实用性。
引言
Knockout js (简称 KO) 是一个非常流行的 JavaScript 库,并且其受欢迎程度日益增长。该库有助于创建丰富/响应式/交互式的 Web 应用程序。它直接与 Web 应用程序的底层数据模型协同工作。在任何 Web 应用程序中使用 KO 都非常简单、干净且直接,在动态 UI 创建方面非常强大。
背景
JavaScript 在当今现代 Web 应用程序的开发中起着非常重要的作用。曾经,我们只写几行 JavaScript 来进行客户端数据验证。但随着时间的推移,JavaScript 不仅在数据验证方面发挥着至关重要的作用,还在创建响应式、健壮的 Web UI 方面发挥着作用。因此,新的 JavaScript 框架/库不断涌现。作为聪明的开发者,我们必须拥抱它们并尝试在我们的应用程序中使用它们。
优点
如果我们与 Web 应用程序一起使用 KO,我们可以获得以下好处:
- 随时可以将 UI 元素与数据模型连接。
- 轻松创建复杂动态数据模型。
- 当数据模型更改时,UI 会自动更新;当 UI 更改时,数据模型也会自动更新。
- 支持事件驱动的编程模型。
- 轻松扩展自定义行为。
- 支持所有主流浏览器(Internet Explorer、FireFox、Crome、Safari)。
KO 与 MVVM
什么是 MVVM?好吧,MVVM 的完整形式是 Model View ViewModel。它是一种源自微软的架构设计模式,主要为 WPF/Silverlight 应用程序创建。但我们也可以将其与 ASP.NET Web 应用程序一起使用。
MVVM 是一种针对支持 WPF/Silverlight 事件驱动编程的 UI 开发平台的具体实现。它遵循“关注点分离”的编程原则。它完全将 GUI 渲染逻辑与应用程序逻辑(数据逻辑/业务逻辑)分开。
KO 是基于该架构设计模式构建的。所以,如果你想正确理解 KO,那么你应该先了解 MVVM。
MVVM 概述
下图将展示 MVVM 的风格。
MVVM 有 3 个部分:
- 模型
- 视图
- ViewModel
模型:负责存储应用程序数据。当用户通过 UI 元素输入数据时,这些数据将存储到模型中。所以我们可以认为它是模式中的数据部分。
视图:负责将模型数据呈现给用户。用户元素是视图的一部分。它提供了用户界面的结构/布局并将其呈现给用户。
视图模型:模型和视图的连接器。主要职责是建立与视图和模型的通信。它包含数据和函数。函数会操作这些数据,并反映到 UI 上。当数据发生变化时,UI 会发生变化;当 UI 发生变化时,数据也会发生变化。视图模型将借助数据绑定概念为我们完成这一切。
MVVM 的好处
MVVM 的主要好处列举如下:
- 为设计师和开发人员的工作提供更大的灵活性。
- 彻底的单元测试。
- 提供更改用户界面的灵活性,而无需重构代码库中的其他逻辑。
- 提供更高的 UI 组件可重用性。
将 KO 库包含到 Web 应用程序中
Knockout js 的 CDN(内容分发网络)引用可在 Microsoft CDN 存储库中找到。有两个版本可用。一个用于压缩(最小化),另一个用于调试。建议使用最小化版本。URL 如下:
- 最小化版本:http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js
- 调试版本:http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.debug.js
如果您的应用程序是 ASP.NET WebForm,则可以从上述位置下载 js 文件,并将其存储在您的 Web 文件夹/子文件夹中,并将其引用到您的页面/母版页。
<script src="~/Scripts/knockout-2.2.1.js" type="text/javascript"></script> CDN Reference: <script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js" type="text/javascript"></script>
如果是 ASP.NET MVC 应用程序,您可以在 @section
中引用它。
@section scripts {
<script src="~/Scripts/knockout-2.2.1.js"></script>
}
CDN Reference:
@section scripts {
<script src="http://ajax.aspnetcdn.com/ajax/knockout/knockout-2.2.1.js"></script>
}
示例 1
我将创建一个简单的 Employee
信息录入表单。Form
将接受用户输入的一些基本员工相关数据,并通过 AJAX 将这些数据提交到 MVC Action。在视图部分,我将使用各种 HTML 元素,以便展示它们如何与 KO 一起工作。
实现
在我开始展示 KO 代码之前,我想先说一点。在 MVVM 模式中,有一个个人选择。那就是哪个先出现,模型还是视图。我将不详细讨论这一点。我只说我的个人偏好,那就是先视图,然后是模型。
<form id="employeeForm" name="employeeForm" method="POST">
<div id="form-root">
<div>
<label class="form-label">First Name:</label>
<input type="text" id="txtFirstName"
name="txtFirstName" data-bind="value:Employee.FirstName" />
</div>
<div>
<label class="form-label">Last Name:</label>
<input type="text" id="txtLastName"
name="txtLastName" data-bind="value:Employee.LastName" />
</div>
<div>
<label class="form-label">Full Name:</label>
<input type="text" id="txtFullName" name="txtFullName"
data-bind="value:Employee.FullName" readonly="readonly" />
</div>
<div>
<label class="form-label">Date Of Birth:</label>
<input type="text" id="txtDateOfBirth"
name="dateOfBirth" data-bind="value:Employee.DateOfBirth" />
</div>
<div>
<label>Education:</label>
<input type="checkbox" value="graduation" id="chkGraduation"
name="chkGraduation" data-bind="checked:Employee.EducationList" />Graduation
<input type="checkbox" value="postGraduation"
id="chkPostGraduation" name="chkPostGraduation"
data-bind="checked:Employee.EducationList" />PostGraduation
</div>
<div>
<label>Gender:</label>
<input type="radio" id="rdoMale" name="gender"
value="0" data-bind="checked:Employee.Gender" />Male
<input type="radio" id="rdoFeMale" name="gender"
value="1" data-bind="checked:Employee.Gender" />FeMale
</div>
<div>
<label class="form-label">Department:</label>
<select id="ddlDepartment" name="ddlDepartment"
data-bind="options:$root.Employee.DepartmentList, optionsValue:'Id',
optionsText:'Name', value:Employee.DepartmentId">
</select>
</div>
<div>
<input type="button" id="btnSubmit"
value="Submit" data-bind = "click: submit" />
<input type="button" id="btnReset"
value="Reset" data-bind = "click: reset" />
</div>
</div>
</form>
视图
首先,我创建一个视图。它看起来像这样:
我使用 KO 的 data-bind 属性将视图的所有 html 表单元素绑定到我的客户端模型。为什么我说客户端模型?因为我也有一个服务器端模型。稍后我将讨论服务器端模型。通过 Data-Bind 属性,KO 将 UI 元素连接到模型的 Model
属性。现在,我将逐一解释 UI 元素如何连接到模型的 Data Member
。
文本框绑定
<input type="text" id="txtFirstName"
name="txtFirstName" data-bind="value:Employee.FirstName" />
Html 表单元素文本框绑定到 Employee
模型的 FirstName
属性。value
是 KO 的关键字,KO 通过它来寻址文本框的值属性。但请记住,data-bind 属性中的 value 是 KO 的关键字,而不是 HTML 原生的。当文本框的值更新时,Employee
模型的 FirstName
属性将自动更新,反之亦然。无需自定义代码。
复选框绑定
<input type="checkbox" value="graduation"
id="chkGraduation" name="chkGraduation" data-bind="checked:Employee.EducationList" />
<input type="checkbox" value="postGraduation" id="chkPostGraduation"
name="chkPostGraduation" data-bind="checked:Employee.EducationList" />
HTML 表单元素复选框(input checkbox)绑定到 Employee
模型的 EducationList string
数组。这里,两个复选框绑定到同一个模型属性。当用户勾选任何一个/两个复选框时,复选框的值(第一个复选框的值是“graduation
”,第二个是“PostGraduation
”)将自动添加到数组中。如果取消勾选,则该值将从此数组中删除。在 textbox
中,我们使用 KO 的关键字 value;在复选框中,我们使用另一个 KO 关键字 named checked
。再次强调,checked 不是任何 HTML 原生的。
单选按钮绑定
<input type="radio" id="rdoMale" name="gender"
value="0" data-bind="checked:Employee.Gender" />
<input type="radio" id="rdoFeMale" name="gender"
value="1" data-bind="checked:Employee.Gender" />
有两个单选按钮,并通过 name
属性将它们分组。如果单选按钮的 name 相同,则它们像组一样工作。如果选中一个单选按钮,则其他单选按钮将自动取消选中,反之亦然。两个单选按钮都绑定到单个 Employee
模型属性 named Gender
(数字类型)。当选择“Male”时,此属性将包含 0
;如果选择“FeMale”,则包含 1
。同样,当 Gender
包含 0 时,rdoMale
单选按钮将被选中;当 Gender
包含 1 时,rdoFemale
单选按钮将被选中(checked)。
下拉列表/组合框/选择绑定
<select id="ddlDepartment" name="ddlDepartment"
data-bind="options:$root.Employee.DepartmentList, optionsValue:'Id',
optionsText:'Name', value:Employee.DepartmentId">
</select>
下拉列表绑定到两个 Model
属性。对于其数据源,它绑定到 Employee
模型的 DepartmentList
对象数组。对象有两个属性:Id
和 Name
。Id
绑定到 value,Name
绑定到 option
的文本。也就是说,下拉列表将显示所有对象的 Name。下拉列表选定的值绑定到 DepartmentId
属性。当用户从下拉列表中选择任何一项时,该项的值将存储在 DepartmentId
中。同样,如果任何值从 ViewModel
分配给 DepartmentId
,则下拉列表将向用户显示与该值匹配的项。
按钮/命令按钮绑定
<input type="button" id="btnSubmit"
value="Submit" data-bind = "click: submit" />
按钮通过 click
事件绑定到 ViewModel
的 submit
方法。当用户单击 Submit 按钮时,ViewModel
的 submit
方法将触发。
KO 中的数据绑定使用了许多关键字。它们都是 KO 自有的关键字。需要明确的是,它们不是 HTML 原生的属性。
模型
function Employee() {
var that = this;
that.FirstName = ko.observable("");
that.LastName = ko.observable("");
that.FullName = ko.computed(function () {
return that.FirstName() + " " + that.LastName();
});
that.DateOfBirth = ko.observable("");
that.EducationList = ko.observableArray();
that.Gender = ko.observable("0");
that.DepartmentList = ko.observableArray([{ Id: '0', Name: "CSE" }, { Id: '1', Name: "MBA" }]);
that.DepartmentId = ko.observable("1");
}
Employee
是我的 Model
。我们可以称之为数据模型。在其每个属性中,我都使用 ko.observable
方法进行初始化。ko.observable
方法实际返回什么?它返回一个函数,该函数会通知 UI 元素进行更新。所以,在 Employee
的属性如 FirstName
、LastName
等中,它们都是函数。如果我想给 FirstName
赋一个值,那么代码应该是 that.FirstName("Mr.")
;如果读取该属性,那么它应该是 that.FirstName()
。computed 是另一个 ko
库函数,负责依赖项跟踪。也就是说,在该函数中,我使用了 FirstName
和 LastName
属性。当 FirstName
或 LastName
属性更新时,fullName
会自动更新。
KO 还有另一个重要之处,那就是 Observable Array。它只不过是可观察方法的一个集合。数组中的每个项都是可观察的。如果我想向该数组添加值,那么我需要使用:
that.DepartmentList.push({Id:3, Name:"BBA"});
如果我想删除数组的最后一个项,那么:
that.DepartmentList.pop();
可能会让人困惑的是,push
和 pop
是 JavaScript 数组的原生方法。但这里,使用可观察数组的 push
和 pop
并不是 JavaScript 的原生方法。实际上,这是 KO 自己实现的方法。KO 为可观察数组实现了 6 种方法。
push
pop
unshift
Shift
reverse
sort
所以要弄清楚这一点,不要将 JavaScript 数组对象的方法与之混淆。
ViewModel
function EmployeeVM() {
var that = this;
that.Employee = new Employee();
that.reset = function () {
that.Employee.FirstName("");
that.Employee.LastName("");
that.Employee.DateOfBirth("");
};
that.submit = function () {
var json1 = ko.toJSON(that.Employee);
$.ajax({
url: '/Employee/SaveEmployee',
type: 'POST',
dataType: 'json',
data: json1,
contentType: 'application/json; charset=utf-8',
success: function (data) {
var message = data.Message;
}
});
};
};
that.Employee
存储了 Employee
模型的引用。这里,我实现了 2 种行为:
reset
:reset
方法将重新初始化模型的值。submit
:将Employee
模型数据转换为 jsonstring
,并借助 JQuery Ajax 方法将其发送到服务器的SaveEmployee
Action 方法。
Controller Action
[HttpPost]
public ActionResult SaveEmployee(Employee emp)
{
//EmployeeModel save code here
return View("EmployeeInfo");
}
这是 Employee
控制器 Action 方法。客户端视图模型的 submit
方法将数据发送到此 Action
方法。实际上,此 Action
从客户端视图接收数据并处理该数据,然后将该数据保存到数据库,或者在发生任何无效情况时抛出异常。
服务器端模型
public class Employee
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime DateOfBirth { get; set; }
public Gender Gender { get; set; }
public IList<string> EducationList { get; set; }
public int DepartmentId { get; set; }
}
public enum Gender
{
Male =0,
FeMale = 1
}
这是我的服务器端 Employee
模型。为什么需要服务器端模型?我们看到,从客户端部分我们创建了一个 Employee ViewModel
。但应该记住的是,在客户端,我们实际上并没有编写任何业务逻辑。我的应用程序处理业务逻辑。那么我将它写在哪里呢?为了处理服务器端业务逻辑,我们创建了服务器端模型,并将该模型发送到各种层,如域服务层/存储库层等。
示例 2
我将创建一个 Employee
电话录入表单。Employee
可能有多个电话号码,并带有国家代码。首次,视图将显示添加单个电话号码录入选项。如果用户需要,那么如果他按下 Add 按钮,它将动态地为用户界面创建另一个电话号码录入选项。用户也可以通过按下 Remove 按钮来删除任何电话号码录入选项。因此,我们可以理解动态 UI 渲染功能将在这里出现。让我们看看 Knockout 将如何帮助我们。
实现
视图
首先,我将完成视图创建部分。视图如下:
<form id="employeeForm" name="employeeForm" method="POST">
<script id="PhoneTemplate" type="text/html">
<div>
<span>
<label>Country Code:</label>
<input type="text" id="txtCountryCode" data-bind="value:CountryCode" />
</span>
<span>
<label>Phone Number:</label>
<input type="text" id="txtPhoneNumber" data-bind="value:PhoneNumber" />
</span>
<input type="button" id="btnRemove"
value="Remove" data-bind="click: $parent.remove" />
</div>
</script>
<div>
<h2>Employee Phone Number</h2>
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}, visible:isVisible">
</div>
<div>
<input type="button" id="btnAdd"
value="Add Another" data-bind="click: add" />
</div>
</div>
</form>
KO 支持数据模板。它开发了用于渲染模板的模板引擎。这也可以与 jQuery 模板一起工作。我创建了一个名为 PhoneTemplate
的模板。
<script id="PhoneTemplate" type="text/html"> </script>
我使用 script
标签声明了 type="text/html"
。这意味着我通知浏览器它只是文本数据,您不会执行它。在该 script
块内,我创建了一个 div
。在该 div
内,我创建了 2 个 html 文本框、2 个标签和 1 个按钮。
在 script 块之外,我创建了另一个 div
,并将其绑定到我的 JavaScript viewmodel
的数组成员 PhoneList
。KO 有自己的绑定到模板的语法。在这里,在 data-bind 属性内部,我声明了:
<div data-bind="template:{name:'PhoneTemplate', foreach:PhoneList}">
</div>
它绑定到一个名为 'Phonetemplate
' 的模板,并使用 foreach
语句。这意味着将此引用的模板渲染的次数等于 PhoneList
中的项数。如果我动态地向 PhoneList
添加项,那么模板将再次使用新 UI 进行渲染;同样,如果我自动从该数组中删除项,则渲染项时显示的 UI 元素将删除。
模型
function Phone(code, number) {
var self = this;
self.CountryCode = ko.observable(code);
self.PhoneNumber = ko.observable(number);
}
客户端 JavaScript 模型有 2 个属性:
CountryCode
PhoneNumber
每个属性都使用可观察方法进行初始化,以便它可以与 KO 一起工作。这两个属性都在模板中使用。
ViewModel
function PhoneViewModel() {
var self = this;
self.PhoneList = ko.observableArray([new Phone("00", "00-00-00")]);
self.remove = function () {
self.PhoneList.remove(this);
};
self.add = function () {
self.PhoneList.push(new Phone("01", "00-00-01"));
};
}
PhoneViewModel
通过 data-bind 属性建立 Phone View 和 Phone Model 之间的连接。该 ViewModel
包含 2 个方法:
加法
移除
当用户按下“Add Another”按钮时,它将创建一个具有默认值的 Phone Model,并将其添加到 PhoneList
数组中。当 PhoneList
数组添加新项时,UI 会自动创建一个基于设计模板的新 div
。
当用户按下“Remove”按钮时,它将调用 ViewModel
的 remove
方法,并从 PhoneList
数组中删除当前项。当从数组中删除一个项时,UI 会自动重新填充。
视图与视图模型的关联
当您开始在 Web 应用程序中实现 MVVM 时,您可能会面临一个问题:一个视图需要多少个 ViewModel
,反之亦然。这意味着视图和模型/ViewModel 需要什么样的关系。
市场上存在一些观点:
- 一个视图对应一个视图模型
- 多个视图对应多个视图模型
- 一个视图对应多个视图模型
这里没有硬性规定。许多人(包括我)支持每个视图有一个视图模型,每个视图模型有一个模型。这将有助于我们保持代码更简单、更易于管理。
如果存在可重用性方面的顾虑,那么我认为您可以使用多个视图配合一个 ViewModel
。
关注点
Knockout js 是我们在 Web 应用程序中使用的库。因此,我们需要充实我们对该库的知识,以便我们能够顺利地在任何 Web 项目中使用它。
我也非常喜欢 angular js。许多 SPA(单页应用程序)也使用此库。我将在下一篇文章中介绍该库。
源代码
我在此文章中上传了一个示例项目。这是一个 ASP.NET 4.5 版本的 MVC 项目,使用 Visual Studio 2012。如果您是 WebForm 开发者,那么只需下载代码并将其添加到您的 WebForm 项目中,并稍微自定义服务器端代码即可。将 Web 方法替换为 ASP.NET Action
方法,它将完美运行。Knock-out 不与任何项目模板(如 MVC 或 WebForm 或任何其他)紧密耦合。在所有地方,您都可以使用它。