20190709のvue.jsに関する記事は9件です。

[Vue]子コンポーネントのメソッドを、親が実行させるサンプル 〜子を意のままに操るには〜

したいこと

親コンポーネントから、子コンポーネントのメソッドを起動させたい場合のサンプルを共有します。

いきなり結論コード

親コンポーネント
<!-- 〜略〜 -->

<child ref="child"></child> <!-- 子コンポーネント -->

<!-- 〜略〜 -->

<script>
    this.$refs.child.$emit('child_method')
</script>
子コンポーネント
<!-- 〜略〜 -->
<script>
    methods: {
        child_method() {
            // 何かの処理
        }
    },
</script>

解説

ポイントを箇条書きすると…

親コンポーネント

  • 使いたいメソッドのある子コンポーネントにrefを仕込み適当な名前をつける。
  • 呼び出す際は、this.$refs.{refに仕込んだ名前}.$emit('{子コンポーネントにある使いたいメソッド名}')と書く。

子コンポーネント

  • 使いたいメソッドを仕込んでおく。

これだけです。

さいごに

端的な内容ですが、最後までありがとうございました。

関連記事

$refsとrefについての記事はこちらにもあります。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js+Firebase環境でreCAPTCHA v3を組み込む

はじめに

フロントエンドでVue.js(Vue CLI 3、Typescript)、バックエンドでFirebaseを使って個人?アプリを作成中ですが、reCAPTCHA v3を組み込むときに時間がかかったので、自身の忘備録として残したいと思います。
結論としては、まんまの↓を実装すれば良いです。
https://developers-jp.googleblog.com/2017/09/guard-your-web-content-from-abuse-with.html

私の場合、要件に合わなかったので合う形に変えたサンプルをgithubにupしています。
secret keyなどは雑に扱ってます。実コードでは埋め込まないようにしましょう。。。
https://github.com/ryoutoku/vue-recaptcha-firebase

想定読者

  • Vue.jsを使う
    • 注:TypeScript使っています
  • reCAPTCHA v3を組み込む必要がある
    • 注:バックエンドとしてFirebase(Cloud Functions)使っています

前提

  • Firebaseでプロジェクトを作成しており、Vue.jsのコードのデプロイが可能な状態

書くこと

  • サンプルコードなど
  • 自身が理解した動作の内容

書かないこと

  • VueとかFirebaseのプロジェクトの作成方法
  • Firebaseのプロジェクトの設定(デプロイなど含む)
  • reCAPTCHAのサイト登録などの方法

最終結果

  • reCAPTCHAのスコアを取得(表示)する

reCAPTCHAとは

ざっくり言うと、ユーザがbotではないと判断するための以下のようなよくあるヤツです。

画像参照:https://developers.google.com/recaptcha/

ちなみにv3はUIはありません。

前準備

組み込むためには、使用するための前準備が必要です

  1. googleアカウントを作成
  2. reCAPTCHAを使用するサイト等を設定

処理イメージ

今回実装したもののデータの流れのイメージはこんな感じになりました。
ちなみに「reCAPTCHAサーバ」とか正確な名称ではありませんが、ニュアンスだけ感じて頂ければ。

Screenshot from 2019-07-08 14-45-20.png

  1. reCAPTCHAの設定で発行されたサイトキーを元にサーバにアクセス
  2. サーバからtokenが発行される
  3. 発行されたtokenを元にCloud Functionsにアクセス
    • CORSの設定上、フロントから直接reCAPTCHAサーバにアクセスできないためCloud Functionsを経由する必要がある
  4. Cloud Functionsからtokenを元にreCAPTCHAサーバにアクセス
  5. 判定結果がCloud Functionsに戻る
  6. Cloud Functionsからロボットか否かの判定が帰ってくる

サンプルのUI

以下の様な簡単な動作をするものを作成しました。
1. reCAPTCHA実行するボタンを押下
2. 通信して戻ってきたtokenをtoken横に表示
3. そのtokenを使ってreCAPTCHAのチェックを実行、結果をresult横に表示
4. errorがあればerror横に表示する

reCAPTCHA実行するボタン押す前
Screenshot from 2019-07-09 14-29-27.png

reCAPTCHA実行するボタン押した後
Screenshot from 2019-07-09 14-29-17.png

実装コード

フォルダ構成

作成したプロジェクトの構成は↓の様な感じです。

├── babel.config.js
├── firebase.json
├── functions
│   ├── node_modules
│   ├── package.json
│   ├── package-lock.json
│   ├── src
│   │   └── index.ts      // ← バックエンド側のメインロジック
│   ├── tsconfig.json
│   └── tslint.json
├── node_modules
├── package.json
├── postcss.config.js
├── public
│   ├── favicon.ico
│   └── index.html
├── README.md
├── src
│   ├── App.vue
│   ├── assets
│   │   └── logo.png
│   ├── components
│   │   └── reCAPTCHAUI.vue   // ← フロント側のメインロジック
│   ├── main.ts
│   ├── shims-tsx.d.ts
│   └── shims-vue.d.ts
├── tsconfig.json
├── tslint.json
└── yarn.lock

VueにはreCAPTCHA v3用のVue reCAPTCHA-v3といったコンポーネントも公開されていますが、あまりメリットを感じられなかったのでreCAPTCHA-v3を使って実装しています。

cd project_root

npm install recaptcha-v3
// or
yarn add recaptcha-v3

バックエンド側

処理コードのメインロジック

functions/src/index.ts
import * as functions from "firebase-functions";
import * as rp from "request-promise";

export const checkRecaptcha = functions.https.onCall(async (data, context) => {
  // ここに発行したsecret keyを設定
  const secret = "secret";

  let response = {};
  await rp({
    uri: "https://recaptcha.google.com/recaptcha/api/siteverify",
    method: "POST",
    formData: {
      secret,
      response: data.token,
    },
    json: true,
  })
    .then(async result => {
      console.log("recaptcha result", result);
      response = result;
    })
    .catch(async reason => {
      console.log("Recaptcha request failure", reason);
      response = reason;
    });
  return response;
});

