使用映射插件时,Knockout 中的 beforeChange 值未定义
beforeChange value is undefined in Knockout when using the mapping plugin
根据 的回答,我尝试使用以下代码获取 observable 更改前的值。
var phoneBook;
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('newvalue: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug(previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'John2',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'peter',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'almond',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}
$(document).ready(function() {
phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
setTimeout(dofunc, 5000)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<div>
<p id="log"></p>
</div>
更改事件发生在再次调用映射插件时(这里是在超时 5 秒后),但是 previousValue
总是出现 undefined
。
我做错了什么?
这也是 jsfiddle:https://jsfiddle.net/icinema/ungbz27s/1/
这里的问题是你使用的映射插件错误,你的测试数据没有意义。
只有一个 "previous" 值,当您将一个新值写入 完全相同的 observable 时。但是当你映射一组完全不同的数据时,映射插件会丢弃你所有的视图模型并创建新的视图模型。
如何知道第一轮中名为"John"的对象与第二轮中名为"peter"的对象应该是同一个人?它不能。因此它会丢弃所有联系人,包括他们所有的 phone 号码并创建新的联系人。在这种情况下从来没有 "previous" 值。
你需要的是
- 为联系人和 phone 号码提供一个键,以便在对
ko.mapping.fromJS
的调用中将它们识别为同一对象。
- 通过向映射配置添加
key
函数,告诉映射插件对象的哪个属性应该是键。
阅读 documentation of the mapping plugin - 阅读整篇文章,开头并不多。
在下面的示例中,我使用 name
作为 contacts 的键,phoneType
作为 phones 的键,我修改了测试数据,使它们具有相同的两个集合中的名称和 phone 类型。您可能希望使用联系人 ID 号码而不是姓名作为密钥。
使用key
功能的好处是,knockout只会更新DOM中的phone数字文本,而不是丢掉重新创建整个<li>
以及其中的所有内容,因为它可以识别现有的视图模型实例并保留它们。这将减少渲染时间。
/* global ko, $ */
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('new value: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug('previous value: ' + previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
},
key: function (data) {
return ko.unwrap(data.phoneType);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
},
key: function (data) {
return ko.unwrap(data.name);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
}
]
};
$(document).ready(function() {
var phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
debug('<hr>');
setTimeout(function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}, 3000);
});
#log { font-family: monospace; font-size: small; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<hr>
<div id="log"></div>
根据
var phoneBook;
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('newvalue: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug(previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'John2',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'peter',
email: 'address@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777'
}]
},
{
name: 'almond',
email: '222address@domain.com',
phones: [{
phoneType: '22Home Phone',
phoneNumber: '22999-888-777'
}, {
phoneType: '22Business Phone',
phoneNumber: '444-888-777'
}]
}
]
};
function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}
$(document).ready(function() {
phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
setTimeout(dofunc, 5000)
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<div>
<p id="log"></p>
</div>
更改事件发生在再次调用映射插件时(这里是在超时 5 秒后),但是 previousValue
总是出现 undefined
。
我做错了什么?
这也是 jsfiddle:https://jsfiddle.net/icinema/ungbz27s/1/
这里的问题是你使用的映射插件错误,你的测试数据没有意义。
只有一个 "previous" 值,当您将一个新值写入 完全相同的 observable 时。但是当你映射一组完全不同的数据时,映射插件会丢弃你所有的视图模型并创建新的视图模型。
如何知道第一轮中名为"John"的对象与第二轮中名为"peter"的对象应该是同一个人?它不能。因此它会丢弃所有联系人,包括他们所有的 phone 号码并创建新的联系人。在这种情况下从来没有 "previous" 值。
你需要的是
- 为联系人和 phone 号码提供一个键,以便在对
ko.mapping.fromJS
的调用中将它们识别为同一对象。 - 通过向映射配置添加
key
函数,告诉映射插件对象的哪个属性应该是键。
阅读 documentation of the mapping plugin - 阅读整篇文章,开头并不多。
在下面的示例中,我使用 name
作为 contacts 的键,phoneType
作为 phones 的键,我修改了测试数据,使它们具有相同的两个集合中的名称和 phone 类型。您可能希望使用联系人 ID 号码而不是姓名作为密钥。
使用key
功能的好处是,knockout只会更新DOM中的phone数字文本,而不是丢掉重新创建整个<li>
以及其中的所有内容,因为它可以识别现有的视图模型实例并保留它们。这将减少渲染时间。
/* global ko, $ */
function debug(s) {
$("#log").append('<br>' + s);
}
function PhoneNumber(data) {
var self = this;
self.phoneType = ko.observable();
self.phoneNumber = ko.observable();
self.phoneNumber.subscribe(function(newValue) {
debug('new value: ' + newValue);
});
self.phoneNumber.subscribe(function(previousValue) {
debug('previous value: ' + previousValue);
}, self, "beforeChange");
ko.mapping.fromJS(data, PhoneNumber.mapping, self);
}
PhoneNumber.mapping = {};
function Contact(data) {
var self = this;
self.name = ko.observable();
self.email = ko.observable();
self.phones = ko.observableArray();
ko.mapping.fromJS(data, Contact.mapping, self);
}
Contact.mapping = {
phones: {
create: function(options) {
return new PhoneNumber(options.data);
},
key: function (data) {
return ko.unwrap(data.phoneType);
}
}
};
function PhoneBook(data) {
var self = this;
self.contacts = ko.observableArray();
ko.mapping.fromJS(data, PhoneBook.mapping, self);
}
PhoneBook.mapping = {
contacts: {
create: function(options) {
return new Contact(options.data);
},
key: function (data) {
return ko.unwrap(data.name);
}
}
};
var phoneBookData = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-old'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-old'
}]
}
]
};
var phoneBookDataOther = {
contacts: [{
name: 'John',
email: 'john@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
},
{
name: 'Peter',
email: 'peter@domain.com',
phones: [{
phoneType: 'Home Phone',
phoneNumber: '22999-888-777-new'
}, {
phoneType: 'Business Phone',
phoneNumber: '444-888-777-new'
}]
}
]
};
$(document).ready(function() {
var phoneBook = new PhoneBook(phoneBookData);
ko.applyBindings(phoneBook);
debug('<hr>');
setTimeout(function dofunc() {
ko.mapping.fromJS(phoneBookDataOther, phoneBook);
}, 3000);
});
#log { font-family: monospace; font-size: small; }
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.5.0/knockout-min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout.mapping/2.4.1/knockout.mapping.min.js"></script>
<ul data-bind="foreach: contacts">
<li>
<div data-bind="text: name"></div>
<div data-bind="text: email"></div>
<ul data-bind="foreach: phones">
<li>
<span data-bind="text: phoneType"></span>:
<span data-bind="text: phoneNumber"></span>
</li>
</ul>
</li>
</ul>
<hr>
<div id="log"></div>