20190707のvue.jsに関する記事は6件です。

【Vue.js】Vuexの入り口

Vuexの学習を始めるにあたり、一体どういうものなのか大枠が分かれば学習も進みやすくなると思い、Vuexを使った基本的な状態管理の流れを書いていきます。

参考記事・文献
Vue.js入門
Vue CLIとVuexでアプリケーションの状態変化を扱う

Node.jsのインストール方法、vue-cliの始め方はこちらの記事が大変参考になります
MacにNode.jsをインストール
Vue.js を vue-cli を使ってシンプルにはじめてみる

Vuexを始める前に

公式ドキュメントを読んでおきましょう。

Vuexとは何か?

始めのうちは書き方も良く分からないですが、ぼんやりと理解し全体として何が出来るのかをつかむことがすごく重要に感じました。
・Vuex とは何か?
・Vuex 入門
・コアコンセプトの5つ
これだけでも目を通しておけば何を意識すれば良いか分かり、後から少しずつ理解できてきます。

Vue-cliを使いVuexを始める

Vue-cliがインストール時にまとめて追加できるので簡単かと思います。
今回はその方法でいきます。


Vue-cliでプロジェクト作成

下記のコマンドでプロジェクトを作成します。project_nameのところは,
お好きな名前で。今回はvue_sumpleとつけました。

terminal
$ vue create project_name

デフォルト設定か自分で設定するか聞かれます。今回はvuexを取り入れるので、Manually select featuresを選択。

terminal
Vue CLI v3.9.2
? Please pick a preset: 
  default (babel, eslint) 
❯ Manually select features 

取り入れられる対象のパッケージが表示されます。BabelとLinter / Formatterは初期でチェックが入っています。Vuexの他にRouterやsassを使うかなども選べます。今回はVuexにチェックを入れます。(スペースキーでチェック出来ます)

terminal
Vue CLI v3.9.2
? Please pick a preset: Manually select features
? Check the features needed for your project: 
 ◉ Babel
 ◯ TypeScript
 ◯ Progressive Web App (PWA) Support
 ◯ Router
❯◉ Vuex
 ◯ CSS Pre-processors
 ◉ Linter / Formatter
 ◯ Unit Testing
 ◯ E2E Testing

この後何個か質問されますが、こだわりがなければ全てEnterで良さそうです。
最後に設定を保存するか聞かれます。(ここではNにしました。)

terminal
? Save this as a preset for future projects? (y/N)

ここで作成が開始されます。
無事作成完了すると、下記の画面が表示されます。
コマンドの指示に従って、作られたディレクトリに移動しnpmを立ち上げます。

terminal
?  Successfully created project vue_sumple.
?  Get started with the following commands:

 $ cd vue_sumple
 $ npm run serve 

下記の画面が表示されるので、LoacalのURLをブラウザにコピペします。

terminal
 App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.3.25:8080/

下記の画面が表示されれば成功です!!
スクリーンショット 2019-07-07 21.24.31.png

今回修正するファイル

・main.js
・store.js
・App.vue
この3つでvuexの大まかな流れは掴めるかと思います。

main.js

初期でVuex用のスクリプトを読み込むようになっているので、ここは今回いじらなくても良いです。

main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'

Vue.config.productionTip = false

new Vue({
  store, //コンポーネントからstoreを利用できるようにする
  render: h => h(App)
}).$mount('#app')

store.js

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

Vue.use(Vuex) //vuexが読み込まれている

// storeをエクスポート
export default new Vuex.Store({ //ここに実装を書く
  state: {

  },
  mutations: {

  },
  actions: {

  }
})

store.jsでは「state」「mutations」「actions」というものがあり、ここで状態管理をまとめて行っています。

【state】
Vueインスタンスでいう「data」オプションのようなもので、初期値としての文字列や配列・ブーリアン値などの値が入ります。

【mutations】
stateの値を更新します。stateの値を更新できるのはmutationsからのみとなります。commit関数で呼び出されます。

【actions】
登録されたmutationsを実行します。commit関数の引数で実行するmutationsを指定します。コンポーネントからはdispatch関数で実行できます。

このようにvuexでは、「actions」で「mutations」の関数を指定し「state」の値を更新、最後にコンポーネントのDOMが書き換わるというイメージになります。
store.jsの内容を表示する為にApp.vueを書き換えていくことになります。

App.vue

App.vue
<template>
  <div>
    <!-- ここに実装を書く -->
  </div>
</template>

<script>
export default {
  // ここに実装を書く
}
</script>

実装例

例として、todoリストのタスク表示のサンプルコードを載せます。
store.jsのstateにタスク内容、それをApp.vueで表示、という流れがわかるかと思います。

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

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    tasks: [
      {
        id: 1,
        name: '牛乳を買う',
        done: false
      },
      {
        id: 2,
        name: 'vueの本を買う',
        done: true
      }
    ],
  },
})
App.vue
<template>
  <div>
    <h2>タスク一覧</h2>
    <ul>
      <li v-for="task in tasks" v-bind:key="task.id">
        <input type="checkbox" v-bind:checked="task.done">
        {{ task.name }}
      </li>
    </ul>
  </div>