簡易解説:

  • functions.https.onCallでデータを受ける
    • functions.https.onRequestを使っても可能だが、APIを外部に公開する訳ではない&外部公開する場合はHTTPメソッド(特にOPTIONS)に対応する必要があるためこちらを使う
  • reqestモジュールを使ってPOSTでhttps://recaptcha.google.com/recaptcha/api/siteverifyにtokenを投げる

フロントエンド側処理コード

処理コードのメインロジック抜き出し

src/components/reCAPTCHAUI.tsの一部
interface IReCAPTCHAResult {
  success: boolean;
  challenge_ts: string;
  hostname: string;
  "error-codes": [];

}

@Component
export default class reCAPTCHAUI extends Vue {
  private token: string = "";
  private result: IReCAPTCHAResult = {
    success: false,
    challenge_ts: "",
    hostname: "",
    "error-codes": []
  };

  // ここに発行したsite keyを設定
  private siteKey = "site-key";

  // ここでreCAPTHCAのチェックレベルを設定
  private action = "homepage";
  private error = {};

  private async click() {

    // モジュールを使ってtokenを取得する
    const recaptcha = await load(this.siteKey);
    this.token = await recaptcha.execute(this.action);

    // tokenを用いてCloud FunctionsのAPIを実行する
    const func = firebase.functions().httpsCallable("checkRecaptcha");
    await func({ token: this.token })
      .then(async response => {
        this.result = (await response.data) as IReCAPTCHAResult;
      })
      .catch(error => {
        this.error = error;
      });
  }
}

簡易解説:

  • ボタン押下でclick()を呼び出す
  • actionには所定の文字列を設定
  • 結果取得まで処理を待ちたいのでasyncawaitを使用
  • Cloud Functionsはfunctions.https.onCallを使用するので、それに準じfirebase.functions().httpsCallableを使用

終わりに

Vue.js+FirebaseでreCAPTCHAのスコアを持ってくるところまでできました。
本サンプルでは結果をそのままフロントまで返していますが、バックエンド側でscoreに依る判定を組み込めばokです。

参考

公式

https://www.google.com/recaptcha/intro/v3.html
https://developers-jp.googleblog.com/2017/09/guard-your-web-content-from-abuse-with.html

その他参考

https://qiita.com/ritou/items/e92aad65c5d8c906edb3
https://github.com/AurityLab/vue-recaptcha-v3
https://github.com/AurityLab/recaptcha-v3

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vue】メニュー周りの権限制御を行う

概要

VueなどでSPAを構築する場合、大体こんな感じのメニューがあると思います。
 2019-08-24 13.39.25.png

クリックされた項目でURLをルーティングして内容を変化させるやつですね。

権限制御を行う

このメニューで、以下のような権限制御を行う場合の例をご紹介します。
(※バックエンドについては今回省略します。)

  • ユーザーのロールに設定された権限によりメニューの項目を出し分けたい
  • 使用する権限を持っていない場合、項目は非表示とする
  • URL直打ちでも開けないようにしたい

環境

テーブル

テーブルはこんな感じです
 2019-08-26 21.32.16.png
ユーザーはロールを1つ持ち、ロールは権限を複数持つ構成となっています。

ユーザーテーブルのレコード例

ユーザーコード パスワード ロールコード
admin ******* admin
user1 ******* writer
user2 ******* readonly

ロール権限のレコード例

ロールコード 権限
admin menu1
admin menu2
admin menu3
writer menu1
writer menu2
readonly menu1

権限を取得

ストア

  • ユーザーに紐付いた権限を保持します。
action-types.js
export const GET_MENU_FLGS = "GET_MENU_FLGS";

export const ACTION = {
  GET_MENU_FLGS
};
mutation-types.js
export const UPDATE = "UPDATE";

export const MUTATION = {
  UPDATE
};
menu-store.js
import { MUTATION } from "./mutation-types";
import { ACTION } from "./action-types";
import axios from "axios";

const state = {
  //権限
  menuFlgs: {}
};

const getters = {
  menuFlgs: state => {
    return state.menuFlgs;
  }
};

const actions = {
  //ログインユーザーのメニュー使用権限を取得します。
  async [ACTION.GET_MENU_FLGS]({ commit }) {
    await axios
      .get("menus")
      .then(res => {
        commit(MUTATION.UPDATE, {
          menuFlgs: res.data
        });
      })
      .catch(err => {
        throw err;
      });
  }
};

const mutations = {
  [MUTATION.UPDATE](state, { menuFlgs }) {
    state.menuFlgs = menuFlgs;
  }
};

export default {
  state: state,
  getters: getters,
  actions: actions,
  mutations: mutations
};

権限のJSONはこのようなイメージです。

menuFlgs: [
  {
    menu1: true
  },
  {
    menu2: true
  },
  {
    menu3: true
  }
];

VueRouter

  • ①URLごとに権限を持っているかを判定します
    • 権限がない場合は遷移をキャンセルします
  • ②ストアに権限が保持されていない場合、取得します
router.js
import Vue from "vue";
import Router from "vue-router";
import multiguard from "vue-router-multiguard";
import store from "/store/store.js";
import { GET_MENU_FLGS } from "/store/menu/action-types";

Vue.use(Router);

/* *
 * ①ナビゲーションガード
 * */
const menu1Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu1) {
    next();
  } else {
    next(false);
  }
};

const menu2Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu2) {
    next();
  } else {
    next(false);
  }
};

const menu3Guard = (to, from, next) => {
  if (store.getters.menuFlgs.menu3) {
    next();
  } else {
    next(false);
  }
};

/* *
 * ルート定義
 * */
const router = new Router({
  routes: [
    {
      path: "/menu1",
      //①
      beforeEnter: multiguard([menu1Guard]),
      component: () => import("@/components/pages/menu1/menu1Template")
    },
    {
      path: "/menu2",
      //①
      beforeEnter: multiguard([menu2Guard]),
      component: () => import("@/components/pages/menu2/menu2Template")
    },
    {
      path: "/menu3",
      //ナビゲーションガードを複数設定することもできます。
      beforeEnter: multiguard([menu1Guard, menu2Guard, menu3Guard]),
      component: () => import("@/components/pages/menu3/menu3Template")
    }
  ]
});


