水無瀬のプログラミング日記

プログラムの勉強した時のメモ日記

TypeScriptをwebpackでビルドしてみる

はじめに

名前はよく聞くけど直接使ったことなかったwebpack。
いい加減ちゃんと勉強してみようと思った回。
簡単なアプリを作って動かすとこまで。

webpackとは

複数のファイルをまとめてくれるモジュールバンドラー。
よく使われるのはJS(TS)を1つのファイルにまとめたりで使われてる印象。
ちなみにTypeScriptのトランスパイルにはデフォルトのtscがあるが、
複数のファイルを纏められない(ES Modules(import,export)をまとめる機能が無い)からwebpackを使うっぽい。

インストール

必要なものをインストールしておく。
お好みで-gなり、-Dなりを付けてください。

npm install webpack webpack-cli typescript ts-loader

ディレクトリ構成

以下のファイルとフォルダを作成する。
* src: ソースファイルを入れておく用 * tsconfig.json: typescript用(中身は後述) * webpack.config.json: webpackの設定用(中身は後述)

作った結果は下記の通り。

.
├── package.json
├── src
├── tsconfig.json
└── webpack.config.js

アプリ作成

の前に……

先程作ったtsconfig.jsonwebpack.config.jsの中身を記載する。

// tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "es5", // TSはECMAScript 5に変換
    "module": "es2015" // TSのモジュールはES Modulesとして出力
  }
}
// webpack.config.js
// output.pathに絶対パスを指定する必要があるため、pathモジュールを読み込んでおく
const path = require('path');

module.exports = {
  // モードの設定、v4系以降はmodeを指定しないと、webpack実行時に警告が出る
  mode: 'development',
  // エントリーポイントの設定
  entry: './src/main.ts',
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: 'ts-loader'
      }
    ]
  },
  resolve: {
    extensions: [
      '.ts'
    ]
  },
  // 出力の設定
  output: {
    // 出力するファイル名
    filename: 'bundle.js',
    // 出力先のパス(v2系以降は絶対パスを指定する必要がある)
    path: path.join(__dirname, 'output')
  }
};

アプリを作る

これでTypeScriptをビルドする準備ができたのでアプリを作っていく。
以下用に必要なファイルを作成していく。 * index.html: 表示するHTML * src/main.ts: メインとなるts。webpack.config.jsに書いたやつ * src/sub.ts: main.tsで読み込まれるts

ここまでのフォルダ構成は下記の通り。
作成したファイルの中身を入れていく。

.
├── index.html
├── package.json
├── src
│   ├── main.ts
│   └── sub.ts
├── tsconfig.json
└── webpack.config.js

index.html

<!doctype html>
<html>
<head>
    <title>こんにちは変換器</title>
    <meta charset="utf-8"/>
</head>
<body>
    <script src="./output/bundle.js"></script>
</body>
</html>

main.ts

import { Sub } from './sub';

class Main {
    private sub: Sub
    constructor() {
        this.sub = new Sub();
        const body = document.getElementsByTagName('body');
        const element = body.item(0);
        const div = document.createElement('div');
        div.innerHTML = '「こんにちは」を他言語に変換します。<br>好きな言語を入れてください。<br>※ちなみに対応しているのは日本語、英語、スペイン語、ドイツ語です。<br>';
        const input = document.createElement('input');
        input.id = 'country'
        const button = document.createElement('button');
        button.textContent = '変換';
        button.addEventListener('click', () => {
            this.transtrationGreeting(document.getElementById('country')['value']);
        });
        div.appendChild(input);
        div.appendChild(button);
        element.appendChild(div);
    }

    public transtrationGreeting(country: string) {
        const addDiv = document.createElement('div');
        addDiv.innerHTML = `こんにちはを${country}で言うと${this.sub.hello(country)}`;
        document.getElementsByTagName('body').item(0).appendChild(addDiv);
    }
}

const main = new Main();

sub.ts

export class Sub {
    private greeting = {
        '日本語': 'こんにちは',
        '英語': 'Hello',
        'スペイン語': 'Hola',
        'ドイツ語': 'Hallo'
    }

    constructor () { }

    public hello(country: string) {
        return this.greeting[country];
    }
}

アプリをビルドする

ビルドは簡単。webpackだけでOK。
webpack.config.jsに書いたとおり、outputフォルダにbudle.jsが生成されるはず。
ここまでのフォルダ構成は下記の通り。

