SIerだけど技術やりたいブログ

5年目のSIerのブログです

Vuejs APIアクセスはcreatedとmountedのどちらで行う?

created と mounted はざっくりした理解だと、こんな感じ。

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

https://jp.vuejs.org/images/lifecycle.png

APIアクセスは created と mounted のどちらで行う?

APIアクセスはほとんどのライブラリで非同期に行われる。なので created と mounted のどちらでAPIアクセスを開始しようが、レスポンスが返ってきた時点でコールバックが実行される。それを踏まえて、コールバックの中で

  • propsにデータを設定するだけの場合は created を使う
    • DOM構築してる間にもHTTP通信を待てるから
    • Chromeは優秀すぎてほぼ違いでなかったけど、Edgeだとけっこう違う
  • DOMにアクセスする必要があるときは mounted を使う
    • レスポンスが即返ってきた場合でもDOMにアクセスできないとダメだから
    • jQuery 時代をひきずったようなDOM直接指定ライブラリ使うときはコレ

だと思う。

動作確認する

  "dependencies": {
    "axios": "^0.17.1",
    "vue": "^2.5.2"
  }

json serverを用意する

$ json-server --watch db.json
{
  "users": [
  {
    "id": 1,
    "name": "Stark"
  },
  {
    "id": 2,
    "name": "Targaryen"
  },
  {
    "id": 3,
    "name": "Tyrell"
  }
  ]
}

サンプルを用意する

<template>
  <section>
  <h1>ユーザリスト</h1>
  <ul>
    <li v-for="user in users" :key="user.id">
    {{user.name}}
    </li>
  </ul>
  </section>
</template>

<script>
import Axios from 'axios'

export default {
  name: 'Users',
  data () {
    return { users: [] }
  },
  created () {
    const self = this
    Axios.get('http://localhost:3000/users')
    .then((res) => {
      self.users = res.data
    })
  }
}
</script>
実行結果
  • mounted が実行される
  • usersが [] の状態でDOMが作られる
  • レスポンスが返ってきたら users に値が設定される
  • Vueが変更を検知してDOMが再構築される

f:id:kimulla:20180203164222g:plain

参考

jp.vuejs.org

Multibranch Pipeline の成果物の保存数を指定する

Muitibranch Pipeline のプロジェクトを利用すると、ブランチごとにジョブが作成される。

f:id:kimulla:20180131211035p:plain

デフォルトだと、各ブランチごとの成果物の保存数は上限がない。
そのため、成果物が溜まってしまう。

f:id:kimulla:20180131214448p:plain

各ブランチごとの成果物の保存数を指定するには、Jenkinsfile内で以下のように指定する。
(Scripted Pipeline, Jenkins 2.60.3で動作確認)

node {
  properties([[$class: 'BuildDiscarderProperty',
    strategy: [$class: 'LogRotator',
      numToKeepStr: '5', artifactNumToKeepStr: '5']
  ]])

  print "build..."
}

すると、成果物の保存数の上限を超えたものは削除される。

f:id:kimulla:20180131215250p:plain

注意点

Muitlbranch Pipelineの「設定」で変更できる設定は「不要になったブランチを指定した保存数だけ残す」という意味なので注意。

試しに実施してみると、

f:id:kimulla:20180131211043p:plain

task4ブランチを削除すると、不要になったブランチとして打消し線が入る。

f:id:kimulla:20180131211228p:plain

もうひとつブランチを削除すると、保存数を超えるので削除される。
f:id:kimulla:20180131211404p:plain

Vuejs vue-router クエリパラメータの一部だけを取り除く

vue-router のクエリを一部のみ取り除く

バージョン

  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  }

やりたいこと

  • 検索したらURLにパラメータが付与される。
  • ボタンを押したらパラメータが一部だけ削除される

f:id:kimulla:20180127180436g:plain

ソースコード

<template>
  <div>
  <div>
    <form @submit.prevent="$router.push({query: {q: q, type: type} })">
    <input type="text" v-model="q" />
    <input type="text" v-model="type" />
    <button type="submit">絞り込む</button>
    </form>
  </div>
  <div style="margin-top:30px">
    <button @click="deleteQuery('q')" type="button">X 絞り込みワード</button>
    <button @click="deleteQuery('type')" type="button">X タイプ</button>
  </div>
  </div>