/* *
 * ②権限を取得します。
 * */
router.beforeEach(async (to, from, next) => {
  if (!Object.keys(store.getters.menuFlgs).length) {
    await store.dispatch(GET_MENU_FLGS);
  }

  next();
});

export default router;


メニュー表示

  • ①メニューを配列で定義しておきます。
  • ②ストアの権限から、使用可能なメニューのみ表示します
<template>
  <v-list>
    <v-list-tile v-for="menu in availableMenus" :key="menu.id">
      <router-link :to="menu.url">{{menu.title}}</router-link>
    </v-list-tile>
  </v-list>
</template>

<script>
export default {
  data() {
    return {
      //①メニューマスタ
      menuMaster: [
        {
          id: 1,
          title: "メニュー1",
          url: "/menu1"
        },
        {
          id: 2,
          title: "メニュー2",
          url: "/menu2"
        },
        {
          id: 3,
          title: "メニュー3",
          url: "/menu3"
        }
      ]
    };
  },
  computed: {
    //②使用可能なメニューで絞り込みます
    availableMenus() {
      let available = [];
      for (const menu of this.menuMaster) {
        if (this.isAvailable(menu.url)) {
          available.push(menu);
        }
      }
      return available;
    }
  },
  methods: {
    //ログインユーザーがメニューの使用権限を持っているかを判定します。
    isAvailable(url) {
      const menuName = url.slice(1);
      return this.$store.getters.menuFlgs[menuName];
    }
  }
};
</script>

まとめ

  • Vue Router Multiguardを使うとナビゲーションガードが扱いやすくなりました
  • 権限をどのタイミングで取得するかは賛否両論ありそうです

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vueをマウントできなくて奮闘

プログラミング歴3か月くらいの初心者です、自分のメモ用。

bladeファイル内になどを記載して、コンポーネントを挿入しても

app.js:38008 [Vue warn]: Failed to mount component: template or render function not defined.

found in

---> <コンポーネント名>
<Root>

上記のエラーが発生する。

app.jsにもしっかり記載している。

Vue.component('my-component', require('./components/MyComponent.vue'));

いろいろな記事を見て、webpack.config.jsに追記だの devコマンドを実行したかだのいろいろ対処法が
書かれていたがどれでも解決できないところ以下の記事を見つけた。

https://stackoverflow.com/questions/49138501/vue-warn-failed-to-mount-component-template-or-render-function-not-defined-i

どうやら app.jsで

Vue.component('my-component', require('./components/MyComponent.vue').default);

最後に.defaultを追記するだけでした。(意味は知らない)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VuetifyのDatepickerの値が消えない

VuetifyのDatepicker、すごく便利なんだけど、v-text-fieldと一緒に使った時によく使うclearableで値が消えないなーと思っていたメモ。

何したのか

v-text-field@click:clear="date = null"を追加した。

コードは公式サンプルを利用
https://vuetifyjs.com/en/components/date-pickers#date-pickers-formatting-date-using-external-libs

<template>
  <v-menu
    v-model="menu1"
    :close-on-content-click="false"
    full-width
    max-width="290"
  >
    <template v-slot:activator="{ on }">
      <v-text-field
        :value="computedDateFormattedMomentjs"
        clearable
        label="Formatted with Moment.js"
        readonly
        v-on="on"
        @click:clear="date = null" // ←を追加
      ></v-text-field>
    </template>
    <v-date-picker
      v-model="date"
      @change="menu1 = false"
    ></v-date-picker>
  </v-menu>
</template>

消えなかった理由

v-text-fieldを普通に使ってるだけならclearableオプションを追加するだけでいいけど、他の要素と組み合わせて使う時readonlyをつけてる。多分これが原因。
見た目部分だけは消せるけど、v-modelで紐付けられた値に変更後の値を返さないよーってなってるはず。

以上!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.js を勉強する Session�6

条件付きレンダリング

v-if

v-ifディレクティブは、要素を条件に応じて描画したい場合に使用する。

html部分
<div id="example">
  <p v-if="awesome"> 条件がtrueの時に表示される </p>
</div>
javascript部分
let app = new Vue({
  el: '#example',
  data: {
    awesome: false
  }
})
開発者モードのConsole
app.awesome = true
// true
ブラウザの表示
条件がtrueの時に表示される

v-else

通常のjavascript同様にv-elseでelseを記述することが可能です。
v-else要素は、v-ifまたはv-else-if要素の直後になければいけません。
それ以外の場合は認識されません。

html部分
<div id="example">
  <p v-if="awesome"> 条件がtrueの時に表示される </p>
  <p v-else="awesome"> 条件がfalseの時に表示される </p>
</div>
javascript部分
let app = new Vue({
  el: '#example',
  data: {
    awesome: false
  }
})
開発者モードのConsole
app.awesome = true
// true
ブラウザの表示
条件がtrueの時に表示される

v-else-if

通常のjavascript同様にv-else-ifでelse ifを記述することが可能です。
v-else-if要素は、v-ifまたはv-else-if要素の直後になければいけません。
それ以外の場合は認識されません。

html部分
<div id="example">
  <p v-if="awesome == 'A'"> 条件がAの時に表示される </p>
  <p v-else-if="awesome == 'B'"> 条件がBの時に表示される </p>
  <p v-else-if="awesome == 'C'"> 条件がCの時に表示される </p>
  <p v-else="awesome"> 条件に当てはまらなった時に表示される </p>
</div>
javascript部分
let app = new Vue({
  el: '#example',
  data: {
    awesome: ''
  }
})
ブラウザの表示
条件に当てはまらなった時に表示される
開発者モードのConsole
app.awesome = 'A'
// "A"
// ブラウザの表示:条件がAの時に表示される

app.awesome = 'B'
// "B"
// ブラウザの表示:条件がBの時に表示される

app.awesome = 'C'
// "C"
// ブラウザの表示:条件がCの時に表示される

