tubone

tubone

Boyaki makes a new world


 Recent posts  6 / 52

SepOctNovDecJanオムロン HV-F312で腰の痛みをなくすGithub Actionを使って、簡単CIを作ってみるChromeDriverがGoogleChrome v76 に対応していないらしい。Googleデータポータルを触ってみるHyper-vにMetasploitableの仮想マシンを立ててみるNightwatch.jsでE2Eテストを回したときにうまく動かないたった一つの理由particles.jsをVue.jsで使ってかっこいいページを作るGitPitchを使ってMarkdownからプレゼンテーションを作ってBadgeをレポジトリに貼るSentryを使ってフロントエンドのエラーを確認するJSON Resume + API With GitHubを使って、さくっと職務経歴書チックなもののAPIなど作ってみるGitHubに30日間草を生やし続けた感想Netlify Formを使って、簡易Contact Formを作ってみる10/1は天下一品の日Ansible + Serverspecを使ってMacの環境構築を自動でする (Ansible編)Ansible + Serverspecを使ってMacの環境構築を自動でする (Serverspec編)今日のラーメン台風の時の我が家のセンサー(netatmo)の値をZabbixで見る究極の謝罪はSlackのスタンプを活用しよう! ~明日から使えるSlackスタンプスニペット集~ハロウィーンはえちえちでチンパンジーなイベントじゃない。GitHubと向き合うイベントだ昔ながらのラーメンたべたい珠玉の一杯。たくさん残業した日はこのラーメンを食べろ!スープの衝撃!ここまでうまいスープはあるのか!?なラーメンGitHubに草を生やし続け90日が経ったので感想を書くGoでAWS Lambdaを動かして、GitHubAPIv4(GraphQL)を叩いてみた感想Hadoopゾウさんについて本気出して考えてみたNuxt.jsでparticles-bg-vueを使うNuxt.jsのmodulesをCompositionAPIで使ってみる(@nuxtjs/toast編)くろおびらーめん with チャーシュー飯Nuxt.jsのmodulesをCompositionAPIで使ってみる(@nuxtjs/toast Global Option編)Nuxt.js + Composition APIでVuexのStateをReactiveに使う方法【初学者】Juliaを使って円周率を求める初めてプログラミングをした時のゴミソース見つけた面倒なことはPythonにやらせよう@GitHub API v4を使ったリリース実績取得Gatsby.jsで作ったブログに読み終わるまで○○分を追加した話Google広告設定でみる属性情報であなたをもっと知ろう!Blog用に新しいLogo作った話今年1年を振り返ってGoのEchoでJaegerを使ってボトルネックを調査するGatsby.jsで作ったBlogの投稿をGitHubの草にして表示させるWeb Developer Roadmap 2020を眺めながら今年の目標(Frontend)をだらだら考えるの会AWS X-RayでLambdaのトレースをしつつ、Datadog APMに連携するMonWedFri
AWS X-RayでLambdaのトレースをしつつ、Datadog APMに連携するWeb Developer Roadmap 2020を眺めながら今年の目標(Frontend)をだらだら考えるの会Gatsby.jsで作ったBlogの投稿をGitHubの草にして表示させるGoのEchoでJaegerを使ってボトルネックを調査する今年1年を振り返ってBlog用に新しいLogo作った話


 SearchBox

Search your interesting by Algolia in this blog.


 All 143 Tags

