tubone

tubone

Boyaki makes a new world


 Recent posts  6 / 53

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に連携するElixirでパラレルな負荷試験ツールを作るMonWedFri
Elixirでパラレルな負荷試験ツールを作るAWS X-RayでLambdaのトレースをしつつ、Datadog APMに連携するWeb Developer Roadmap 2020を眺めながら今年の目標(Frontend)をだらだら考えるの会Gatsby.jsで作ったBlogの投稿をGitHubの草にして表示させるGoのEchoでJaegerを使ってボトルネックを調査する今年1年を振り返って


 SearchBox

Search your interesting by Algolia in this blog.


 All 145 Tags

ぼやき 19JavaScript 12GitHub 7ラーメン 6Vue.js 6TypeScript 6デブ活 5Nuxt.js 4React 3Gatsby.js 3Ansible 3Serverspec 3Mac 3自動テスト 3AWS 3Python 3Auto Provisioning 3GitHub Action 2Go 2Netlify 2Lambda 2GraphQL 2ChromeDriver 2Chainer 2Write Code Every Day 2toast 2CompositionAPI 2test 1particle.js 1GitPitch 1GitHub Badges 1Azusa Colors 1E2E Test 1Sentry 1監視 1JSON Resume 1GitHub Pages 1Resume 1CV 1 1weed 1Selenium 1Netlify Form 1GoogleChrome 1Contact Form 1Travis 1天下一品 1健康 1Gatsby 1腰痛 1Headless CMS 1GitHubAction 1Azure Devops Build Pipeline 1netatmo 1台風 1IoT 1センシング 1Google Apps Script 1API FLASH 1SlackAPI 1Clasp 1CI 1Jest 1Unit Test 1Slack 1Stamp 1ハロウィーン 1Nim 1docopt 1CLI 1昆布 1Googleデータポータル 1BI 1Google Analytics 1仮想化 1Hadoop 1ゾウ 1AWS認定ソリューションアーキテクトプロフェッショナル 1資格 1勉強法 1Hyper-v 1metasploitable 1RNN 1LSTM 1Chat BOT 1アイマス 1デレマス 1ひなこのーと 1Deep Learing 1OpenCV 1機械学習 1CNN 1分類学習 1顔認識 1powerShell 1particles-bg-vue 1particle 1Proton 1particles.js 1modules 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 1Logo 1SVG 1Server 1Seti@Home 1振り返り 1Frontend 1Web Developer Roadmap 2020 1Echo 1Jaeger 1ボトルネック調査 1Elixir 1Load Test 1React Calendar Heatmap 1X-Ray 1Datadog 1APM 1
この記事は771文字3.9で読めます

フロントエンド初学者の私が、Vue.jsの新しいAPIであるComposition APIを使ってNuxt.jsの実装を行う機会があり、Vuexを使ったストアで非常につらい思いをしたのでまとめます。

多分、もっとちゃんとしたやり方があるとは思いますがひとまず動いたので記事にしていきます。

とりあえず動いたゴミコードはこちらのGitHubにあげてます。

結構適当にやってます。

なお、Composition-APIについてよくまとまっている記事はVue Advent Calendar 2019に載っていたComposition APIってなんだという記事が大変に参考になるかと思いますし、本実装のモチベーションになった記事としてきたるべきvue-nextのコアを理解するという記事の存在があります。

大変参考にさせていただきました!

Table of Contents

前提

今回の検証、というか無理やり実装の前提事項です。

  • TypeScriptで実装します
  • Composition APIは@vue/composition-apiをVue.useする形で実装します
  • Composition APIでの具体的な実装例(Composition Functionなど)は解説省きます。

作りたいもののイメージ

  • バックエンドからAPI経由で情報を取得する
  • 取得した情報はVuexでステート管理する
  • Vuexストアにactionsを作成し、呼び出すことでaxiosを使ってバックエンドのAPIから情報を受け取りストアに格納する
  • getterを設定し、getter経由で最新のストア情報が取得できるようにする

検証に使うショボAPI

今回はバックエンドAPIとして拙作の下記APIを使います。

本APIからサーバのステータスバージョンを取得し、リアクティブに画面に表現することを目標にします。

APIのリソースはstatusという名前です。詳しくは下記OpenAPIをご参照↓

Herokuで作ったエンドポイント

ソースGitHub

img

Nuxt.jsでのVuexの使い方

Nuxt.jsではVuexのストアを使いたい場合比較的簡単に実装できstoreディレクトリの中に、hoge.ts のような形でファイルを作ることで、モジュールモードというストアモードが利用できるようになります。(素のVuexでいうところのnamespace付のストアですね)

参考: Vuex ストア

早速ストアを作っていきます。

間違った使い方

まずは間違った例です。

端的に言えばストアがReactiveじゃないので値更新がされません。

詳しくは後で解説しますが、いたって普通のVuexストアを書いています。

import axios from 'axios';

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

 //Stateの型を宣言
export default interface State {
  status: string;
  version: string;
}

//State: 先ほど宣言したState型を使ってます
export const state = (): State => ({
  status: '',
  version: '',
});