.
├── index.html
├── output
│   └── bundle.js
├── package.json
├── src
│   ├── main.ts
│   └── sub.ts
├── tsconfig.json
└── webpack.config.js

ビルドが通れば、index.htmlを開くとアプリが動く。
アプリは超絶適当なので内容は割愛。htmlに内容書けっていう内容。

まとめ

とりあえずTypeScriptだけで使った場合についてやってみた。
実際にはTSだけなんて無いと思うから、今度はReactなりVueなりと合わせて使ってみようかな。
ちなみに作ったものはここに置いた。
アプリはこっち

参考サイト

公式 最新版TypeScript 3.0+Webpack 4の環境構築まとめ(React, Vue.js, Three.jsのサンプル付き)

Javascriptのthisまとめ

はじめに

JavaScriptを触ってるけど、thisの使い方は特に意識しないでやってきた。
thisの挙動が複雑だとか4種類あるとかは聞いたことあったけど、
その問題に直面することがなかったから無視してたというのが本音。
天からthisから逃げるなと言われてる気がしたので、いい加減に調べてまとめることにする。

this4種

JavaScriptのthisは以下の4つ。
それぞれについて簡単な例とともにまとめる。

  • 関数(function)呼び出しパターン
  • メソッド呼び出しパターン
  • コンストラクタパターン
  • apply,bind,callパターン

関数(function)呼び出しパターン

関数を呼ぶ際のthisは基本的にはglobalオブジェクトを指す。
ただし、use strictを指定しているとundefinedになる。

// 'use strict'なしパターン 
function test() { 
    console.log(this); 
} 
test(); // Window{...} 
// 'use strict'ありパターン 
'use strict' 
  
function test() { 
    console.log(this); 
} 
test(); // undefined 

メソッド呼び出しパターン

関数もメソッドも同じだろって感じだけど実は違う。 下記のようなパターンのこと。

const obj = { 
    test: function() { 
        console.log(this); 
    } 
} 
obj.test(); // { test: [Function: test] } 

ちなみにこのthisをどうしてもglobalとして使いたいなら下記のようにする。

const obj = { 
    test: function() { 
        console.log(this); 
    } 
} 
// 下記パターンどちらでもglobalになる 
// パターン1 
const globalThis = obj.test; 
globalThis(); // Window{...} 
// パターン2 
(0, obj.test)(); // Window{...} 

コンストラクタパターン

コンストラクタのthisは生成するobject(自分自身)を指すようになる。
何言ってるかわからなくても、見ればなんとなくわかるはず。

class Test { 
    constructor(value){ 
        this.value = value; // 自身(Test)のvalueに引数のvalueを入れてる 
    } 
} 
const newClass = new Test(10); 
console.log(newClass.value); // 自身のvalueにちゃんと入っている 

ちょっと古いクラスの書き方するなら下のような感じ。
もちろん結果は同じ。 ただしこの書き方だと、あくまで関数なのでnewを忘れるとthisはglobalを指すようになってしまう。
おとなしくclassと付けるのがいいと思う。

function Test(value) { 
    this.value = value; 
} 
const newClass = new Test(10); 
console.log(newClass.value); 

apply,bind,callパターン

apply,call

applyとcallは共にthisの内容を設定できるようにするもの。
下記のようなパターンのこと。

function test() { 
    console.log(this); 
} 
const obj = { name: 'test' }; 
test(); 
// testのthisをobjに設定 
test.call(obj); // obj{...} 
test.apply(obj); // obj{...} 

applyとcallの違いは引数のとり方が違う。
applyは配列、callはそのままといった具合。

const testObj = { 
    test: function(value1, value2) { 
        console.log('this:', this); 
        console.log('value1:', value1); 
        console.log('value2:', value2); 
    } 
} 
const obj = { name: 'test' }; 
testObj.test.apply(obj, [1, 3]); 
testObj.test.call(obj, 1, 3); 
# 実行結果 
this: { name: 'test' } 
value1: 1 
value2: 3 
this: { name: 'test' } 
value1: 1 
value2: 3 

bind

ここで参照するthisはこれじゃなきゃ嫌なんだ!という時に使う。
要は強制的にthisの内容を紐付けてやること。

function test() { 
    console.log(this); 
} 
const obj = { name: 'test' }; 
// testのthisをobjにする 
const bid = test.bind(obj); 
test(); // こっちはglobal参照 
bind(); // こっちはobj参照 