app.awesome = 'D'
// "D"
// ブラウザの表示:条件に当てはまらなった時に表示される

テンプレートでのv-ifによる条件グループ

v-ifはディレクティブなので、1つの要素に付加する必要があります。
1要素よりも多くの要素を切り替えたい場合にHTML のコンテンツテンプレート(< template >)要素を使用します。

html部分
<div id="example">
  <template v-if="boxflg">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
  </template>
</div>
javascript部分
let app = new Vue({
  el: '#example',
  data: {
    boxflg: false
  }
})
開発者モードのConsole
app.boxflg = true
// true
ブラウザの表示
Title
Paragraph 1
Paragraph 2
開発者モードのElement
<div id="example">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</div>

htmlのElementにコンテンツテンプレートが含まれることはありません。

key による再利用可能な要素の制御

Vue.jsは要素を可能な限り効率的に描画しようとしますが、1からレンダリングするかわりにそれら要素を再利用することがよくあります。

Vue.jsを非常に速くするのに役立つ以外にも、これにはいくつかの便利な利点があります。

たとえば、ユーザーが複数のログインタイプを切り替えることを許可する場合は、次のようにします。

  • UserNameでログイン
  • Emailでログイン

上記の2つを切り替える入力画面を作ります。

html部分
<div id="example">
  <template v-if="loginType === 'username'">
    <label>Username</label>
    <input placeholder="Enter your username">
  </template>
  <template v-else>
    <label>Email</label>
    <input placeholder="Enter your email address">
  </template>
  <button @click="change">ログイン方法変更</button>
</div>

正直愚直なコードな気がして最善な書き方では無い気がしますが…

javascript部分
let app = new Vue({
  el: '#example',
  data: {
    loginType: 'username'
  },
  methods: {
    change(){
      if (!(this.loginType == "username")) {
        this.loginType = "username"
      }else{
        this.loginType = "email"
      }
    }
  }
})

上記のコードでloginTypeを切り替えても、ユーザーが既に入力したものは消去されません。
両方のテンプレートが同じ要素を使用するので、< input > は置き換えられません。
placeholderが切り替わるだけです。
この結果は望ましい結果では無いかもしれません。
この2つの要素は完全に別個のもので、再利用したく無い場合があり、要素に一意であることを示すためにkey属性を追加します。

html部分
<div id="example">
  <template v-if="loginType === 'username'">
    <label>Username</label>
    <input placeholder="Enter your username" key="username-input">
  </template>
  <template v-else>
    <label>Email</label>
    <input placeholder="Enter your email address" key="email-input">
  </template>
  <button @click="change">ログイン方法変更</button>
</div>

javascriptのコードは上記の例と変更はありません。
これでログイン方法が変更ボタンが押されるたびにinputが再利用されなくなります。

v-show

条件的に要素を表示するための別のオプションです。

使用方法はv-ifとほとんど同じです。

html部分
<div id="example">
  <p v-show="awesome"> 条件がtrueの時に表示される </p>
</div>
javascript部分
let app = new Vue({
  el: '#example',
  data: {
    awesome: false
  }
})
開発者モードのElements
<div id="example">
  <p style="display: none;"> 条件がtrueの時に表示される </p>
</div>
開発者モードのConsole
app.awesome = true
// true
ブラウザの表示
条件がtrueの時に表示される
開発者モードのElements
<div id="example">
  <p style> 条件がtrueの時に表示される </p>
</div>

v-ifとの違いは v-sowディレクティブでは要素自体は常に描画されてDOMに維持するということです。
v-showはシンプルに要素のdisplayCSSプロパティを切り替えているだけです。

【注意】

v-showはコンテンツテンプレートをサポートしていません。

アンチパターン
<div id="example">
  <template v-show="boxflg">
    <h1>Title</h1>
    <p>Paragraph 1</p>
    <p>Paragraph 2</p>
  </template>
</div>

これはコンテンツテンプレートがDOMのElementsに含まれないためです。

v-if vs v-show

v-if

イベントリスナと子コンポーネント内部の条件ブロックが適切に破棄され、切り替えられると再レンダリングされるため、"リアル"な条件レンダリングです。

v-ifは遅延描画(lazy)です。
初期表示においてfalseの場合レンダリングされず、条件がtrueになったときにレンダリングされます。

v-show

要素は条件に関わらず常に描画されており、displayCSSプロパティを切り替えているだけです。

結論

シンプルかつ頻繁に切り替える必要があるのならばv-showを使用し
複雑な処理、条件が実行時から変更されることがほとんどない場合はv-ifを選ぶ

あとがき

v-ifとv-forの題目はv-ifとv-forを同時に利用する事は推奨されておらずバッドプラクティスですということです。
詳細はスタイルガイドを参照してください

参考資料

公式リファレンス

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Vuex】ストアの4つの概念まとめ【唯一の情報源】

Vuexの根幹 【ストア】

【役割】
Vuexを使う上でアプリケーションの状態(情報)を保持する役割です。

Vuexのコンセプトである『Vuexは信頼できる唯一の情報源である事』が前提にあるため、ストアはアプリケーションの中でただ一つだけの根幹となる存在です。
今回はその大事な役割であるストアの4つの概念をまとめます。

4つの概念

・state→アプリケーションの状態(情報)
・getter→stateの一部やstateから返された値を保持する
・mutation→stateを更新(変化)させる
・action→非同期通信や外部APIとのやりとりを行う

この4つを一纏めにしたものをモジュールと言います。
モジュールでこの4つを守っているイメージです。
502-vue-useful-link_vuex.png

ストアの作成

Vue-cliでVuexを取り込んでおけば、srcフォルダにstore.jsが入っています。
Vuexも読み込まれていますので、そちらを使います。
まだ準備ができていない方はこちらの記事を参考にしてみてください。
【Vue.js】Vuexの入り口

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({ //説明の為、代入しました
  //ここに実装を書きます
})

export default store

説明のため少しいじりました。この状態から始めます。

state

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  //stateオプションで初期値を設定
  state: {
    count: 10
  }
})
//store.stateで参照
console.log(store.state.count) //10