</template>

<script>
 export default {
  name: 'App',
  computed: {
    tasks () { 
      return this.$store.state.tasks //storeを読む
    },
  },
 }
</script>

長くなってしまうのでここでは画面にstate内容を表示するだけですが、例えば新しくタスク追加する機能を実装する処理をmutationsに宣言しておき、それをApp.vueで動かす処理を書く、といった流れになっていきます。


今回は以上になります。
始めはvuexは大きく漠然としたもののように感じるかもしれませんが、細分化し始めの一歩はどこになるのかを考え紐解いていくことで、少しずつ仕組みが理解できると思います。

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

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

Vue.js チュートリアル for Rails エンジニア

はじめに

この記事は、普段 rails を使用して開発を行なっているエンジニアが、 Vue.js を触り始めようとする時に見たら役にたつかもしれないものです。

チュートリアルの内容

チュートリアルは、以下の2本立てで行うことで、 Vue.js に触れたことがない Rails エンジニアでも理解しやすい形にしています。すでに Vue.js に触れたことがある場合は、1を飛ばして、2から始めるのが良いかと思います。

  1. 簡単な Vue.js アプリケーションの開発
  2. Webpacker を使用した簡単な Rails + Vue.js アプリケーションの開発

使用するバージョン

  • ruby: 2.6.2
  • rails: 5.2.3
  • webpacker: 4.0.7
  • yarn: 1.16.0

1. 簡単な Vue.js アプリケーションの開発

を書こうと思ったら、すでにめちゃくちゃいいチュートリアル記事が既にあったので、そちらのリンクを掲載させていただきます。(タイトル詐欺)
Vue.js を vue-cli を使ってシンプルにはじめてみる

2. Webpacker を使用した簡単な Rails + Vue.js アプリケーションの開発

1 がだいたい終わって、 Vue.js のことをだいたい理解していることが前提です。
サンプルコードはこちら で公開しています。

環境構築

環境構築には、 homebrew , rbenv を使用します。インストールがまだな方は、各自インストールをお願いします。

rubyのバージョン設定

$ brew update && brew upgrade ruby-build
$ rbenv install 2.6.2

アプリケーションを作成するディレクトリに、 cd コマンドで遷移し、下記を実行してください。

$ rbenv local 2.6.2
$ ruby -v 
ruby 2.6.2p47 (2019-03-13 revision 67232) [x86_64-darwin18]

yarn のインストール

$ brew install yarn

インストールの確認(バージョンは下記のもの以上であれば基本問題ないと思います。)

$ yarn -v
1.16.0

Vue.js devtools のインストール

インストールがまだな場合は、下記のページを参考にインストールしておきましょう。
Vue.js Devtoolsの導入方法と機能まとめ。Vue.jsを用いた開発を効率化させよう!

プロジェクトの作成

下記コマンドを実行して、プロジェクトを作成します。

$ rails new rails-vue-app --webpack=vue --skip-turbolinks --skip-coffee

--skip-turbolinks --skip-coffee のオプションをつけて、今回は不要となるものを削ぎ落としています。 1

実行結果のログを眺めていると、 gem のインストールの後に、 rails webpacker:install から始まる webpacker 関連のインストールが行われていることがわかります。

rails  webpacker:install
RAILS_ENV=development environment is not defined in config/webpacker.yml, falling back to production environment

webpacker のインストールが完了すると、普段の rails プロジェクトでは見られない、 app/javascript というディレクトリが、自動で生成されているかと思います。

$ cd rails-vue-app/app/javascript
$ ls
  app.vue packs

※ 今回の実装では、新規に Rails + Vue.js のプロジェクトを作ることを想定していますが、もちろん既存の Rails プロジェクトに途中から Vue.js を導入することもできます。2

それでは、これから rails アプリケーションの中身の実装を進めていこうと思います。

アプリケーションの実装

一番シンプルな実装

まずは、 rails 上で vue.js を動かす一番シンプルな実装をしていきます。
cd コマンドなどで先ほど作成したディレクトリに移動し、まずはコントローラーを作成します。
※ 今後 [] はコマンドを実行するディレクトリを表します。

[rails-vue-app] $ rails g controller HelloVue index --no-helper --no-assets
      create  app/controllers/hello_vue_controller.rb
       route  get 'hello_vue/index'
      invoke  erb
      create    app/views/hello_vue
      create    app/views/hello_vue/index.html.erb
      invoke  test_unit
      create    test/controllers/hello_vue_controller_test.rb

※ 今回は、ヘルパーやアセットファイルなど不要なものを生成しないオプションを指定しています。 3

一旦サーバーを起動して、画面が表示するかみて見ましょう。

[rails-vue-app] $ rails s

http://localhost:3000/hello_vue/index にアクセスして、下記のページが表示されると ok です。

javascript_pack_tag の設定

javascript_pack_tagapp/javascript/packs 配下にあるファイルを読み込むことができます。 (この辺は webpackerの仕様で決まっています)
今回は、読み込む対象として hello_vue.js を指定してみます。 hello_vue.js は webpacker のインストール時に自動生成されているファイルです。

