Web猫ブログを TypeScript化しました

先月よりジョインさせていただいている Web屋さんで猫好きな @jiyuujin 今回は度々登場となって申し訳ないですが、自身のWeb猫ブログを TypeScript化した話をさせていただきます。

webneko.info

nuxt-tsを採用します

結論を先に申し上げると nuxt-ts を採用した件で、その導入周りに留まります。導入にあたってかれこれ1年以上、メンテストップ中の公式テンプレート。。 一切頼りにはできません。今年のどこかで必ず更新してほしさあり、新たに出していただけると大変嬉しいですね😊

github.com

Web猫ブログでは個人的なアナウンスこそしていませんでしたが、今回この場を借りて TypeScript化しましたという経緯です。つい最近、 nuxt-ts 不要になるかもという Issue (下記を参照)を見たのですが、とりあえず nuxt-ts をインストールするしかありません。ビルドコマンドも nuxt から nuxt-ts を使うよう変更してあげる必要があります。

yarn add nuxt-ts
yarn add typescript @types/node ts-loader -D

nuxt-ts 不要になるの?

上記でも触れた nuxt-ts 不要になるかも、というのが以下 Issueを見たことでした。 tslint から @typescript-eslint に移行する一環と思いますが、気になる話でした。

github.com

Nuxt.configを TypeScriptで書く

Web猫ブログでは Headless CMSに Contentfulを採用。 Sitemapの生成処理や Makdownのパース処理など単純な設定に縛られず比較的コードを書く箇所が多く存在すると思いますが、これらの実装箇所が全て型安全に書けるようになったことは今までと大きく違う点だと思います。

APIを使うために型定義を設定する

asyncDatafetch などのAPIをを型安全に利用するために自分自身でVueのInterfaceを拡張してあげる必要があります (/types/nuxt.d.tsを作成すると良いでしょう) この辺りはさすがVue界隈と思う部分ですが、丁寧に書いてくださっていることに対しては感謝しかありません。Nuxt公式ページに Context一覧が存在します。そのページと照らし合わせながらぐりぐり型定義を設定します。

ja.nuxtjs.org

declare module 'nuxt' {
  import { Store } from 'vuex';
  import { Route } from 'vue-router';

  export class Builder {
    constructor(nuxt: Nuxt);
  }

  export class Nuxt {
    constructor(config?: NuxtConfig);
  }

  export interface NuxtConfig {
    dev: boolean;
    [key: string]: any;
  }

  export interface NuxtContext<S = any> {
    app: NuxtApp;
    isClient: boolean;
    isServer: boolean;
    isStatic: boolean;
    isDev: boolean;
    isHMR: boolean;
    route: Route;
    req: any;
    res: any;
    store: Store<S>;
    env: any;
    params: any;
    query: any;
    redirect(path: string): void;
    error(params: { statusCode: number; message: string }): void;
    nuxtState: any;
    beforeNuxtRender(fn: Function): any;
  }
}

上記はあくまでミニマムな構成になっています。当ブログではこの構成に加えて、 contentful 用のModelも定義しています。このように必要に応じて随時追加してあげなければならず少々面倒ではありますが、後々を考えると良い「投資」になってくれるかもしれません。

今回はミニマムな構成の下で動作確認をとることができたのでこの辺で。

リポジトリ公開中

ブログでは随時お問い合わせコメント受付中、PRも絶賛受付中です。

Web猫ブログ 本番運用中

今までの TypeScriptを採用していない版です。

github.com

Web猫ブログ β版 (いずれ本番運用予定)

既に切りました、本番運用開始されたら再度アナウンスしたいと思います。

github.com

最後に、

SCOUTERではエンジニア、デザイナーともに募集しております! 新規事業、絶賛グロース中の事業ともにLaravel, Vue.jsで開発しておりますので、 興味のある方はお声がけください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

Laravel+NuxtでLIFFアプリを作ってみた

はじめに

こんにちは、株式会社SCOUTERの開発責任者の小平(@ryotakodaira )です。 業務では、SARDINEという人材紹介会社向けの業務管理システムを開発しています。

日常的な業務とはそこまで関係がありませんが、LINE社が2018年にリリースした LIFF が気になっていたため、普段使っている技術を用いてLIFFを触ってみたので構築内容を紹介していきます。

LIFFとは

LIFFはLINE Front-end Frameworkの略で、LINEのトークルーム内で動作するウェブアプリの実装を可能にするプラットフォームとなっています。

LINEのトークルーム内でLIFFに登録したウェブアプリを開くと、LINEのユーザーIDやユーザー名などを取得することが可能となっています。

