LoginSignup
1
1

More than 1 year has passed since last update.

【Electron】MenuItemConstructorOptionsの型推論

Posted at

初書:2021/10/14
mac : 11.6
electron : 15.1.2
typescript : 4.4.3

前置き

electronとtypescriptを使ってアプリケーションを開発中のこと。
メニューバーを作成しようと試みたときに起きた出来事。

「型推論が通らない」

ので、それの修正メモ。

該当コード

該当ページMenu | Electron

Electronの日本語公式ページにある「サンプル」コードで問題は起きた。
ちなみにそのコードを、Typescript用に若干書き換えたものがこちら。

import { app, Menu } from ('electron');

const isMac = process.platform === 'darwin';

const template = [
  // { role: 'appMenu' }
  ...(isMac ? [{
    label: app.name,
    submenu: [
      { role: 'about' },
      { type: 'separator' },
      { role: 'services' },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideOthers' },
      { role: 'unhide' },
      { type: 'separator' },
      { role: 'quit' }
    ]
  }] : []),
  // { role: 'fileMenu' }
  {
    label: 'ファイル',
    submenu: [
      isMac ? { role: 'close' } : { role: 'quit' }
    ]
  },
  // { role: 'editMenu' }
  {
    label: 'Edit',
    submenu: [
      { role: 'undo' },
      { role: 'redo' },
      { type: 'separator' },
      { role: 'cut' },
      { role: 'copy' },
      { role: 'paste' },
      ...(isMac ? [
        { role: 'pasteAndMatchStyle' },
        { role: 'delete' },
        { role: 'selectAll' },
        { type: 'separator' },
        {
          label: 'Speech',
          submenu: [
            { role: 'startSpeaking' },
            { role: 'stopSpeaking' }
          ]
        }
      ] : [
        { role: 'delete' },
        { type: 'separator' },
        { role: 'selectAll' }
      ])
    ]
  },
  // { role: 'viewMenu' }
  {
    label: 'View',
    submenu: [
      { role: 'reload' },
      { role: 'forceReload' },
      { role: 'toggleDevTools' },
      { type: 'separator' },
      { role: 'resetZoom' },
      { role: 'zoomIn' },
      { role: 'zoomOut' },
      { type: 'separator' },
      { role: 'togglefullscreen' }
    ]
  },
  // { role: 'windowMenu' }
  {
    label: 'Window',
    submenu: [
      { role: 'minimize' },
      { role: 'zoom' },
      ...(isMac ? [
        { type: 'separator' },
        { role: 'front' },
        { type: 'separator' },
        { role: 'window' }
      ] : [
        { role: 'close' }
      ])
    ]
  },
  {
    role: 'help',
    submenu: [
      {
        label: 'Learn More',
        click: async () => {
          const { shell } = require('electron')
          await shell.openExternal('https://electronjs.org')
        }
      }
    ]
  }
];

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

requireがimportになって個人的に気に入らなかったセミコロンがついただけ。

ここで下から2行目のMenu.buildFromTemplate(template);のtemplateにエラーが付きます。

エラーの原因

まずはエラー文はこちら。

型 '({ label: string; submenu: ({ role: string; type?: undefined; label?: undefined; submenu?: undefined; } | { type: string; role?: undefined; label?: undefined; submenu?: undefined; } | { label: string; submenu: { ...; }[]; role?: undefined; type?: undefined; })[]; role?: undefined; } | { ...; })[]' の引数を型 '(MenuItemConstructorOptions | MenuItem)[]' のパラメーターに割り当てることはできません。
  型 '{ label: string; submenu: ({ role: string; type?: undefined; label?: undefined; submenu?: undefined; } | { type: string; role?: undefined; label?: undefined; submenu?: undefined; } | { label: string; submenu: { ...; }[]; role?: undefined; type?: undefined; })[]; role?: undefined; } | { ...; }' を型 'MenuItemConstructorOptions | MenuItem' に割り当てることはできません。
    型 '{ label: string; submenu: ({ role: string; type?: undefined; label?: undefined; submenu?: undefined; } | { type: string; role?: undefined; label?: undefined; submenu?: undefined; } | { label: string; submenu: { ...; }[]; role?: undefined; type?: undefined; })[]; role?: undefined; }' を型 'MenuItemConstructorOptions | MenuItem' に割り当てることはできません。
      型 '{ label: string; submenu: ({ role: string; type?: undefined; label?: undefined; submenu?: undefined; } | { type: string; role?: undefined; label?: undefined; submenu?: undefined; } | { label: string; submenu: { ...; }[]; role?: undefined; type?: undefined; })[]; role?: undefined; }' には 型 'MenuItem' からの次のプロパティがありません: checked, click, commandId, enabled、9 など。ts(2345)