export default store

【役割】
アプリケーション全体の状態を保持します。

stateはdataオプションのような存在で、stateが変更されるとコンポーネントの算出プロパティやテンプレートへと反映されます。
これはVuexがVueのリアクティブシステムを活用して実装されている為です。
state更新時にUIの再描画が必要最小限に留まり、開発コストが下がります。

【参照】
store.state.state名

【注意点】
なんでもstateに情報を保持させるのではなく、コンポーネント内部でしか使わないようなものはこれまでと同様にdataオプションに、アプリケーション全体で管理するものはstore内で管理すると良いです。

getter

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 10
  },
  //gettersオプションで定義
  getters: {
    //stateから別の値を計算
    squared: function(state) {
      return state.count * state.count
    },
    //他のgettersの値を使うことも可能 (省略技法ももちろん使える)
    cubed: (state, getters) => state.count * getters.squared
  }
})

console.log(store.state.count) //10
//store.gettersで参照
console.log(store.getters.cubed) //1000

export default store

【役割】
stateから別の値を算出する為に使われます。

gettersは算出プロパティcomputedと同様な働きをしてくれます。
値がキャシュされ、そのgettersが依存しているstateが更新されない限り再評価しません。
違う点は引数にstateと他のgettersを持つことで、それらを使って違う値を返します。

【参照】
store.getters.getters名

【引数】
state
他のgetters

【注意点】
getterを参照した時に定義した関数が常に発火する訳ではありません。
computedと同様に、計算した値を返す以外の処理は行うべきではありません。

mutation

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 10
  },
  //mutationsオプションで定義
  mutations: {
    // 'increment'mutationを定義
    increment: function(state, payload) {
      state.count += payload.amount
    }
  }
})

console.log(store.state.count) //10
// store.commitでmutationを呼び出す
store.commit('increment', { amount: 5 })
console.log(store.state.count) //15

export default store

【役割】
stateの値を更新する為に使われます。

【呼び出し】
store.commit('mutation名', payload)

【引数】
mutation→ 第一引数: state(渡されたstateを更新)
commit→ 第二引数: payload
(store.commitの第二引数をpayloadと呼び、これを使用することで同じmutationで異なる値にstateを変更できます。)

【注意点】
原則として、mutation以外でstateの更新を行うことを禁止しています。
stateの状態がいつどこで発生したのかを追跡しやすくする為です。
また、全て同期的な処理にする必要があります。
非同期処理を用いると特定のケースで意図しない動作を行ってしまう可能性がある為です。
非同期処理を行いたい時はactionsで定義します。

action

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 10
  },
  mutations: {
    increment: function(state) {
      state.count += 1
    }
  },
  // actionオプションで定義
  actions: {
    incrementAction: function(ctx) {
      // 'increment'mutationを実行
      ctx.commit('increment')
    }
  }
})

console.log(store.state.count) //10
// store.dispatchでactionを呼び出す
store.dispatch('incrementAction')
console.log(store.state.count) //11

export default store

【役割】
非同期処理や外部APIとの通信を行い、最終的にmutationを呼び出す為に使われます。

【呼び出し】
store.dispatch.action名

【引数】
ctx(context)

contextとは特別なオブジェクトで、次の内容が含まれています。
・state: 現在のstate
・getters: 定義されているgetter
・dispatch: 他のactionを実行するメソッド
・commit: 他のmutationを実行するメソッド

分割代入{ 変数名 }という記法があり、オブジェクトのプロパティを変数に代入する記述を省略できます。
(例)
分割代入不使用: let commit = ctx.commit
分割代入を使用: { commit }

【注意点】
mutation同様に、直接呼び出すことはできません。
また、dispatchを使うことによって共通の処理を一つのactionにまとめる事ができますが、使いすぎるとどのactionから呼ばれているのか分かりづらくなってしまいます。

非同期処理の例

Ajaxでデータを取得し、そのデータをpayloadに含めたmutationを呼び出すactionを定義している例です。

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 例として非同期処理を行う関数
function getCountNum(type) {
  return new Promise(resolve => {
    // 1秒後にtypeに応じたデータを返す
    setTimeout(() => {
      let amount
      switch (type) {
        case 'one':
          amount = 1
          break;
        case 'two':
          amount = 2
          break;
        case 'ten':
          amount = 10
          break;
        default:
          amount = 0
      }
      resolve({ amount })
    }, 1000);
  })
}

const store = new Vuex.Store({
  state: {
    count: 10
  },
  mutations: {
    increment: function(state, payload) {
      state.count += payload.amount
    }
  },
  // actionオプションで定義
  actions: {
    incrementAsync: function({ commit }, payload) {
      // 非同期にデータを取得
      return getCountNum(payload.type)

      .then(data => {
        console.log(data)
        commit('increment', {amount: data.amount})
      })
    }
  }
})
//後ほど下記に書き換えます
store.dispatch('incrementAsync', {type: 'one'}) //後ほど下記に書き換え

action内でPromiseを返している場合、store.dispatchの戻り値のPromiseを使い、action内の非同期処理の完了を検知できます。
上記はaction内でPromiseを返しているので、下記のようにstore.dispatchの戻り値のPromiseに対してthenでコールバックしactionを完了を検知しています。

store.js
//上記に追記
console.log(store.state.count) //10
store.dispatch('incrementAsync', {type: 'one'})
  .then(() => {
    //actionの処理が完了した後に実行される
    console.log(store.state.count) //11
  })
export default store

今回は以上です。
また学習が進み次第、更新、掲載していきます。
ここまでで補足や訂正などありましたら是非ご教授いただけると嬉しいです。
最後まで読んでいただきありがとうございます。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsのプロジェクト作成(Vuex、vue-router、Axios、ESLint、Prettier、Jest)

書いてあること

  • テンプレートを利用しないVue.jsプロジェクト作成手順
  • 各パッケージの設定方法、および簡単な動作確認
    • Vuex
    • vue-router
    • ESLint
    • Prettier
    • Jest

