据我所知,onbeforeupdate 似乎不支持异步调用样式,这是有道理的,因为它会阻止渲染。 onbeforeupdate 的用例是当您有一个包含 1000 行的表时。在这种情况下,您需要手动执行该“差异”。通过将项目长度与最后一个长度或其他一些简单计算进行比较来说。
当父模型发生变化时,这种变化检测应该发生在你的模型中,触发一些改变子模型的东西。然后子视图将返回一个将被渲染的新子树。
在这个小示例中,项目列表直接从父组件传递到子组件。当项目列表增加时,通过按下加载按钮,更新的项目列表将传递给子项,并且在重绘期间更新 DOM。如果决定在视图中获取差异是否应该手动完成,还有另一个按钮可以切换。
您可以在控制台中看到视图何时被调用。
第二个例子是更常见/正常的秘银用法(带有类)。
手动差异决策处理
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class AppModel {
/* Encapsulate our model. */
constructor() {
this.child = {
items: [],
manualDiff: true,
};
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.child.items[this.child.items.length] = values[i];
}
}
getChildItems() {
return this.child.items;
}
toggleManualDiff() {
this.child.manualDiff = !this.child.manualDiff;
}
getManualDiffFlag() {
return this.child.manualDiff;
}
}
function setupApp(model) {
/* Set our app up in a namespace. */
class App {
constructor(vnode) {
this.model = model;
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
manualDiff: this.model.getManualDiffFlag(),
items: this.model.getChildItems(),
}),
m('button[type=button]', {
onclick: (e) => {
this.model.toggleManualDiff();
}
}, 'Toggle Manual Diff Flag'),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.model.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
constructor(vnode) {
this.lastLength = vnode.attrs.items.length;
}
onbeforeupdate(vnode, old) {
if (vnode.attrs.manualDiff) {
// Only perform the diff if the list of items has grown.
// THIS ONLY WORKS FOR AN APPEND ONLY LIST AND SHOULD ONLY
// BE DONE WHEN DEALING WITH HUGE SUBTREES, LIKE 1000s OF
// TABLE ROWS. THIS IS NOT SMART ENOUGH TO TELL IF THE
// ITEM CONTENT HAS CHANGED.
let changed = vnode.attrs.items.length > this.lastLength;
if (changed) {
this.lastLength = vnode.attrs.items.length;
}
console.log("changed=" + changed + (changed ? ", Peforming diff..." : ", Skipping diff..."));
return changed;
} else {
// Always take diff, default behaviour.
return true;
}
}
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
}
// Inject our model.
setupApp(new AppModel());
</script>
</body>
</html>
正常使用
<!doctype html>
<html>
<body>
<script src="https://unpkg.com/mithril/mithril.js"></script>
<div id="app-container"></div>
<script>
let appContainerEl = document.getElementById('app-container');
function asyncRequest() {
return new Promise(function (resolve, reject) {
window.setTimeout(() => {
let res = [];
if (Math.random() < 0.5) {
res.push('' + (new Date().getTime() / 1000));
console.log('Found new items: '+ res[0]);
} else {
console.log('No new items.');
}
resolve(res);
// Otherwise reject()
}, 1000);
});
}
class App {
constructor(vnode) {
this.items = [];
}
async loadMoreChildItems() {
let values = await asyncRequest();
for (let i = 0, limit = values.length; i < limit; i += 1) {
this.items[this.items.length] = values[i];
}
}
view(vnode) {
console.log("Calling app view");
return m('div[id=app]', [
m(Child, {
items: this.items
}),
m('button[type=button]', {
onclick: (e) => {
e.preventDefault();
// Use promise returned by async function.
this.loadMoreChildItems().then(function () {
// Async call doesn't trigger redraw so do it manually.
m.redraw();
}, function (e) {
// Handle reject() in asyncRequest.
console.log('Item loading failed:' + e);
});
}
}, 'Load Child Items')]);
}
}
class Child {
view(vnode) {
console.log("Calling child view");
// This will first will be an empty list because items is [].
// As more items are loaded mithril will take diffs and render the new items.
return m('.child', vnode.attrs.items.map(function (item) { return m('div', item); }));
}
}
// Mount our entire app at this element.
m.mount(appContainerEl, App);
</script>
</body>
</html>