app/views/hello_vue/index.html.erb
  <h1>HelloVue#index</h1>
  <p>Find me in app/views/hello_vue/index.html.erb</p>
+ <%= javascript_pack_tag 'hello_vue.js' %>

hello_vue.js の中身は、下記のような感じになっており、同じく初期作成された app.vue ファイルを描画するようになっています。

app/javascript/packs/hello_vue.js
import Vue from 'vue'
import App from '../app.vue'

document.addEventListener('DOMContentLoaded', () => {
  const app = new Vue({
    render: h => h(App)
  }).$mount()
  document.body.appendChild(app.$el)

  console.log(app)
})
app/javascript/app.vue
  <div id="app">
    <p>{{ message }}</p>
  </div>
</template>

<script>
export default {
  data: function () {
    return {
      message: "Hello Vue!"
    }
  }
}
</script>

<style scoped>
p {
  font-size: 2em;
  text-align: center;
}
</style>

これらのファイルがどう言う役割をしているのかが曖昧な場合は、もう一度 簡単な Vue.js アプリケーションの開発 の項を見てみましょう。

webpacker を使用した webpack のビルド

下記のコマンドを実行することで、 app/javascript 配下の js ファイルをビルドすることができます。

[rails-vue-app] $ bin/webpack

このコマンドは、内部的には下記のコマンドを実行しているのにほぼ等しいらしいです。(知らなかった)

[rails-vue-app] ./node_modules/.bin/webpack --config config/webpack/development.js

Webpacker使うなら最低限これだけは知っておいてほしいこと から抜粋させてもらいました。

再度 http://localhost:3000/hello_vue/index にアクセスしてみると、下記のような画面になっているかと思います。

image.png

環境構築で、 Vue.js devtools のインストールが完了している場合、 chrome の拡張機能の部分に、 V のマークが色付きで出てきているはずです。
image.png

この時点で、一番の基本となる rails + Vue.js のアプリケーション実装が完了しました。:tada:

webpack の自動ビルド

先ほどは、 bin/webpack のビルドを行いましたが、これでは js ファイルを変更するたびに再度コマンドを叩いてビルドをする必要があります。
流石にそれは面倒なので、開発中は bin/webpack の代わりに下記コマンドを実行して、ファイルを保存するたびに自動ビルドが走るようにしておくといいでしょう。

[rails-vue-app] $ bin/webpack-dev-server

Vue.js devtools

せっかくなので Vue.js devtools の使い方をここで確認しておきます。
使い方は簡単で、 chrome の developer ツールを開いて、 Vue のタブを選択するだけです。

Vue のタブを選択し、コンポーネントを選択してみると、内部の data などを確認することができます。

実践的な実装

先ほど実装はただ単に、作成した vue ファイルの描画を行っただけで、 controller などからのデータの受け渡しを行なっていませんでした。そこで、ここからはその点を深掘っていきます。

webpacker を使用した、 rails + vue のアプリケーションを作成する際、データの渡し方には色々あるらしいですが、今回は2通りのデータの受け渡し方でサンプルアプリケーションを作っていきます(個人的にはこの2つの渡し方は、データフローがわかりやすい)。

  • HTML のデータ属性に値を設定して渡す方法
  • API を使用して渡す方法

前者の方法については、下記の記事を参考にさせていただきました。
メンテ不能になったフロントエンド環境を立て直す話

HTML のデータ属性に値を設定して渡す方法

まずはページを表示するためのコントローラーを、 HomeController という名前で作成します。

$ cd rails-vue-app
[rails-vue-app] $ rails g controller Home index --no-helper --no-assets
      create  app/controllers/home_controller.rb
       route  get 'home/index'
      invoke  erb
       exist    app/views/home
      create    app/views/home/index.html.erb
      invoke  test_unit
      create    test/controllers/home_controller_test.rb

次にコントローラー内部の実装を進めていきます。 index メソッドのインスタンス変数として、 title description そして Hash 形式の contents を用意します。

app/controllers/home_controller.rb
class HomeController < ApplicationController
  def index
+   @title = 'Home#index'
+   @description = 'トップページ'
+   @contents = get_contents
  end
+
+ private
+
+ def get_contents
+   {
+     outer_links: [
+       {
+         name: 'Qiitaページ',
+         text: 'Qiita',
+         url: 'https://qiita.com/t0yohei/items/d516fefaaad69b4022ec'
+       },
+       {
+         name: 'ソースコード',
+         text: 'GitHub',
+         url: 'https://github.com/t0yohei/rails-vue-app'
+       }
+     ],
+   }
+ end
end

続いて view の実装です。ここでポイントとなるのが、 rails の content_tag ヘルパーを利用して、 div タグの data 属性に vue 側に受け渡したいデータ設定している点です。 vue に受け渡すデータは json 形式に変換しておきます。

app/views/home/index.html.erb
- <h1>Home#index</h1>
- <p>Find me in app/views/home/index.html.erb</p>
+ <%= javascript_pack_tag 'home/index.js' %>
+ <%= content_tag :div,
+   id: "homeIndex",
+   data: {
+     title: @title,
+     description: @description,
+     contents: @contents
+   }.to_json do %>
+ <% end %>

