Magento 2 - Alan Storm 博客 - KnockoutJS 入门指南

6.61K 浏览翻译

Magento 2 - Alan Storm 博客 - KnockoutJS 入门指南

这是速成课程,意在让工作中的 Magento 开发者熟悉 KnockoutJS 的基本概念,着重于 Magento 用到的功能特点。如果你打算使用 KnockoutJS 构建任何实质性的内容,我们强烈建议通读KnockoutJS 官方教程


初始 Model,View,View Model

最快理解 KnockoutJS 的方法是通过基本例子。首先,让我们来新建下面这个 HTML 页面。

<!-- File: page.html -->  
 <!DOCTYPE html> <html> <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>

 这个页面:

  • 从 cloudflare CDN 加载 KnockoutJS 库
  • 从 code CDN 加载 jQuery 库
  • 建立一个空 DOM 节点结构

你没必要一定从 CDN 加载 jQuery 和 KnockoutJS,但这是做对这个教程来说是最简单。如果你在浏览器中加载这个页面,它是完全空白的。那是因为我们需要:

  1. 添加创建一个 view model 的 Javascript 代码,并且应用 KnockoutJS 绑定
  2. 给读取 view model 的 HTML 页面添加 view 代码

解决第一个,我们给页面添加第三个 Javascript 文件。按照以下内容创建一个名为 ko-init.js 的文件。

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

并且把 ko-init.js 作为第三个 Javascript 添加到页面中。

<!-- 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>

最终,改变 h1 和 p 标签以让他们带有 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 。


刚发生了什么

KnockoutJS 宣称自己是 “MVVM” 系统,即 Model,View,View Model 。尽管,最好说 KnockoutJS 是一个 VVM 系统。因为它跟用来获取数据的 model 代码跟无关。View 是你的 HTML 页面。View Model 是包含 Javascript 对象。看一下这个 Javascript 代码:

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

虽然jQuery 不是必须的。但在整个 documen t或者 DOM 加载之前KnockoutJS 不能开始渲染 view ,并且 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 属性。当它找到这些属性的时候,它会解析这个属性上以获取 name 和 value ,然后根据绑定的 name 调用一套规则。比如,上面提到的绑定时 text 绑定。我们传入 text 绑定的值是 title 。text 绑定应用的一组规则是“使用传入绑定的 value 来从 view model 对象中获取值,并且把获取的值作为 text 节点添加到页面中”。如果用纯 Javascript 写出来,最终结果是可能是这样的:value = viewModel['title']; textNode = document.createTextNode(value); h1.appendChild(textNode); KnockoutJS 的第一招是让开发者摆脱直接使用 Javascript 来创建和更新 DOM 节点。反而可以写HTML,用 data-bind 属性进行标记,然后只是把值分配给一个对象。你也不要局限于键值对。考虑一下更复杂的 view model 。

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);
});

这里,我们使用了一个 Javascript 构造函数创建了一个带有 getTitle 方法的简单对象。如果我们改变 view 来调用 getTitle 方法,你会看到它会如你想要的那个:

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

另一种考虑绑定参数的方式是他们是临时的有限的访问 view model 值和方法的 Javascript 作用域。


其他的绑定

虽然这个例子简单,但是你可以开始看到这个基本的构造是如何执行更复杂的视图逻辑的。data-bind 实现了更新 DOM,纯非 DOM 的 Javascript 代码实现了 model 的更新。你将会看到其他的绑定。比如,我们在 viewModelConstructor 添加一个属性 theVaule 。

//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);         
});

然后,添加一个带有新绑定的 input 标签。

<!-- 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>

刷新页面,你会看到一个值为 2 的 HTML input 标签:

这里,我们用了一个新的 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 设置了值。

虽然这个例子简单,但是这背后的概念却不简单。不需要改变任何 Javascript 应用的代码,这个 value 绑定就可以改变 UI 。


Observables

到现在为止,我们所看到的是一个强大的简单的把戏。简洁,也许也很有用,但是它只是给  KnockoutJS 真正的 “重量级”功能 —— observables 奠定了基础。我们将再一次用一个例子开始。按照以下内容改变你的 view :

<!-- 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>       
 <button id="button">Do It</button>

按照以下内容改变 view model 和绑定:

//File: ko-init.js 
jQuery(function(){
     var viewModelConstructor = function() {
                this.theValue = ko.observable("1");     
     }     
     window.viewModel = new viewModelConstructor;     
     ko.applyBindings(window.viewModel);         
});

