【问题标题】:How to redraw specific DOM asynchronously in Mithril如何在 Mithril 中异步重绘特定的 DOM
【发布时间】:2021-09-07 18:42:56
【问题描述】:

我有通过从父组件注入状态来更新的子组件。

当父组件的值发生变化时,我需要使用异步函数填充模型。

并且我想在异步操作完成后绘制一个新的子组件。

我检查了onbeforeupdate中父组件值的变化,执行了异步函数,然后又执行了redraw函数,但是陷入了死循环。

...

async onbeforeupdate((vnode)) => {
  if (this.prev !== vnode.attrs.after) {
    // Update model data
    await asyncRequest();
    m.redraw();
  }
}

view() {
  return (...)
}

...

【问题讨论】:

  • 您能告诉我们更多关于子组件/父组件以及它们如何交互的信息吗?这通常不是您使用 onbeforeupdate 的方式,我认为它不支持异步使用。通常这将在您的模型代码中处理。 IE。更改父模型将触发加载子模型。然后会发生重绘。

标签: reactjs mithril.js


【解决方案1】:

据我所知,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>

【讨论】:

    【解决方案2】:

    应该是work。更新this.prev可能有问题

    【讨论】:

      猜你喜欢
      • 2016-02-09
      • 2019-09-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-20
      • 1970-01-01
      • 2013-03-02
      • 2018-04-30
      相关资源
      最近更新 更多