生成される html は下記画像のようになります。
image.png

data 属性に設定した情報は、 developer tool などを使うことで閲覧することができるので、 API 同様にユーザーのプライペート情報など、秘匿情報は公開しないようにして注意してください。

表示するコンポーネントの実装

先ほど view で設定したデータを、 js 側から読み取ってみましょう。 app/javascript/packs/home/index.js を下記の通り実装します。

app/javascript/packs/home/index.js
import Vue from "vue";

document.addEventListener("DOMContentLoaded", () => {
  const node = document.getElementById("homeIndex");
  const props = JSON.parse(node.getAttribute("data"));
  console.log(props);
});

http://localhost:3000/home/index にアクセスして、 developer tool の console を開けてみると、
image.png

このように、 rails の view ファイルで設定したデータが、JS の Object 形式で取得できていることがわかります。
この Object データを使用して、実装を進めていきましょう。

home/index.js では、 home/Index というコンポーネントを render することを想定して実装を進めていきます。 render 関数の第二引数に、先ほど取得した Object のデータを設定します。

app/javascript/packs/home/index.js
 import Vue from "vue";
+import Index from "../../components/home/Index.vue";

 document.addEventListener("DOMContentLoaded", () => {
   const node = document.getElementById("homeIndex");
   const props = JSON.parse(node.getAttribute("data"));
+  const app = new Vue({
+    render: h => h(Index, { props })
+  }).$mount();
+  document.body.appendChild(app.$el);
-  console.log(props);
 });

render 対象の、 home/Index コンポーネントでは、受け取る props を定義しておきます。これで home/index.js からデータを受け取って、コンポーネント内で参照することができます。

app/javascript/components/home/Index.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
    <table class="contents-table">
      <tr>
        <th>名前</th>
        <th>リンク</th>
      </tr>
      <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name">
        <td>{{ outer_link.name }}</td>
        <td>
          <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
        </td>
      </tr>
    </table>
  </div>
</template>

<style scoped>
.contents-table {
  border: 1px solid gray;
  margin: 10px;
}
.contents-table th,
.contents-table td {
  border: 1px solid gray;
}
</style>

<script>
export default {
  props: {
    title: {
      type: String,
      default: () => ""
    },
    description: {
      type: String,
      default: () => ""
    },
    contents: {
      type: Object,
      default: () => {}
    }
  }
};
</script>

ページの表示

app/javascript/packs 配下に追加した js ファイルを読み込ませるためには、 webpack-dev-server の再起動が必要です。 webpack-dev-server を実行中の場合は一度止めて、再度下記コマンドを実行しましょう。

[rails-vue-app] $ bin/webpack-dev-server

http://localhost:3000/home/index にアクセスした時に、下記のようなページが表示されていると成功です。

実装のリファクタリング

とりあえず表示させることを優先で、 Index.vue に全てを書いていたので、簡単にコンポーネントの分割をしていきます。ざっとこんな感じのイメージで分割していきます。
image.png

HeaderView.vue の作成

app/javascript/components/HeaderView.vue
<template>
  <div>
    <h1>{{ title }}</h1>
    <p>{{ description }}</p>
  </div>
</template>
<style></style>
<script>
export default {
  props: {
    title: {
      type: String,
      default: () => ""
    },
    description: {
      type: String,
      default: () => ""
    }
  }
};
</script>

Contents.vue の作成

app/javascript/components/home/Contents.vue
<template>
  <div>
    <table class="contents-table">
      <tr>
        <th>名前</th>
        <th>リンク</th>
      </tr>
      <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name">
        <td>{{ outer_link.name }}</td>
        <td>
          <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
        </td>
      </tr>
    </table>
  </div>
</template>
<style scoped></style>
<script>
export default {
  props: {
    contents: {
      type: Object,
      default: () => {}
    }
  }
};
</script>

Index.vue の修正

app/javascript/components/home/Index.vue
 <template>
   <div>
-    <h1>{{ title }}</h1>
-    <p>{{ description }}</p>
-    <table class="contents-table">
-      <tr>
-        <th>名前</th>
-        <th>リンク</th>
-      </tr>
-      <tr v-for="outer_link in contents.outer_links" v-bind:key="outer_link.name">
-        <td>{{ outer_link.name }}</td>
-        <td>
-          <a v-bind:href="outer_link.url">{{ outer_link.text }}</a>
-        </td>
-      </tr>
-    </table>
+    <header-view v-bind:title="title" v-bind:description="description"></header-view>
+    <contents v-bind:contents="contents"></contents>
   </div>
 </template>

-<style scoped>
-.contents-table {
-  border: 1px solid gray;
-  margin: 10px;
-}
-.contents-table th,
-.contents-table td {
-  border: 1px solid gray;
-}
-</style>
+<style scoped></style>

 <script>
+import HeaderView from "../HeaderView.vue";
+import Contents from "./Contents.vue";
+
 export default {
+  components: {
+    "header-view": HeaderView,
+    contents: Contents
+  },
  props: {
    title: {
      type: String,
      default: () => ""
    },
    description: {
      type: String,
      default: () => ""
    },
    contents: {
      type: Object,
      default: () => {}
    }
  }
};
</script>