//Mutation: stateに新しい値をセットするだけ
export const mutations = {
  setStatus(state: State, status: string) {
    state.status = status;
  },
  setVersion(state: State, version: string) {
    state.version = version;
  },
};

//Action: サーバからステータスなどを取得し、mutation経由で値をセット
//Actionなのでasync awaitな非同期な処理もOK
export const actions = {
  async fetchServerInfo({ commit }): Promise<void> {
    await axios.get(backendURL + 'status').then((response) => {
      commit(setStatus, response.data.status);
      commit(setVersion, response.data.version);
    }).catch((err) => {
      commit(setStatus, 'error');
      commit(setVersion, 'error');
    })
  },
};

//Getter: stateの中身を取り出してreturnする
export const getters = {
  getStatus(state: State): string{
    return state.status;
  },
  getVersion(state: State): string{
    return state.version;
  }
};

基本的なことかもしれませんが、Stateの更新はAction, Mutationどちらからでもできますが、Componentsからの更新は非同期を許容するActionに統一したほうがいいかもです。

img

VuexのstoreをComposition APIで使う方法

それでは上記で作成したVuexストアをComponentsで使っていきます。

何度も言っていますがVuexストアを正しく直さないと動きませんよ

<template>
  <div id="status">
    <!-- actionの呼び出し(5) -->
    <b-button id="get-status" type="is-primary" @click="fetchStatus(store)">Get Status NOW</b-button>
    <!-- setupのreturnに設定したものはtemplate内で使える(6)-->
    <p>ServerStatus: <b>{{ store.getters['status/getStatus'] }}</b></p>
    <p>ServerVersion: <b>{{ store.getters['status/getStatus'] }}</b></p>
  </div>
</template>

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

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

  type Props = {
    propHello: string;
  };

  const fetchStatus = async (store) => {
   // store.dispatchでActionを呼び出す
   // setupからstoreを受け取る (※4)
    await store.dispatch('status/fetchServerInfo');
  };

  // createComponentする
  export default createComponent({
    props: {
      propHello: {
        type: String
      }
    },

    //setupを呼び出すとSetupContextのrootでVueインスタンス内の要素にアクセスできる
    setup (props: Props, { root }:SetupContext) {
      // props
      const propsHello = props.propHello;
      
      //storeをVueインスタンスから取り出す(※1)
      const store = root.$store;

      //methods
      onBeforeMount( async () => {
        // 当然setup外で設定した関数にもアクセス可能(※4)
        // 関数内でstoreを使うため引数で渡しておく(※2)
        await fetchStatus(store);
      });


      return {
        fetchStatus,
        store, //storeをtemplate内で利用するためにreturn(※3)
        propsHello,
      };
    }
  });
</script>

ストアへのアクセス方法

Composition APIではVuexストアはsetup内でしか取り出せません

なぜならComposition APIはsetupしたタイミングでVueインスタンスが利用できるため、いままでのVue.jsでいうところのthis.$storeで取り出すことがsetup内でしか使えないからです。

なので、setupの中でroot.$storeを取り出して(※1)、Vuexストアを使う別の関数(※2)やtemplateで利用するためsetupのreturnに渡しています。(※3)

Actionの呼び出し

上記Componentsでは、Vue.jsライフサイクルのonBeforeMountと、template内のget-statusボタンの押下でfetchStatusという関数が呼び出され、同関数でActionがdispatchされる作りです。(※4)(※5)

fetchStatusはsetup外に作られた関数なので、seutupの中でfetshStatusの引数にstoreを渡してあげます。

Getterの呼び出し

(※3)のようにあらかじめVuexストア(store)をsetupのreturnに設定することで(※6)のように store.getters['status/getStatus']というVuexモジュールモードのgetterの呼び出しの形でtemplate内でGetterが利用できます。

何度も言ってますが動きません

何度も言っておりますが上記のコードでは正しく動きません。

Actionを正しくdispatchしていても、ServerStatus, ServerVersionはtemplateで更新されません

img

なぜならVuexストアのステートがReactiveじゃない。つまり、値更新を検知できないためです。

ではVuexのストアをReactiveにしてしまいましょう。

ReactiveなVuexストアを作る

結論から言えば、VuexストアのStateをReactiveにしちゃえばいいわけなので

ストアを次のように作り直します。

import axios from 'axios';

import {
  reactive,
  Ref,
  toRefs,
} from '@vue/composition-api';

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

// Reactiveなstateを作る(7)
export const state = () => { //型について(※9)
  return toRefs(reactive<{ //toRefsでreturnする(※8)
    status: string;
    version: string;
  }>({
    status: '',
    version: '',
  }))
};

export const mutations = {
    setStatus(state: State, status: string) {
    state.status = status;
  },
    setVersion(state: State, version: string) {
    state.version = version;
  },
};

export const actions = {
  async fetchServerInfo({ commit }): Promise<void> {
    await axios.get(backendURL + 'status').then((response) => {
      commit(SET_STATUS, response.data.status);
      commit(SET_VERSION, response.data.version);
    }).catch((err) => {
      commit(SET_STATUS, 'error');
      commit(SET_VERSION, 'error');
    })
  },
};

