KnockoutComputed< {}>退回&添加"写入时观察到的行为差异:"输入到ko.computed()

时间:2014-04-08 06:13:48

标签: knockout.js typescript

我正在尝试学习Typescript和Knockout,并开始观看有关Pluralsight的在线课程。当我遇到一些我不了解并且无法找到帮助的行为时,我正在玩这些课程材料。如果我遗漏了一些明显的东西或者没找对地方,请告诉我。否则,我希望这也能帮助那些有这个问题的人。

在我的打字稿类中,如果我这样做 -

this.extendedPrice = ko.computed(function () {
        return this.product()
             ? this.product().salesPrice() * 0.8 + parseFloat(this.product().salesPrice())
             : 0;
        }, 

OR

this.extendedPrice = ko.computed({
        read: function () {
            return this.product()
                 ? this.product().salesPrice() * 0.8 + parseFloat(this.product().salesPrice())
                 : 0;
        },
        owner: this,
     });

一切正常,我可以使用其中任何一个绑定 -

    <input data-bind="visible:product, value:extendedPrice"/>

OR

    <input data-bind="visible:product, value:extendedPrice()"/>

但是一旦我介绍&#34;写:&#34;进入上面的

this.extendedPrice = <KnockoutComputed<number>>ko.computed({
            read: function () {
                return this.product()
                     ? this.product().salesPrice() * 0.8 + parseFloat(this.product().salesPrice())
                     : 0;
            },

            write: function (value: string) {
                var num = parseFloat(value.replace(/[^\.\d]/g,""));
                num = isNaN(num) ? 0 : num;
                var unitPrice = num / this.quantity();
                this.product().salesPrice(unitPrice);
            },

            owner: this,
        });

我观察到两件我不理解的事情 -

  1. 我无法使用<input data-bind="visible:product, value:extendedPrice()"/> 了。绑定仅在页面加载后最初起作用,此后salesPrice中的更改不会反映在extendedPrice中。只有<input data-bind="visible:product, value:extendedPrice"/>按预期工作。

  2. 我在this.extendedPrice = ko.computed(....语句中收到错误,除非我使用<KnockoutComputed<number>>进行投射(如上面的代码所示)。错误说 -

    "Cannot convert KnockoutComputed<{}> to KnockoutComputed<number>. Types of property 'peek' of types KnockoutComputed<{}> and KnockoutComputed<number> are incompatible:Call signatures of types '()=>{}' and '()=>number' are incompatible.(property)guitarsalesportalmodule.LineItem.extendedPrice: KnockoutComputed<number>"

  3. 我正在使用Knockout v3.1.0。

    我想理解为什么Knockout在添加&#34;写的功能时会这样做:&#34;。

    谢谢。

    编辑1

    抱歉,我之前应该提到我已经尝试过lambda而不是函数,因为我读过很多文章都提到了lambda如何处理this。但是,它并不适合我。

    我在这种情况下观察到的问题 -

    1. 我仍然需要使用ko.computed(....)投射<KnockoutComputed<number>>

    2. <input data-bind="visible:product, value:extendedPrice"/>适用于更新salesPrice导致新的extendedPrice反映(read:部分),反之亦然(write:部分)的方式,但<input data-bind="visible:product, value:extendedPrice()"/>只有read:部分似乎有用

    3. 这是我的TS和HTML的样子。

      TS

      ///<reference path="data.ts" />
      ///<reference path="../typedefinitions/jquery.d.ts" />
      ///<reference path="../typedefinitions/knockout.d.ts" />
      
              module guitarsalesportalmodule {
          export class Mod {
              constructor(public Name: string, public Id: number) { }
          }
      
          export class Cat {
              constructor(public Name: string, public Id: number) { }
          }
      
          export class Product {
              salesPrice: KnockoutObservable<number>;
      
              constructor(public modelId: number, sp: number, public listPrice: number, public rating: number, public photo: string, public description: string, public model: Mod, public category: Cat) {
                  this.salesPrice = ko.observable(sp);
              }
          }
      
          export class LineItem {
              product: KnockoutObservable<Product>;
              quantity: KnockoutObservable<number>;
              extendedPrice: KnockoutComputed<number>;
      
              constructor(product: Product, quantity: number) {
                  this.product = ko.observable(product);
                  this.quantity = ko.observable(quantity);
                  this.extendedPrice = <KnockoutComputed<number>>ko.computed({
                      read: ()=>{
                          return this.product()
                              ? this.product().salesPrice() * 0.8 + parseFloat(this.product().salesPrice().toString())/*Just leaving it as this.product().salesPrice() results in it being considered string and getting concatenated. Doing parseFloat(this.product().salesPrice().toString()) instead of parseFloat(this.product().salesPrice()) strangely doesn't work either saying it's not a string but a number(doesn't happen when I use function instead of ()=>).*/
                              : 0;
                      },
      
                      write: (value: string) => {
                          var num = parseFloat(value.replace(/[^\.\d]/g,""));
                          num = isNaN(num) ? 0 : num;
                          var unitPrice = num / this.quantity();
                          this.product().salesPrice(unitPrice);
                      },
      
                      owner: this,
                  });
              }
          }
      
          export class Vm {
              products: KnockoutObservableArray<Product>;
              lines: KnockoutObservableArray<LineItem>;
              grandTotal: KnockoutComputed<number>;
      
              constructor() {
                  this.loadProducts();
                  this.lines = ko.observableArray([new LineItem(undefined, undefined)]);
                  this.grandTotal = ko.computed(
                      () => {
                          var total = 0;
                          this.lines().forEach((line) => {
                              if (line.extendedPrice() && line.quantity()) {
                                  total += line.extendedPrice() * line.quantity();
                              }
                          });
                          return total;
                      },
                      this);
              }
      
              loadProducts() {
                  this.products = ko.observableArray([]);
                  $.each(data.Products, (i) => {
                      this.products.push(new Product(
                          data.Products[i].ModelId,
                          data.Products[i].SalePrice,
                          data.Products[i].ListPrice,
                          data.Products[i].Rating,
                          data.Products[i].Photo,
                          data.Products[i].Description,
                          data.Products[i].Model,
                          data.Products[i].Category));
                  });
              }
      
              addLineItem = () => {
                  this.lines.push(new LineItem(undefined, undefined));
              }
      
              removeLineItem = (line) => {
                  this.lines.remove(line);
              }
      
              formatCurrency(curr: string) {
                  return "$" + parseFloat(curr).toFixed(2);
              }
          }
      
          export var viewModel = new Vm();
      }
      
      //apply bindings
      window.onload = () => {
          ko.applyBindings(guitarsalesportalmodule.viewModel);
      };
      

      HTML

      <!DOCTYPE html>
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head>
          <title></title>
          <link href="../css/fonts.css" rel="stylesheet" />
          <link href="../css/styles.css" rel="stylesheet" />
          <script src="../scripts/jquery-2.1.0.js"></script>
          <script src="../scripts/knockout-3.1.0.js"></script>
          <script src="../scripts/data.js"></script>
          <script src="../scripts/guitarsalesportal.js"></script>
      </head>
      <body>
          <div class="showroom">
              <table>
                  <tr>
                      <th>Product</th>
                      <th>Price</th>
                      <th>Quantity</th>
                      <th>Extended Price</th>
                      <th>Remove</th>
                  </tr>
                  <tbody data-bind="foreach: lines">
                      <tr>
                          <td>
                              <select data-bind="options:$parent.products, value:product, optionsText:'description', optionsCaption:'Choose...'"></select>
                          </td>
                          <td data-bind="if:product">
                              <input data-bind="value:product().salesPrice"/>
                          </td>
                          <td>
                              <input data-bind="visible:product, value:quantity"/>
                          </td>
                          <td>
                              <input data-bind="visible:product, value:extendedPrice()"/>
                              <!--<input data-bind="visible:product, value:extendedPrice"/>-->
                          </td>
                          <td>
                              <a href="#" data-bind="click: $parent.removeLineItem">Remove</a>
                          </td>
                      </tr>
                  </tbody>
              </table>
              <a href="#" data-bind="click: addLineItem">Add</a>
              <div>Grand Total: <span data-bind="text:formatCurrency(grandTotal()), valueUpdate:'afterkeydown'"></span></div>
          </div>
      </body>
      </html>
      

      谢谢。

2 个答案:

答案 0 :(得分:1)

最有可能this是错误的。尝试使用lambda而不是function

 this.extendedPrice = ko.computed( () =>  { // Notice () => 
    return this.product()
         ? this.product().salesPrice() * 0.8 + parseFloat(this.product().salesPrice())
         : 0;
    }, 

这将确保this独立于调用上下文。

答案 1 :(得分:1)

关于第2项:

    Types of property 'peek' of types [...] are incompatible

这是因为extendedPrice被声明为number,但写入块中的value参数是string。如果您将其更改为:

,则此错误应该消失
    write: (value: number)