【问题标题】:Why does the Mithril child component state change not trigger an update?为什么秘银子组件状态改变不触发更新?
【发布时间】:2021-04-26 01:09:00
【问题描述】:

以下代码按预期工作:它创建了两个计数器按钮,它们保持其状态并在点击时更新:

let Counter = function (vnode) {
  let count = 0

  return {
    view: (vnode) => {
      return m("button",
        {
          onclick: function () {
            console.log(count++)
          }
        }, "Count: " + count)
    }
  }
}

let Counters = {
  view: () => [
    m(Counter),
    m(Counter),
  ]
}

m.mount(document.body, Counters)

但是,如果我在单独的变量中定义 Counter 组件数组并将其传递给 Counters 视图函数,则视图将停止更新。状态持续存在,我可以看到控制台记录了递增计数,但屏幕上没有任何变化。这是更新后的代码:

let Counter = function (vnode) {
  let count = 0

  return {
    view: (vnode) => {
      return m("button",
        {
          onclick: function () {
            console.log(count++)
          }
        }, "Count: " + count)
    }
  }
}

let counters = 
  [
    m(Counter),
    m(Counter),
  ]
let Counters = {
  view: () => counters
}

m.mount(document.body, Counters)

为什么会发生这种情况?这是我正在开发的一个更复杂的 Mithril 应用程序的玩具示例,我想对子组件数组进行任意排序。

【问题讨论】:

    标签: mithril.js


    【解决方案1】:

    我认为可能发生的情况是,当您设置数组时,组件正在“渲染”,因为 JavaScript 如何评估表达式,counters 数组中的 Mithril 组件在您尝试挂载它们之前已经执行稍后,因此 Mithril 重绘机制(通过 m.mountm.route)可能不“知道”这些组件,因此不属于重绘队列。

    let counters = 
      [
        m(Counter),
        m(Counter),
      ]
    

    我不确定您要实现什么,但也许更好的解决方案是将数据数组保存在纯 JS 数据结构中,当您需要实际渲染组件时,您可以映射数据结构,例如

    let Counter = function (vnode) {
      return {
        view: (vnode) => {
          return m("button",
            {
              onclick: function () {
                console.log(this, vnode.attrs.count++);
              }
            }, "Counter #" + vnode.attrs.id +": " + vnode.attrs.count)
        }
      }
    }
    
    let counterList = [
      { id: 1, name: "Something 1", count: 0 },
      { id: 2, name: "Something 2", count: 0 },
      { id: 3, name: "Something 3", count: 0 },
    ]
    
    let reorderCountersByCountAscending = () => {
      counterList.sort((a, b) => (a.count > b.count) ? 1 : -1)
    }
    
    let Counters = {
      view: () => {
        return m('div',
          m('button', { onclick: reorderCountersByCountAscending }, 'Reorder Children'),
          counterList
            .map((counterListItem) => m(Counter, counterListItem))
        )
      }
    }
    
    m.mount(document.body, Counters);
    

    这不是一个理想的实现,有很多状态突变以非常隐含的方式发生,它可能会在现实世界的应用程序中导致很多混乱,但我希望它描述了这种方法。

    这里还有几个示例可供查看: https://jsfiddle.net/9hu2yd1s/

    【讨论】:

    • 感谢您的回答,@barryels。我设法从 Mithril Gitter 聊天中获得反馈,并在下面发布了解决方案。您的回答也以类似的方式解决了这个问题,尽管额外的间接级别似乎会减慢实现速度(这是我从小提琴中得到的印象)。
    • @phi_land_trophyist,我很高兴您从 Mithril Gitter 社区获得了解决方案。关于您在示例小提琴中看到的“缓慢”,这完全取决于 jsfiddle 如何在预览窗格中“运行”代码。将该示例代码放在一个纯 HTML 文件中并直接运行它,您会注意到 Mithril 的性能是您所期望的……超快。
    【解决方案2】:

    我能够在 Mithril Gitter 聊天中收集到有用的反馈,并将在下面发布我学到的内容。

    我的示例中的计数器没有更新的原因是因为它们在counters 数组中定义过一次,随后又被返回。由于每次调用Countersview() 函数时都会返回相同的数组,Counters 认为不需要更新,因为它认为它返回的元素没有改变。即使数组中的Counter 元素更新,对数组的引用保持不变。

    处理此问题的一种方法是定义一个工厂函数,该函数将在每次调用 Counters 上的 view() 函数时返回一个新的排序数组 Counter 元素。此外,应用程序状态需要保存在全局位置,传递给每个 Counter 的唯一参数是它所引用的全局计数器的索引,如下所示:

    let counterData = [0, 0]
    
    let Counter = {
      view: ({
        attrs: {
          index
        }
      }) => {
        return m("button", {
          onclick: () => counterData[index]++
        }, "Count: " + counterData[index])
      }
    }
    
    let Counters = function () {
      const counters = () => {
        return counterData.sort((a, b) => a - b).map((count, i) => m(Counter, {
          index: i
        }, count))
      }
    
      return {
        view: () => counters()
      }
    }
    
    m.mount(document.body, Counters)
    

    可以在 here 找到一个工作的 JSFiddle。

    【讨论】:

      猜你喜欢
      • 2017-07-04
      • 2021-03-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-06-08
      • 2016-09-18
      • 1970-01-01
      相关资源
      最近更新 更多