ぼやき 19JavaScript 12GitHub 7ラーメン 6Vue.js 6TypeScript 6デブ活 5Nuxt.js 4React 3Gatsby.js 3Ansible 3Serverspec 3Mac 3自動テスト 3AWS 3Python 3Auto Provisioning 3GitHub Action 2Netlify 2Lambda 2Go 2GraphQL 2ChromeDriver 2Chainer 2Write Code Every Day 2CompositionAPI 2toast 2test 1particle.js 1GitPitch 1GitHub Badges 1Azusa Colors 1GitHubAction 1Sentry 1監視 1JSON Resume 1GitHub Pages 1Resume 1CV 1 1weed 1CI 1Netlify Form 1健康 1Contact Form 1Travis 1天下一品 1腰痛 1Gatsby 1E2E Test 1Selenium 1GoogleChrome 1Azure Devops Build Pipeline 1netatmo 1台風 1IoT 1センシング 1Google Apps Script 1API FLASH 1SlackAPI 1Clasp 1Headless CMS 1Jest 1Unit Test 1Slack 1Stamp 1ハロウィーン 1Nim 1docopt 1CLI 1昆布 1Googleデータポータル 1BI 1Google Analytics 1仮想化 1AWS認定ソリューションアーキテクトプロフェッショナル 1資格 1勉強法 1Hyper-v 1metasploitable 1RNN 1LSTM 1Chat BOT 1アイマス 1デレマス 1Hadoop 1ゾウ 1ひなこのーと 1Deep Learing 1OpenCV 1機械学習 1CNN 1分類学習 1顔認識 1powerShell 1particles-bg-vue 1particle 1Proton 1particles.js 1E2Eテスト 1Nightwatch.js 1チャーシュー飯 1Composition API 1Vuex 1ストアパターン 1ギター 1DTM 1エフェクター 1ATELIERZ 1Caparison 1VOCALOID 1Julia 1円周率 1初心者 1The Gauss–Legendre algorithm 1Leibniz formula for π 1かわいい 1恐竜時代 1ことり隊 1早稲田 1GitHub API v4 1リリース実績 1Estimated Reading Time 1あと何分 1Google広告設定 1Google 1属性情報 1Persolal Data 1Server 1Seti@Home 1Logo 1SVG 1振り返り 1Echo 1Jaeger 1ボトルネック調査 1React Calendar Heatmap 1Frontend 1Web Developer Roadmap 2020 1X-Ray 1Datadog 1APM 1modules 1
この記事は567文字2.8で読めます

わからん

最近Nuxt.jsと戯れるようにしてますが、Nuxt.jsとVue.jsの新しいAPIであるCompositionAPIの相性があまりよくないのか色々苦戦してます。

いよいよツラミもわかってきた頃合いなので一つずつまとめていこうかと思います。

今回はNuxt.jsmodulesCompositionAPIでどう使っていくかを書きます。

Table of Contents

先に結論

実装方法だけ見たい人は下記に進んでください。

CompositionAPIだとVueインスタンスにアクセスできるのはsetup内のみ

また、本実装を施したWebアプリを作ってみました。

ebook-homebrew-nuxt-with-typescript-client

該当のComponent

img

そもそもCompositionAPIとは?

CompositionAPIとは、Vue3.x系から正式採用される新しいVue.jsの使い方です。

公式的には

a set of additive, function-based APIs that allow flexible composition of component logic. (コンポーネントロジックの構成を柔軟にできる関数ベースな追加API)

とのこと。

ここら辺はだんだん使っていけば何となく良いところが見えてきますが、そちらのまとめはまた今度。

CompositionAPIを使おうと思ったのは、Vue3.xで採用されるというのと、もはやTypeScriptで書かないと現場でいじめられてしまうこの世の中で、VueもTypeScriptで書くことが急務になりつつある状況の中、Vue + TypeScriptで一定のデファクトスタンダードを勝ち得たClassAPIという使い方が、色々問題になっているようだったのでそのツラミを取り除いたらしいCompositionAPIを採用しました。

上記のツラミ・スゴミについて詳しくは下記のプレゼンがすごくわかりやすかったです。

Composition API TypeScriptはVue.jsの夢を見るか?

ざっくりと書き方の違いとしては

ClassAPI(decoratorを使ったパターン)

<script lang="ts">
import axios from 'axios';
import { Component, Prop, Vue } from "vue-property-decorator";

@Component
export default class HelloWorld extends Vue {

  // props
  @Prop() private propHoge!: string;