おまけ(アロー関数)

アロー関数はthisを変えたりしない。
変に値が変わらない分わかりやすいかも知れない。

const obj = { 
    func: function(){ 
        console.log(this); 
    }, 
    arrow: () => { 
        console.log(this); 
    } 
} 
obj.func(); // { func: [Function: func], arrow: [Function: arrow] } 
obj.arrow(); // Windw{...} 

まとめ

ようやく向き合ったthisは思ってたよりも理解不能じゃなかった。
理解不能ではなかったけど、わかりやすく説明するのは無理だなって思った。
今度はprototype辺りとも向き合いたい。

Javascriptのfor系統まとめ(for,for..in,for..of,forEach)

はじめに

for...inとfor...ofどっちがどっちか忘れるからまとめる。
ついでに他のforについてもまとめて、個人的なメリットデメリットも記載する。

forの種類

  • for
  • for...in
  • for...of
  • Array.forEach()

for

単純なfor。指定した回数繰り返し処理をしてくれる。

// 単純ループ 
let sum = 0; 
for (let i = 1; i <= 10; i++) { 
    sum += i; 
} 

// リスト全ループ 
const list = [1, 3, 5, 7]; 
for (let i = 0; i < list.length; i++) { 
    console.log('value:', list[i]); 
} 

メリット

わかりにくいとかは無いと思う。
これといったメリットは思いつかなかった。

デメリット

いちいち書くのがめんどくさいと思う。

for...in

オブジェクトのプロパティ(キー)分ループしてくれる。

const obj = {'a':1, 'b':2, 'c':3}; 
for (const value in obj) { 
    console.log('key:', value); 
    console.log('value:', obj[value]); 
} 

メリット

オブジェクト(連想配列)のループができる。

デメリット

配列に使うとあんまりよくない。

const list = [1, 3, 5]; 
list.text = 'hoge'; 
list.func = function(){
    console.log('hoge');
}
for (const key in list) { 
    console.log('key:', key); 
    console.log('value:', list[key]); 
}

出力は下記の通り。
配列のループは直感だと値が逐次取れるイメージだと思う。
実際はindexが取れる。
しかも後から追加した変なのも普通に取れてしまう。

key: 0
value: 1
key: 1
value: 3
key: 2
value: 5
key: text
value: hoge
key: func
value: function (){
    console.log('hoge');
}

当たり前だけど、TypeScriptでも変わらない。

class test {
    constructor() {
        const list:number[] = [1, 3, 5];
        list['text'] = '1';
        for (const value in list) {
            console.log(value);
        }
    }
}

new test();

/*
  結果
  0
  1
  2
  text
*/

for...of

プロパティの値分ループするfor文。
Javaの拡張forが近しい動きだと思う。

const list = [1, 3, 5]; 
for (const value of list) { 
    console.log(value); 
} 

メリット

下みたいなことしても問題なく出力してくれる。

const list = [1, 3, 5]; 
list.text = '1'; 
for (const value of list) { 
    console.log(value); 
} 

デメリット

ES6で導入されたから、古いブラウザだと動かない。
ブラウザ変えろ。

Array.forEach

配列要素に対しての繰り返しを行ってくれるメソッド。
できることはfor...ofとほぼ同じ。

const list = [1, 3, 5]; 
list.forEach(element => { 
    console.log(element); 
}); 

callbackの引数全指定した場合は下記の通り。
下記例のarrayについては、特に何もしなければループ中同じ値が取れる。

const list = [1, 3, 5]; 
list.forEach((element, index, array) => { 
    // 取り出した配列の値 
    // 例:1,3,5 
    console.log('element:', element); 
    // 配列のインデックス 
    // 例:0,1,2... 
    console.log('index:', index); 
    // ループしてる配列自身 
    // 例:[1, 3, 5] 
    console.log('array:', array); 
}); 

メリット

indexを使うことができる。
(人によると思うけど)見やすい。
callbackだから関数を切り出せる。
(切り出されているところを見たこと無いし、for...ofでも同じ気がするけど)

const loop = element => { 
    console.log('callback:', element); 
} 
list.forEach(loop); 

デメリット

あくまでcallbackなので、breakできない。
※continueはreturnと書けばOK。
速度に不安があるみたいなので、速度が重要な 場合は使わないほうが無難だと思う。

おまけ

