LoginSignup
0
1

More than 5 years have passed since last update.

Electron & React & Redux & TypeScript アプリ作成ワークショップ をやってみた1

Last updated at Posted at 2019-03-29

概要

以下をやってみた記録。良記事に感謝。

環境

Node.jsとnpmのバージョン
$ node -v
v10.13.0
$ npm -v
6.4.1

npmプロジェクトの作成

フォルダとpackage.json作成
$ mkdir electron-react-app
$ cd electron-react-app
$ npm init
package.json
{
  "name": "electron-react-app",
  "version": "1.0.0",
  "description": "",
  "main": "main.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "",
  "license": "ISC"
}

gitで作業を記録することにする。
以降、記事には記載しないが、適時コミットしてワークショップを進める。

gitの初期設定
$ git init
$ git add .
$ git commit -m "npm init"
$ git remote add origin (your remote repo)
$ git push -u origin master
日本語対策
$ set LANG=ja_JP.UTF-8
.gitignore
node_modules/
/dist/

必要なライブラリをインストールする

各ライブラリの詳細は、元記事参照。

各種ライブラリインストール
$ npm install --save react react-dom redux react-redux styled-components
$ npm install --save-dev electron typescript tslint webpack webpack-cli ts-loader tslint-loader
$ npm install --save-dev @types/react @types/react-dom @types/redux @types/react-redux

ここから二日目の内容です。

TypeScript コンパイラ・オプションファイルの作成

tsconfig.json作成
$ "./node_modules/.bin/tsc" --init
tsconfig.json
{
  "compilerOptions": {
    "target": "es5",
    "module": "commonjs",
    "jsx": "react",
    "outDir": "./dist",
    "strict": true,
    "esModuleInterop": true,
    "sourceRoot": "./tsx",
    "inlineSourceMap": true,
    "inlineSources": true
  },
  "include": [
    "./ts/**/*"
  ]
}

tslint 設定ファイル の作成

tslint.json作成
$ "./node_modules/.bin/tslint.cmd" --init
tslint.json
{
    "defaultSeverity": "error",
    "extends": [
        "tslint:recommended"
    ],
    "jsRules": {},
    "rules": {
        "quotemark": [
            true,
            "single",
            "jsx-double"
        ]
    },
    "rulesDirectory": []
}

webpack.config.js の作成

2か所タイポがあったのは秘密(以下は修正済み)。

webpack.config.js
const path = require('path');

module.exports = {
    // node.js で動作することを指定する
    target: 'node',
    // 起点となるファイル
    entry: './ts/index.tsx',
    // webpack watch したときに差分ビルドができる
    cache: true,
    // development は、source map fileを作成。再ビルド時間の短縮などの設定となる
    mode: 'development', // "production" | "development" | "none"
    // ソースマップのタイプ
    devtool: 'source-map',
    // 出力先設定 __dirname は node ではカレントディレクトリのパスが格納される変数
    output: {
        path: path.join(__dirname, 'dist'),
        filename: 'index.js'
    },
    // ファイルタイプ毎の処理を記述する
    module: {
        rules: [{
            // 正規表現で指定する
            // 拡張子 .ts または .tsx の場合
            test: /\.tsx?$/,
            // ローダーの指定
            // TypeScript をコンパイルする
            use: 'ts-loader'
        }, {
            // 拡張子 .ts または .tsx の場合
            test: /\.tsx?$/,
            // 事前処理
            enforce: 'pre',
            // TypeScript をコードチェックする
            loader: 'tslint-loader',
            // 定義ファイル
            options: {
                configFile: './tslint.json',
                // airbnb という JavaScript スタイルガイドに従うには下記が必要
                typeCheck: true,
            },
        }],
    },
    // 処理対象のファイルを記載する
    resolve: {
        extensions: [
            '.ts',
            '.tsx',
            '.js', // node_modules のライブラリ読み込みに必要
        ]
    },
};

HTML の作成

index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Electronチュートリアル</title>
</head>

<body>
    <div id="contents"></div>
    <script src="dist/index.js"></script>
</body>