環境

  • CentOS Linux release 7.6.1810 (Core)
  • Node.js v10.16.0
  • Npm 6.10.0
  • Vue 3.9.1

事前準備

CentOSへNode.jsをインストール
CentOSへVue.jsをインストール
CentOSへGitをインストール

プロジェクト作成

プロジェクト作成

bash
$ vue create vue-project

Vue CLI v3.9.1
? Please pick a preset: Manually select features
? Check the features needed for your project: Babel, Router, Vuex, Linter, Unit
? Use history mode for router? (Requires proper server setup for index fallback in production) Yes
? Pick a linter / formatter config: Standard
? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save
? Pick a unit testing solution: Jest
? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files
? Save this as a preset for future projects? No

開発サーバーで起動確認

bash
$ cd vue-project
$ npm run serve

Axios

パッケージをインストール

bash
npm install axios --save

プロトタイプ設定

この設定により、すべてのコンポーネントにおいて「this.$axios」でaxiosが利用可能となる。

main.js
import Vue from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
+import axios from 'axios'

Vue.config.productionTip = false

+Vue.prototype.$axios = axios

new Vue({
  router,
  store,
  render: h => h(App)
}).$mount('#app')

動作確認

About.vue(ビュー)

src/views/About.vue
<template>
  <div class="about">
    <h1>This is an about page</h1>
    <hr />
    <ZipSearch />
  </div>
</template>

<script>
import ZipSearch from '@/components/ZipSearch.vue'

export default {
  name: 'about',
  components: {
    ZipSearch,
  },
}
</script>

ZipSearch.vue(コンポーネント)

src/components/ZipSearch.vue
<template>
  <div class="about">
    <button @click="doSearch">
      Search
    </button>
    <hr />
    <p>status: {{ result.status }}</p>
    <p>zipcode: {{ result.zipcode }}</p>
    <p>prefcode: {{ result.prefcode }}</p>
    <p>address1: {{ result.address1 }}</p>
    <p>address2: {{ result.address2 }}</p>
    <p>address3: {{ result.address3 }}</p>
  </div>
</template>

<script>
export default {
  name: 'ZipSearch',
  data: () => {
    return {
      result: {
        status: null,
        zipcode: '',
        prefcode: '',
        address1: '',
        address2: '',
        address3: '',
      },
    }
  },
  methods: {
    doSearch() {
      this.$axios
        .get('/zipcode.json')
        .then(res => {
          // console.log(res)
          const dt = res.data.results[0]
          // console.log(dt)
          this.result.status = res.data.status
          this.result.zipcode = dt.zipcode
          this.result.prefcode = dt.prefcode
          this.result.address1 = dt.address1
          this.result.address2 = dt.address2
          this.result.address3 = dt.address3
        })
        .catch(err => {
          console.log(err)
        })
    },
  },
}
</script>

zipcode.json(モックデータ)

public/zipcode.json
{
  "message": null,
  "results": [
    {
      "address1": "神奈川県",
      "address2": "川崎市高津区",
      "address3": "久本",
      "kana1": "カナガワケン",
      "kana2": "カワサキシタカツク",
      "kana3": "ヒサモト",
      "prefcode": "14",
      "zipcode": "2130011"
    }
  ],
  "status": 200
}

開発サーバーで起動確認

bash
$ npm run serve

vue-router

動作確認

ルーティング設定

src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'
+import About from './views/About.vue'
+import Test from './views/Test.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
  routes: [
    {
      path: '/',
      name: 'home',
      component: Home,
    },
    {
      path: '/about',
      name: 'about',
-     // route level code-splitting
-     // this generates a separate chunk (about.[hash].js) for this route
-     // which is lazy-loaded when the route is visited.
-     component: () => import(/* webpackChunkName: "about" */ './views/About.vue'),
+     component: About,
    },
+   {
+     path: '/test',
+     name: 'test',
+     component: Test,
+   },
  ],
})

App.vue

src/App.vue
<template>
  <div id="app">
    <div id="nav">
      <router-link to="/">Home</router-link>
      <router-link to="/about">About</router-link>
+     <router-link to="/test">Test</router-link>
    </div>
    <router-view />
  </div>
</template>

Test.vue

src/views/Test.vue
+<template>
+  <div class="test">
+    <h1>This is a test page</h1>
+  </div>
+</template>

開発サーバーで起動確認

bash
$ npm run serve

Vuex

動作確認

ストア設定

src/store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
+   count: 0,
  },
  getters: {
+   count(state) {
+     return state.count
+   },
  },
  mutations: {
+   increment(state, payload) {
+     state.count += payload.cnt * 1
+   },
  },
  actions: {
+   increment({ commit }, payload) {
+     commit('increment', payload)
+   },
  },
})

TestComp.vue(コンポーネント)

src/components/TestComp.vue
<template>
  <div class="test">
    <h2>{{ msg }}</h2>
    <p>Count: {{ count }}</p>
    <hr />
    <input type="number" v-model="num" />
    <button @click="doAdd">追加</button>
    <hr />
    <div>
      <router-link to="/">Home</router-link>
    </div>
  </div>
</template>

<script>
import store from '@/store.js'

export default {
  name: 'TestComp',
  props: {
    msg: String,
  },
  data: function() {
    return {
      num: 0,
    }
  },
  computed: {
    count() {
      return store.getters.count
    },
  },
  methods: {
    doAdd() {
      store.dispatch('increment', { cnt: this.num })
    },
  },
}
</script>

Test.vue(ビュー)

src/views/Test.vue
<template>
  <div class="test">
    <h1>This is a test page</h1>
    <TestComp msg="test" />
  </div>
</template>

<script>
// import store from '@/store.js'
import TestComp from '@/components/TestComp.vue'

// 直接ステートを取得
// console.log('store', store.state.count)
// ミューテーションを実行
// store.commit('increment', { cnt: 3 })
// ゲッターでデータを取得
// console.log('getters', store.getters.count)
// アクションを実行
// store.dispatch('increment', { cnt: 5 })
// ゲッターでデータを取得
// console.log('getters', store.getters.count)

export default {
  name: 'test',
  components: {
    TestComp,
  },
}
</script>

