KnockoutJS Primer for Magento Developers (翻译)

原文地址

在我们可以继续探索 Magento 的 js 高级特性之前,我们得先补习 KnockoutJSKnockoutJS 是一个 javaScript MVVM 系统,他是 Magento 2 中的主要 DOM 操作框架。

本篇教程旨在帮助 Magento 开发者熟悉 KnockoutJS 的基本概念,重点介绍 Magento 使用的一些 KnockoutJS 功能。如果你打算用 KnockoutJS 创建东西的话,强烈建议你参阅KnockoutJS 官方 tutorials

Hello Model,View,View Model

首先,让我们来创建下面的 HTML 页面

<!-- File: page.html -->
<!DOCTYPE html>
<html lang="en">
<head>
    <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
    <script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script>
</head>
<body>
<div id="main">
    <h1></h1>
    <p></p>
</div>
</body>
</html>

这个页面:

  1. 从 CDN 加载 KnockoutJS 库
  2. 从 CDN 加载 JQuery 库
  3. 设置了一个空的 DOM 结构

你不需要从 CDN 加载 jQuery 和 KnockoutJS,不过这样做比较容易。

如果你用浏览器打开这个页面,你会看到一个空白页。因为你得:

  1. Add the javascript code that creates a view model and applies the KnockoutJS bindings
  2. Add the view code to the HTML page that reads from the view model

我们先来做第一个,创建一个名为ko-init.js的文件

//File: ko-init.js
jQuery(function(){
    viewModel = {
        title:"Hello World",
        content:"So many years of hello world"
    };
    ko.applyBindings(viewModel);
});

然后在我们的页面中引入这个js 文件

<!-- File: page.html -->
<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.0/knockout-min.js"></script>
<script src="https://code.jquery.com/jquery-3.0.0.min.js" integrity="sha256-JmvOoLtYsmqlsWxa7mDSLMwa6dZ9rrIdtrrVYRnDRH0=" crossorigin="anonymous"></script>
<script type="text/javascript" src="ko-init.js"></script>

最后,修改h1p标签,给他们添加如下的data-bind属性

<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:title"></h1>
    <p data-bind="text:content"></p>
</div>

现在刷新页面,你应该会看到:

恭喜你!你成功创建了第一 KnockoutJS view model 和 view

What Just Happened

KnockoutJS 本身是 MVVM系统,MVVM 代表着 Model,View,View Model。Really though, KnockoutJS is better billed as a VVM system, since its agnostic about what sort of model code you use to fetch data. view 是指 HTML 页面,view model 是指带数据的 js 对象。

让我们来看看js 代码:

//File: ko-init.js
jQuery(function(){
    viewModel = {
        title:"Hello World",
        content:"So many years of hello world"
    };
    ko.applyBindings(viewModel);
});

jQuery 并非必要的,只不过 KnockoutJS 不能在 DOM 还没有完成加载前进行渲染,jQuery 的 document ready 函数正可以帮助我们达到目的。jQuery();相当于$(document).ready();

这里我们创建了一个带键值对的 view model

//File: ko-init.js
viewModel = {
    title:"Hello World",
    content:"So many years of hello world"
};

然后我们应用了 KnockoutJS 的绑定。换一种说法是我们让 KnockoutJS 用 view model 去渲染 view。再说一遍,view 是整个 HTML 页面。

我们来看看 view 部分,注意data-bind属性

<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:title"></h1>
    <p data-bind="text:content"></p>
</div>

当你调用applyBindings时,KnockoutJS 会扫描整个 HTML 页面寻找data-bind属性。it parses the attribute for the binding name and value, and then invokes a set of rules based on the name of the binding.

举例来说,上面我们调用的绑定是text,我们传递给text绑定的是title。text 绑定应用的规则是:“从 view model 对象中使用传递过来的值作为键取出对应的值,然后将该值插入到 DOM 中,作为一个 text 节点。”如果用纯 js 来表示的话,就是:

value = viewModel['title'];
textNode = document.createTextNode(value);
h1.appendChild(textNode);

KnockoutJS 的第一个招数是他使开发者免于自己使用 js 创建和更新 DOM 节点。开发者通关过书写带有data-bind属性的 HTML 标签来给他赋值。你不仅可以使用键值对,看下面这个更复杂的 view model。

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.getTitle = function()
        {
            return "Hello Method World";
        }
       this.content = "So many years of hello world";
    }

    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);
});

这里我们使用了 js 构造函数创建了一个简单的对象。这个对象有一个getTitle方法。如果我们修改下page.html来调用getTitle方法,你会发现效果和预期的一样。