そのためウェブアプリでLINEのコンテキストを利用した機能の提供が可能となり、今までのMessagingAPIだけでは出来なかった体験をユーザーに提供することが出来ます。

linecorp.com

流れ

  • LINE Botの準備
  • LIFFで表示するwebページを用意(Nuxt)
  • LINE Botを操作するためのアプリケーションの準備(Laravel)
  • LIFFアプリを登録

Image from Gyazo

LINE Botの準備

LINE Botの作成

  • LINE Developersのアカウントを作成
  • 新規プロバイダーを作成
  • Messaging API で新規チャネルを作成する
    • アプリ名
      • 適当に入力
      • ここでは「サンプルBot」を指定
    • プラン
      • 「Developer Trial」を選択

LINE Botの設定

  • アクセストークン(ロングターム)を発行
    • テストのため失効までの時間を「0時間」で発行する
    • 本番で使うときには失効時間を設定した方が安全です
  • Webhook送信を「利用する」に変更
    • Webhook URL は後ほど設定します
  • 自動応答メッセージを「利用しない」に変更

ここまででLINE Botの基本的な設定は完了となります。

設定画面の一番下にQRコードが表示されていますので、QRコードを読み取って友だち追加を行います。追加後に友だち追加時のメッセージが送られてきていれば成功となります。

LIFFで表示するwebページを用意(Nuxt)

今回はNuxtで作ったwebページをNetlifyで公開することを前提に進めます。

(本来はvue-cliを使ったVueアプリケーションでも良いですが、Nuxtの方が諸々の設定が楽だっったため今回はNuxtを利用しています。)

LIFFのテストを行うためだけで合ってもhttps接続が可能なwebサイトであることをもとられますので、Netlifyやherokuでサクッと静的ファイルをホスティングするのがおすすめです。

JSでLIFFを扱うためのSDKを読み込む

SDKはLINE社がCDNで公開しているため、それを読み込む。

Nuxtは nuxt.config.js でheadタグの設定をすることができるのでNuxtの書き方に従って記述します。

// nuxt.config.js

module.exports = {
  /*
  ** Headers of the page
  */
  head: {
    script: [{ src: 'https://d.line-scdn.net/liff/1.0/sdk.js' }]
  },
}

LIFFで使うページを作成

// pages/index.vue

<template>
  <section class="container">
    <p class="line-id">LINE ID:{{ lineId }}</p>
    <div class="form">
      <div class="control">
        <input class="input" type="text" placeholder="お名前" v-model="formData.name">
      </div>
      <button class="button is-info is-fullwidth" @click="onSubmit()">送信する</button>
      <button class="button is-light is-fullwidth" @click="handleCancel()">キャンセル</button>
    </div>
  </section>
</template>

<script>
export default {
  data() {
    return {
      formData: {
        name: ''
      },
      lineId: null
    }
  },
  mounted() {  
    if (!this.canUseLIFF()) {
      return
    }

    window.liff.init(data => {
      this.lineId = data.context.userId || null
    })
  },
  methods: {
    onSubmit() {
      if (!this.canUseLIFF()) {
        return
      }

      window.liff
        .sendMessages([
          {
            type: 'text',
            text: `お名前:\n${this.formData.name}`
          },
          {
            type: 'text',
            text: '送信が完了しました'
          }
        ])
        .then(() => {
          window.liff.closeWindow()
        })
        .catch(e => {
          window.alert('Error sending message: ' + e)
        })
    },
    handleCancel() {
      if (!this.canUseLIFF()) {
        return
      }
      window.liff.closeWindow()
    },
    canUseLIFF() {
      return navigator.userAgent.indexOf('Line') !== -1 && window.liff
    }
  }
}
</script>

<style>
.container {
  margin: 0 auto;
  padding: 20px;
  min-height: 100vh;
}

.line-id {
  margin-bottom: 30px;
}

.form > * {
  margin-bottom: 10px;
}
</style>
  • mounted()
    • LINEのコンテキストを受け取ってコンポーネントのデータプロパティーに入れています
    • ここではLINEIDをコンテキストから取得してページに表示している
    • 取得できるコンテキストの一覧はこちらを御覧ください
  • onSubmit()
    • でフォームに入力されたテキストをLINEのトークにメッセージを送信している
    • liff.sendMessages() でメッセージを送信することができる
    • 送信が成功したら liff.closeWindow() でLIFFを閉じる
  • canUseLIFF()
    • LIFFを使える状態にあるかをチェックする
    • ユーザーエージェントがLINEか、LIFFのSDKが適切に読み込まれていることを確認している

