2021-05-16

Electronアプリ開発中に出会ったこといくつか

Electronのアプリ開発中にいくつか勘違いしたり,よく分からなかったり,調べたことをまとめた.

React

textboxの値を取得する

取得には,staterefを使う.単に入力されたテキストを取得したいなら,refのようが良さそう.refを使えばノードの参照を得られる

class Content extends React.Component<Record<string, unknown>> {
  searchBoxRef: RefObject<HTMLInputElement>;
  
  constructor(props: Record<string, unknown>) {
        super(props);
        this.searchBoxRef = createRef();
  }
  
  render() {
        return <>
           <input ref={this.searchBoxRef} />
        </>;
  }
  
  // this.searchBoxRef.current!.value で入力された値が取れる
}

stateを使う場合は,onChangeで変更のたびにstateを更新する.

forwardRefの型

forwardRefを使った例.<select>で選んだ値を取得するためにrefを使っている.

import React, { ForwardedRef, ForwardRefRenderFunction } from "react";

type Props = JSX.IntrinsicElements["select"];

const element: ForwardRefRenderFunction<HTMLSelectElement, Props> = ({ /*  */ }: Props, ref: ForwardedRef<HTMLSelectElement>) => {
    return (
        <select ref={ref}>
      			/* options */
        </select>
    );
};

const Selector = React.forwardRef<HTMLSelectElement, Props>(element);

export default Selector;

普通の関数コンポーネントだとエラーが出るので,ForwardRefRenderFunctionにする必要がある.

Spectron

テストでメインプロセスにメッセージを送る

import { Application } from 'spectron';
  
app = new Application(/* applicationConfig */);

app.electronは,Electron.RemovetMainInterface型である.この型にはipcMainは存在するがipcRendererは存在しない.

しかし,Spectronのドキュメントには,

The electron property is your gateway to accessing the full Electron API.

と書いてある.

app.electron.ipcRenderer.send(/* message */, /* value */));

と書くと,型的にはipcRendererがないのでエラーが出るが,実行すると動く.

テストでメインプロセスにメッセージを送りたいときは,どうするのが正しいんだろう?

lowdb

値の更新が反映されない

レンダラープロセスで値を更新した後にメインプロセスで値を取得すると,変更前の値が返ってくる.

雑な解決法

 adapter = new FileSync<>(ファイル);
 db = low(adapter)

上記のコードが書かれたファイルをメインプロセスとレンダラープロセスでインポートしており,dbが複数作成されていた.ファイルが変更された後にnew FileSyncをし直して値を読み込むと,ちゃんと変更後の値になっていた.

nodeIntegration: trueにして,何でもかんでもレンダラープロセスから呼び出すようにしているのがそもそも良くない.

Typescript / JavaScript

メソッドの抽出をしたらテストが実行されない

非同期のメソッドapi.get()のテストがしたいので,以下のようなテストを書いた.

description('APIのテスト', () => {
	it('get', async () => {
    const res = await api.get();
    // 処理
    assert.strictEqual(/* 条件 */);
  });
});

別のテストと処理が重複していたので,共通部分を関数に抽出した.

async function syori(){
	const res = await api.get();
  // 処理
  return ...
}

description('APIのテスト', () => {
	it('get', async () => {
    const syori_result = syori();
    assert.strictEqual(/* 条件 */)
  });
});

その結果,syoriが実行されなくなり,テストが常に成功するようになった.

原因

抽出した関数は非同期なので,awaitが必要だった(またはdoneを使う).何も考えずに抽出したらダメだった.

description('APIのテスト', () => {
	it('get', async () => {
    const syori_result = await syori(); // await してなかった
    assert.strictEqual(/* 条件 */)
  });
});

Array.findが常に一番目の値を返す

以下のようなコードを書くと,常にxsの一番目の値が返ってくる.

xs.find(async (x) => await x.get())

原因

非同期関数はPromiseを返すので,常に真.そりゃそうだ.,「awaitで中身を取り出す」みたいな印象があるらしい.

結局はPromise.allを使った.

感想

  • 非同期関数に関しては,理解せずにコピペしています,みたいな酷い間違いだった.長時間ハマっていたわけではないけど,そんなコードを書いてしまうのは良くない.
  • とりあえずnodeIntegration: trueにして開発しているけど,それのせいで複雑になっている気がする
  • Electronのe2eテストはawaitだらけになるので,どうにかしたくなった
    • 書き方が悪いのかフレーキーテストで困る