</template>

<script>
export default {
  name: 'App',
  data () {
  return {
    q: null,
    type: null
  }
  },
  methods: {
    deleteQuery: function (key) {
      var query = Object.assign({}, this.$route.query)
      delete query[key]
      this.$router.push({query: query})
    }
  }
}
</script>

注意点

絶対に$routeオブジェクトの戻り値をそのまま利用しないこと。

以下のように$routeオブジェクトの戻り値をそのまま利用すると、$router.pushしたときにvue-routerがパラメータの変更を検出できず、URLが切り替わらない。

<script>
...
    deleteQuery: function (key) {
      var query = this.$route.query
      delete query[key]
      this.$router.push({query: query})
    }
</script>

これに2hくらいハマった。内部で$routeオブジェクトを比較してリクエストするかどうか判別してるらしい。vue-router 頭いいー。

github.com

Vuejs Axiosで共通的な例外をあつかう

Vuejsと一緒に利用されることが多いHTTPクライアントライブラリのAxiosでは、interceptorsの仕組みを利用することでレスポンスに関する共通的な処理をはさみこめる。これを利用してVuejsで共通的な例外をあつかう。

Axios interceptors
GitHub - axios/axios: Promise based HTTP client for the browser and node.js


動作確認

モックサーバを用意する

サンプルとしてエラーを返すモックサーバを用意する。

$ npm install -D json-server

モック定義のファイルを用意する。

const jsonServer = require('json-server')
const server = jsonServer.create()
const middlewares = jsonServer.defaults()

server.use(middlewares)
server.listen(3000, () => {
  console.log('JSON Server is running')
})
server.get('/unauthorized', (req, res) => {
  res.status(401).jsonp({
    message: "unauthorized"
  })
})

server.get('/systemerror', (req, res) => {
  res.status(500).jsonp({
    message: "something wrong"
  })
})

モックサーバを起動する。

$ node mock.js
JSON Server is running


システムエラーの場合は、500を返す。

$ curl -i localhost:3000/systemerror
HTTP/1.1 500 Internal Server Error
X-Powered-By: Express
Vary: Origin, Accept-Encoding
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Content-Length: 32
ETag: W/"20-fhnUB5BwaOsQsXyG8exFr0MEGzY"
Date: Wed, 17 Jan 2018 13:16:01 GMT
Connection: keep-alive

{
  "message": "something wrong"
}

認証エラーの場合は、401を返す。

$ curl -i localhost:3000/unauthorized
HTTP/1.1 401 Unauthorized
X-Powered-By: Express
Vary: Origin, Accept-Encoding
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Expires: -1
X-Content-Type-Options: nosniff
Content-Type: application/json; charset=utf-8
Content-Length: 29
ETag: W/"1d-1AQxoXvOBStoEV/A43KaFU1XEOg"
Date: Wed, 17 Jan 2018 13:16:42 GMT
Connection: keep-alive

{
  "message": "unauthorized"
}

アプリを用意する

完成版はgithubに。
GitHub - kimullamen/axios-sample


バージョンは以下の通り。

  "dependencies": {
    "axios": "^0.17.1",
    "vue": "^2.5.2",
    "vue-router": "^3.0.1",
    "vue-toasted": "^1.1.24"
  },

ポイントはAxiosのinterceptorsの仕組みを利用すること。これによってレスポンスの共通処理を作成できる。

今回はvue-toastedというトースト表示のライブラリを利用する。

import Vue from 'vue'
import Axios from 'axios'

const http = Axios.create({
  // for cors
  withCredentials: true
})
http.interceptors.response.use(function (response) {
}, function (error) {
  // 認証エラー時の処理
  if (error.response.status === 401) {
    Vue.toasted.clear()
    Vue.toasted.error(error.response.data.message)
  // システムエラー時の処理
  } else if (error.response.status === 500) {
    Vue.toasted.clear()
    Vue.toasted.error(error.response.data.message)
  }
  return Promise.reject(error)
})

export default http

Axiosをimportし、Vueのprototypeに設定する。こうすることでVueインスタンスから共通処理が設定済みのAxiosを$httpで利用できるようになる。

// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
import Vue from 'vue'
import App from './App'
import router from './router'
import Axios from './axios'
import Toasted from 'vue-toasted'

Vue.prototype.$http = Axios
Vue.use(Toasted)
Vue.config.productionTip = false

/* eslint-disable no-new */

new Vue({
  el: '#app',
  router,
  components: { App },
  template: '<App/>'
})

実際のアクセス部分。$httpを利用する。

<template>
  <div id="app">
    <button type="button" @click="callUnauthorized">unauthorized</button>
    <button type="button" @click="callSystemError">system error</button>
  </div>
</template>

<script>
export default {
  name: 'App',
  methods: {
    callUnauthorized: function () {
      this.$http.get('http://localhost:3000/unauthorized')
    },
    callSystemError: function () {
      this.$http.get('http://localhost:3000/systemerror')
    }
  }
}
</script>

実行結果

HTTPレスポンスの共通処理が実行されて、トーストが表示される。

f:id:kimulla:20180118235708g:plain

今回の例だと1コンポーネントしかないので、トーストを表示しっぱなしにしている。コンポーネントが切り替わったときにトーストを消したい場合は、vue-router のナビゲーションガードやコンポーネントのdestroyedフック内で Vue.toasted.clear()を呼び出すといい。
ナビゲーションガード · vue-router

結論

HTTPレスポンスの例外は共通化できる。

Vuejs vue-router利用時にはアクティブなリンクに自動でクラスを振ってくれる

タイトルでほぼ説明終わり。

  "dependencies": {
    "vue": "^2.5.2",
    "vue-router": "^3.0.1"
  },

コンポーネント内にrouter-viewタグがネストしている場合に、アクティブなリンクにcssのスタイルを適用したいとする。

f:id:kimulla:20180115225703g:plain

Vuejsだけの知識で解決しようとすると、以下のように書ける。

<template>
  <div>
    <ul>
      <!-- class属性を割り当てる -->
      <li :class="{ active : this.$route.path.match(/articles\/stark/) }">
        <router-link to="/articles/stark">Stark</router-link>
      </li>
      <li :class="{ active : this.$route.path.match(/articles\/tyrell/) }">
        <router-link to="/articles/tyrell">Tyrell</router-link>
      </li>
      <li :class="{ active : this.$route.path.match(/articles\/targaryen/) }">
        <router-link to="/articles/targaryen">Targaryen</router-link>
      </li>
    </ul>
    <router-view/>
  </div>
</template>

<script>
export default {
  name: 'App'
}
</script>

<style scoped="scoped">
.active > a{
  color: red;
  border: solid;
}
</style>

上記のように自分でアクティブなクラスを割り当てるのは、URLが変わるたびにclassの式を変える必要が出てきて変更に弱くなる。なので、なるべく控えたい。


なんかいい方法ないかなとリファレンス見たら、ズバリそのものが…!

router-link · vue-router

router-link コンポーネントはアクティブなタグには自動でクラスを振るらしく、確認すると確かにその通り。

f:id:kimulla:20180115230028p:plain

しかも exact-active-class でクラス名の変更もできるので、cssフレームワークのルールに応じた名前に変えられる、と。

  <router-link to="/article/stark" exact-active-class="active">Stark</router-link>

いやーリファレンスはこまめにチェックしないとダメですね。

Vuejs利用時に <button type="reset"> は使わないほうがいい

button type="reset"とは?

HTMLのButtonのひとつで、Formを自動でクリアしてくれるやつ。
button 要素 - HTML | MDN

<!doctype html>
<html>
  <body>
    <form>
      <input type="text" placeholder="username" />
      <input type="password" placeholder="password" />
      <button type="reset">reset</button>
    </form>
  </body>
</html>

resetボタンをクリックするとFormが自動でクリアされて地味にうれしい。

f:id:kimulla:20180108184041g:plain

Vuejsと併用すると…

resetボタン押下時に見ため上はクリアされたようにみえるけど、Vuejsで管理しているdataプロパティの値はクリアされない。