yarn dev で開発環境を立ち上げると以下の画像のような表示になります。 ここでは、LINEのコンテキストを取得出来ませんので、LINEIDは表示されておらず、「送信する」ボタンをクリックしても何も表示されません。

LIFFアプリを登録

Image from Gyazo

  • LIFFアプリの設定画面より新規作成をします
    • エンドポイント URL に上で作ったNuxtアプリをNetlifyなどで公開したときのURLを入力してください
  • LIFF URL が発行されればLIFFアプリの作成は完了です
    • エンドポイント URLサイズ は後から編集可能です

LINE Botを操作するためのアプリケーションの準備(Laravel)

Laravelアプリケーションを用意

普段からLaravelを使っており、慣れている分素早くアプリケーションを作れるためLaravelを使用しました。 Laravelのインストール方法や初期設定は今回は割愛します。

以下のURLを御覧ください。

qiita.com

ENVの設定

.env ファイルに以下のLINEの情報を追加します。

# .env

LINE_ACCESS_TOKEN={アクセストークン(ロングターム)}
LINE_CHANNEL_SECRET={Channel Secret}

webhookを受けるエンドポイントを用意

// routes/api.php

<?php
/** @var Illuminate\Routing\Router $router */
$router = app('Illuminate\Routing\Router');

$router->post('/line/webhook', 'LineWebHookController@webHook')->name('line.webhook');

飛んでくるリクエスト処理してBotを動作させる処理の作成

// app/Http/Controllers/Api/LineWebHookController.php

<?php
/**
 * Class LineWebHookController
 * @package App\Http\Controllers\Api
 */
class LineWebHookController extends Controller
{

    /**
     * @param Request $request
     * @throws LINEBot\Exception\InvalidSignatureException
     */
    public function webHook(Request $request)
    {
        // Botの印象情報をENVから受け取る
        $lineAccessToken = config('line.access_token');
        $lineChannelSecret = config('line.channel_secret');

        // 署名をチェックする
        // 署名が正しくなければ不正なアクセスとみなして何も行わない
        $signature = $request->headers->get(HTTPHeader::LINE_SIGNATURE);
        if (!SignatureValidator::validateSignature($request->getContent(), $lineChannelSecret, $signature)) {
            return;
        }

        $lineBot = new LINEBot(new CurlHTTPClient ($lineAccessToken), ['channelSecret' => $lineChannelSecret]);

        try {
            // イベントをパースする
            /** @var LINEBot\Event\BaseEvent[]|LINEBot\Event\MessageEvent\TextMessage[] $events */
            $events = $lineBot->parseEventRequest($request->getContent(), $signature);

            foreach ($events as $event) {
                // LINEから送られてきたメッセージの内容を抽出
                $receiveText = '';
                if ($event->getType() === ActionType::MESSAGE && $event->getMessageType() === MessageType::TEXT) {
                    $receiveText = $event->getText();
                }

                // 「liff」と投稿されたら、LIFFアプリを開く
                if ($receiveText === 'liff') {
                    $message = new TextMessageBuilder('line://app/1554604976-anxJ8ogY');
                } else {
                    $message = new TextMessageBuilder('送信ありがとうございます!🙇<200d>');
                }

                // メッセージを送信
                $replyToken = $event->getReplyToken();
                $lineBot->replyMessage($replyToken, $message);
            }
        } catch (Exception $e) {
            logger($e->getMessage());
            return;
        }

        return;
    }
}

webhookURLをLINE Developersの設定画面で設定

LINE Developersで設定を行う前にwebhookURLを公開する必要があります。

今回はサンプルの開発のため、ngrokというサービスを使ってwebhookURLを公開しました。

ngrokの導入についてはこちらの記事を御覧ください。

qiita.com

webhookURLが公開出来たら、Developersの設定画面の「webhook URL」欄に公開したエンドポイントのURL文字列を入力します。

URLの設定後に「接続確認」をクリックして成功すれば設定は完了です。

完成物

LINEのトークルームで「liff」と入力すると、LIFFアプリのURLがBotから送られて来ます。

そのURLをクリックしてLIFFアプリを起動するとLINEユーザーIDが表示されています。

「お名前」フォームにテキストを入力して、送信後、LINEトークルームに入力した内容が投稿されていれば成功です!

Image from Gyazo

まとめ

LIFFという新しい技術を初めて試してみましたが、想像よりも簡単にLIFFアプリを作成することが出来ました。ただ、当然普通のウェブアプリと比べると開発中のデバックが大変でした。LINE上だとコンソールが確認できないため、ウェブアプリ上にプリントしてデバックを行うしかなさそうでした。