<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
</div>

Another way of thinking about binding parameters is they’re a temporary, limited javascript scope to access your view model’s values and methods.

Other Bindings

虽然这个例子比较简单,但是从中可以开始了解到,这种简单的构建 block 是如何实现更为复杂的视图逻辑的。(While this example is simple, you can start to see how this basic building block could implement far more complicated view logic)更新DOM的事交给数据绑定来做,更新模型的事情交给non-DOM js 代码来完成。

再举个例子了解下其他的绑定。

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.getTitle = function()
        {
            return "Hello World";
        }
       this.content = "So many years of hello world";
       this.theValue = "2";
    }
    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);
});
<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
    <input type="text" data-bind="value:theValue"/>
</div>

刷新页面,你会看到:

这里我们使用了一个新的 KnockoutJS 绑定

data-bind="value:theValue"

我们使用value绑定将值赋给表单字段。下面让我们把 input 换成 select 看看

<!-- File: page.html -->
<div id="main">
    <h1 data-bind="text:getTitle()"></h1>
    <p data-bind="text:content"></p>
    <select data-bind="value:theValue">
        <option value="">-- Choose --</option>
        <option value="1">First</option>
        <option value="2">Second</option>
        <option value="3">Third</option>
    </select>
</div>

刷新页面后,你会看到已经设置 select 的值为2了。

虽然这个例子很简单,背后的概念可不简单。不需要修改任何 js 程序代码,value binding 就可以让我们更改 UI

Observables

到目前为止,我们看到还只是小招数,已经很强大了。Neat, maybe useful, but it only sets the stage for KnockoutJS’s real “knockout” feature — observables.

下面,我们还是以例子开始。

<!-- File: page.html -->
<div id="main">
    <p data-bind="text:theValue"></p>
    <select data-bind="value:theValue">
        <option value="">-- Choose --</option>
        <option value="1">First</option>
        <option value="2">Second</option>
        <option value="3">Third</option>
    </select>
    <input type="text" data-bind="value:theValue"/>

    <div>
    <br/><br/>
    <button id="button">Do It</button>
    </div>
</div>
//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
       this.theValue = ko.observable("1");
    }

    window.viewModel = new viewModelConstructor;
    ko.applyBindings(window.viewModel);
});

重新载入页面,你会发现我们给<input/><p/>标签绑定了值1。到目前为止我们的页面没什么新变化——这和我们之前做的绑定一样。但是,你注意到在我们的 view model 中,我写了一点不一样的代码:

//File: ko-init.js
this.theValue = ko.observable("1");

我们没有给theValue硬编一个值或者是一个custom function,我们给他的是一个 KnockoutJS observableAn observable is a special sort of getter and setter.

打开浏览器 console ,输入viewModel.theValue(),你会发现我们可以像调用函数一样取得 observable 的值。(因为我们定义viewModel为全局对象window 的属性,所以我们可以通过 console 访问到他)

> viewModel.theValue()
> "1"

我们可以给 observable 传递参数来设置他的值。

> viewModel.theValue("3")
//...
> viewModel.theValue()
> "3"

However, the real power of an observable is in what happens to the DOM nodes we’ve bound that observable to。试着在console 中改变 observer 的值,注意页面的变化。

> viewModel.theValue("3");
> viewModel.theValue("2");
> viewModel.theValue("1");
> viewModel.theValue("10");

一旦你修改了 observable 的值,被绑定的 DOM 也立刻有了变化。作为开发者,我们再也不用关心怎么更新 DOM nodes 了。一旦我们给 model 中的值做了改变,他的值会立刻自动反应到用户界面上。

看下面一个view model 带方法的例子。

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.theValue = ko.observable("1");
        var that = this;
        this.pickRandomValue = function(){
            var val = Math.floor(Math.random() * (3));
            that.theValue(val);
        };
    }

    window.viewModel = new viewModelConstructor;
    ko.applyBindings(window.viewModel);
});

使用 KnockoutJS 的事件绑定(比如click

<!-- File: page.html -->
<button data-bind="click:pickRandomValue">Do It</button>

Template Binding

另一个要重点理解的是 KnockoutJS 的模板绑定。考虑下面的 view model

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.first = {
            theTitle:ko.observable("Hello World"),
            theContent:ko.observable("Back to Hello World")
        };
        this.second = {
            theTitle:ko.observable("Goodbye World"),
            theContent:ko.observable("We're sailing west now")
        };
    }

    viewModel = new viewModelConstructor;
    ko.applyBindings(viewModel);
});

这里我们创建了一个标准的view model 但是 data 对象是嵌套的。然后我么修改view

