文系エンジニアの勉強記録

文系エンジニアが日々勉強していることを備忘録として投稿していきます

Nuxt.jsでVuex+TypeScriptを使う

はじめに

VuexをTypeScript使って型安全かつインテリセンス効かせたいってときに、様々な方法があり迷いました。 今回は個人的にわかりやすく使いやすいなと思った、Nuxt.js公式で推奨されているvuex-module-decoratorsを使用します。

セットアップ

まずはプロジェクトを作成していきます。

$ npx create-nuxt-app <project-name>

上記コマンドを実行すると、いくつか聞かれるのですが、今回は以下のように選択しました。

質問 選択した項目
Programming language TypeScript
Package manager Npm
UI framework Vuetify.js
Nuxt.js modules Axios
Linting tools ESLint, Prettier
Testing framework None
Rendering mode Single Page App
Deployment target Server (Node.js hosting)
Development tools 選択なし
Continuous integration None
Version control system Git

vuex-module-decoratorsのインストール

次にvuex-module-decoratorsをインストールします。

npm install --save vuex-module-decorators

Vuex moduleを作成

まずはVuex moduleを作成します。 booksというモジュールを作成していきます。

~/store/Book/interface.ts

export interface Book {
    title: string;
    author: string;
}

~/store/books.ts

import { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators';
import { Book } from './Books/Interface';
import { ConvertToHttps } from './Books/utility'


@Module({ stateFactory: true, namespaced: true, name: 'books' })
export default class Books extends VuexModule {
    private books: Book[] = [];

    public get Books(): Book[] {
        return this.books;
    }

    @Mutation
    private SET_BOOKS(books: Book[]) {
        this.books = books;
    }

    @Action({})
    public async setBooks(books: Book[]): Promise<void> {
        this.SET_BOOKS(books);
    }

}

モジュールへのアクセサを作成する

次は、bookStore.setBooks() のようにモジュールへアクセスするためのアクセサを作成します。

~/utils/store-accessor.ts

import { Store } from 'vuex';
import { getModule } from 'vuex-module-decorators';
import Books from '@/store/books'

let bookStore: Books;

function initializeStores(store: Store<any>): void {
    bookStore = getModule(Books, store);
}

export { initializeStores, bookStore }

~/store/index.ts

import { Store } from 'vuex';
import { initializeStores } from '~/utils/store-accessor';

const initializer = (store: Store<any>) => initializeStores(store);

export const plugins = [initializer]
export * from '~/utils/store-accessor'

このように設定すると以下のようにコンポーネントからVuexモジュールに型安全でアクセスすることができます。

import { bookStore } from '@/store'
...
exampleMethod() {
  let books: Book = { title: 'Test', author: 'test' }
  bookStore.setBooks(books);
}

解説

モジュールクラスの作成

モジュールクラスの作成では、クラス宣言の前に@Moduleデコレーターの付与が必要です。 stateFactory: trueを渡すことで、Nuxt.jsのモジュールであることを宣言します。

@Module({ stateFactory: true, namespaced: true, name: 'books' })
export default class Books extends VuexModule {

state

stateはクラスのフィールド変数として作成します。 stateは内部クラスでのみ扱うので、privateとします。

private books: Book[] = [];

getters

gettersはゲッタープロパティとして作成します。

public get Books(): Book[] {
        return this.books;
    }

mutations

mutationには@Mutationデコレーターを付与します。 actionsのメソッド名と被らないようにスネークケースにしています。 こちらもactions経由で使用するため、privateにします。

@Mutation
    private SET_BOOKS(books: Book[]) {
        this.books = books;
    }

actions

actionsは@Actionデコレーターを付与します。

@Action({})
    public async setBooks(books: Book[]): Promise<void> {
        this.SET_BOOKS(books);
    }

おわりに

私は普段はTypeScriptをゴリゴリにクラス記法で使っているので、こっちのほうが型なしVuexよりも使いやすいと感じました。

GoogleAppScriptを使ってSlackAppを作成する。

どうも、GoogleAppScriptにハマり始めた社会人二年目文系エンジニアです。 今回はGoogleAppScriptを使用し、Slack内で使用するアプリ(SlackApp)を作成しようと思います。 今回はSlack内のショートカットからModalを出すところまで実装したいと思います。

ここで扱わない項目

Slack側の設定(別途投稿する予定) claspの使い方、Install方法

前提条件

  1. 以下Slack側の設定が終わっている
  2. SlackAppの新規作成
  3. InteractiveComponentsの設定でInteractivityをOnにする。
  4. Shortcutの設定

  5. claspがインストールされている

作成するModal

書籍のレビューを投稿するModalを作成していきます。 ModalはSlack内のShortcutから表示します。 作成するModalのイメージはこんな感じです。

f:id:wattanX:20201024193949p:plain

使用技術

  • TypeScript
  • Clasp
  • GoogleAppScript
  • SlackApp

ModalのPayload作成

Modalを表示するためには、Slackからのリクエストに対してView情報(Payload)をResponseとして返す必要があります。 View情報には以下の項目があります。

  • Blocks 入力項目のコンポーネント 作成するModalのイメージでいうと以下の部分。 f:id:wattanX:20201024194011p:plain

  • Block elements ボタンやプルダウンのような部品 作成するModalのイメージでいうと以下の部分。

f:id:wattanX:20201024194033p:plain

  • Composition objects BlockやBlock elementsに組み合わせて使う単純なJSONオブジェクト LabelやPlaceholder等に利用される。

これらを組み合わせてView情報をResponseとして返すことでModalを表示できるようになっています。 View情報はJSON形式になっており、Slack Block-kit-builderを使用することで簡単に作成ができるようになっています。

block-kit-builder

作成するModalをBlock-kit-builderで作成すると以下のPayloadが作成されます。

{
    "type": "modal",
    "title": {
        "type": "plain_text",
        "text": "Test",
        "emoji": true
    },
    "submit": {
        "type": "plain_text",
        "text": "投稿する",
        "emoji": true
    },
    "close": {
        "type": "plain_text",
        "text": "キャンセル",
        "emoji": true
    },
    "blocks": [
        {
            "type": "divider"
        },
        {
            "type": "input",
            "block_id": "title",
            "element": {
                "type": "plain_text_input",
                "action_id": "title_id"
            },
            "label": {
                "type": "plain_text",
                "text": "タイトル",
                "emoji": true
            }
        },
        {
            "type": "divider"
        },
        {
            "type": "input",
            "block_id": "select_review",
            "element": {
                "type": "static_select",
                "action_id": "selected_id",
                "placeholder": {
                    "type": "plain_text",
                    "text": "Select an item",
                    "emoji": true
                },
                "options": [
                    {
                        "text": {
                            "type": "plain_text",
                            "text": "★★★★★",
                            "emoji": true
                        },
                        "value": "5"
                    },
                    {
                        "text": {
                            "type": "plain_text",
                            "text": "★★★★☆",
                            "emoji": true
                        },
                        "value": "4"
                    },
                    {
                        "text": {
                            "type": "plain_text",
                            "text": "★★★☆☆",
                            "emoji": true
                        },
                        "value": "3"
                    },
                    {
                        "text": {
                            "type": "plain_text",
                            "text": "★★☆☆☆",
                            "emoji": true
                        },
                        "value": "2"
                    },
                    {
                        "text": {
                            "type": "plain_text",
                            "text": "★☆☆☆☆",
                            "emoji": true
                        },
                        "value": "1"
                    }
                ]
            },
            "label": {
                "type": "plain_text",
                "text": "評価",
                "emoji": true
            }
        },
        {
            "type": "input",
            "block_id": "review",
            "element": {
                "type": "plain_text_input",
                "action_id": "review_id",
                "multiline": true
            },
            "label": {
                "type": "plain_text",
                "text": "レビュー",
                "emoji": true
            }
        }
    ]
}

上記のPayloadをResponseとして返すように実装すると以下のようになります。

View情報をSlackに返す

WebhookUrlやSlackのAccessTokenはGoogleAppScript内の「スクリプトのプロパティ」に定義する。

export class Properties {
    private webhookURl: string;
    private accessToken: string;

    constructor() {
        this.webhookURl = PropertiesService.getScriptProperties().getProperty('WEB_HOOK_URL');
        this.accessToken = PropertiesService.getScriptProperties().getProperty('SLACK_ACCESS_TOKEN');
    }

    public GetAccessToken(): string {
        return this.accessToken;
    }

    public GetWebhookUrl(): string{
        return this.webhookURl;
    }
}
function doPost(e) {
    let params = e.parameter;
    let data = params.payload;
    let json = JSON.parse(decodeURIComponent(data));
    let trigger_id = json.trigger_id;
    let properties = new Properties();
    if (json.type === 'shortcut') {
        let url = 'https://slack.com/api/views.open';
        let token = properties.GetAccessToken();
        let viewData = {
            "trigger_id": trigger_id,
            "token": token,
            "view": JSON.stringify({
                "type": "modal",
                "callback_id": "test",
                "title": {
                    "type": "plain_text",
                    "text": "Test",
                    "emoji": true
                },
                "submit": {
                    "type": "plain_text",
                    "text": "投稿する",
                    "emoji": true
                },
                "close": {
                    "type": "plain_text",
                    "text": "キャンセル",
                    "emoji": true
                },
                "blocks": [
                    {
                        "type": "divider"
                    },
                    {
                        "type": "input",
                        "block_id": "title",
                        "element": {
                            "type": "plain_text_input",
                            "action_id": "title_id"
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "タイトル",
                            "emoji": true
                        }
                    },
                    {
                        "type": "divider"
                    },
                    {
                        "type": "input",
                        "block_id": "select_review",
                        "element": {
                            "type": "static_select",
                            "action_id": "selected_id",
                            "placeholder": {
                                "type": "plain_text",
                                "text": "Select an item",
                                "emoji": true
                            },
                            "options": [
                                {
                                    "text": {
                                        "type": "plain_text",
                                        "text": "★★★★★",
                                        "emoji": true
                                    },
                                    "value": "5"
                                },
                                {
                                    "text": {
                                        "type": "plain_text",
                                        "text": "★★★★☆",
                                        "emoji": true
                                    },
                                    "value": "4"
                                },
                                {
                                    "text": {
                                        "type": "plain_text",
                                        "text": "★★★☆☆",
                                        "emoji": true
                                    },
                                    "value": "3"
                                },
                                {
                                    "text": {
                                        "type": "plain_text",
                                        "text": "★★☆☆☆",
                                        "emoji": true
                                    },
                                    "value": "2"
                                },
                                {
                                    "text": {
                                        "type": "plain_text",
                                        "text": "★☆☆☆☆",
                                        "emoji": true
                                    },
                                    "value": "1"
                                }
                            ]
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "評価",
                            "emoji": true
                        }
                    },
                    {
                        "type": "input",
                        "block_id": "review",
                        "element": {
                            "type": "plain_text_input",
                            "action_id": "review_id",
                            "multiline": true
                        },
                        "label": {
                            "type": "plain_text",
                            "text": "レビュー",
                            "emoji": true
                        }
                    }
                ]
            })
        };
        let header = {
            "Authorization": "Bearer" + token
        };
        let options: GoogleAppsScript.URL_Fetch.URLFetchRequestOptions = {
            method: "post",
            headers: header,
            payload: viewData
        };
        UrlFetchApp.fetch(url, options);
        return ContentService.createTextOutput();
    }
    else if (json.type === 'view_submission') {
        // 次回投稿予定
    }
}

ここまででModalの表示までできるようになっているはずです。 Modalから実際にレビューを投稿できるようにするには、もう少し処理が必要なので次回投稿します。

SQLServerのストアドプロシージャ

ストアドプロシージャを学んだので復習のために投稿します。 今回はSQLseverを使います。

ストアドプロシージャとは

データベースに対する処理をまとめた手続きにして、RDBMSに保存したもの。

テーブル作成

以下のSQLを実行し、StudentTableを作成

 CREATE TABLE StudentTable (
   studentID INT IDENTITY NOT NULL,
   firstname VARCHAR(255),
   lastname  VARCHAR(255),
        age  INT,
   
   PRIMARY KEY(studentID)
 )

ストアドプロシージャ作成

以下のSQLを実行し、ストアドプロシージャを作成する。

  • CREATE PROCEDUREからASまでの中に引数を設定
  • AS以降で処理を記述
 CREATE PROCEDURE InsertStudent
   @firstname VARCHAR(255),
   @lastname  VARCHAR(255),
   @age       INT
 AS
   INSERT INTO StudentTable(firstname, lastname, age)
   VALUES (@firstname, @lastname, @age)

ストアドプロシージャを実行

EXEC ストアド名 引数で実行します

 EXEC InsertStudent 'hoge', 'bar', 23

実行した結果、以下のデータが追加されてます。

StudentTable

studentID firstname lastname age
1 hoge bar 23

補足

MySQLの場合

 DELIMITER //
 
 CREATE PROCEDURE SelectData(IN selectedID INT)
 BEGIN
    SELECT * FROM StudentTable WHERE studentID = selectedID;
 END //
 
 DELIMITER ;

DELIMITERとは終端文字のこと。 MySQLでは終端文字が ; に設定されており、そのままだとselect文のところで実行されてしまう。

DELIMITER //とすることで、終端文字を//に変更し、最後に;に戻すことでエスケープすることができる

ストアド実行するSQLは以下の通り

 CALL SelectData(1);

StudentTable

studentID firstname lastname age
1 hoge bar 23