オブジェクトに対してforEachかけるなら、Object.keys()と組み合わせる。

const obj = { 'a': 1, 'b': 2, 'c': 3 }; 
Object.keys(obj).forEach(key => { 
    console.log('key:', key); 
    console.log('value:', obj[key]); 
}); 

まとめ

for...in以外ならどれでも良いと思う。
軽く調べた感じ、for...ofが優勢だった。
配列にしか使えないforEachと違って、
for...ofはiterable objectならなんでも使えるらしい。
更に速度面に不安なのもあると思われる。
個人的には速度をとても気にする必要もないし見やすいので、
breakする必要が無いならforEachを使ってる。

GoogleSiteの変更通知をSlackで受け取る

はじめに

ある日、Googleサイトの通知をSlackで受け取りたいという話がありました。
しかし、DriveやCalendarの通知を受け取ることはできるSlackくんですが、なんとSiteの通知は受け取れないのです。
調べたところ先人の方たちは自力で実装していた。
今回は先人に習って自力での実装をしてみた。

Googleサイトの変更通知をSlackに流すまで

上記の通り、SlackとGoogleサイトを紐付けるAPPはなかった。
仕方ないから自力で実装する。
流れは下記の通り。

  1. サイトの変更通知をメールで受け取る
  2. GASでGmail見て変更通知から必要な情報を抜き取る
  3. SlackAPI使って抜き取った情報を適当なチャンネルに流す

サイトの変更通知を受け取る

サイトでShift + fで通知を受け取れるようになる。
もしくは、サイト右上の歯車から「サイトの操作→サイトの変更通知を受け取る」でもOK。

GASからGmailの情報を抜きとる

サイトの通知メールはnoreply@google.comというアドレスで来るので、
このアドレスから送られてきた未読メールを対象に情報を取得する。
未読を取得するには下記ようにするだけでOK。

function getUnreadNotificationMail() {
  const unReadThreads = GmailApp.search("from:noreply@google.com is:unread");
}

メールはHTML形式 & テンプレフォーマットで届くので、
必要な情報をごちゃごちゃして抜き出す。
抜き出すのは下記のように。

function getUnreadNotificationMail() {
  const unReadThreads = GmailApp.search("from:noreply@google.com is:unread");
  unReadThreads.forEach(function(unReadThread, index, array) {
    const mails = unReadThread.getMessages();
    mails.forEach(function(mail, index, array){
      if (mail.isUnread()){
        const items = createItems(mail);
        if (items != null) {
            // ここにSlackへの通知処理を記載する
        }
      }
    });
  });
}

function extractBody(body) {
  if (body.indexOf("しました") == -1) {
    // テンプレフォーマット以外は無視
    return null;
  }
  //本文から必要な範囲を取り出す
  return body.substring(0,body.indexOf("しました") + "しました".length);
}

function createItems(mail) {
  const message = extractBody(mail.getBody());
  if (message == null) {
    return null;
  }
  //通知に必要な情報をオブジェクトにまとめる
  const items = {};
  // タイトルから変更ページ名を取得する。
  // タイトルの頭にはサイト名が着いているのでそこは削除
  items.text = mail.getSubject().substring("[サイト名称]".length) + "\r" + mail.getDate().toLocaleString();
  // 更新者取得
  items.author_name = message.substring(message.indexOf(">") + 1, message.indexOf("さん") + "さん".length);
  // 更新ページ名取得
  items.title = message.substring(message.indexOf(">", 80) + 1, message.indexOf("<", 80)) + message.substring(message.lastIndexOf("を"));
  // 更新ページURL取得
  items.title_link = message.substring(message.indexOf("http://sites.google.com/site/"), message.indexOf("\"", message.indexOf("http://sites.google.com/site/")));
  return items;
}

Slackに変更内容を通知する

toke取得

SlackAPIを使うのにtokenが必要となる。
ここからtoken取得しておく。

Slackに通知を飛ばす

Slackへの通知はAPIを使う。
プレーンテキストで送ることもできるが、見やすいようにちょっと見た目を変えて上げる。