// 意味わかんないif文。ここが問題点(※10)
export const getters = {
  getStatus(state): string{
    if (state.status.value === '') {
      return '';
    }else{
      return state.status;
    }

  },
  getVersion(state): string{
    if (state.version.value === '') {
      return '';
    }else{
      return state.version;
    }
  }
};

ぶっちゃけこれだけの変更です。

少し細かく見ていきます。

Composition APIのReactiveについて

そもそもReactiveって何よ?って話はきたるべきvue-nextのコアを理解する: そもそも「リアクティブ」とは?がすさまじくわかりやすいので解説はそちらに譲ります。

Composition APIにはReactiveな値を作り出すことのできる方法として有名どころでrefreactiveがありますが、VuexストアはStateという自己定義のオブジェクトなのでプリミティブのみ許容のrefは使えませんreactiveを使います

(注7)のようにStateオブジェクトをreactiveで囲んであげればReactiveなStateになります。

Reactiveな関係性を引き継ぐtoRefs

StateをReactiveにすることはできました。

ですが、reactiveにはスコープが存在しますのでreturnで戻してしまうと戻り先でReactiveな関係が解消されてしまいます。

それを解決する方法としてtoRefsというものがあります。

(注8)のようにtoRefsにreactiveな値を引数に設定し、returnすることで戻り先でもreactiveな変数として扱えます。

toRefsはどうやらComposition APIのソースを見ると、受け取ったreactiveな値をRefで再定義し、一個一個getter, setterを設定しているproxyらしいです。

インターセプトな動き・・・。なるほどわからん。

export function toRefs<T extends Data = Data>(obj: T): Refs<T> {
  if (!isPlainObject(obj)) return obj as any;

  const res: Refs<T> = {} as any;
  Object.keys(obj).forEach(key => {
    let val: any = obj[key];
    // use ref to proxy the property
    if (!isRef(val)) {
      val = createRef<any>({
        get: () => obj[key],
        set: v => (obj[key as keyof T] = v),
      });
    }
    // todo
    res[key as keyof T] = val;
  });

  return res;
}

ともかくこれでComponents側でもreactiveな値として扱えますね!

問題点1: Stateの型ってどうやって設定するの?(※9)

確かにこれで無事ReactiveなVuexストアができたのですが、少し問題点があります。

間違った旧ストアコードではVuexストアのStateにinterfaceを使ってきちんと型を設定していたかと思いますが新コードではできてません。

 //Stateの型を宣言
export default interface State {
  status: string;
  version: string;
}

//State: 先ほど宣言したState型を使ってます
export const state = (): State => ({
  status: '',
  version: '',
});
 //Stateの型を宣言
export default interface State {
  status: string;
  version: string;
}

export const state = () => { //型がないよ!!
  return toRefs(reactive<{
    status: string;
    version: string;
  }>({
    status: '',
    version: '',
  }))
};

解決法

実はStateの型問題はかなりトリッキーというか、Composition APIの深掘れば何とか解決できます。

import {
  reactive,
  Ref,
  toRefs,
} from '@vue/composition-api';

declare type Refs<Data> = { //謎のtype
  [K in keyof Data]: Data[K] extends Ref<infer V> ? Ref<V> : Ref<Data[K]>;
};

export default interface State {
  status: string;
  version: string;
}

export const state = ():Refs<State> => { //Refs<State>が型です
  return toRefs(reactive<State>({
    status: '',
    version: '',
  }))
};

declare type Refsという超謎なことをしてますが、これは、toRefsの戻り型としてComposition APIのソースそのものに定義されていた型ものそのものです。

Ref型は@vue/composition-apiからimportで取ってこれるのですが、Refs型は取ってこれないので、取ってこれるRef型からゴリゴリ作って、それを使ってあげるわけです。

これで一応Stateの型が設定できます。

問題点2: getterの謎のif文(※10)

新しいgetterは謎のif文がかまされています。

export const getters = {
  getStatus(state): string{ //引数の型ないよー
    if (state.status.value === '') { //謎if文
      return '';
    }else{
      return state.status;
    }

  },
  getVersion(state): string{
    if (state.version.value === '') { //謎・・
      return '';
    }else{
      return state.version;
    }
  }
};

これは、StateをtoRefs化した弊害によって入れざるを得ないif文です。

Components内でtoRefsを受け取らないことにはRefsのProxyが効かないようで、Computeしたときに返るrefオブジェクトっぽいものが返ってきます。

なので、その場合はvalueで値を取得しなければいけません。

んー。これはどうしようもありませんね・・・。

あと、toRefsがProxy的な動きをするのでTypeScriptに怒られないような型定義がうまくできませんでした(涙)

結論

色々試行錯誤しましたが、なんとか動きました。

img

Composition APIとVuexの相性があまりよくないことが分かった気がしますが、それでもdevtool使いたいとかでVuexの需要はあると思うので何か抜け道が発見できればと願うばかりです。

参考

˚