読む気失せるほど長い。
単純にいうと、typeroleがstring型になっているが、MenuItemConstructorOptions内ではストリングリテラル型(限定されたstring型)だよーというエラー。

これは、templateを定義する際に型指定をしていないため、単にstring型として認識されているのが原因。
ということで、templateに型をつける。

const template : Electron.MenuItemConstructorOptions[] = [
    // 略

はい、終わりです。エラーの量が。

...(isMac ? [] : []) // 上記コードの一部を切り取っているだけなので、当たり前ですが空の配列ではないです。

この辺が全部エラー吐きます。

解決策を考える

おそらく三項演算子を使っているため、その箇所で型推論していないのが原因なのではないかと予想し、

// 上記と同じエラー
...(isMac ? [] as Electron.MenuItemConstructorOptions[]: [])
// 型 'MenuItemConstructorOptions' には、反復子を返す '[Symbol.iterator]()' メソッドが必要です。ts(2488)
...(isMac ? [] : []) as Electron.MenuItemConstructorOptions[]

いずれもダメ。

仕方ないので外部に切り出す。

let NameSubMenu : Electron.MenuItemConstructorOptions[] = [];
if(isMac){
  NameSubMenu = [
    {
      label: app.name,
      submenu: [
        { role: 'about' },
        { type: 'separator' },
        { role: 'services' },
        { type: 'separator' },
        { role: 'hide' },
        { role: 'hideOthers' },
        { role: 'unhide' },
        { type: 'separator' },
        { role: 'quit' }
      ]
    }
  ];
}

const template : Electron.MenuItemConstructorOptions[] = [
  // { role: 'appMenu' }
  ...NameSubMenu ,
  // 以下略

これも結局エラーが出るので意味なし。

結論

誰かに頼る。
Create Electron Menu in TypeScript? - Stack Overflow

pushにするといいとかなんとか。
ということで仕方なく全部pushに書き換えた。

const template: Electron.MenuItemConstructorOptions[] = [];

const macMenu: Electron.MenuItemConstructorOptions[] = [
  {
    label: app.name,
    submenu: [
      { role: 'about' },
      { type: 'separator' },
      { role: 'services' },
      { type: 'separator' },
      { role: 'hide' },
      { role: 'hideOthers' },
      { role: 'quit' },
    ],
  },
];

if (isMac) {
  // { role: 'appMenu' }
  template.push(...macMenu);
}
// { role: 'fileMenu' }
template.push({
  label: 'ファイル',
  submenu: [isMac ? { role: 'close' } : { role: 'quit' }],
});

// { role: 'editMenu' }
const editSubMenu: Electron.MenuItemConstructorOptions[] = [
  { role: 'undo' },
  { role: 'redo' },
  { type: 'separator' },
  { role: 'cut' },
  { role: 'copy' },
  { role: 'paste' },
];

if (isMac) {
  editSubMenu.push(
    ...([
      { role: 'pasteAndMatchStyle' },
      { role: 'delete' },
      { role: 'selectAll' },
      { type: 'separator' },
      {
        label: 'Speech',
        submenu: [{ role: 'startSpeaking' }, { role: 'stopSpeaking' }],
      },
    ] as Electron.MenuItemConstructorOptions[])
  );
} else {
  editSubMenu.push(
    ...([
      { role: 'delete' },
      { type: 'separator' },
      { role: 'selectAll' },
    ] as Electron.MenuItemConstructorOptions[])
  );
}

template.push({
  label: 'Edit',
  submenu: editSubMenu,
});

// { role: 'viewMenu' }
template.push({
  label: 'View',
  submenu: [
    { role: 'reload' },
    { role: 'forceReload' },
    { role: 'toggleDevTools' },
    { type: 'separator' },
    { role: 'resetZoom' },
    { role: 'zoomIn' },
    { role: 'zoomOut' },
    { type: 'separator' },
    { role: 'togglefullscreen' },
  ],
});

// { role: 'windowMenu' }

const windowSubMenu: Electron.MenuItemConstructorOptions[] = [
  { role: 'minimize' },
  { role: 'zoom' },
];

if (isMac) {
  windowSubMenu.push(
    ...([
      { type: 'separator' },
      { role: 'front' },
      { type: 'separator' },
      { role: 'window' },
    ] as Electron.MenuItemConstructorOptions[])
  );
} else {
  windowSubMenu.push({ role: 'close' });
}

template.push({
  label: 'Window',
  submenu: windowSubMenu,
});

template.push({
  role: 'help',
  submenu: [
    {
      label: 'Learn More',
      click: async () => {
        const { shell } = require('electron');
        await shell.openExternal('https://electronjs.org');
      },
    },
  ],
});

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

いや何かもう少しいい方法が存在している気がしてならない。でもこれ以上思いつきませんでした。
何かいい案あれば教えてください。
それではー

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1