function postNotice(items) {
  const token = "先程取得したトークン";
  const channel = "チャンネル名";
  const username = "通知するBOT名";
  const attachments = JSON.stringify([
      {
        color: "#89CEEB", //インデント線の色
        author_name: items.author_name, //インデント内に表示される著者名
        title: items.title ,//インデント内に表示されるタイトル
        title_link: items.title_link,//そのリンク
        text: "上記リンクをクリックすると対象のページやファイルを表示します。" //インデント内に表示されるテキスト
      }
    ]);
  const payload = {
    "channel" : channel, //通知先チャンネル名
    "text" : items.text, //送信テキスト
    "username": username, //BOTの名前
    "attachments": attachments //リッチなメッセージを送る用データ
  };
  const option = {
    "method" : "POST", 
    "payload" : payload
  };
  return UrlFetchApp.fetch("https://slack.com/api/chat.postMessage?token=" + token, option);
}

この関数をgetUnreadNotificationMail()のコメント部で呼び出してあげれば完成。

まとめ

コードの最終形は下記の通り。

function getUnreadNotificationMail() {
  const unReadThreads = GmailApp.search("from:noreply@google.com is:unread");
  unReadThreads.forEach(function(unReadThread, index, array) {
    const mails = unReadThread.getMessages();
    mails.forEach(function(mail, index, array){
      if (mail.isUnread()){
        const items = createItems(mail);
        if (items != null) {
          // ここにSlackへの通知処理を記載する
          const result = JSON.parse(postNotice(items));
          if (result.ok) {
            mail.markRead();
          }
        }
      }
    });
  });
}

function extractBody(body) {
  if (body.indexOf("しました") == -1) {
    return null;
  }
  //本文から必要な範囲を取り出す
  return body.substring(0,body.indexOf("しました") + "しました".length);
}

function createItems(mail) {
  const message = extractBody(mail.getBody());
  if (message == null) {
    return null;
  }
  //通知に必要な情報をオブジェクトにまとめる
  const items = {};
  items.text = mail.getSubject().substring("[UbiquitousHome]".length) + "\r" + mail.getDate().toLocaleString(); //年/月/日 時:分:秒 JST形式になる
  items.author_name = message.substring(message.indexOf(">") + 1, message.indexOf("さん") + "さん".length);
  items.title = message.substring(message.indexOf(">", 80) + 1, message.indexOf("<", 80)) + message.substring(message.lastIndexOf("を"));
  items.title_link = message.substring(message.indexOf("http://sites.google.com/site/"), message.indexOf("\"", message.indexOf("http://sites.google.com/site/")));
  return items;
}

function postNotice(items) {
  const token = "先程取得したトークン";
  const channel = "チャンネル名";
  const username = "通知するBOT名";
  const attachments = JSON.stringify([
      {
        color: "#89CEEB", //インデント線の色
        author_name: items.author_name, //インデント内に表示される著者名
        title: items.title ,//インデント内に表示されるタイトル
        title_link: items.title_link,//そのリンク
        text: "上記リンクをクリックすると対象のページやファイルを表示します。" //インデント内に表示されるテキスト
      }
    ]);
  const payload = {
    "channel" : channel, //通知先チャンネル名
    "text" : items.text, //送信テキスト
    "username": username, //BOTの名前
    "attachments": attachments //リッチなメッセージを送る用データ
  };
  const option = {
    "method" : "POST", 
    "payload" : payload
  };
  return UrlFetchApp.fetch("https://slack.com/api/chat.postMessage?token=" + token, option);
}

参考サイト

Google Apps Scriptを用いてライブラリを使わずにSlackにGoogleサイトの変更通知を投稿する

npm-scriptsを使ってみる

npm scriptsとは

package.jsonscriptに書いてある下記のようなやつ。
(下のはnpm initの初期値)

"scripts": {
  "test": "echo \"Error: no test specified\" && exit 1"
}

事前準備

便利そうなやつがデフォルトで使えないから、先に入れておく。

# スクリプトの直列、並列実行をするために必要
npm install --save-dev npm-run-all
# ファイルの監視をして何かをするために必要
npm install --save-dev watch
# クロスプラットフォーム用(コピー,リムーブの代用)
npm install --save-dev cpx
npm install --save-dev rimraf

書けること

shell(bat),alias,scriptsに書いた自分以外のスクリプトが動かせる。
ターミナル(コマンドプロンプト)で動くものは基本的に動くと思われる。

使い方

基本的にはnpm run {script名}でOK。
以下、scriptの例と実行結果サンプル。

"scripts": {
  "echo": "echo テスト"
}
$ npm run echo
> hoge@1.0.0 echo /test
> echo テスト

テスト

予約語