<template>
  <form @submit.prevent="save">
    <p>
      <input type="text" v-model="text" />
    </p>
    <p>
      <textarea v-model="textarea" />
    </p>
    <p>
      <input type="checkbox" v-model="checked" />
    </p>
    <p>
      <input type="radio" id="one" value="one" v-model="radio" />
      <input type="radio" id="two" value="two" v-model="radio" />
    </p>
    <p>
      <select v-model="select">
        <option disabled value="" selected>please select</option>
        <option>A</option>
        <option>B</option>
        <option>C</option>
      </select>
    </p>
    <button type="reset">reset</button>
    <button type="submit">save</button>
  </form>
</template>

<script>
export default {
  name: 'Form',
  data () {
    return {
      text: '',
      checked: false,
      textarea: '',
      radio: '',
      select: ''
    }
  },
  methods: {
    save: function () {
      console.log(`save text: ${this.text} textarea: ${this.textarea} checked: ${this.checked} radio: ${this.radio} select: ${this.select}`)
    }
  }
}
</script>

resetボタンを押したあとも、dataプロパティの値がそのまま残っている。

f:id:kimulla:20180108184615g:plain

解決方法

button type="reset"を利用しない。
自分でハンドラを設定して、dataプロパティをリセットする。

<template>
...
    <button @click="clear" type="button">reset</button>
...
</template>

<script>
...
methods: {
    ...
  clear: function () {
    this.text = ''
    this.checked = false
    this.textarea = ''
    this.radio = ''
    this.select = ''
  }
}
</script>

resetボタンを押したときに、dataプロパティもリセットされている。

f:id:kimulla:20180108190142g:plain

dataプロパティの初期値を丸ごと設定する場合は、以下のようにも書けるらしい。

<script>
...
// ここを再利用する
data () {
  return {
    text: '',
    checked: false,
    textarea: '',
    radio: '',
    select: ''
  }
},
methods: {
    ...
    clear: function () {
      Object.assign(this.$data, this.$options.data.call(this))
    }
  }
}
</script>

stackoverflow.com

ついでに

Vuetifyというフレームワークを利用していた場合、

こんな感じで組み込みのFormコンポーネントをrefで参照してreset()メソッドを呼び出せば、nullで初期化してくれる。
https://github.com/vuetifyjs/vuetify/issues/2752

<template>
...
      <v-form ref="form">
...
</template>

<script>
...
    clear: function () {
      this.$refs.form.reset()
    }
...
</script>

いやぁー、リッチなUIフレームワークって、本当(ほんっとう)に素晴らしいものですね。

今年のふりかえり(ブログの運営について)

年の瀬でやる気もでないので、2017年をふりかえろうと思います。

技術要素はゼロです。面白さもゼロです。

アクセス数

2016年から始めたブログですが、1年前と比べると2倍強のアクセス数になりました。
f:id:kimulla:20171231000828p:plain


日本の総人口は1億2670万人らしいので、このペース(1年に+6000pv)で成長し続ければ21,116年後には国民全員に読んでもらえそうです。
http://www.stat.go.jp/data/jinsui/img/pop.gif

統計局ホームページ/人口推計(平成29年(2017年)7月確定値,平成29年12月概算値) (2017年12月20日公表)


もっというと日本の人口減少は以下のように深刻なので、10,000年後には国民全員に読んでもらえるチャンスすらありそうです。そのころには、国民の生活を支えるインフラ事業のようになっているかもしれません。

http://www8.cao.go.jp/kourei/whitepaper/w-2012/zenbun/img/z1_1_03.gif

第1章 第1節 1 (2)将来推計人口でみる50年後の日本|平成24年版高齢社会白書(全体版) - 内閣府

アウトプットする利点

記事を読んでもらえてると思うと、勉強するモチベーションがわきますし、また、記事にする以上はもう少し理解しておこう、という意識が働きます。これが勉強にとても役立ちます。

自分は、OSSソースコードを読んでみようかなぁと思えるようになりました(かなり苦労しますが)。2年前は「OSSソースコードは、尖りすぎたクレイジーな人が読むもの。あそこまでイッちゃうと怖い。」と思っていたので、大きな進歩です。

勉強したいけど、ついつい土日に寝てしまう、無駄に過ごしたなぁと後悔してしまう、という人は、ぜひアウトプットしましょう。世の中に情報があれば私も助かります。