<!-- File: page.html -->
<div id="main">
    <div id="one" data-bind="template:{'name':'hello-world','data':first}"></div>

    <div id="two" data-bind="template:{'name':'hello-world','data':second}">
    </div>

    <script type="text/html" id="hello-world">
        <h1 data-bind="text:theTitle"></h1>
        <p data-bind="text:theContent"></p>
    </script>
</div>

你将会看到:

template 绑定接受一个 js 对象作为参数

<!-- File: page.html -->
<div id="one" data-bind="template:{'name':'hello-world','data':first}"></div>

data 参数是我们要渲染的 view model 的属性。name 是要查找和渲染的模板名称。

添加模板最基本的做法是添加<script type="text/html">并且给他命名。

<!-- File: page.html -->
<script type="text/html" id="hello-world">
    <h1 data-bind="text:theTitle"></h1>
    <p data-bind="text:theContent"></p>
</script>

If you’ve never seen this before it may seem weird/foreign, but many modern javascript frameworks use non-text/javascript <script/> tags as a way to add non-rendered (but DOM accessible) content to a page.模板是一些带有 KnockoutJS 绑定的 HTML 片段

Components

下面是组件绑定。 Components are a way to package together a KnockoutJS template, and a KnockoutJS view file. This means you can have a relatively simple view。

<!-- File: page.html -->
<div data-bind="component:'component-hello-world'"></div>

隐藏了注册了的组件的复杂性

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.message = "Hello World";
    }

    var theTemplate = "<h1 data-bind=\"text:message\"></h1>";

    ko.components.register('component-hello-world', {
        viewModel:viewModelConstructor,
        template:theTemplate
    });

    ko.applyBindings();
});

component 对象的register方法第一个参数接受组件的名称,第二个参数接受一个 KnockoutJS component 对象。component 对象是一个有两个属性的 js 对象。viewModel属性接受一个 view model 构造函数,template属性是 KnockoutJS 模板字符串。一旦完成组件注册,你就可以通过传递组件的名称来使用它了。

<!-- File: page.html -->
<div data-bind="component:'component-hello-world'"></div>

如果你不想用data-bind这样的语法,KnockoutJS 提供了另一种基于组件名称的自定义标签,试试看下面的代码:

<!-- File: page.html -->
<component-hello-world></component-hello-world>

这依然仅仅触及 KnockoutJS 的表面,官方文档对组件绑定讲得不错,可以参阅以下。

Custom Binding

KnockoutJS 允许开发者创建自定义绑定。举例来说,下面我们调用了名为pulseStormHelloWorld的绑定,并且将viewModel中的message属性的值传递给他。

<!-- File: page.html -->
<div data-bind="pulseStormHelloWorld:message"></div>

我们还没有实现pulseStormHelloWorld绑定,所以现在刷新页面是空白页。因为 KnockoutJS 忽略了它。下面修改ko-init.js

//File: ko-init.js
jQuery(function(){
    var viewModelConstructor = function()
    {
        this.message = "Hello World";
    }

    ko.bindingHandlers.pulseStormHelloWorld = {
        update: function(element, valueAccessor){
            jQuery(element).html('<h1>' + valueAccessor() + '</h1>');
        }
    };
    ko.applyBindings(new viewModelConstructor);
});

创建自定义的KnockoutJS绑定,我只需要给 ko.bindingHandlers对象添加一个属性。这个属性的名称就是我们自定义绑定的名称。The handler is a JS object with an update method.当绑定被唤起时,KnockoutJS 就会调用update方法—— 不管是applyBindings或是通过 observable

刷新页面,你会看到自定义绑定起作用了。当然,这只是个普通的例子,但是通过自定义绑定你可以让 KnockoutJS 做任何你想得到的事。

如果想了解更多关于KnockoutJS自定义绑定的,请移步官方custom binding tutorial

Wrap up

KnockoutJS 是一个强大现代的js框架。Its semantics, and disregard for traditional HTML concepts, may make some developers shy away at first, but as a tool of organizing and taming DOM complexity in a modern javascript application KnockoutJS has few peers.我们的教程是不完整的,但是希望足以引发你对KnockoutJS的兴趣,自己探索它的文档和教程。

KncokoutJS 本身不足以构成一个完整的 JS 应用程序。下周我们会探索 Magento 中与 KnockoutJS 密切相关的框架。这样会让你更了解 Magento 核心系统是如何工作的,还有怎样在你自己的模块和应用中应用 KnockoutJS。

下载

文中使用的源代码

发表评论

电子邮件地址不会被公开。 必填项已用*标注