{T}

Electron 菜单系统、角色菜单、快捷键与动态菜单项更新

概述

这一节正式进入 Electron 的原生菜单系统。课程先把菜单的几种形态铺开——顶部应用菜单、macOS 的 Dock 菜单、页面内的右键菜单,并指出它们都属于菜单系统的一部分。随后用最常见、也最好理解的应用菜单入手,给出创建菜单的最小链路:先写 template,再 buildFromTemplate(),最后 setApplicationMenu()。课程还重点讲了内置 role 的省事之处、自定义项的 click 边界、accelerator 的快捷键礼仪,以及用 id + getMenuItemById() 动态改 visible / enabled / checked 的状态更新方式。

学习目标

  • 知道 Electron 菜单至少分三类:顶部应用菜单、Dock 菜单、上下文菜单,底层思维相近但入口不同。
  • 会用“先模板、后实例”的最小链路创建应用菜单:templatebuildFromTemplate()setApplicationMenu()
  • 理解内置 role 的价值:很多标准菜单不用自己造,但 role 不支持自定义 click。
  • 明白自定义菜单项和 role 不是互斥,而是标准系统行为交给 role、业务动作自己定义。
  • 掌握 accelerator 的快捷键礼仪与通过 id + getMenuItemById() 动态修改菜单项状态的写法。

一、Electron 菜单不只是一种,先铺开整张地图

很多前端同学第一次接触桌面端时,一说菜单就只会想到顶部菜单栏。但 Electron 实际上常见的菜单形态至少有三类:应用顶部菜单、macOS 的 Dock 菜单、页面里的右键 / 上下文菜单。课程先从顶部菜单入手,因为它最常见、也最好理解,但它先把整个地图铺开,是为了让大家知道菜单系统不是单一入口,后面学到的模板、快捷键、动态更新这些能力,其实都可以迁移到别的菜单场景里。

ts
type ElectronMenuKind =
  | "application-menu"
  | "dock-menu"
  | "context-menu"

后面右键菜单和 Dock 菜单的实现思想会和这里非常相近。课程先从最常见的开始,是一个很好的学习顺序,但不要把它误解成 Electron 只有顶部这一类菜单。

二、顶部菜单的最小链路:模板 → 构建 → 设置

课程在实操菜单时,给了一个非常清晰的最小链路:先定义 template,再通过 Menu.buildFromTemplate(template) 生成 menu 对象,最后 Menu.setApplicationMenu(menu) 让它生效。这个流程很值得记,因为它其实就是 Electron 菜单的基础骨架。后面不管你做的是顶部菜单、右键菜单,还是别的扩展菜单,核心都绕不开“先模板、后实例”这条思路。

ts
const { Menu } = require("electron")

const template = [
  { role: "appMenu" },
  { role: "editMenu" },
]

const menu = Menu.buildFromTemplate(template)
Menu.setApplicationMenu(menu)

课程里用最简单的 appMenueditMenu role 做演示,也是为了让大家先看到结果,再回头理解模板结构。template 是声明式结构,menu 才是真正被应用的实例。如果你只记住一个最小菜单公式,就记住这三步。

三、role 是菜单里最省事也最容易忽略的能力

课程很快展示了 appMenueditMenureloadzoomInzoomOut 这些内置 role。它的价值非常大,因为它让你不需要手写 click 回调、自己处理系统平台差异、自己把这些常见动作重新实现一遍。role 是 Electron 在做桌面应用时给你的一份“菜单能力内置件”。

ts
const template = [
  { role: "appMenu" },
  { role: "editMenu" },
  {
    label: "我的菜单",
    submenu: [
      { role: "reload" },
      { type: "separator" },
      { role: "zoomIn" },
      { role: "zoomOut" },
    ],
  },
]

需要提醒的是:role 虽然不支持自定义 click,但非常适合快速搭出一套标准菜单。它更适合标准系统行为,不适合业务定制动作,这也是 Electron 菜单和普通前端按钮最大的不同之一。

四、自定义项与 role 的边界:业务动作自己定义,标准行为交给 role

