65.9K
CodeProject 正在变化。 阅读更多。
Home

Knockout js 入门

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (99投票s)

2013年11月11日

CPOL

11分钟阅读

viewsIcon

309289

downloadIcon

9350

本文将帮助初学者理解 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 个部分:

  1. 模型
  2. 视图
  3. ViewModel

模型:负责存储应用程序数据。当用户通过 UI 元素输入数据时,这些数据将存储到模型中。所以我们可以认为它是模式中的数据部分。

视图:负责将模型数据呈现给用户。用户元素是视图的一部分。它提供了用户界面的结构/布局并将其呈现给用户。

视图模型:模型和视图的连接器。主要职责是建立与视图和模型的通信。它包含数据和函数。函数会操作这些数据,并反映到 UI 上。当数据发生变化时,UI 会发生变化;当 UI 发生变化时,数据也会发生变化。视图模型将借助数据绑定概念为我们完成这一切。

MVVM 的好处

MVVM 的主要好处列举如下:

  • 为设计师和开发人员的工作提供更大的灵活性。
  • 彻底的单元测试。
  • 提供更改用户界面的灵活性,而无需重构代码库中的其他逻辑。
  • 提供更高的 UI 组件可重用性。

将 KO 库包含到 Web 应用程序中

Knockout js 的 CDN(内容分发网络)引用可在 Microsoft CDN 存储库中找到。有两个版本可用。一个用于压缩(最小化),另一个用于调试。建议使用最小化版本。URL 如下:

如果您的应用程序是 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 对象数组。对象有两个属性:IdNameId 绑定到 value,Name 绑定到 option 的文本。也就是说,下拉列表将显示所有对象的 Name。下拉列表选定的值绑定到 DepartmentId 属性。当用户从下拉列表中选择任何一项时,该项的值将存储在 DepartmentId 中。同样,如果任何值从 ViewModel 分配给 DepartmentId,则下拉列表将向用户显示与该值匹配的项。

按钮/命令按钮绑定

<input type="button" id="btnSubmit" 
value="Submit" data-bind = "click: submit" />  

按钮通过 click 事件绑定到 ViewModelsubmit 方法。当用户单击 Submit 按钮时,ViewModelsubmit 方法将触发。

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 的属性如 FirstNameLastName 等中,它们都是函数。如果我想给 FirstName 赋一个值,那么代码应该是 that.FirstName("Mr.");如果读取该属性,那么它应该是 that.FirstName()。computed 是另一个 ko 库函数,负责依赖项跟踪。也就是说,在该函数中,我使用了 FirstNameLastName 属性。当 FirstNameLastName 属性更新时,fullName 会自动更新。

KO 还有另一个重要之处,那就是 Observable Array。它只不过是可观察方法的一个集合。数组中的每个项都是可观察的。如果我想向该数组添加值,那么我需要使用:

that.DepartmentList.push({Id:3, Name:"BBA"}); 

如果我想删除数组的最后一个项,那么:

that.DepartmentList.pop(); 

可能会让人困惑的是,pushpop 是 JavaScript 数组的原生方法。但这里,使用可观察数组的 pushpop 并不是 JavaScript 的原生方法。实际上,这是 KO 自己实现的方法。KO 为可观察数组实现了 6 种方法。

  1. push
  2. pop
  3. unshift
  4. Shift
  5. reverse
  6. 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 种行为:

  1. resetreset 方法将重新初始化模型的值。
  2. submit:将 Employee 模型数据转换为 json string,并借助 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 个属性:

  1. CountryCode
  2. 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 个方法:

  1. 加法
  2. 移除

当用户按下“Add Another”按钮时,它将创建一个具有默认值的 Phone Model,并将其添加到 PhoneList 数组中。当 PhoneList 数组添加新项时,UI 会自动创建一个基于设计模板的新 div

当用户按下“Remove”按钮时,它将调用 ViewModelremove 方法,并从 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 或任何其他)紧密耦合。在所有地方,您都可以使用它。

参考文献

© . All rights reserved.