代码之家  ›  专栏  ›  技术社区  ›  Jez

为什么要设置选项值break knockout更新?

  •  1
  • Jez  · 技术社区  · 6 年前

    我一直在学习淘汰赛的教程,当我在玩一个教程的时候,有些事情让我困惑。这是我的HTML:

    <h2>Your seat reservations</h2>
    
    <table>
        <thead><tr>
            <th>Passenger name</th><th>Meal</th><th>Surcharge</th>
        </tr></thead>
        <tbody data-bind="foreach: seats">
            <tr>
                <td><input data-bind="value: name" /></td>
                <td><select data-bind="options: $root.availableMeals, optionsValue: 'mealVal', optionsText: 'mealName', value: meal"></select></td>
                <td data-bind="text: formattedPrice"></td>
            </tr>    
        </tbody>
    </table>
    
    <button data-bind="click: addSeat">Reserve another seat</button>
    

    …这是我的javascript:

    // Class to represent a row in the seat reservations grid
    function SeatReservation(name, initialMeal) {
        var self = this;
        self.name = name;
        self.meal = ko.observable(initialMeal);
    
        self.formattedPrice = ko.computed(function() {
            var price = self.meal().price;
            return price ? "$" + price.toFixed(2) : "None";        
        });
    }
    
    // Overall viewmodel for this screen, along with initial state
    function ReservationsViewModel() {
        var self = this;
    
        // Non-editable catalog data - would come from the server
        self.availableMeals = [
            { mealVal: "STD", mealName: "Standard (sandwich)", price: 0 },
            { mealVal: "PRM", mealName: "Premium (lobster)", price: 34.95 },
            { mealVal: "ULT", mealName: "Ultimate (whole zebra)", price: 290 }
        ];    
    
        // Editable data
        self.seats = ko.observableArray([
            new SeatReservation("Steve", self.availableMeals[0]),
            new SeatReservation("Bert", self.availableMeals[0])
        ]);
    
        // Operations
        self.addSeat = function() {
            self.seats.push(new SeatReservation("", self.availableMeals[0]));
        }
    }
    
    ko.applyBindings(new ReservationsViewModel());
    

    当我运行此示例并从下拉菜单中为乘客选择不同的“用餐”时,“附加费”值为 已更新。原因似乎是我补充说的 optionsValue: 'mealVal' 进入 data-bind 的属性 select 当我删除时,“附加费”确实会在选择新的下拉选项时更新。但是为什么要添加 optionsValue 中断更新?只需设置 选择 列表的选项 value 属性,对于表单提交非常有用-我不明白为什么它应该阻止淘汰自动更新。

    更新: 经过进一步调查,我发现 formattedPrice FN仍在被呼叫,但是 self.meal() 正在解析为值字符串,例如 PRM 而不是整个用餐对象。但这是为什么?文件上说 选项值 设置 价值 属性,但对更改视图模型行为没有任何说明。

    我想当你指定的时候 options: $root.availableMeals ,但不要指定 选项值 ,knockout可以神奇地确定在更改选择时所做的列表中的选择,并使您能够访问 对象 availableMeals 而不仅仅是放入 价值 属性。这似乎没有很好的记录。

    2 回复  |  直到 6 年前
        1
  •  1
  •   user3297291    6 年前

    我想你知道发生了什么,为什么它会破坏你的代码,但是你仍然在寻找一个解释,当你真正需要使用 optionsValue ,如果没有。

    何时使用 选项值 结合

    假设您的饭菜可以卖完,您想向服务器查询 availableMeals :

    const availableMeals = ko.observableArray([]);
    const loadMeals = () => getMeals().then(availableMeals);
    const selectedMeal = ko.observable(null);
    
    loadMeals();
    
    ko.applyBindings({ loadMeals, availableMeals, selectedMeal });
    
    function getMeals() {
      return {
        then: function(cb) {
          setTimeout(cb.bind(null, [{ mealVal: "STD", mealName: "Standard (sandwich)", price: 0 }, { mealVal: "PRM", mealName: "Premium (lobster)", price: 34.95 }, { mealVal: "ULT", mealName: "Ultimate (whole zebra)", price: 290 }]), 500);  
        }
      }
    
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <select data-bind="options: availableMeals, 
                       value: selectedMeal,
                       optionsText: 'mealName'"></select>
                       
    <button data-bind="click: loadMeals">refresh meals</button>
    
    <div data-bind="with: selectedMeal">
      You've selected <em data-bind="text: mealName"></em>
    </div>
    
    <div data-bind="ifnot: selectedMeal">No selection</div>
    
    <p>Make a selection, click on refresh and notice the selection is lost when new data arrives.</p>

    当您替换中的对象时会发生什么 可用餐 :

    • 淘汰重新呈现选择框的选项
    • 淘汰检查新值 selectedMeal() === mealObject
    • 击倒没有找到目标 selectedMeal 默认为第一个选项
    • knockout将新对象的引用写入 已选择的邮件

    问题:您松开了UI选择,因为它指向的对象不再在可用选项中。

    选项值 去营救!

    这个 选项值 允许我们解决这个问题。而不是存储对 对象 可以随时更换,我们存储 原始的 值,内部字符串 mealVal ,这允许我们检查不同API调用之间的相等性!淘汰赛现在的表现是:

    selection = newObjects.find(o => o["mealVal"] === selectedMeal());
    

    让我们看看它的作用:

    const availablefoods=ko.observablerray([]);
    const loadmeals=()=获取膳食()。然后(可用膳食);
    const selectedmeal=ko.observable(空);
    
    加载膳食();
    
    ko.applybindings(装入餐点、可用餐点、选择的餐点);
    
    函数getFeeds()。{
    返回{
    然后:功能(CB){
    settimeout(cb.bind(空,[mealval:“std”,mealname:“standard(sandwich)”,price:0,mealval:“prm”,mealname:“premium(lobster)”,price:34.95,mealval:“ult”,mealname:“Ultimate(whole zebra)”,price:290],500);
    }
    }
    
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.4.2/knockout-min.js"></script>
    
    <select data-bind="options: availableMeals, 
                       value: selectedMeal,
                       optionsText: 'mealName',
                       optionsValue: 'mealVal'"></select>
                       
    <button data-bind="click: loadMeals">refresh meals</button>
    
    <div data-bind="if: selectedMeal">
      You've selected <em data-bind="text: selectedMeal"></em>
    </div>
    
    <div data-bind="ifnot: selectedMeal">No selection</div>
    
    <p>Make a selection, click on refresh and notice the selection is lost when new data arrives.</p>

    的缺点 选项值

    注意我是如何重写 with 结合?突然间,我们只有一个 meal 的属性在我们的ViewModel中可用,这是非常有限的。如果你希望你的应用能够更新数据,你需要在这里做一些额外的工作。您的两个选项:

    1. 存储所选内容的字符串(哈希) 独立的实际对象,或
    2. 拥有一个视图模型的存储库,当新的服务器数据到达时,映射到现有实例以确保保持选择状态。

    如果有帮助,我可以添加代码片段来更好地解释这两种方法

        2
  •  1
  •   Jez    6 年前

    好的,在浏览完淘汰代码之后,我已经知道发生了什么事情——到编写代码的时候,这还没有记录下来。

    这个 value 绑定,当它读取 select 元素,不只是查看元素的DOM值; it calls var elementValue = ko.selectExtensions.readValue(element);

    现在,什么 selectExtensions 不出所料,是否为 选择 (还有他们的孩子 object )元素。这就是魔法发生的地方,因为正如代码中的注释所说:

        // Normally, SELECT elements and their OPTIONs can only take value of type 'string' (because the values
        // are stored on DOM attributes). ko.selectExtensions provides a way for SELECTs/OPTIONs to have values
        // that are arbitrary objects. This is very convenient when implementing things like cascading dropdowns.
    

    因此,当值绑定尝试读取 选择 要素 via selectExtensions.readValue(...) ,将出现以下代码:

            case 'select':
                return element.selectedIndex >= 0 ? ko.selectExtensions.readValue(element.options[element.selectedIndex]) : undefined;
    

    这基本上是说“好的,找到选定的索引,然后再次使用此函数读取 option 该索引处的元素。所以它读到 选项 元素并得出以下结论:

            case 'option':
                if (element[hasDomDataExpandoProperty] === true)
                    return ko.utils.domData.get(element, ko.bindingHandlers.options.optionValueDomDataKey);
                return ko.utils.ieVersion <= 7
                    ? (element.getAttributeNode('value') && element.getAttributeNode('value').specified ? element.value : element.text)
                    : element.value;
    

    啊哈!因此,它存储自己的“has dom data expando property”标志,如果设置了该标志,则不会得到简单的 element.value 但它会进入自己的javascript内存并获取值。这就是它如何返回复杂的JS对象(如我问题示例中的MEAT对象),而不仅仅是 价值 属性字符串。但是,如果没有设置该标志,它实际上只是返回 价值 属性字符串。

    这个 writeValue 可以预见,扩展的另一面是,如果复杂数据不是字符串,它会将其写入JS内存,否则它只会将其存储在 价值 的属性字符串 选项 :

        switch (ko.utils.tagNameLower(element)) {
            case 'option':
                if (typeof value === "string") {
                    ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, undefined);
                    if (hasDomDataExpandoProperty in element) { // IE <= 8 throws errors if you delete non-existent properties from a DOM node
                        delete element[hasDomDataExpandoProperty];
                    }
                    element.value = value;
                }
                else {
                    // Store arbitrary object using DomData
                    ko.utils.domData.set(element, ko.bindingHandlers.options.optionValueDomDataKey, value);
                    element[hasDomDataExpandoProperty] = true;
    
                    // Special treatment of numbers is just for backward compatibility. KO 1.2.1 wrote numerical values to element.value.
                    element.value = typeof value === "number" ? value : "";
                }
                break;
    

    所以是的,正如我所怀疑的,knockout在后台存储复杂的数据,但只有当您要求它存储复杂的JS对象时。这就解释了为什么,当您不指定 optionsValue: [someStringValue] ,您的计算函数接收到复杂的用餐对象,而当您指定它时,您只需获得传入的基本字符串—knockout只是从 选项 价值 属性。

    就我个人而言,我认为这应该被清楚地记录下来,因为这是一个有点出乎意料和特殊的行为,可能会让人困惑,即使这很方便。我将要求他们将其添加到文档中。