</html>

main.js の作成

main.js
const {
    app,
    BrowserWindow
} = require('electron')

// レンダープロセスとなるブラウザ・ウィンドウのオブジェクト。
// オブジェクトが破棄されると、プロセスも終了するので、グローバルオブジェクトとする。
let win

function createWindow() {
    // ブラウザウィンドウの作成
    win = new BrowserWindow({
        width: 800,
        height: 600
    })
    // index.htmlをロードする
    win.loadFile('index.html')
    // 起動オプションに、"--debug"があれば開発者ツールを起動する
    if (process.argv.find((arg) => arg === '--debug')) {
        win.webContents.openDevTools()
    }
    // ブラウザウィンドウを閉じたときのイベントハンドラ
    win.on('closed', () => {
        // 閉じたウィンドウオブジェクトにはアクセスできない
        win = null
    })
}

// このメソッドは、Electronが初期化を終了し、
// ブラウザウィンドウを作成する準備ができたら呼び出される。
// 一部のAPIは、このイベントが発生した後にのみ使用できる。 
app.on('ready', createWindow)

// 全てのウィンドウオブジェクトが閉じたときのイベントハンドラ
app.on('window-all-closed', () => {
    // macOSでは、アプリケーションとそのメニューバーがCmd + Qで
    // 明示的に終了するまでアクティブになるのが一般的なため、
    // メインプロセスは終了させない
    if (process.platform !== 'darwin') {
        app.quit()
    }
});

app.on('activate', () => {
    // MacOSでは、ドックアイコンがクリックされ、
    // 他のウィンドウが開いていないときに、アプリケーションでウィンドウを
    // 再作成するのが一般的です。
    if (win === null) {
        createWindow()
    }
});

コンパイル確認用スクリプトの記述

ts/index.tsx
import React from 'react';
import ReactDom from 'react-dom';

const container = document.getElementById('contents');

ReactDom.render(
    <p>こんにちは、世界</p>,
    container,
);

コンパイルの確認

webpack実行とelectron起動
$ "./node_modules/.bin/webpack"
$ "./node_modules/.bin/electron" ./

npm script を利用する

package.json
diff --git a/package.json b/package.json
index a98d36d..f69de75 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,8 @@
   "description": "",
   "main": "main.js",
   "scripts": {
+    "build": "webpack",
+    "start": "electron ./",
     "test": "echo \"Error: no test specified\" && exit 1"
   },
   "author": "",
webpack実行とelectron起動
$ npm run build
$ npm start

ここから三日目の内容です。

child_state の作成

ts/IUser.ts
export default interface IUser {
    name: string;
}

export const initUser: IUser = {
    name: '',
};

component の作成

ラベル付きテキストボックスの作成

ts/components/TextBox.tsx
import React from 'react';

// 親コンポーネントから渡されるプロパティを定義する
interface IProps {
    // ラベル文字列
    label: string;
    // テキストボックスのタイプ
    type: 'text' | 'password';
    // テキストボックスに表示する値
    value: string;
    // 値の確定時にその値を親プロパティが取得するためにコールバック関数を提供する
    onChangeText: (value: string) => void;
}

export class TextBox extends React.Component<IProps, {}>{
    // DOMエレメントをレンダリングする
    public render() {
        // ラベルが設定されていない場合は、label を出力しない
        const label = (!!this.props.label) ?
            <label>{this.props.label}</label> :
            null;
        return (
            <span>
                {label}
                <input name="username" type={this.props.type} value={this.props.value}
                    onChange={this.onChangeText}></input>
            </span>
        );
    }

    // 値を変更したら、store.dispatch で action を reducer に渡して、state を更新する。
    // state が更新されたら component の prop が更新され、再レンダリングされ、テキストボックスの内容が変更される。
    private onChangeText = (e: React.ChangeEvent<HTMLInputElement>) => {
        this.props.onChangeText(e.target.value);
    }
}

続きは次回。

感想

  • Electronはもちろんだけど、周辺ツールについても、とても勉強になる
  • TypeScriptの書き方も同上

以上

0
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
0
1