这是我第一次使用Mobx,我试图了解它是如何应用于我的项目(不是React基础,只是使用jquery更新DOM)。 我从 Mobx 4 开始(我需要支持旧浏览器,所以我不使用最新版本 6 ),并按照 here 的示例进行操作,但它使用了 Mobx 2 版本。 当与 Mobx 4 一起使用时,它会抛出一个错误:
function ShoppingCart() {
mobx.extendObservable(this, {
entries: [],
total: function() {
return this.entries.reduce(function (sum, entry) {
return sum + entry.price;
}, 0);
}
});
}
控制台显示:
mobx] Encountered an uncaught exception that was thrown by a reaction or observer component, in: 'Reaction[Autorun@19]' TypeError: Cannot read properties of undefined (reading 'reduce')
我尝试使用这样的匿名函数:
function ShoppingCart() {
mobx.extendObservable(this, {
entries: [],
total: () => {
return this.entries.reduce(function (sum, entry) {
return sum + entry.price;
}, 0);
}
});
}
但是它返回了这样的函数声明:
0function () { return this.article ? this.article.price * this.amount : 0; }
我想知道这里有什么区别?在这种情况下 Mobx 如何处理 this(.entries) ? 为什么这个计算无法获取条目的值?谢谢!
这是jsfiddle中项目的链接:
或者我的全部来源:
/** Data model */
function Article(name, price) {
mobx.extendObservable(this, {
name: name,
price: price
});
}
function ShoppingCartEntry(article) {
mobx.extendObservable(this, {
article: article,
amount: 1,
price: function () {
return this.article ? this.article.price * this.amount : 0;
}
});
}
function ShoppingCart() {
mobx.extendObservable(this, {
entries: [],
total: function () {
return this.entries.reduce(function (sum, entry) {
return sum + entry.price;
}, 0);
}
});
}
$(function () {
// Some available articles
var articles = mobx.observable([
["Funny Bunnies", 17.63],
["Awesome React", 23.95],
["Second hand netbook", 50.00]
].map(function (e) {
return new Article(e[0], e[1]);
}));
// Our shopping cart
var shoppingCart = new ShoppingCart();
// With a demo item inside
shoppingCart.entries.push(new ShoppingCartEntry(articles[0]));
$.fn.insertAt = function (index, $parent) {
return this.each(function () {
if (index === 0) {
$parent.prepend(this);
} else {
$parent.children().eq(index - 1).after(this);
}
});
};
/** UI Logic */
var $articles = $("#articles");
// Make the articles list follow the array
articles.observe(function (change) {
console.log(change);
// items where added or removed
if (change.type === "splice") {
$articles.children().slice(change.index, change.index + change.removed.length).remove();
for (var i = 0; i < change.addedCount; i++) {
renderArticle(articles[change.index + i])
.insertAt(change.index + i, $articles);
}
}
}, true); // true makes sure the observe function is invoked immediately
// Render an article in the articles overview, and watch or changes
function renderArticle(article) {
var $name = $("<span>").text(article.name);
var $price = $("<span>").addClass("price").text(article.price);
mobx.autorun(function () {
$name.text(article.name);
});
mobx.autorun(function () {
$price.text(article.price);
});
return $("<li>").append($name,
$("<button>").text("+").addClass("add-article"),
$("<button>").text("edit").addClass("edit-article"),
$price);
}
// ShoppingCart ui
var $cart = $("#cart");
var $total = $("#total");
mobx.autorun(function () {
// console.log(shoppingCart, shoppingCart.total, shoppingCart.entries);
$total.text(shoppingCart.total);
}, true);
shoppingCart.entries.observe(function (change) {
// items where added or removed
if (change.type === "splice") {
$cart.children().slice(change.index, change.index + change.removed.length).remove();
for (var i = 0; i < change.addedCount; i++) {
renderCartEntry(shoppingCart.entries[change.index + i])
.insertAt(change.index + i, $cart);
}
}
}, true);
function renderCartEntry(entry) {
var $name = $("<span>").text(entry.article.name);
var $amount = $("<span>").addClass("price").text(entry.amount);
mobx.autorun(function () {
$name.text(entry.article.name);
});
mobx.autorun(function () {
$amount.text(entry.amount);
});
return $("<li>").append(
$("<button>").text("-").addClass("remove-article"),
$name,
$amount);
}
// Events
$("#new-article").on("click", function () {
articles.push(new Article(prompt("Article name"), prompt("Price")));
});
$(document).on("click", ".edit-article", function (event) {
var idx = $(event.target).parent().index();
articles[idx].name = prompt("New name");
articles[idx].price = prompt("New price");
});
$(document).on("click", ".add-article", function (event) {
var article = articles[$(event.target).parent().index()];
var existingEntry = shoppingCart.entries.find(function (entry) {
return entry.article === article;
});
if (existingEntry)
existingEntry.amount += 1;
else
shoppingCart.entries.unshift(new ShoppingCartEntry(article));
});
$(document).on("click", ".remove-article", function (event) {
var entryIndex = $(event.target).parent().index();
if ((shoppingCart.entries[entryIndex].amount -= 1) < 1)
shoppingCart.entries.splice(entryIndex, 1);
});
})
我找到了答案。就像@phuzi 的建议。这就是 JS 中的
this
。我错误地接受了 Mobx 使用示例而不怀疑 this
的价值。正确的ShoppingCartEntry
函数应该是这样的:
function ShoppingCartEntry(article) {
mobx.extendObservable(this, {
article: article,
amount: 1,
price: () => {
return this.article ? this.article.price * this.amount : 0;
}
});
}
我使用箭头函数来表示价格,所以
this
现在将是ShoppingCartEntry
。
然后我们需要将 ShoppingCart
更新为:
function ShoppingCart() {
mobx.extendObservable(this, {
entries: [],
total: () => {
return this.entries.reduce(function (sum, entry) {
return sum + entry.price();
}, 0);
}
});
}
与 Mobx v2 的示例相比,
entry.price
必须作为函数调用。
或者我们可以像这样更新 ShoppingCartEntry
以使用价格的 getter 让 entry.price
与之前相同:
class ShoppingCartEntry {
article;
constructor(article) {
this.article = article;
mobx.extendObservable(this, {
article: article,
amount: 1,
});
}
get price() {
return this.article ? this.article.price * this.amount : 0;
}
}