開発サーバーで起動確認

bash
$ npm run serve

ESLint

パッケージをインストール

Vue.jsプロジェクト作成時にeslint、eslint-plugin-vueの2つが既にインストールされていることを確認。

bash
npm install eslint-config-standard eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard --save-dev

設定ファイルを修正

.eslintrc.js
module.exports = {
  root: true,
  env: {
+   browser: true,
    node: true
  },
  'extends': [
    'plugin:vue/essential',
    '@vue/standard',
+   'standard'
  ],
+ plugins: [
+   'vue'
+ ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+   'generator-star-spacing': 'off'
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

ESlint対象外のディレクトリ・ファイル設定

.eslintignore
/dist/
/node_modules/
/public/
/*.js
/package.json
/package-lock.json

動作確認

エラーが発生するようにソースを修正

src/router.js
import Vue from 'vue'
import Router from 'vue-router'
import Home from './views/Home.vue'

Vue.use(Router)

export default new Router({
  mode: 'history',
  base: process.env.BASE_URL,
- routes: [
+ routes:[
    {
-     path: '/',
+       path: '/',
      name: 'home',
      component: Home
    },
    {
      path: '/about',
      name: 'about',
      // route level code-splitting
      // this generates a separate chunk (about.[hash].js) for this route
      // which is lazy-loaded when the route is visited.
      component: () => import(/* webpackChunkName: "about" */ './views/About.vue')
    }
  ]
-})
+});

ESLintを実行(自動修正なし)

bash
$ npx eslint -c .eslintrc.js --color './src/**/*.{html,js,vue}'

/root/vue-project/src/router.js
  10:10  error  Missing space before value for key 'routes'   key-spacing
  12:1   error  Expected indentation of 6 spaces but found 8  indent
  25:3   error  Extra semicolon                               semi

✖ 3 problems (3 errors, 0 warnings)
  3 errors and 0 warnings potentially fixable with the `--fix` option.

ESLintを実行(自動修正あり)

bash
$ npx eslint -c .eslintrc.js --color './src/**/*.{html,js,vue}' --fix

スクリプトを変更

package.json
{
  "name": "vue-project",
  "version": "0.1.0",
  "private": true,
  "scripts": {
    "serve": "vue-cli-service serve",
    "build": "vue-cli-service build",
-   "lint": "vue-cli-service lint"
+   "lint": "eslint -c .eslintrc.js --color './src/**/*.{html,js,vue}' --fix"
  },
}
(略)

Prettier

パッケージをインストール

bash
$ npm install prettier prettier-eslint prettier-eslint-cli eslint-plugin-prettier eslint-config-prettier --save-dev

Prettier設定ファイルを作成

Configuration File・Prettier

.prettierrc.js
module.exports = {
  trailingComma: 'es5',
  printWidth: 120,
  tabWidth: 2,
  singleQuote: true,
  semi: false,
}

Prettier対象外のディレクトリ・ファイル設定

.prettierignore
/dist/
/node_modules/
/public/
/*.js
/package.json
/package-lock.json

ESLint設定ファイルを修正

ESLintと同時にPrettier(コード整形)を合わせて実行するように設定

.eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  'extends': [
    'plugin:vue/essential',
    '@vue/standard',
    'standard',
+   'prettier'
  ],
  plugins: [
    'vue',
+   'prettier'
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'generator-star-spacing': 'off',
+   'prettier/prettier': 'error'
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

ESLintを実行(自動修正あり)

ESLintと合わせてPrettierが実行される(ESLint動作確認時のソース修正に加え、タブサイズを変えておくとPrettierが動いたことが確認可能)

bash
npm run lint

Jest

パッケージをインストール

Vue.jsプロジェクト作成時に@vue/test-utils、babel-jestの2つが既にインストールされていることを確認。

bash
npm install jest vue-jest @babel/core babel-preset-vue-app eslint-plugin-jest --save-dev

設定ファイルを修正

jest.config.js
module.exports = {
  moduleFileExtensions: [
    'js',
-   'jsx',
    'json',
    'vue'
  ],
  transform: {
    '^.+\\.vue$': 'vue-jest',
    '.+\\.(css|styl|less|sass|scss|svg|png|jpg|ttf|woff|woff2)$': 'jest-transform-stub',
-   '^.+\\.jsx?$': 'babel-jest',
+   '^.+\\.js?$': 'babel-jest'
  },
  transformIgnorePatterns: [
    '/node_modules/'
  ],
  moduleNameMapper: {
    '^@/(.*)$': '<rootDir>/src/$1',
+   '^~/(.*)$': '<rootDir>/src/$1'
  },
  snapshotSerializers: [
    'jest-serializer-vue'
  ],
  testMatch: [
    '**/tests/unit/**/*.spec.(js|jsx|ts|tsx)|**/__tests__/*.(js|jsx|ts|tsx)'
  ],
  testURL: 'http://localhost/',
  watchPlugins: [
    'jest-watch-typeahead/filename',
    'jest-watch-typeahead/testname'
  ]
}
  • moduleFileExtensions:テスト対象のファイルの拡張子
  • transform:変換を行うモジュール
  • transformIgnorePatterns:変換時に無視するファイル
  • moduleNameMapper:パスの省略記法を指定。Vue.jsにおける「@」「~」記法を利用する場合はこちらを指定
  • snapshotSerializers:スナップショットに利用するシリアライザーのモジュールパス
  • testMatch:テストファイルを検出するためのglobパターン。
  • testURL:JSDom環境のURL
  • watchPlugins:カスタムウォッチプラグインを利用

ESLint設定ファイルを修正

テストファイルのtestexpectがESLintエラーとならないようにする設定

bash
$ npx eslint -c .eslintrc.js --color './src/**/*.{html,js,vue}'

# ESLint設定変更前(test、expectがエラーとなる)
/root/vue-project2/src/func/__tests__/sum.spec.js
  3:1  error  'test' is not defined    no-undef
  4:3  error  'expect' is not defined  no-undef