  // data
  message: string = "Hoge";
  hogeCount: number = 0;

  // computed
  get double() {
    return this.hogeCount* 2;
  }

  // mounted
  mounted() {
    this.gethoge();
  }

  // methods
  getData() {
    axios
      .get("https://hogehoge.com")
      .then(response => (this.message = response));
  }
}
</script>

CompositionAPIで書くパターン

<script lang="ts">
  import {
    createComponent,
    reactive,
    onBeforeMount,
    onMounted,
    computed,
    ref
  } from '@vue/composition-api';
  import axios from 'axios';
  import toast from '@nuxtjs/toast';
  import {PdfFileNotFoundError} from "~/types/error";
  const backendURL = 'https://ebook-homebrew.herokuapp.com/';

  // data
  // ref,またはreactiveとして設定するとTemplateでreactiveに変更が反映される
  // setup()の外でも中でもOK
  const state = reactive<{
    uploadList: Array<Map<string, string>>
  }>({
    uploadList: []
  });

  // methods
  const updateFileList = async (): Promise<void> => {
     (ロジック)
  };

  const downloadPDF = async (filePath: string): Promise<Blob> => {
   (ロジック)
  };
  
  // typeまたはinterfaceでpropsの型指定
  type Props = {
    propHello: string;
  };
  
  //createComponet内でprops, components, layoutなどを設定
  export default createComponent({
    //props
    props: {
      propHello: {
        type: String
      }
    },
    //setup()で初めてVueインスタンス化されるのでinjectされたものはsetup内でしかとれない。
    setup (props: Props, ctx) {

      // propsをsetup内ローカル変数で再設定
      const propsHello = props.propHello;

      //Contextをsetupで受け取ることができ、module化されたものはroot要素からとれる
      const toast = ctx.root.$root.$toast;

      //setup内でもmethods作成可能。Context rootから取得するものを使わないといけない場合、setup内で実装するしか道はなさそう
      const doDownload = async (filePath: string): Promise<void> => {
         (ロジック)
      };
      //ライフサイクルはsetup内で記載、またライフサイクル自体も従来と異なる
      onBeforeMount( async () => {
        await updateFileList()
        }
      );
      //setupのreturnで返したものがtemplateで使える変数
      return {
        state,
        propsHello,
        doDownload
      };
    }
  });
</script>

となります。

ぜんぜんかきっぷり違ってびっくり!

ぱっと見ClassAPIのデコレーターの方がコード量少なくて見通しはいい気がしますが、ロジック、ステート、レンダリングを好きなように(究極別ファイルに切り出しも可)宣言して、setupでまとめ上げるのは確かに見通しよいかもしれませんね。

まだ、ここらへんは自分の中でのベストプラクティスができあがってないので今後考察します。

あと、テストコードはまだ書いてないのですが、毎回VueインスタンスをshallowMountして頑張って書く感じから解放されそうでテストコード的なメリットはありそうです。

Nuxt.jsとの相性

CompositionAPIとNuxt.jsの相性は今のところよくないと思います。

その一例がmodulesだと思うので検証がてら考察していきます。

Nuxt.jsのmodulesがCompositionAPIで使いたいんだが

ここからが本題なのですが、よくあるNuxt.jsのmodulesを使う実装例の中で全くといっていいほどCompositionAPIでやってるものがないので、Nuxt.jsの動き方を逐次確認しながらmodulesを使ってみます。

例えば、ClassAPI(またはOptionsAPI)の場合よくあるNuxt.jsモジュールの例はaxiosです。

次のようなお困りごとを解決する使い方が例によく出ます。

  • HeadlessCMSなど他コンテンツURIをProxyしている場合などで、APIコール時にHTTP Statusチェックし、404だった場合は別ページを表示させる

こういったケースだとOptionsAPIでは下記のような実装例があります。