だいぶスッキリしましたね。リファクタリングは一旦こんな感じで終わりましょうか。
念の為、再度 http://localhost:3000/home/index にアクセスして、画面がちゃんと表示されることを確認しておきましょう。

API を使用して渡す方法での実装

次に、API を使用して渡す方法での実装を進めていきましょう。
今回は特に理由もないんですが、整数リテラルの分類表を作成してみます。
手順としては、

  • APIを叩いて取得したデータを受け取るコンポーネントの実装
  • APIエンドポイントの実装
  • コンポーネントから API を叩いてデータを取得
  • 取得したデータをコンポーネント内で描画

という順番で進めていきます。

APIを叩いて取得したデータを受け取るコンポーネントの実装

まずはページを表示するためのコントローラーを作成します。

$ cd rails-vue-app
[rails-vue-app] $ rails g controller IntegerLiteralDescriptions index --no-helper --no-assets
      create  app/controllers/integer_literal_descriptions_controller.rb
       route  get 'integer_literal_descriptions/index'
      invoke  erb
      create    app/views/integer_literal_descriptions
      create    app/views/integer_literal_descriptions/index.html.erb
      invoke  test_unit
      create    test/controllers/integer_literal_descriptions_controller_test.rb

コントローラーの実装

今回は何もしないです。

View の実装

javascript_pack_tag を設定します。

app/views/integer_literal_descriptions/index.html.erb
- <h1>IntegerLiteralDescriptions#index</h1>
- <p>Find me in app/views/integer_literal_descriptions/index.html.erb</p>
+ <%= javascript_pack_tag 'integerLiteralDescriptions/index.js' %>

表示するコンポーネントの実装

integerLiteralDescriptions/index.js の実装

app/javascript/packs/integerLiteralDescriptions/index.js
import Vue from "vue";
import Index from "../../components/integerLiteralDescriptions/Index.vue";

document.addEventListener("DOMContentLoaded", () => {
  const app = new Vue({
    render: h => h(Index)
  }).$mount();
  document.body.appendChild(app.$el);
});

コンポーネントの実装

app/javascript/components/integerLiteralDescriptions/Index.vue
<template>
  <div>
    <header-view v-bind:title="title" v-bind:description="description"></header-view>
    <contents v-bind:contents="contents"></contents>
  </div>
</template>

<script>
import HeaderView from "../HeaderView.vue";
import Contents from "./Contents.vue";

export default {
  components: {
    "header-view": HeaderView,
    contents: Contents
  },
  data: function() {
    return {
      title: "title",
      description: "description",
      contents: []
    };
  }
};
</script>

<style scoped>
</style>
app/javascript/components/integerLiteralDescriptions/Contents.vue
<template>
  <div>
    <table class="contents">
      <tr>
        <th>名前</th>
        <th>英語訳</th>
        <th>表記例</th>
        <th>用途</th>
      </tr>
      <tr v-for="content in contents" v-bind:key="content.name">
        <td>{{ content.name }}</td>
        <td>{{ content.english }}</td>
        <td>{{ content.sample }}</td>
        <td>{{ content.usage }}</td>
      </tr>
    </table>
  </div>
</template>
<style scoped>
.contents {
  border: 1px solid gray;
}
.contents th,
.contents td {
  border: 1px solid gray;
}
</style>
<script>
export default {
  props: {
    contents: Array
  }
};
</script>

再び bin/webpack-dev-server を実行し直し、 http://localhost:3000/integer_literal_descriptions/index にアクセスすると、下記画像のようなページが表示されるかと思います。

これで API を叩いて取得したデータを受け取り、表示するためのコンポーネントが完成しました。

次は、先ほど作成したコンポーネントに、データを渡す処理を実装していこうと思います。

APIエンドポイントの実装

[rails-vue-app] rails g controller api/v1/integer_literal_descriptions index --no-helper --no-assets --no-view-specs
      create  app/controllers/api/v1/integer_literal_descriptions_controller.rb
       route  namespace :api do
  namespace :v1 do
    get 'integer_literal_descriptions/index'
  end
end
      invoke  erb
      create    app/views/api/v1/integer_literal_descriptions
      create    app/views/api/v1/integer_literal_descriptions/index.html.erb
      invoke  test_unit
      create    test/controllers/api/v1/integer_literal_descriptions_controller_test.rb

コントローラーの実装

app/controllers/api/v1/integer_literal_descriptions_controller.rb
 class Api::V1::IntegerLiteralDescriptionsController < ApplicationController
   def index
+    title = 'IntegerLiteralDescriptions#index'
+    description = '整数リテラルの分類表'
+    contents = get_integer_literals
+    result_values = {
+      title: title,
+      description: description,
+      contents: contents
+    }
+    render json: result_values
+    # https://jsprimer.net/basic/data-type/#integer-literal
   end