✖ 2 problems (2 errors, 0 warnings)
.eslintrc.js
module.exports = {
  root: true,
  env: {
    browser: true,
    node: true,
+   'jest/globals': true
  },
  'extends': [
    'plugin:vue/essential',
    '@vue/standard',
    'standard',
    'prettier'
  ],
  plugins: [
    'vue',
    'prettier',
+   'jest'
  ],
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off',
    'generator-star-spacing': 'off',
    'prettier/prettier': 'error'
  },
  parserOptions: {
    parser: 'babel-eslint'
  }
}

スクリプトを修正・追加

package.json
{
  "scripts": {
-   "test:unit": "vue-cli-service test:unit",
+   "test": "vue-cli-service test:unit",
+   "test:coverage": "vue-cli-service test:unit --coverage"
  },
}

動作確認

テスト対象のJavaScript、テストファイルを準備

src/func/sum.js
function sum(a, b) {
  return a + b
}

export default sum
src/func/__tests__/sum.spec.js
import sum from '@/func/sum'

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3)
})
src/store/__tests__/store.spec.js
import Vuex from 'vuex'
import store from '@/store.js'
import { createLocalVue } from '@vue/test-utils'

const localVue = createLocalVue()
localVue.use(Vuex)

describe('mutations', () => {
  test('commit increment mutations ⇒ count+1', () => {
    expect(store.getters['count']).toBe(0)
    store.commit('increment', { cnt: 1 })
    expect(store.getters['count']).toBe(1)
  })
})

describe('actions', () => {
  test('dispatch increment actions ⇒ commit increment mutations', () => {
    expect(store.getters['count']).toBe(1)
    store.dispatch('increment', { cnt: 2 })
    expect(store.getters['count']).toBe(3)
  })
})
src/components/__test__/TestComp.spec.js
import TestComp from '@/components/TestComp.vue'
import { mount, createLocalVue, RouterLinkStub } from '@vue/test-utils'

const localVue = createLocalVue()

describe('TestComp.vue', () => {
  let wrapper

  beforeEach(() => {
    wrapper = null
    wrapper = mount(TestComp, {
      localVue,
      stubs: {
        RouterLink: RouterLinkStub,
      },
    })
  })

  // デフォルト表示テスト
  test('Default Output Test', () => {
    expect(wrapper.find('p').text()).toBe('Count: 0')
  })

  // ボタンクリック後表示テスト
  test('number input ⇒ Output Test', () => {
    wrapper.find('input').setValue('10')
    wrapper.find('button').trigger('click')
    expect(wrapper.find('p').text()).toBe('Count: 10')
  })

  // RouterLinkテスト
  test('router link home', () => {
    expect(wrapper.find(RouterLinkStub).props().to).toBe('/')
  })
})

テスト実行

bash
$ npm run test

> vue-project@0.1.0 test /root/vue-project2
> vue-cli-service test:unit

 PASS  src/components/__tests__/TestComp.spec.js
 PASS  src/func/__tests__/sum.spec.js
 PASS  tests/unit/example.spec.js
 PASS  src/store/__tests__/store.spec.js

Test Suites: 4 passed, 4 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        1.969s, estimated 2s
Ran all test suites.

テスト実行(カバレッジ取得)

bash
$ npm run test:coverage

> vue-project@0.1.0 test:coverage /root/vue-project2
> vue-cli-service test:unit --coverage

 PASS  src/components/__tests__/TestComp.spec.js
 PASS  src/store/__tests__/store.spec.js
 PASS  src/func/__tests__/sum.spec.js
 PASS  tests/unit/example.spec.js
----------------|----------|----------|----------|----------|-------------------|
File            |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
----------------|----------|----------|----------|----------|-------------------|
All files       |      100 |      100 |      100 |      100 |                   |
 src            |      100 |      100 |      100 |      100 |                   |
  store.js      |      100 |      100 |      100 |      100 |                   |
 src/components |      100 |      100 |      100 |      100 |                   |
  TestComp.vue  |      100 |      100 |      100 |      100 |                   |
 src/func       |      100 |      100 |      100 |      100 |                   |
  sum.js        |      100 |      100 |      100 |      100 |                   |
----------------|----------|----------|----------|----------|-------------------|

Test Suites: 4 passed, 4 total
Tests:       7 passed, 7 total
Snapshots:   0 total
Time:        1.929s, estimated 2s
Ran all test suites.

【補足】VSCode ESLint設定

拡張機能をインストール

image.png

設定(settings.json)に下記を追記

settings.json
"eslint.validate": [
  "javascript",
  {
    "autoFix": true,
    "language": "vue"
  }
]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[備忘録]GitHubの情報を取得する

GitHubの情報を取得する

準備

https://github.com/settings/tokensPersonal access tokensGenerate new token してapiを用意

簡易的な方法

https://api.github.com/users/[username]?access_token=[Personal access token key]

ソースコード

axiosを使用します

npm install --save-dev axios

app.vue

vue.js
<template>
  <div id="app">
    <GitData repoName="UserName" />
  </div>
</template>

<script>
import GitData from "./components/Git.vue";

export default {
  name: "app",
  components: {
    GitData
  }
};
</script>

Git.vue

vue.js
<template>
  <div class="gitdata">
    <h>public repo num : {{this.userDatas.public_repos}}</h>
  </div>
</template>


<script>
import axios from "axios";

export default {
  name: "GitData",
  props: {
    repoName: String
  },
  data() {
    return { userDatas: [] };
  },
  mounted() {
    const request = axios.create({
      baseURL: "https://api.github.com"
    });
    request
      .get(
        `/users/${this.repoName}?access_token=api_key` // api key は gitにあげない!.envなどで管理
      )
      .then(res => {
        this.userDatas = res.data;
        console.log(res);
      });
  }
};
</script>

<style lang="scss">
</style>


memo

apiの取得はどこですれば?
今回はDOMに描画するのでmountedでする。
https://qiita.com/kimullaa/items/2bf8948dffb8c52d2b6b

created
インスタンスの初期化が済んで props や computed にアクセスできる
DOMにはアクセスできない
mounted
created + DOMにアクセスできる

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む