//あらかじめnuxt.config.jsにmodules: ['@nuxtjs/axios']を宣言し、同configにplugins: ['~/plugins/axios'] も宣言しておく
//@/plugins/axios.js

// modulesのaxiosを呼び出す際の共通のエラー処理を記載
export default function ({ $axios, redirect }) {

    $axios.onError(err => {
        const statusCode = parseInt(err.response && err.response.status)
        if (statusCode === 404) {
            redirect('/not-found-page')
 
        }
    })

}
//利用側components: hoge.vue

export default {
  methods: {
    async sendRequest() {
 //methods内では this.$axios
      const response = await this.$axios.$get('https://hoge.com');
      res = response.headers.Accept;
    }
  },
  async asyncData({ $axios }) {
 //asyncData, fetchなどでは $axiosで取得
    const hoges= await $axios.$get("https://hoge/hoge",{
        params: {
          userId: "hoon"
        }
      }
    )
    return { hoges };
  }
};

共通のエラーハンドリングをpluginsに記載するだけで冗長なハンドリングを回避できるのはすごいですね。

ポイントはmodulesで宣言した@nuxtjs/axiosは書くpage, componentで利用可能でVueインスタンス内ではthis.$axiosで取得できるということです。

CompositionAPIだとVueインスタンスにアクセスできるのはsetup内のみ

ということは先ほど話した通りなのですがそうするとmodulesの利用側はsetup内でのみ使えることになります。

ということを頭に入れながら@nuxtjs/toastを実装していきます。

まずnuxt.config.ts にmodulesを設定していきます。

//nuxt.config.ts

(中略)
modules: [
    '@nuxtjs/toast',
  ],

toastの利用側のコンポーネントではsetup内で使います。

ただ、できるだけロジックをsetup内にごちゃごちゃ書きたくないのでエラーハンドリングを使いながら頑張ります。

<script lang="ts">
  import {
    createComponent,
    reactive,
    onBeforeMount,
    onMounted,
    computed,
    ref
  } from '@vue/composition-api';
  import axios from 'axios';
  import toast from '@nuxtjs/toast';

  // 独自エラー(404 NotFound)を作ってtoastを出しわける
  class PdfFileNotFoundError extends Error {
    constructor(e?: string) {
      super(e);
      this.name = new.target.name;
      Object.setPrototypeOf(this, new.target.prototype);
    }  
  }

  const backendURL = 'https://ebook-homebrew.herokuapp.com/';

  // ロジック
  const downloadPDF = async (filePath: string): Promise<Blob> => {
    const res = await axios.post(backendURL + 'convert/pdf/download', { uploadId: filePath, },
      {responseType: 'blob'}).catch((err) => {
      if (err.response.status === 404) {
        throw new PdfFileNotFoundError('PdfFileNotFound');
 //404 NotFoundだったら独自エラーをthrow
      } else {
        throw err;
      }
    },
    );
    return new Blob([res.data], {type: 'application/pdf'});
  };

  export default createComponent({

    setup (ctx) {
 //setupでContextを受け取れるので受け取る
      
      //modulesはContextのrootから取れる
      const toast = ctx.root.$root.$toast;
      const doDownload = async (filePath: string): Promise<void> => {
        const options = {
          position: 'top-center',
          duration: 2000,
          fullWidth: true,
          type: 'error',
        } as any;
        try{
          const blob = await downloadPDF(filePath);
          const link = document.createElement('a');
          link.href = window.URL.createObjectURL(blob);
          link.download = 'result.pdf';
          link.click();
        } catch (e) {
          //errorをキャッチ
          if (e instanceof PdfFileNotFoundError) {
            toast.show('No File!!', options)
 //エラーハンドリングでtoast呼び出し
          } else {
            toast.show('UnknownError!!', options)
          }
        }
      };
      return {
        state,
        doDownload
      };
    }
  });
</script>

とまぁ、結局のところmodulesはsetupで使うのですが、stateの処理やAPIコール部分はなるべく外だししました。

結論

むずかしい。

˚