-end
+
+  private
+
+  def get_integer_literals
+    [
+      {
+        name: '10進数',
+        english: 'decimal',
+        sample: '42',
+        usage: '数値'
+      },
+      {
+        name: '2進数',
+        english: 'binary digits',
+        sample: '0b0001',
+        usage: 'ビット演算など'
+      },
+      {
+        name: '8進数',
+        english: 'octal',
+        sample: '0o777',
+        usage: 'ファイルのパーミッションなど'
+      },
+      {
+        name: '16進数',
+        english: 'hexadecimal, hex',
+        sample: '0xEEFF',
+        usage: '文字のコードポイント、RGB値など'
+      }
+    ]
+  end
+end

アクセスの確認

この状態で、 http://localhost:3000/api/v1/integer_literal_descriptions/index にアクセスしてみると、下記のような画面が表示されるはずです。
 
image.png

一旦これで、 API のエンドポイントが完成しました。

コンポーネントから API を叩いてデータを取得

コンポーネントと API の Ajax 通信は axios というライブラリを使用して行います。4
まずは axios をインストールします。

[rails-vue-app] yarn add --dev axios

install に成功していると、 package.json axios の項目が追記されているはずです。

package.json
   "devDependencies": {
+    "axios": "^0.19.0",
     "webpack-dev-server": "^3.7.2"
   }

次に integerLiteralDescriptions/Index.vue を書き換えて、 axios でのデータ取得を実装します。

app/javascript/components/integerLiteralDescriptions/Index.vue
 import Contents from "./Contents.vue";
+import Axios from "axios";

 export default {
   components: {
     "header-view": HeaderView,
     contents: Contents
   },
   data: function() {
     return {
       title: "title",
       description: "description",
       contents: []
     };
+  },
+
+  created: function() {
+    this.updateContents();
+  },
+
+  methods: {
+    updateContents() {
+      Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
+        response => {
+          const responseData = response.data;
+          console.log(responseData);
+        }
+      );
+    }
   }

created で vue コンポーネントが作成されたタイミングで axios によるデータ取得を走らせるようにしています。
console.log で取得したデータを表示するようにしているので、 http://localhost:3000/integer_literal_descriptions/index を見てみましょう。
↓のようなログが出て、データ取得ができているはずです。
image.png

Tips: JS のデバッグ

ご存知な方も多いと思いますが、 JS では debugger を仕込むことで、デバッグ実行が可能になります。
debugger ステートメント | MDN

具体的には下記のように仕込むことができます。

app/javascript/components/integerLiteralDescriptions/Index.vue
   Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
     response => {
       const responseData = response.data;
-      console.log(responseData);
+      debugger;
     }
   );

開発者ツールを開きながら、再度先ほどの http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、
image.png

debugger を仕込んだ部分で処理が止まり、 Console からその時点の各種データを覗くことができます。(↑画像の場合、画像下部で responseData の値を確認しています。)

取得したデータをコンポーネント内で描画

先ほど axios で取得したデータを、画面に反映させます。
今回の場合、下記の部分を書き換えるだけです。

app/javascript/components/integerLiteralDescriptions/Index.vue
   Axios.get("/api/v1/integer_literal_descriptions/index.json").then(
     response => {
       const responseData = response.data;
-      console.log(responseData);
+      this.title = responseData.title;
+      this.description = responseData.description;
+      this.contents = responseData.contents;
     }
   );

http://localhost:3000/integer_literal_descriptions/index にアクセスしてみると、整数リテラル分類表が出てくるはず!

整数リテラル分類表

JS では 0 で始まる数値の直後に b, o, x をつけると、それぞれ2進数、8進数、16進数が表現できるみたいですね。 b, o, x は 英語訳を見てみると、それぞれ binary digits, octal, hex となっており、なるほどなーとなるんじゃないでしょうか。今回整数リテラル分類表をサンプルに組み込んだ理由は特にないです。気まぐれです。

最後にちょろちょろ

せっかくなので、 http://localhost:3000/home/index から http://localhost:3000/integer_literal_descriptions/index に飛べるように、リンクボタンを追加しておきましょう。

app/controllers/home_controller.rb
           url: 'https://github.com/t0yohei/rails-vue-app'
         }
       ],
+      inner_links: [{
+        label: '整数リテラル分類表',
+        url: url_for(action: 'index', controller: 'integer_literal_descriptions')
+      }]
     }
app/javascript/components/home/Contents.vue
         </td>
       </tr>
     </table>
+    <div v-for="inner_link in contents.inner_links" v-bind:key="inner_link.label">
+      <button v-on:click="changeLocation(inner_link.url)" class="btn-push">{{ inner_link.label }}</button>
+    </div>
   </div>
 </template>
 <style scoped>
@@ -23,6 +26,23 @@
 .contents-table td {
   border: 1px solid gray;
 }
+.btn-push {
+  margin: 10px;
+  max-width: 180px;
+  text-align: left;
+  background-color: rgb(24, 174, 238);
+  font-size: 14px;
+  color: #fff;
+  text-decoration: none;
+  font-weight: bold;
+  padding: 10px 24px;
+  border-radius: 4px;
+  border-bottom: 4px solid rgb(24, 174, 238);
+}
+.btn-push:active {
+  transform: translateY(4px);
+  border-bottom: none;
+}
 </style>
 <script>
 export default {
@@ -31,6 +51,11 @@ export default {
       type: Object,
       default: () => {}
     }
+  },
+  methods: {
+    changeLocation(url) {
+      window.location.href = url;
+    }
   }
 };
 </script>

