挂载在控制器虚拟机内部不工作

Mount not working inside controller vm

老实说,我不确定为什么这不起作用。似乎是一个非常标准的操作。它没有安装组件,没有抛出错误,也没有直接在它后面 运行 函数。 cfg.AddToCart.vm.addToCart()

一切都好
cfg.AddToCart = {
  vm: {
    init() {
        return;
    },

    addToCart() {
        let parent = document.getElementById('atc-error');
        let errEl = document.getElementById('atc-error-component');

        if(cfg.state.selections.SIZE) {
            m.mount(errEl, null);
        } else {
            let component = new cfg.selectComponent(cfg.Options, cfg.optionsView);
            m.mount(errEl, component);

            cfg.util.toggleSlide(parent);
        }
    }
  },

  controller() {
    cfg.AddToCart.vm.init();
  }
};

cfg.AddToCart.view = function() {
  return <div id="add-to-cart-container">
    <div id="atc-error">
        <span>Select a size and add to cart again.</span>
        <div id="atc-error-component"></div>
    </div>
    <div class="small-12 columns">
        <button class="large button alert"
            onclick={() => {
                this.vm.addToCart();
            }}>
            Add To Cart
        </button>
    </div>
  </div>;
};

我们在整个应用程序中多次使用 new cfg.selectComponent(cfg.Options, cfg.optionsView) 组件,因此这不是一个错误。 #atc-error 设置为 display:none,但这似乎也不是问题所在。这不是应用程序中唯一的条件挂载,所以这就是为什么我有点难过。

通过将其更改为此模式来工作:

cfg.AddToCart = {
  vm: {
    init() {
        this.errorComponent = m.prop();
    },

    addToCart() {
        let parent = document.getElementById('atc-error');
        let errEl = document.getElementById('atc-error-component');

        if(cfg.state.selections.SIZE) {
            cfg.util.toggleSlide(parent);
            setTimeout(() => {
                this.errorComponent(null);
            }, 400);
        } else {
            let component = new cfg.selectComponent(cfg.Options, cfg.optionsView);
            this.errorComponent(component);

            setTimeout(() => {
                cfg.util.toggleSlide(parent);
            }, 100);
        }
    }
  },

  controller() {
    cfg.AddToCart.vm.init();
  }
};

cfg.AddToCart.view = function() {
  return <div id="add-to-cart-container">
    <div id="atc-error">
        <span>Select a size and add to cart again.</span>
        <div id="atc-error-component" class="row">
            {this.vm.errorComponent() ? m.component(this.vm.errorComponent()) : ''}
        </div>
    </div>
    <div class="small-12 columns">
        <button class="large button alert"
            onclick={() => {
                this.vm.addToCart();
            }}>
            Add To Cart
        </button>
    </div>
  </div>;
};

从您构建代码的方式来看,我觉得您错过了很多 Mithril 的好处。特别是:

  1. 如果您的 'vm' 与控制器没有区别,那么您不需要为此创建和管理一个完整的单独对象。特别是当您使用方法来控制本地组件状态时, 控制器的工作。控制器向视图公开一个对象——在那个程度上,这应该被认为是 'vm'。当状态与组件实例外部相关时,拥有一个单独的对象来保存模型状态很有用:您已经在 cfg.state 中拥有它,因此在这种情况下 vm 是多余的。
  2. Mithril 视图有一个 config 方法,它在每次绘制后公开真正的 DOM 元素。您不需要存储对视图元素的引用,因为您可以在此处执行此操作。这是虚拟 DOM 库如此吸引人的很大一部分原因:视图很聪明,您可以直接在其中引入特定于视图的逻辑。
  3. 可以直接从视图中调用组件,视图可以使用条件逻辑来确定是否调用它们。 m.mount 仅在初始化 Mithril 应用程序和定义 'top level' 组件时需要;在 Mithril 代码中,您可以直接通过 m 函数调用嵌套组件。

其他一些误解:

  1. 控制器在之前视图被渲染执行(并且一旦它被执行,它初始化的属性作为第一个参数暴露给你的视图函数),所以你不能在控制器初始化时访问视图创建的元素。
  2. vm 中的 init 函数没有任何作用。

这里是考虑到上述因素的重写代码。我使用普通的 Mithril 而不是 MSX 来避免编译,但你可以轻松地将它转换回来:

// Determine what your external dependencies are
const { state, selectComponent } = cfg

// Define the component
const AddToCart = {
  // No need for a separate VM: it is identical in purpose & function to the controller
  controller : function(){
    // No need to store element references in the model: those are the view's concern.
    // Keep the VM / ctrl size to a minimum by only using it to deal with state
    this.addToCart = () => {
      if( state.selections.SIZE )
        this.showSize   = false 

      else {
        this.showSize   = true

        this.slideToErr = true 
      }
    }
  },

  view : ctrl =>
    m( '#add-to-cart-container',
      m( '#atc-error', {
        // Config exposes the element and runs after every draw.
        config : el => {
          // Observe state, and affect the view accordingly:
          if( ctrl.slideToErr ){
            el.scrollIntoView()

            // Reset the state flag
            ctrl.slideToErr = false
          }
        }
      },
        m( 'span', 'Select a size and add to cart again.' ),

        // This is an and condition, ie 'if A, then B
           ctrl.showSize 
           // This is how you invoke a component from within a view
        && m( selectComponent )
      ),

      m( '.small-12 columns',
        m( 'button.large button alert',  {
          onclick : () =>
            ctrl.addToCart();
        },
          'Add To Cart'
        )
      )
    )
}