基本的には上記使い方で問題ないが、4種類の予約後が用意されている。
start,stop,restart,testの4つが予約語となっており、npm startのようにrunを書かなくても実行できる。

  "scripts": {
    "start": "echo run省略"
  }
$ npm start
> hoge@1.0.0 start
> echo run省略

run省略

クラスプラットフォーム向け

scriptはシェルなり、ターミナルコマンドなりを使うことができる。
package.jsonに書くことはできるが当然ウインドウズでは動かないので、そこを解消する。
下記jsonのうち、cpxはコピーコマンドの代替。
rimrafrm -rfの代替となる。
※実行結果は元のコマンドと同じなので省略。

  "scripts": {
    "copy": "cpx ./text.txt ./text2.txt",
    "clean": "rimraf ./dist"
  }

直列実行

2つのscriptsを直列実行したいときは、
run-s {コマンド1} {コマンド2}でコマンド1の結果を待ってコマンド2を実行できる。
例えばビルドして結果をどこかにコピーしたいとき。
以下のようなスクリプトを用意しておくと救われる。
(例はAngular-cli利用した時のもの)

"script": {
    "all": "run-s build copy"
    , "build": "ng build"
    , "copy": "cpx ./dist/* ../server/"
}

並列実行

2つの複数のスクリプトを走らせたいときは、
run-p {コマンド1} {コマンド2}でコマンド1と2を同時に実行できる。
パッと用途が思いつかなかった。下記例だと、2種類のビルドを同時に実行している。

"script": {
    "parallel": "run-p build:tsc build:angular"
    , "build:tsc": "tsc test.ts"
    , "build:angular": "ng build"
}

監視

ファイルの変更検知して自動でビルドしたいときは、
npm run watch {コマンド} {監視対象}で監視対象の変更を検知してコマンドを実行できる。
使い方としては、開発中にホットデプロイのようなことをしたい場合。
ローカルで開発中にいちいちビルドと再起動を行わなくて済むので便利。
下記例はビルドからサーバにファイルを配置、サーバを起動ということをしている。
※フォルダ構成やファイル名は適当に記述

"script": {
    "watch": "watch \"npm run start:app\" ./src",
    "start:app": "run-s build boot:server",
    "boot:server": "nodemon ./app.js",
    "build": "run-s build:app copy",
    "build:app": "ng build",
    "copy": "cpx ./dist ../server/"
}

まとめ

以上npm-scriptの簡単な説明でした。
nodeを使うアプリであれば、おそらくpackage.jsonnpmはあると思うのでとっかりとしては始めやすい方だと思います。
スクランナーとして使ってる方もいるみたいなので是非使ってみてはいかがでしょうか。

参考サイト

公式

Angular6 + Reduxで簡単なToDoアプリを作る

はじめに

Reduxについて勉強するために、Redux + Angularで簡単なアプリを作成する。
(React知らないので、Angularでやります・・・)
作成したアプリはこちらから

Reduxとは

Facebookが提唱しているFluxと言うアーキテクチャの派生形。
Reactと使われているのが多いイメージだが、Reactに依存している訳では無いので他でも使用可能。

Reduxの構成要素

Action

Actionは何をするかと言う情報を持ったオブジェクト。
UIからStoreへ送るデータとなる。
そしてStoreのための唯一の情報源となる。
Actionは単なるJavascriptのオブジェクトである。
ただし、Actionのタイプを示すtypeプロパティを必ず持つ。

{
    type: 'ADD_TODO'
    , text: 'Reduxについてまとめて記事を書く'
};

ActionCreator

Actionを生成する関数のこと。
単純にActionを返す。
Actionと混同しやすいため、要注意。

function addTodo(text: string) {
    const newTodo: TodoAction = {
        type: 'ADD_TODO'
        , text: text
    };

    return newTodo;
}

State

Reduxでは、すべてのアプリケーションの状態は1つのオブジェクトとして保持される。
その状態を保持しているのがState。

{
  todos: [
    {
      text: 'Reduxについてまとめて記事を書く',
      completed: true,
    },
    {
      text: '記事をアップロードする',
      completed: false
    }
  ]
}

Reducer

ActionとStateをもとに新しいStateを作成して返す。
Reducerはピュアな関数であることが必須。
そのため、下記のことは厳禁。
※ピュアな関数:インプットが同じなら毎回必ず同じ結果が変える関数 * 引数に手を加える * 副作用を起こす。例)APIコールやページ遷移 * 純粋ではない関数を呼び出す。 例)Date.now() や Math.random()

