Hyperstack 和 MaterialUI 抽屉切换状态导致抽屉反复打开和关闭

Hyperstack and MaterialUI Drawer Toggling State is causing the drawer to open and close repeatedly

我正在 Hyperstack 项目中使用 MaterialUI 实现标题栏和菜单抽屉。我有两个组件,一个 Header 组件和一个 Menu 组件。 Menu 组件是可扩展的 Drawer。我将状态存储在 Header 组件中,并将它和一个处理程序传递给 Menu 组件,以便在单击抽屉关闭按钮时切换抽屉状态。出于某种原因,抽屉只是非常快速和反复地切换打开和关闭。

在我实现关闭按钮之前,抽屉打开得很好。我已经尝试将状态向上移动到主应用程序组件并将其一直向下传递,但它产生相同的结果。我尝试在 Header 组件上设置一个 class 函数,并从 Menu 组件中调用它,而不是传入事件处理程序。

Header 组件

class Header < HyperComponent
  before_mount do
    @drawer_open = false
  end

  def toggle_drawer
    mutate @drawer_open = !@drawer_open
  end

  render(DIV) do
    AppBar(position: 'static', class: 'appBar') do
      Toolbar do
        IconButton(class: 'menuButton', color: 'inherit', aria_label: 'Menu') do
          MenuIcon(class: 'icon')
        end
        .on(:click) do
          toggle_drawer
        end
        Typography(variant: 'h6', color: 'inherit', class: 'grow') { 'Admin Main' }
        Button(color: 'inherit', class: 'float-right') { 'Login' } # unless App.history != '/admin'
      end
    end
    Menu(open_drawer: @drawer_open, toggle_drawer: toggle_drawer)
  end
end

Menu 组件

class Menu < HyperComponent
  param :open_drawer
  param :toggle_drawer

  def menu_items
    %w(Main Inventory Customers)
  end

  def is_open?
    @OpenDrawer
  end

  render(DIV) do
    Drawer(className: 'drawer, drawerPaper', variant: 'persistent', anchor: 'left', open: is_open?) do
      IconButton(className: 'drawerHeader') { ChevronLeftIcon() }
      .on(:click) { @ToggleDrawer }

      List do
        menu_items.each do |mi|
          ListItem(button: true, key: mi) { ListItemText(primary: mi) }
        end
      end
    end
  end
end

我希望抽屉在单击打开按钮时打开并在单击关闭按钮时关闭,但它打开和关闭的速度非常快。

它快速打开和关闭的原因是您将 toggle_drawerHeader 组件传递给 Menu零件。每次调用 toggle_drawer 时,它都会更改状态变量 @drawer_open,并重新渲染组件,然后重复泡沫-漂洗-重复。

你需要做的是将 proc 传递给 Menu,然后让 Menu 调用 on_click 处理程序中的过程。

所以它看起来像这样:

class Header < HyperComponent
 ...
 render(DIV) do
   ...
   Menu(open_drawer: @drawer_open, toggle_drawer: method(:toggle_drawer))
 end
end

class Menu < HyperComponent
  ...
  param :toggle_drawer
  ...
      IconButton(className: 'drawerHeader') { ChevronLeftIcon() }
      .on(:click) { @ToggleDrawer.call } # note you have to say .call
  ...
end

By the way nice article here on how method(:toggle_drawer) works and compares it the same behavior in Javascript.

但是等等! Hyperstack 有一些很好的语法糖,使它更具可读性。

不应将 toggle_drawer 声明为普通参数,而应使用 fires 方法声明它,表明您将向调用组件触发事件(或回调)。这不仅会让你的生活更轻松一些,而且还会向 reader 宣布你的意图。

class Menu < HyperComponent
  ...
  fires :toggle_drawer # toggle_drawer is a callback/event that we will fire!
  ...
      IconButton(className: 'drawerHeader') { ChevronLeftIcon() }
      .on(:click) { toggle_drawer! } # fire the toggle_drawer event (note the !) 
  ...
end

现在Header可以使用正常的事件处理程序语法:

class Header < HyperComponent
 ...
 render(DIV) do
   ...
   Menu(open_drawer: @drawer_open)
   .on(:toggle_drawer) { toggle_drawer }
 end
end 

顺便说一句,如果我能提供一点风格建议:因为菜单只能关闭抽屉,这就是我所说的事件,在事件处理程序中我会直接改变抽屉状态(并且只是失去toggle_drawer 方法)。

这样看代码就很清楚你要过渡到什么状态了

生成的代码如下所示:

class Header < HyperComponent
  before_mount do
    @drawer_open = false  # fyi you don't need this, but its also not bad practice
  end

  render(DIV) do
    AppBar(position: 'static', class: 'appBar') do
      Toolbar do
        IconButton(class: 'menuButton', color: 'inherit', aria_label: 'Menu') do
          MenuIcon(class: 'icon')
        end.on(:click) { mutate @drawer_open = true }
        Typography(variant: 'h6', color: 'inherit', class: 'grow') { 'Admin Main' }
        Button(color: 'inherit', class: 'float-right') { 'Login' } # unless App.history != '/admin'
      end
    end
    Menu(open_drawer: @drawer_open)
    .on(:close_drawer) { mutate @drawer_open = false }
  end
end