LIFFアプリを使えばウェブアプリでLINEのコンテキストを扱うことができるため、LINEBotの可能性が一気に広がったのではないかという印象を受けたため、様々なサービスがLIFFを利用してより良いユーザー体験を提供してくれるのではないでしょうか。

今後、弊社のプロダクトでも機会があれば取り入れたいなと思える機能でした。

最後に

弊社では最新技術のキャッチアップ、プロダクション導入を積極的に行っています!

そして、事業・サービスが成長していくにあたって、これからもメンバーを増やしていきたいと思っています。

興味のある方は下記からご応募いただくか、@ryotakodairaにご連絡ください!!

www.wantedly.com

www.wantedly.com

www.wantedly.com

JWT認証と流れのやわらかい解説

こんにちは。 SCOUTERフロントエンドエンジニア、現在Laravel勉強中の匠平@show60です。

弊社のプロダクトはフロントエンドをVue.js 、サーバーサイドをLaravelで実装しています。 私が携わっている新規事業のback checkでは、LaravelでAPIを実装し、フロントエンドからリクエストを送ることでアプリケーションを動作させています。

APIを実装する際に必要になるのがセキュアな認証。そこで広く使われるJWT認証について学んだことを今回お話したいと思います。

なぜ必要なのか、どのように使っているのかを焦点にやさしくお話しします。

ユーザー認証とは

ユーザー認証とは、アクセスしてきたユーザーが正当なユーザーかどうかをチェックすることです。 チェックに使用される要素としては以下のようなものがあります。

  • 知る要素
    • パスワード
  • 持つ要素
    • トークン
  • 備える要素
    • 指紋
    • 虹彩 (目)

JWTはその中にユーザー認証情報を含ませることができるため、正当なユーザーかどうかをチェックできるということですね。

余談、よくパスワードを忘れた際に、母親の旧姓やペットの名前、出身校名を聞かれますが、これらは「知る要素」に分類されますね。 とはいえ、これらは他人にも知られる情報であるため、安全ではなく推奨されていません。

実家のペットの名前がモカちゃんであるとここに書くことで、私のアカウントはもはや安全ではなくなってしまいます。