http://localhost:3000/home/index を開いて、こんな感じのボタンができていたら成功です。

最後に

今回のチュートリアルはここで終了です。 会社の人が Vue.js を触り始める時に使ってもらえたらなーと思いこのチュートリアルの作成を計画したのですが、せっかくなので Qiita に投稿してみることにしました。どこかのエンジニアの役に立つと幸いです。


  1. 今回の実装では CoffeeScriptturbolinks のセットアップを省いています。既存の rails プロジェクトで Vue.js を使用する場合は、 turbolinks と戦う必要がありそうです。 

  2. もし既存の rails プロジェクトに途中から導入する場合は、右記をご参考ください。rails/webpacker#installation 

  3. オプションの詳細は、 こちらrails g controller -h コマンドで確認できます。 

  4. axios を利用した API の使用 

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

【Laravel】Vue.jsでFile APIを使って画像プレビューを行なう方法

画像プレビューを実装

今回のゴールはこちら。

プレビュー.mov.gif

File API

File APIについて
ローカルで選択した File情報を取得できます。

ただし、File APIを使わなくても以下の項目は普通に取得できます。
更新日時
ファイル名
データサイズ(バイト数)
MIMEタイプ(ファイルの種類)

コード

sample.php
    <div id="imgview">
        <img-view></img-view>
    </div>

使用するVueをコンポーネントとして登録。

app.js
Vue.component('img-upload', require('./components/ImgView.vue').default);
ImgView.vue
<template>
  <label for="file-sample">
     <div class="drop">
        <input class="input" id="file-sample" type="file" name="user_img" @change="onFileChange">
        <i aria-hidden="true" class="fas fa-plus fa-7x"></i>
        <img class="img" id="file-preview" v-show="uploadedImage" :src="uploadedImage">
     </div>
  </label>
</template>

<script>
export default {
    data() {
         return {
             uploadedImage: "",
         };
    },
    methods: {
        onFileChange(e) {
            let files = e.target.files;
            this.createImage(files[0]); //File情報格納
        },
        //アップロードした画像を表示
        createImage(file) {
            let reader = new FileReader(); //File API生成
            reader.onload = (e) => {
                this.uploadedImage = e.target.result;
            };

            reader.readAsDataURL(file);
        },
    },
 }
</script>

inputタグに@changeを、imgタグにv-showを使います。
v-showに設定しているuploadedImageはimgタグのsrcにも設定されていて、uploadedImageにFile情報が入ると同時にsrcにもソースとしてFile情報が格納され表示されると言う仕組みです。

なので最初の時点では何も入っておらず、v-showはfalseになるのでdisplay: none;が適用されています。

methods

onFileChangeでは選択されたFile情報を変数に格納。

files[0]を引数にcreateImage()メソッドを呼び出します。

ここでは、FileReaderときうオブジェクトを生成します。
onloadは正常にファイルを読み込んだときに発生するイベントです。
この中でuploadedImageに event.target.resultのデータを格納。

event.target.resultには画像データをテキストデータにしたものが入っています。
※これをData URIをスキーム化させると言うみたいです。

最後にreadAsDataURL()で画像の読み込みを実行します。

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

Vue, Nuxtで使いそうな CSS フレームワークをまとめてみた

はじめに

今後、新しいプロジェクトで、Vue,Nuxtを使いそうなので、調べたところ今時のフロントエンドはいい感じのライブラリーがたくさんあることを知りました。
どれが、どのようにいいとかが正直わからかったので、とりあえず公式サイトまとめを作ってみました。

対象読者

  • Vue(Nuxt)を始めるよって人
  • フレームワークたくさんあるので、ドキュメントをまとめたものをみたいって人

フレームワークたち

Boostrap

言わずと知れた。
その昔、サイトのデザインを見ればこれはBootsrapでできているってわかったなぁ。

公式サイト

Bulma

Flex Box レスポンシブ対応
公式サイト

Tachyins

軽量cssフレームワーク
公式サイト

参考記事
手軽にコーディングするならやっぱりBootstrapよりTACHYONSだなぁ

Element UI

デスクトップ用のフレームワーク(レスポンシブではない)

公式サイト

iView

中華製のフレームワーク
Element UI と似ている
公式サイト

参考記事
Vue.jsのUIデザインフレームワークにiViewを導入する
Who's using iView

vuetify

日本語記事が多い印象

公式サイト
参考記事
Vueでもっと幸せになりたいあなたへ。VueのUIコンポーネントライブラリVuetifyのススメ

Buefy

Bulma をベースにした、vue用のフレームワークらしい
Bulmaと何が違うのかな?軽量なのかな?
公式サイト

Ant desing

管理画面用のフレームワーク
https://ant.design/docs/spec/introduce

Tailwind CSS

UIをコンポーネントで提供するのではなく、classで提供している
いい感じのを作って欲しいという、ふんわりとした要件に使いやすいかも

参考サイト
Nuxt.js + Tailwind CSS で爆速コーポレートサイト作成

終わりに

