ReactとElectron間のデータの送受信の手法と一例
Tags: react, redux, electron, japanese
Published: 2018-10-01 14:50:46

---
title: ReactとElectron間のデータの送受信の手法と一例
published: true
description: 
tags: react, redux, electron, japanese
---

# 注意！
この記事内のいくつかの項目はoutdatedになりました。特に、`remote.require`は非推奨です。

バーチャルユーチューバーです。皆さんもバーチャルユーチューバーですか？

前回の[React+Electronアプリを作る話](https://dev.to/origamium/create-reactelectron-application-in-quickly--36nl)ではアプリを作るまでの事しか話しませんでした。そして、React及びRedux上で物事がだいたい済むのならば、ElectronやNode.jsについて深く思い悩むこともないでしょう。  
しかし、Electronでアプリケーションを作りたいのならば、Node.jsおよびその上で動作するパッケージを使用したいことや、Electron上で取得したデータをReact上で表示したい、という望みがあるはずです。
ここで問題になるのは、そのElectron-メインプロセス-とReact+Redux-レンダラプロセス-の間をどのように処理するか、についてです。  
  
# `remote.require`
次に示すコードは、レンダラプロセスで使用している部分です。

```js
// レンダラプロセス
//@flow
const { remote } = window.require('electron');
const app = remote.require('electron').app;

export default (): Promise<string> => new Promise((resolve) => {
    resolve(app.getAppPath()); //アプリケーションの実行場所が絶対パスとして返ります
});
```

`remote.require`はレンダラプロセスで利用可能な`require`の変わりだと思えば十分です。内部的にはinter-process communication (IPC)を利用してメインプロセスのメソッドを呼び出しており、最もシンプルにメインプロセスとのやり取りが可能です。
基本的に、シリアライズ可能なデータであれば受け渡しが可能です。

```js
// レンダラプロセス
//@flow
const { remote } = window.require('electron');
const app = remote.require('electron').app;
const fs = remote.require('fs');

export default (): Promise<any> => new Promise((resolve, reject) => {
    try {
        fs.writeFileSync(app.getAppPath()+"/internet.json", {googlePublicDns: "8:8.8.8"});
        resolve();
    } catch (e) {
        reject(e);
    }
});
```

# `ipcMain`、`ipcRenderer`
`ipcMain`と`ipcRenderer`はそれぞれEvent-Drivenな方策でメインプロセスとレンダラプロセスのデータの受け渡しを可能にする機能です。基本的な使い方は[Electron ipcMain](https://electronjs.org/docs/api/ipc-main)のドキュメントに載っていますが、実際に使用するとなると（データフローをすべてredux上で済ませたいという気持ちがあるため）reduxとそのmiddlewareを利用した上で`ipcRenderer`を利用する場合があります。
少し複雑になりますが、`redux-saga`を用いて`ipcRenderer`を利用した例を次のコードに示します。

```js
// レンダラプロセス
// @flow
import { eventChannel, END } from "redux-saga";
import { fork, take } from 'redux-saga/effects';

const { ipcRenderer } = window.require('electron');

const ipc = function* () {
    try {
        const channel = yield eventChannel(emit => {
            ipcRenderer.on('asynchronous-reply', (event, arg) => {
                console.log(arg);
                emit({type: "EVENT_RECEIVED", payload: {}});
            });

            return () => {};
        });

        while (true) {
            yield take(channel)
        }
    } catch (e) {
        console.error("ipc connection disconnected with unknown error");
    }
};

export default function*() { // redux-sagaのrootsagaで1度だけ呼び出す
    yield fork(ipc);
}
```

`eventChannel`はイベントエミッターを監視するのによく使用されます。`emit => {}`の中でリスナーを実装し、`emit`でactionをdispatchすることができます。
本来であればoncloseかなにかが送信された時点で`emit(END)`を利用しイベントが終了したことをsagaに通知すべきですが、ipcRendererには無いので無視します。

```js
// メインプロセス
ipcMain.on('asynchronous-message', (event, arg) => {
    console.log(arg); // prints "ping"
    event.sender.send('asynchronous-reply', 'pong')
});
```

今回の場合、`asynchronous-message`という名前のついたイベントがレンダラプロセスから送信された場合、メインプロセスは`asynchronous-reply`イベントを返送し、データとして`'pong'`を返送するようにしています。勿論、イベント名は何でもよく、レンダラプロセスとメインプロセス側で名前さえ一致していればよいです。
そして、やはりシリアライズ可能なものであれば何であれ送受信可能です。

試しに、レンダラプロセスの適当な場所で`'asynchronous-message'`イベントを投げてみましょう。

```js
// レンダラプロセス
const ipcRenderer = window.require('electron').ipcRenderer;

setInterval(() => {
    console.log('sending...');
    ipcRenderer.send('asynchronous-message', 'ping');
}, 1000);
```

成功しているならば、コンソールは次のように表示されているでしょう。

![](https://thepracticaldev.s3.amazonaws.com/i/rvhcj47zt58qrq76eivw.png)

# `WebContents.send`
`ipcMain`のドキュメンテーションにレンダラプロセスに一方的に送信するメソッドがないことに気付きましたか？
レンダラプロセスからメインプロセスへ一方的に送信しまくることはできますが、`ipcMain`では基本的に「返送」しかできませんでした。
メインプロセスからレンダラプロセスに何かを一方的に送信しまくりたいときもあります。その時は、`WebContents.send`を用います。

```js
// メインプロセス
// BrowserWindowのインスタンスmainWindowがあることを前提にしています
mainWindow.webContents.send("asynchronous-message-from-love", "yeah");
```8

ここまで読んできたならもう分かると思いますが、`"asynchronous-message-from-love"`イベントを送信しています。これを受信するには先程のipcRendererの実装を使い回せばよいだけです。

# `WebContents.executeJavaScript`
これはかなり直球的なやり方です。見たままのコードが実行されます。

```js
// メインプロセス
// BrowserWindowのインスタンスmainWindowがあることを前提にしています
mainWindow.webContents.executeJavascript("console.log('yeah')");
```

このコードが実行されると、レンダラ側のコンソールに`yeah`が表示されます。文字列を組み合わせることで、最強のコード実行が可能です。ただし、`eval`と同様、セキュリティ的な危うさもあるため、基本的にはハードコートした文字列の組み合わせのみで`executeJavascript`を実行すべきです。

# conclusion
* レンダラプロセスでは`remote.require`、`ipcRenderer`を利用すればよい
* メインプロセスでは`webContents.send`、`ipcMain`、`webContents.executeJavasSript`を利用すればよい

おわり