课程往菜单里加“我的菜单”“我的主页”这类自定义项,并在 click 里接了 shell.openExternal(...)。这时就能看到 role 和自定义项的边界了:role 复用系统或 Electron 预置能力,快捷但不灵活;自定义菜单项你可以控制 label、接业务逻辑、调系统 API。课程给出的正确结论是:两者不是互斥关系,一个菜单里完全可以混用。

ts
{
  id: "home",
  label: "我的主页",
  click() {
    shell.openExternal("https://example.com")
  },
}

这正是桌面端菜单设计很典型的形态:标准项交给 role,业务项自己定义。不要什么都手写 click,也不要什么都硬套 role,混合使用才是最符合实际项目的菜单设计方式。

五、accelerator 考验的是桌面端交互礼仪,不是语法

Electron 菜单项可以直接配 accelerator,例如 CommandOrControl+1,这样菜单后面会直接显示快捷键,用户也能触发对应动作。但课程真正想提醒的是:这不是技术问题,而是产品和交互礼仪问题。因为桌面端快捷键一旦乱设,会直接和用户习惯冲突,例如 Ctrl/Cmd + C / V / P 已经被系统和编辑器广泛占用。

ts
{
  id: "home",
  label: "我的主页",
  accelerator: "CommandOrControl+1",
  click() {
    shell.openExternal("https://example.com")
  },
}

课程给的建议很实用:如果某些快捷键可能和系统常用操作冲突,更好的做法是把它做成用户可配置项。Electron 的 accelerator 很好用,但不要贪图方便乱占系统键位;桌面端快捷键的体验问题,远比 Web 里普通按钮冲突更明显。

六、菜单结构不能随意增删,但状态可以动态改

课程后半段演示了一个非常有代表性的能力:动态修改菜单项状态。它先给菜单项加 id,然后通过 menu.getMenuItemById(id) 拿到对应项,再去改它的属性。这里最关键的认知是:菜单结构本身不是你想增删就随便增删的那种响应式对象,但某些状态型属性是可以动态改的,例如 visibleenabledchecked

ts
const item = menu.getMenuItemById("home")
if (item)
  item.visible = false

Menu.setApplicationMenu(menu)

课程不是说菜单完全不能动态改,而是在强调“结构更新”和“状态更新”的边界不同。id 是动态操作菜单项最重要的抓手之一,改完状态后通常还要重新 setApplicationMenu(menu),这一点很关键。

七、type 不只是 separator,还支持 checkbox / radio

课程除分隔线外,还顺手演示了 checkboxradio。这说明 Electron 菜单并不是只有文本项和点击项,它本身就支持一些更贴近桌面端习惯的状态型交互,例如显示 / 隐藏工具栏、某种模式是否开启、一组选项里当前选中哪一个。

ts
{
  label: "显示工具栏",
  type: "checkbox",
  checked: true,
}

状态型菜单在桌面端应用里非常常见。课程点到为止,但已经足够说明菜单系统的表达力:菜单不仅能触发动作,也能承载状态。后续如果要做复杂桌面工具,这块能力会非常有用。

八、这一节打通的是 Electron 原生菜单的基础骨架

到这一节为止,Electron 原生菜单系统的最基础能力已经被打通。它真正交付的是一套可复用的菜单心智:菜单是“先模板后实例”的结构、标准行为用 role、业务动作自定义、快捷键要尊重系统习惯、状态用 id + getMenuItemById() 动态改。带着这套骨架,后面学 Dock 菜单和右键菜单时会非常顺,因为它们共享同一套底层思维。


常见问题

问题原因解决方案
为什么自定义菜单项点击没反应可能只写了 label 没写 click,或菜单实例没重新设置确认 click 存在,并在需要时重新 setApplicationMenu(menu)
为什么内置 role 不能自定义点击逻辑role 代表系统 / Electron 内置行为自定义动作用普通菜单项,不要强求 role 处理业务逻辑
快捷键配了但感觉别扭可能和系统或常见应用快捷键冲突重新设计 accelerator,避免抢占用户已有习惯
菜单项能隐藏,但结构改起来不方便Electron 菜单不是前端响应式列表尽量做属性更新,复杂结构变化就重新构建模板
页面右键菜单和顶部菜单是不是一回事不是,但底层思路相近先把应用菜单搞懂,后面右键菜单会更容易上手

延伸阅读