如果重新加载页面,你可以看到我们绑定在 input 和 p 标签上的值。到现在为止,view 没什么新鲜的东西,和之前做到绑定一样。然而,你可能注意到了,在 view model 中有一些不同的。

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

没有给 theValue 设置一个值或者自定义的方法,而是设置了一个 KnockoutJS 叫做 observable 的一个值。Observable 是特殊的,某种程度上是设值函数和取值函数。如果你打开 Javascript 控制台,输入以下内容,你可以通过把它当函数调用来获取 observable 的值(viewModel 在控制台是一个变量,因为我们把定义为 window 对象下的全局对象)

> viewModel.theValue()     
> "1"

我们可以通过传入一个参数给 observable 设置一个值。以下是如果设置和获取 observable 的值:

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

然而,observable 真正的作用是如果作用于绑定了 observable 的 DOM 节点。通过控制台改变 observable 的值,然后观察浏览器:

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

当你更新 observable 的值的时候,绑定的节点的值同时也在改变。有一次,作为开发者,我们不用担心 DOM 节点如何更新了,一旦我们在 model 中设置了值,这个值自动反映在用户界面。虽然这个不再本文范围内,但是你仍然可以看到当 view model 中包含函数的时候他们是如何形成复杂的 Javascript 应用的。

//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>

把对于这个的解析作为练习留给读者。


模板绑定

另一个对于理解来说很重要的绑定是 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 。如果这样的 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>

你将会看到:

把 Javascript 对象作为模板绑定的参数

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

 这个数据参数是我们想要用来渲染的 view model 的属性。模板的名字 —— 是要找到和渲染的模板的名字。给系统添加命名模板的最基本的方法是添加一个带有 type 是 text/html 的 <script/> 标签。

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

    如果之前没见过这个,它可能看起来比较奇怪,但是很多现在的 Javascript 框架使用不带 text/javascript 的 <script/> 标签作为给页面添加不渲染的内容(但是 DOM 可以获取到)的一种方式。模板就是带有 KnockoutJS 绑定的标准的 HTML 节点的集合。


组件

最后一个要讲的绑定是组件绑定。组件是把 KnockoutJS 模板和 KnockoutJS view 文件打包到一起的一种方式。这意味着你可以有一个相对简单的 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();        
});

 组件对象中的 register 方法要求有你组件的名称和一个 KnockoutJS 组件对象。组件对象是一个带有两个属性的 Javascript 对象。viewModel 属性要求是一个 view model 构造函数,template 属性应该是一个带有 KnockoutJS 模板的字符串。一旦注册,你可以通过把组件的名字传入绑定中来使用你的组件。

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

如果你不想使用 data-bind 这个语法,KnockoutJS 还提供了通过基于组件名字的自定义标签来插入组件。在你的 view 或者 HTML 文件中试试:

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

这只是 KnockoutJS 的皮毛。对于组件绑定,官方文档有很好介绍。


自定义绑定

今天我们讨论的 KnockoutJS 的最后一个功能是自定义绑定。KnockoutJS 让开发者可以创建他们自己的绑定。比如,我们调用一个名为 pulseStormHelloWorld 的自定绑定,并且把属性 message 的值传给它。

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

没有其他操作,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 对象的 binidngHandlers 对象添加一个属性。属性的名字就是绑定的名字。handler 是一个带有 update 方法的 JS 对象。绑定一被调用,KnockoutJS 就调用 update 方法 —— 不是在 applyBindings 中被调用,就是是 observable 中被调用。完成以上操作,重新加载你的 HTML 页面你会看到自定义绑定执行了。当然,这是一个无关紧要的例子,但是,用自定绑定可以让 KnockoutJS 做任何你想用程序实现的。如果你有兴趣学习更多,KnockoutJS 核心文档对自定义绑定有不错的教程。


结束

KnockoutJS 是很强大的、先进的 Javascript 框架。他的语义化、对于传统 HTML 观念的颠覆可能让让很多开发人员开始的时候会胆怯。但是,在现代的 Javascript 应用中,作为组织和对付 DOM 复杂性的工具 KnockoutJS 少有对手。这里的教程不是完整的,但是有帮助的,足够让你对了解学习 KnockoutJS 的官方教程感兴趣。总而言之,KnockoutJS 通过它自己不足以构建一个完整的 Javascript 应用。接下来我们将会看一下 Mangento 围绕 KnockoutJS 构建的框架 —— 这样你既可以更好的理解 Magento 系统是如何工作的,也可以把 KnockoutJS 应用到你自己的模块和应用中去。

2