Reducerは必ずピュアな関数でなくてはいけないため、
引数が与えられると、次の状態を計算して返す。

public reducer(action: any, state: any) {
    switch (action['type']) {
        case 'ADD_TODO':
            const retState: Array<ITodoState> = state.concat();
            retState.push({
                text: action['text']
                , completed: action['completed']
            });
            return retState;
    }
}

Store

StoreはActionとReducerをまとめるオブジェクト。
下記のような役割を持つ。
* アプリケーションの状態を保持する * getState()による状態へのアクセスを許可する * dispatch(action)による状態更新を許可する * subscribe(listener)によりリスナーを登録する * subscribe(listener)で返される関数により、リスナーの登録解除を処理する

Storeはアプリケーション中に1つのみ。
複数のStoreを作ってはいけない。

Reduxの3大原則

Reduxは以下の3大原則に沿って設計されている。
1. Single source of truth
アプリケーション内でStoreは1つのみとし、Stateは単独のオブジェクトとしてStoreに保持される。 2. State is read-only
Stateを直接変更することはできず、actionをStoreへdispatchすることでしかStateは変更できない。 3. Mutations are written as pure functions
Stateを変更する関数(Reducer)はpureな関数にする。

まとめ

Reduxの勉強のために1から?簡単なTODOアプリを作ってみた。
(追加しか出来ない上に、どこにも保存されていませんが……)
ちなみに、Angularにも便利なライブラリがあるらしく、1から作る必要は特になかった……
そのうち、上のライブラリを使ってちゃんと作ろうと思います。

参考にさせて頂いたサイト

SlackBotにDocomo雑談対話APIを組み込む

はじめに

SlackBotと会話するのが夢だったので、実現します。

前提

作業しているPC:Mac(Sierra 10.12.6)

雑談対話APIの準備

今回は、Docomo雑談対話APIを使う。
事前に色々登録を済ませてAPIキーを取得しておく。
申請に時間が〜みたいな事書いてあるけど、すぐ帰ってくるはず。

登録したらAPIを使うためにAppIDが必要なので、ここを参考に事前に取得しておく。
PostmanでもCurlでもPOST飛ばせれば何でもいいと思われる。
準備が出来たらようやくプログラムが作れる。

Botに組み込む

とりあえず全部に反応してくれれば問題ないので、@default_replyで指定しとく。
ソースは以下を参照。

# -*- coding: utf-8 -*-

import requests
import json
from slackbot.bot import default_reply
import base64


# エンドポイントの設定
KEY = '取得したAPIキー'
endpoint = 'https://api.apigw.smt.docomo.ne.jp/naturalChatting/v1/dialogue?APIKEY=REGISTER_KEY'
url = endpoint.replace('REGISTER_KEY', KEY)
appid = '取得したAppId'

# requestパラメータ
# voiceTextがAPIに投げる会話文になる
# language,botIdはこれで固定
payload = {'language': 'ja-JP', 'botId': 'Chatting', 'appId': appid, 'voiceText': '', 'clientData': {'option': {'mode': ''}}}

# レスポンス
data = {
    'systemText': {
        'expression': '',
        'utterance': ''
    }}
HEADERS = {'Content-type': 'application/json'}


@default_reply()
def chat(message):
    global payload
    global data

    payload['voiceText'] = message.body['text']

    try:
        # 送信
        r = requests.post(url, data=json.dumps(payload), headers=HEADERS)
        data = r.json()

        # jsonの解析
        response = data['systemText']['expression']

        # command取り出し
        # しりとりのときはモードを'srtr'に設定するため、レスポンスからモードを受け取る
        # base64でエンコードされてるので、デコードしてリクエストに詰める
        command = json.loads(base64.b64decode(data['command']).decode('utf-8'))
        payload['clientData']['option']['mode'] = command['mode']

    except Exceptin as e:
        # エラーだとスタックトレースが出るので、一応ハンドリングしておく
        message.reply('は?何言ってんの?')

    # 表示
    message.reply(response)

話しかけて雑談してくれればOK。

f:id:minase_mira:20180603212830p:plain

まぁ、上辺の会話は出来てそう・・・?

最後に

会話の精度はそこそこないイメージ。
簡単な雑談くらいなら問題ないと思います。
的はずれな会話はご愛嬌というこで。