参考: [Google Online Security Blog | New Research: Some Tough Questions for ‘Security Questions’ 2015年5月] (https://security.googleblog.com/2015/05/new-research-some-tough-questions-for.html)

今回の題材であるJWTは「持つ要素」に分類されるものです。

JWTとは

JWTとはJSON Web Tokenの略です。

JSONとはJavaScript Object Notationの略で、JavaScriptのオブジェクトの構造を持ったデータフォーマット。

Tokenとはユーザーを識別するための認証情報。

つまりJWTとは、JavaScriptのオブジェクトの形をした認証情報のことです。

JWTの構成

JWTは大きく3つの要素で構成されます。

  • ヘッダー
  • クレーム情報
  • 署名

ヘッダー

この後の署名に使われている暗号化アルゴリズムや、トークンタイプなどのメタ情報が入ります。

クレーム情報

任意の情報を含ませることができ、ここにユーザー認証情報を記述します。

私の携わるbackcheckというプロダクトには、企業、候補者、推薦者、管理者と4つのユーザータイプがあります。それぞれのページを行き来できないようアクセスに制限をかけるため、このクレーム情報内にユーザータイプを表すキーを実装しています。

署名

ヘッダーに記述した暗号化アルゴリズムにより、ヘッダーとクレーム情報をBase64urlに変換したものから生成されます。

なぜJWT認証を使うのか

JWTを使うメリットは以下のようなものが挙げられます。

  • 安全
    • JWTに署名が含まれているため、改ざんがあってもチェックできるようになっている。
  • 実装のしやすさ
    • セキュアなToken発行が楽に実装できる。
  • 管理のしやすさ
    • URLに含むことができる文字で構成されているから、HTTPリクエストでの取り扱いが楽。
    • 認証に必要となる情報はすべてJWTの中に入っているため、ユーザー認証情報をサーバーで管理する必要がない。DB問い合わせを行う必要がない。

JWT認証を使えば、安全かつ管理がしやすいセキュアな認証を簡単に実装できるということですね。

サーバーサイドで行うこととフロントエンドで行うこと

サーバーサイドとフロントエンドで行うことを分類しながら、JWT認証を使用する流れを説明します。

ユーザー初回登録時

  • 【フロント】
    • ユーザー情報 (メールアドレス、パスワード等) を送信
  • 【サーバー】
    • リクエストされたユーザー情報を元にJWTでトークンを生成
    • トークンをフロントへ返す
  • 【フロント】
    • 返ってきたトークンがbase64urlでエンコードされているため、base64 に変換
    • 変換したものをLocalStorageに保存

このときトークン内のユーザー情報には、上に書いた4種類のユーザータイプ情報も含んでいます。

2回目以降のログイン・画面更新時

  • 【フロント】
    • Tokenの有無の確認
      • LocalStorageにTokenがあるかどうかを確認
        • ある場合は、HTTPヘッダに入れてサーバーにアクセスする
        • ない場合は、そのユーザータイプによって適切なページへリダイレクトさせる
  • 【フロント】
    • Tokenの有効期限の確認
      • LocalStorageにTokenがある場合、その有効期限を確認
      • 有効期限が切れている場合、サーバーに問い合わせし、Tokenを更新
  • 【サーバー】
    • Tokenの更新リクエストがきたらJWTをリフレッシュして返す(このときフロントでは、初回登録時と同じように返ってきたトークンをLocalStorageに保存します)
  • 【フロント】
    • サーバーにアクセス
      • 有効なアクセスであることがフロントで確認されたらサーバーにアクセスをします
  • 【サーバー】
    • トークンの認証を確認しデータを返す

初回登録時に発行したトークンをLocalStorageに保管しており、有効期限やユーザータイプの情報を持っているため、2回目以降のログイン・画面更新時のサーバーへのアクセスは多くても2回に抑えられています。

フロントエンドでの処理は、ユーザー情報がすべてトークン内に記述されており、JSONフォーマットなので処理もとても簡単に行えますね。

まとめ

実際にはサーバー内での処理を加えるとまだまだ言及しなくてはいけないことがありますが、流れをさっくりと説明させてもらいました。

さいごに

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記よりご応募ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

API仕様の出力プラグインをtraitにしてみた話

みなさんこんにちは
先月よりジョインしました、 niisan-tokyo です。
今後とも宜しくお願いします。

で、今回のテーマですが、以前にkotamatが公開していた、テストを通したAPIスペックの自動生成プラグインをちょいと改造したという話です。

APIスペック自動生成ツールの改造

TL;DR

  • API仕様の自動吐き出しがクラス継承式だったので、使いにくかった
  • traitでも使えるようにした
  • 一応後方互換性を確保した

対象のリポジトリ

https://github.com/kotamat/laravel-apispec-generator

ちょっと使いにくかった

このプラグインは、使い方は容易だったのですが、一つ欠点がありました。
それは、このプラグインをベースクラスとして継承しなければならないということです。

<?php

use ApiSpec\ApiSpecTestCase;

class TestCase extends ApiSpecTestCase
{

}

APIスペックを出力する必要のないところでは、このクラスを継承したくないため、別のベースクラスを使いたくなりますが、一方で、ベースクラスでアプリで特有の設定やら便利なテスト機能を実装することがあるでしょう。
結局、普通のベーステストクラスとともに、APIスペック出力用のクラスを継承したベーステストクラスを用意するという、少々厄介なことになります。

traitにする

そこで、ベースクラスの代わりにtraitを使うという技が考えられます。
traitは使用することで、そのクラスに単純にtraitの中のコードをコピーしたのと同じ状態を作ることができます。
ApiSpecTestCaseは、実際にはいくつかのメソッドにAPI仕様を吐き出すコードを入れているだけですので、traitで十分ということになります。

traitを使う場合のAPI仕様の出力は以下のようにすると可能になります。

<?php

use ApiSpec\ApiSpecOutput;

class SomeTestCase extends TestCase
{
    use ApiSpecOutput;

    //...
    
    /**
     * @test
     */
    public function API仕様を吐き出すテスト()
    {
        $this->isExportSpec = true;
        $this->getJson('/someone/status');
    }
}

こうして、必要なときにだけ、use ApiSpecOutputすることで、ベースクラスをいじることなく、API仕様を吐き出すことができます。

後方互換性の維持

こうして、traitにしようとしたときに、すでにベースクラスで使っているところはどうなるんだという話になります。
これは、プロダクト開発において、仕様を追加する場合にも発生する問題で、後方互換性をどうするのかということです。 今回は後方互換性は残すことにします。

後方互換性の確保の方法は簡単で、要するにテストが通っていればいいということです。
API仕様出力ベースクラスはテストクラスのベースクラスなので、テストの方法が厄介でした。

https://github.com/kotamat/laravel-apispec-generator/blob/0ade044a5a8f41d54a4b872978a1c3cbb2647bc9/test/ApiSpecTestCaseTest.php

テスト自体はかなり無理矢理なコードでとにかくテストできればいいやって感じになっています。

<?php

// ... 中略

    public function createApplication()
    {
        $app = new class extends Application {
            private $acceptor;
            public function __construct($basePath = null)
            {
                //
            }
            public function setAcceptor($acceptor)
            {
                $this->acceptor = $acceptor;
            }
            public function make($class, array $param = []) {
                $mock = m::mock(FilesystemAdapter::class);
                $mock->shouldReceive('drive')->andReturn($this->acceptor);
                return $mock;
            }
        };
        $app->setAcceptor($this->acceptor);
        $this->app = $app;
    }
    protected function setUp()
    {
        $this->acceptor = new class {
            public $filename;
            public $str;
            public function put($filename, $str) {
                $this->filename = $filename;
                $this->str = $str;
            }
        };
        $this->createApplication();
    }

これがテストの前提部分ですね。
無名クラスを連発しておきながら、思い出したようにMockeryを利用したりしています。
テスト部分は上のリンクで見てください。単純にpostJsonとかを投げたら、API仕様の出力ができていることを確認しているだけです。

このテストを作った上で、ApiSpecTestCaseにあったロジックの大半をApiSpecOutputに移し、ApiSpecTestCaseApiSpecOutputを使っているだけという状態にすることができました。

まあ、ベースクラスに手を入れずに、traitだけ作ればこんな苦労はないんですが、同じコードがあるって気持ち悪いので、やってしまいしました。

まとめ

というわけで、API仕様の出力をtrait化することに成功しました。
個人的にはもうちょい改造したいところですが、ひとまずはtraitで使えるようになったというところで満足しておきましょう。
今回はこんなところです。

最後に

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

Vueのライフサイクルフックまとめ

こんにちは!
株式会社SCOUTER開発部フロントエンドエンジニアの佐藤(@r_sato1201)です

弊社ではフロントエンドのフレームワークにVue.jsやNuxt.jsを採用しています。 業務中で開発をしてる際に、Vueのライフサイクルフックについて理解が乏しいまま実装していることに気がついたので 今回はVueのライフサイクルフックについてまとめたいと思います。

ライフサイクルとは?

f:id:ryonnsui1201:20190225221150p:plain:w250

ライフサイクルフックについて話す前に、まずライフサイクルについて。 Vueに触れている方なら、上の図を一度は見たことがあると思います。 ライフサイクルとは、Vueインスタンスが生成されてから削除されるまでの流れのことで、上の図はその流れを表した図です。

Vueのライフサイクルは大きく分けて以下の5つに分けられます。

① Vueインスタンスの生成
② DOMへのマウント
③ 画面の更新、リアクティブデータの変更
④ Vueインスタンスの破棄
⑤ エラー時

それでは次に、上記の分類に沿ってライフサイクルフックを紹介したいと思います。

ライフサイクルフック

ライフサイクルフックとはVueのライフサイクルにおける一連の処理の中の各タイミングで実行する関数のことをいいます。 各フック内に記述することで、任意の処理を行うことができます。

先程記述したライフサイクルの5つの分類に沿ってライフサイクルフックを説明していきます。

① Vueインスタンスの生成

実行されるフック:beforeCreate created
まずは Vueインスタンスが生成されます。この時点ではインスタンスがマウントされていないので、まだコンポーネントの内容は反映されていません。 Vueインスタンスが生成され、リアクティブデータが初期化される前にbeforeCreateが呼ばれ、リアクティブデータが初期化された後にcreatedが呼ばれます。

② DOMへのマウント

実行されるフック:beforeMount mounted 
生成された Vueインスタンスは、次にDOMにマウントされます。 マウント前にbeforeMountが呼ばれ、マウント後にmountedが呼ばれます。 したがって、DOMにアクセス可能なのはmounted以降になります。

③ 画面の更新、リアクティブデータの変更

実行されるフック:beforeUpdate updated 
Vueインスタンスdataが変更された場合や、propsで渡されているデータが親で変更された場合など、それらの値の変更のタイミングでDOMを自動的に更新し再描画します。その際に実行されるフックが上記2つです。 DOMが更新される前にbeforeUpdateが、DOMが更新された後にupdatedが呼ばれます。

④ Vue インスタンスの破棄

実行されるフック:beforeDestroy destroyed
v-ifなどの機能によりVueインスタンスが破棄された時、上記2つのフックが呼ばれます。 Vueインスタンスが破棄される直前にbeforeDestroyが呼ばれ、Vueインスタンスが破棄された直後にdestroyedが呼ばれます。

⑤ エラー時

実行されるフック:errorCaptured 任意の子孫コンポーネントからエラーをキャッチしたときに呼び出されます。 このフックは ・エラー ・エラーをトリガするVueインスタンス ・どこでエラーが捕捉されたかの文字列情報 これら3つの引数を受け取ります。 また、このフックではエラーが自分より上位に伝播するのを防ぐために、false を返すことができます。

表にしてまとめると以下のようになります。
ライフサイクルフックの一覧

フック名 説明
beforeCreate インスタンスが生成され、リアクティブデータが初期化される前
created インスタンスが生成され、リアクティブデータが初期化された後
beforeMount インスタンスがマウントされる前
mounted インスタンスがマウントされた後
beforeUpdate データが更新され、DOMに適用される前
updated データが更新され、DOMに適用される前
beforeDestroy インスタンスが破棄される直前
destroyed インスタンスが破棄された直後
errorCaptured 任意の子孫コンポーネントからエラーがキャプチャされたとき

createdとmounted

ライフサイクルフックで使用頻度が高いのはcreatedmountedだと思います。 それぞれのユースケースをまとめてみました。

created

非同期通信を使ってデータを初期化するとき

よく使われるパターンとしてAPIなどを叩いて非同期通信で取得したデータをdataに初期値として挿入する処理が挙げられると思います。 このケースでは初期化のタイミングで使うという面ではbeforeMountmountedで 非同期通信を行ってもよいのですが、DOMのマウントが行われデータを画面に反映するまでの時間を少しでも減らすためにはcreatedを使う必要があります。また、beforeCreateではリアクティブデータが初期化されていないため、dataに挿入することができません。

mounted

DOMにアクセスする必要がある場合

DOM要素にアクセスする必要がある場合や、HTML要素の高さや幅を取得する場合の初期化処理はmountedに記述するとよいでしょう。 ただし、mountedは 全ての子コンポーネントがマウントされていることを保証しないことに注意してください。Vue全体がレンダリングされた後に処理を行いときは、vm.$nextTickを使いましょう

mounted: function () {
  this.$nextTick(function () {
    // Vue全体がレンダリングされた後にのみ実行されるコード
  })
}

まとめ

Vueのライフサイクルについてまとめてみました。 今回触れなかったライフサイクルフックについても、分かりやすいユースケースなどあれば教えて頂けると幸いです!

さいごに

現在、株式会社SCOUTERでは、エンジニア、デザイナーの募集をしております。

興味のある方は、是非下記からご応募お願い致します!

www.wantedly.com

www.wantedly.com

www.wantedly.com

www.wantedly.com

参考資料

転戦1ヵ月 前と後、どうなん?

まず、誰ですか?

昨年後半よりめきめきと露出度を増やしていますが、Webエンジニアで猫好きな @jiyuujinプロフィールをご覧ください。

PHPを中心に、特にLaravelを使った制作は、2年近くと少々長め。Vue.jsとの出会いもこの時です。最近は関西を拠点としたコミュニティv-kansaiの立ち上げ、昨年年末のv-kansai Vue.js/Nuxt Meetup #1初主催をはじめ活動しています。

転職前に書いた記事はこちら!

webneko.info

慣れましたか?

慣れました。しかし、まだまだ改善の余地。

今回、リモートでお仕事を進めていますが、最大の違いは「リモート生活」です。ちなみに私個人のリモート経験は、ほぼ無いです(少なくとも、フルリモートは)。まずこの判断をしていただいたことは、今でも感謝すべきことと思います。

使えるものは使う、分報とか

その上でコミュニケーションを進めるため説明にどうしても口頭で伝えたい場合にテレビ会議で、など手段のレベルで困ったことは特にありませんでした。特に自身の意図が伝わらない、といったことにならないようにするため、Slackの「分報」を最大限活用している。前職にも似たようなものが存在、使う上で困ることはありませんでした。ですがあくまで口頭の補助的な役割で使っていたため、当時と位置付けが異なりその点で手こずった。

聞くこと、存在感を示すことを心がけ

また自分自身から他メンバーに対して「聞く」、「わからないことがあったら聞く」ことができなかった。前職から染み付いていることだが、実質一人で管理画面周りを廻していた環境で自身でやり切ること。このようにやり切ることはもちろん重要だが前職と違って、既に熟知されている方もいらっしゃる現状、聞いた方が早いのも事実。自身から声を上げていかないといけないことを改めて痛感させられました。このような試行錯誤を経ながらも成長していければ、確実に慣れてくるだろうと思っています。

その他、「前」と「後」

ステークホルダーの多さ

言ってもこれに尽きると思いました。今までゲームユーザーを筆頭に、社内でのCS、プランナーと言った限られた職種の人たちに対してのみ、想定すれば良かった世界。極端なことを言うと利益は、基本的にゲームユーザーに対してのみ考慮すれば良かったのが、エージェント企業や求職者、その採用企業までと立場がとにかく多いと感じました。

工程管理

先述の通り実質一人で廻していた環境もあり、今回初耳だった「スプリントプランニング」。プランニングでどのように設計をこなし、実装を進めていくかということ。これをしっかりと行うためには、事前の知識として仕様をおおまかに把握していないと中々できない部分もあり、これからだなぁと思っています。

開発環境・使用技術

基本的に大きな違いは存在しません。強いて言うならフロントエンド周りNode.jsの管理で、NodebrewからNodeenvに変わったこと、NpmからYarnに変わったことなど。

外出機会の量

想定の範囲内でしたが毎日4000歩(5km)は歩いていた日々が一切無くなる訳で、意識していないと不健康の塊になるのは必至です。汗笑 どちらかと言うと関西では不活性な部類に入る勉強会やコミュニティ活動を平日晩に充実、もっと交流を深められればと思っています。v-kansaiもそのうちの一つ。

最後に、

SCOUTERではエンジニア、デザイナーともに募集しております! 新規事業、絶賛グロース中の事業ともにLaravel, Vue.jsで開発しておりますので、 興味のある方はお声がけください!

www.wantedly.com

www.wantedly.com

www.wantedly.com

外部パッケージを修正したあと確認するまでのコストの削減

こんにちはSCOUTERでエンジニアをしているhirokinishizawaです。

弊社ではVue製のUIコンポーネント集をはじめいくつか外部パッケージ化をしています。以前まで外部パッケージの開発を行う際に変更したら外部パッケージを毎回ビルドし直して、その外部パッケージを使用しているアプリケーションに反映をさせていました。少しの変更でも再ビルドを行い反映をするという作業が入るため、外部パッケージの大きめなアップデートをするときにはとてもじゃないですが面倒臭すぎてやってられませんでした。なので今回差分ビルドを行い確認するまでのコストを削減するようにしたのでその話をしていこうかと思います。

どのような問題があったのか

冒頭でも説明したのですが、外部パッケージを修正したあとローカル環境で確認コストが高いというのが開発メンバー内で挙げられた課題でした。実際パッケージを修正してからアプリケーションに反映をするまでに下記を実行していました。

外部パッケージを修正
↓
外部パッケージを再ビルド(yarn dist{webpack --progress --config build/webpack.base.conf.js})
↓
アプリケーションに修正を反映させるためにビルドしたファイルをrsyncでコピーする
↓
アプリケーションで反映完了を待つ

これを少しでも修正する度に行っていました。

外部パッケージの再ビルドに時間がかかるためコストが高いというのもあるのですが、弊社で開発をしているSARDINEというサービスではadminを含め3つのステークホルダーが存在しているため、それぞれに反映させるのがとても面倒臭いというのが今回差分ビルドやyarn linkを出来るようにする起因となりました。

なぜ外部パッケージを作成したのか

上記でも同じことを言いましたが弊社で開発をしているSARDINEというサービスには3つのステークホルダーが存在します。

それぞれユーザーの種類は違いますがいくつか共通な画面があります。なので共通部分のコンポーネントをパッケージ化することによってコードを1箇所にまとめることができるため、修正を加えるレポジトリが1つで済むようになり、同じドメイン領域に関するコードがまとまっているためどのような処理が行われているのかを見通しやすくなるため作成しました。

yarn link + 差分ビルドするための方法

変更自体はほとんどありません。 外部パッケージで差分ビルドできるようにするために外部パッケージのpackage.jsonにscriptsを追加。

"watch": "webpack --progress --config build/webpack.base.conf.js --watch"

これでyarn watchでパッケージの差分ビルドを行えるようになります。

次にwebpackの設定を変更しないと外部パッケージでyarn link で貼ったシンボリックリンクの解決に失敗しアプリケーション側で参照できないため、resolve.symlinks を false に変更します。

以上で準備完了です。各リポジトリ1,2行ぐらいですねw

最後にターミナルでの実行方法を書いて終わりにしたいと思います。

外部パッケージでyarn link
↓
外部パッケージでyarn watch
↓
アプリケーションでyarn link <package-name>
↓
アプリケーションをビルド

実行したあとは外部パッケージを修正しターミナルでパッケージのyarn watchとアプリケーション側のビルドが動いているのを確認できれば完了です。

余計なコストを減らして爆速で開発をしていきましょう!

最後に

SCOUTER社では一緒に働く仲間を募集しています! 興味のある方は下記からご応募いただくか、弊社CTOまでご連絡ください!

www.wantedly.com

www.wantedly.com

www.wantedly.com