パッとみ、簡素な記事になってしまった。。。
もっとUIを考えたいと思った記事でした。

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

Nuxtでaxiosのエラーハンドリングをグローバルに行う

Nuxtを書いていて、401、500エラーのような対応処理が同じエラーに対しては、プラグインファイルの中で一括で行いたかったので、その方法を調べました。

onErrorヘルパーを使う

例えば認証にJWTを使ってajax通信を行う際、プラグインファイルを作って、interceptor的にresponseヘッダにjwtトークンを埋め込む処理を書くと思います。こんな感じ。

plugins/axios.js
export default ({ $axios }) => {
  $axios.onRequest(config => {
    const accountToken = localStorage.getItem('accountToken')
    if (accountToken) {
      config.headers.common['x-access-token'] = accountToken
    }
  })
}

エラーハンドリングをグローバルに行いたい場合、onErrorヘルパーを使います。

// $axios.onRequest(){...}

$axios.onError(error => {
  if (error.response.status === 401) {
    // 401エラーの場合の処理を書く
  }
})

他にもヘルパーはいくつかあるので、気になる人はaxios公式ドキュメントを読みましょう。
Axios Modules

404エラーのハンドリング

404エラーのハンドリングはaxios側ではできません。(ajax通信が関係ないので当たり前ですが...)

404エラーに関してはrouterに任せましょう。Nuxtの場合は404エラーのハンドリングはnuxt.config.jsに記述できます。

router: {
  extendRoutes(routes, resolve) {
    routes.push({
      path: '*',
      component: resolve(__dirname, 'pages/404.vue')
    })
  }
}

こちらの記述は、「Nuxt.jsで存在しないURLにアクセスされた時に404ページ(任意のページ)を表示する方法」を参考にさせてもらいました。

これにより、ルーティングに存在しないURLにアクセスされた場合に/404に遷移させることができます。あとは404.vueをpages/配下に置けばOKです。

まとめ

Nuxtはこういった処理が簡単にできるので便利ですね。

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

ふとしたキッカケで Svelte を使ってみた

最近、ふとしたキッカケで JSフレームワークである Svelte を使ってみたので簡単に感想をメモっておく。

結論

結論だけいうと、コンパイラなので依存が実質的に何もないし、実際に軽くて早くてコード量が少ないのは事実なので、使い慣れてきたらかなり良さそう。

ただし複数人でコードをいじる場合には、フレームワーク自体のルールがほとんどないので、チーム内での統一ルールなどが無いと扱いにくいかも、特に store 周り

Svelteの特徴

  • JSコンパイラで Vanilla JS としてコンパイル結果を出力する
  • 海外ではそれなりに知名度がある
  • vuejs に影響を受けているので書き方や概念が近い
  • 軽量かつ高速
  • ボイラーブレートコードを忌み嫌っている
  • コード量を出来るだけ少なく済むようにしている
  • SSRが可能だが試してはいない
  • Legacyモードでコンパイルすることにより IE9 対応可能
  • store の state を直接バインディングできる
  • テンプレート側で Promise の状態を見て分岐ができる
こんな感じ
{#await promise}
  <p>pendingだよ</p>
{:then hoge}
  <p>resolveした</p>
{:catch}
  <p>rejectした</p>
{/await}

私見

  • Riot をブラッシュアップした感じ
  • コンポーネントは vuejs の SFC と感覚としては同様
  • 軽量かつ高速なのは体感できるレベルでわかる
  • store はかなり使い勝手が違う
  • state と props に相当するものがあるが、React や vuejs とは違うのであんまり気にしない方がいい
  • React や vuejs ですら「重い」という環境で採用されている模様
  • router はないのだが、sapper という svelte を利用したフレームワークの方にはついてる

メリット

  • 依存が無いので環境の立ち上げが楽
  • コード量は少ない
  • store がビルトインされている
  • 学習コストは低い
  • コンパイラなのである程度のエラーは事前に検知できる
  • 本体リポジトリは議論や更新が活発

デメリット

  • 日本語のドキュメントがない
  • 日本のユーザーグループなどもない
  • したがって講習会や勉強会などもない
  • というか日本語の情報は基本的に無い
  • react や vuejs のような便利な devtool がない

よくわかっていない点

  • store の set/update で state がまるごと更新される(詳細は下記)
  • vuex の getter にあたる deriverd というものがある
  • store の deriverd 内では非同期処理は扱えない(多分)
store.js
/* store はこんな感じ */
export const hogeStore = writable(0);

参照するときは $hogeStore 

/* カスタムするとこんな感じ */
const router = () => {
  const { subscribe, set } = writable('index')
  return {
    subscribe,
    index: () => set('index'),
    detail: () => set('detail'),
  }
}

 $router  index が返ってくる、router.detail() で更新、async でも書ける

/* 複数の値を持ちたい場合 */
export const hogeStore = writable({ hoge: '',  fuga: '' });

$hogeStore.hoge で参照
$hogeStore.hoge = 'hogehoge' で普通に更新される

hogeStore.set とかすると $hogeStore が丸ごと更新される
hogeStore.hoge.set() とかそういうことはできないっぽい
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む