- 投稿日:2021-01-13T21:44:32+09:00
Rails APIモード + devise_token_auth + Vue.js 3 で認証機能付きのSPAを作る(Vue.js編 その1)
はじめに
本記事はAPIをRailsのAPIモードで開発し、フロント側をVue.js 3で開発して、認証基盤にdevise_token_authを用いてトークンベースの認証機能付きのSPAを作るチュートリアルのVue.js編の記事(その1)になります。
Rails側のチュートリアルを終わらせてからこちらのチュートリアルに取り組まれることを推奨します。
Rails編はこちら
https://qiita.com/kk-icare/items/c4cd040824c7d6b434d6環境
Vue.js 3.0.5
Vue CLI 4.5.9
npm 6.14.8
node 14.15.0
TypeScript 3.9.7Vue.jsのボイラープレートを作る
インストール
まずはvue/cliをインストールします。
yarnでもOKですが、今回はnpmでインストールを行います。既にvue/cliをインストールされている方は、
$ vue --versionでVue CLIのバージョンを確認し、4.5.0よりバージョンが低い場合は、4.5.0以上にアップグレードを行うようにお願いします。
この時、既に作成されているVueアプリの開発に影響が出ることがあるので、
作業ディレクトリ内にのみ最新版のVue CLI を入れるようにしましょう。初めてVue CLIをインストールする場合
$ npm install -g @vue/cli4.5.0よりバージョンが低いVue CLIをインストールしたことがある方
$ mkdir 任意のディレクトリ $ cd 任意のディレクトリ 任意のディレクトリ $ npm install @vue/clivue createの実行
無事Vue CLIのインストールに成功したら、vue createコマンドを実行してボイラープレートを作成します。
プロジェクト名はなんでもいいですが、今回はspa-frontとします。$ vue create spa-front初期設定は以下のようにしました。(LintやCSS、パッケージ管理ツールの設定はお好みで)
? Please pick a preset: Manually select features ? Check the features needed for your project: Choose Vue version, Babel, TS, Router, CSS Pre-processors, Linter ? Choose a version of Vue.js that you want to start the project with 3.x (Preview) ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) ? Pick a linter / formatter config: Standard ? Pick additional lint features: Lint on save ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No ? Pick the package manager to use when installing dependencies: NPM
作成終了したら、以下のコマンドを実行しましょう。
$ cd sample-front $ npm run serve DONE Compiled successfully in 1831ms 12:34:08 App running at: - Local: http://localhost:8080/ - Network: http://192.168.11.9:8080/ Note that the development build is not optimized. To create a production build, run npm run build. Issues checking in progress... No issues found.http://localhost:8080にアクセスして、
以下のように表示されればボイラープレートの作成は成功です。axiosを用いてAPIを叩く準備
今回はPromiseベースのHTTPクライアントであるaxiosを用いてRailsで作ったAPIを叩きます。
https://github.com/axios/axiosインストールおよびコード作成
$ npm i axiosAPI関連のコードはsrc/apiにまとめます。
コンポーネント内に直接APIを叩くコードを書くと、コンポーネントの責務が増えてしまうため、
別ディレクトリにまとめるようにします。$ mkdir src/api $ touch src/api/client.tsclient.tsには以下のコードを記述してください。
import axios from 'axios' export default axios.create({ baseURL: process.env.VUE_APP_API_BASE })process.env.VUE_APP_API_BASEは、環境変数からデータを取得する記述方法です。
今回の場合、 http://localhost:3000 がベースのURLになります。今回は.envファイルで環境変数を管理します。
参考: https://qiita.com/yoshi0518/items/f8cd408f8ef86fb02d74以下のファイルをルートディレクトリに作成します。
$ touch .env.developmentそして、.env.developmentのファイルに以下を記述します。
VUE_APP_API_BASE=http://localhost:3000これで、「process.env.VUE_APP_API_BASE」が開発環境だと http://localhost:3000 を返すようになります。
試しにsrc/views/Home.vueでconsole.logを使って、process.env.VUE_APP_API_BASEの値を確認してみましょう。
<script lang="ts"> import { defineComponent } from 'vue' import HelloWorld from '@/components/HelloWorld.vue' // @ is an alias to /src export default defineComponent({ name: 'Home', components: { HelloWorld } }) console.log(process.env.VUE_APP_API_BASE) </script>chromeの検証ツールを開いて、consoleのタブを開くと、添付画像のようにURLが表示されているかと思います。
問題なく.env.developmentに設定した環境変数を取得できることが確認できました。
これで、他のファイルで以下のようにES6のimport構文で読み込むと、
import Client from '@/api/client'読み込んだファイル内で以下のようにAPIをコールできます。
Client.post('/auth_sign_in', {...})今回はaxiosをラップするように書きましたが、仮にaxiosではなく別の(kyとかjavascript標準のfetch APIとか)APIクライアントを使うことになっても、ラップしておけば修正範囲を最小限に留めることができます。
ログイン画面を作成する
簡単なログイン画面を構築してみます。
Login.vueの作成
src/views/Login.vueを作成します。
$ touch src/views/Login.vue<template> <div> <label for="email"> Email </label> <input v-model="email" id="Email" type="text" placeholder="Email"> </div> <div> <label for="password"> Password </label> <input v-model="password" id="password" type="password" placeholder="******************"> </div> <button @click="handleLogin()"> Sign In </button> </template> <script lang="ts"> import { defineComponent, reactive, toRefs } from 'vue' import { login } from '@/api/auth' export default defineComponent({ name: 'Login', setup () { const formData = reactive({ email: '', password: '' }) const handleLogin = () => { login(formData.email, formData.password) .then((data) => { console.log(data) }) } return { ...toRefs(formData), handleLogin } } }) </script>ルーティングの設定
src/router/index.tsに/loginのルーティングを定義します。
import Home from '@/views/Home.vue' // @記法に修正 import Login from '@/views/Login.vue' // 追加 const routes: Array<RouteRecordRaw> = [ { path: '/', name: 'Home', component: Home }, { // 追加 path: '/login', // 追加 name: 'Login', // 追加 component: Login // 追加 }, // 追加 { 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') } ]この状態で http://localhost:8080/login にアクセスし、添付画像のような表示がされていればOKです。
ログイン・ログアウトAPIを叩く準備
次はログイン・ログアウトのAPIを叩けるようにします。
認証tokenを管理する必要があるのですが、ここではLocalStorageにtokenを保存する実装を採用します。
LocalStorageに認証用のtokenを保存することの危険性については各所で指摘されていますが、
管理するデータの機微性に応じて、保存場所を決めるべきかと思います。LocalStorageにTokenを保存する
まずは認証tokenをLocalStorageに保存する実装を行います。
$ mkdir src/utils $ touch src/utils/auth-data.ts $ touch src/types/auth.ts// src/utils/auth-data.ts import { AuthHeaders } from '@/types/auth' export const getAuthDataFromStorage = (): AuthHeaders => { return { 'access-token': localStorage.getItem('access-token'), 'client': localStorage.getItem('client'), 'expiry': localStorage.getItem('expiry'), 'uid': localStorage.getItem('uid'), 'Content-Type': 'application/json' } } export const setAuthDataFromResponse = (authData: AuthHeaders): void => { if (authData['access-token'] && authData['client'] && authData['uid'] && authData['expiry']) { localStorage.setItem('access-token', authData['access-token']) localStorage.setItem('client', authData['client']) localStorage.setItem('uid', authData['uid']) localStorage.setItem('expiry', authData['expiry']) } } export const removeAuthDataFromStorage = (): void => { localStorage.removeItem('access-token') localStorage.removeItem('client') localStorage.removeItem('uid') localStorage.removeItem('expiry') }// src/types/auth.ts export type AuthHeaders = { 'access-token': string | null; 'uid': string | null; 'client': string | null; 'expiry': string | null; 'Content-Type': string; }上記の関数は
「LocalStorageからtokenを取得してオブジェクトとして返す」
「引数のtokenをLocalStorageに格納する」
「LocalStorageからtokenを削除する」を行っています。この各種関数をsrc/api/auth.tsで呼び出すようにします。
ログイン・ログアウトのAPIを叩く関数を実装
src/api/auth.tsファイルを作成し、以下のコードを記述します。
$ touch src/api/auth.ts// src/api/auth.ts import Client from '@/api/client' import { User } from '@/types/user' import { getAuthDataFromStorage, removeAuthDataFromStorage, setAuthDataFromResponse } from '@/utils/auth-data' export const login = async (email: string, password: string): Promise<User> => { return await Client.post('/auth/sign_in', {email, password}) .then((response) => { setAuthDataFromResponse(response.headers) return response.data }) } export const logout = async (): Promise<void> => { return await Client.delete('/auth/sign_out', { headers: getAuthDataFromStorage() }) .then(() => { removeAuthDataFromStorage() }) }返却される値はUser型として定義したいので、以下のファイルを作成します。
$ mkdir src/types $ touch src/types/user.ts// src/types/user.ts export type User = { allow_password_change: boolean; email: string; id: string; image: string | null; nickname: string; provider: string; uid: string; }ここまで実装できたら、画面からEmailとPasswordを入力して認証ができるかどうかをテストしてみましょう。
画面からログインの動作を確認
http://localhost:8080/login にアクセスして、検証ツールを開いて、Consoleにタブを合わせた状態でログインをしてみます。
ログインに成功すると、Consoleに添付画像のようなデータが表示されるかと思います。
この値が、login関数の返り値になっています。
ApplicationタブのLocal Storageの中身を見てみましょう。
setAuthDataFromResponse関数の呼び出しが正しく行われて、LocalStorageに認証に必要な情報が格納できています。
画面からログアウトの動作を確認
簡易的なログアウトボタンを実装してみます。
Home.vueを以下のように編集してください。<template> <button @click="handleLogout()">Logout</button> </template> <script lang='ts'> import { defineComponent } from 'vue' import { logout } from '@/api/auth' import router from '@/router' export default defineComponent({ name: 'Home', setup () { const handleLogout = () => { logout().then(() => { router.push('/about') }) } return { handleLogout } } }) </script>logout関数をimportし、buttonのclickイベントが発生した際にlogout関数を呼び出し、
aboutの画面に遷移するようにしています。ログアウトボタンを押した後、ApplicationタブのLocal Storageからaccess-token等が削除されれば、ログアウト成功です。
おわりに
その2へ続く予定です。
次回は「投稿機能」を実装していきます。
- 投稿日:2021-01-13T20:25:32+09:00
Vue.js でリストにフィルターをかける
初めに
公式ドキュメントにフィルターされた結果の表示の方法があった。ドキュメントでは算出プロパティを用いていたので、最近勉強したメソッドと
v-if
ディレクティブを使って同じ結果になることを確認してみた。
https://jp.vuejs.org/v2/guide/list.html<div id="app"> <li v-for="n in numbers">{{ n }}</li> </div>var app = new Vue({ el: '#app', data: { numbers: [ 1, 2, 3, 4, 5 ] } })
- 動作結果
算出プロパティでの結果
<div id="app"> <li v-for="n in evenNumbers">{{ n }}</li> </div>var app = new Vue({ el: '#app', data: { numbers: [ 1, 2, 3, 4, 5 ] }, computed: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } } })
- 動作結果
メソッドを用いる方法
こちらは算出プロパティとほとんど変わらない。ただ
v-for
の中に()を書くことをよく忘れてしまう。<div id="app"> <li v-for="n in evenNumbers()">{{ n }}</li> </div>var app = new Vue({ el: '#app', data: { numbers: [ 1, 2, 3, 4, 5 ] }, methods: { evenNumbers: function () { return this.numbers.filter(function (number) { return number % 2 === 0 }) } } })
- 動作結果
v-if を用いる方法
v-if
の中に条件式を書くだけで動いた。<div id="app"> <li v-for="n in numbers" v-if="n % 2 == 0">{{ n }}</li> </div>var app = new Vue({ el: '#app', data: { numbers: [ 1, 2, 3, 4, 5 ] } })
- 動作結果
参考記事
- 投稿日:2021-01-13T13:48:47+09:00
vueのrouterの設定
vue-router
vue-routerを使ってみよう!備忘録
ファイル構成
今回関係あるファイル
- route.js
- main.js
- App.vue
- pages
- A.vue
- B.vue
- components
- PageContent.vue
App.js
template内にrouter-linkを設置
<template> <div id="app"> <router-link to="/a">go to A</router-link> <span>|</span> <router-link to="/b">go to B</router-link> <router-view></router-view> </div> </template>PageContent.vue
<template> <div class="hello"> <h1>{{ msg }}</h1> <p>ページの内容をそれぞれ表示するためのコンポーネント</p> </div> </template> <script> export default { name: "PageContent", props: { msg: String } }; </script>A.vue
<template> <div id="app"> <PageContent msg="A" /> </div> </template> <script> import PageContent from "@/components/PageContent"; export default { name: "A", components: { PageContent } }; </script>B.vue
<template> <div id="app"> <PageContent msg="B" /> </div> </template> <script> import PageContent from "@/components/PageContent"; export default { name: "B", components: { PageContent } }; </script>route.js
import Vue from "vue"; import VueRouter from "vue-router"; import A from "@/pages/A"; import B from "@/pages/B"; Vue.use(VueRouter); const routes = [ { path: "/", component: A, }, { path: "/a", component: A, }, { path: "/b", component: B, }, ]; const router = new VueRouter({ routes: routes, }); export default router;main.js
import Vue from "vue"; import App from "./App.vue"; import router from "./route.js"; Vue.config.productionTip = false; new Vue({ router, render: (h) => h(App), }).$mount("#app");参考?:
Vue Routerの書き方、使い方について解説
https://www.e-loop.jp/knowledges/14/
- 投稿日:2021-01-13T13:48:47+09:00
vueのrouterの設定(備忘録)
vue-router
vue-routerを使ってみよう!備忘録
ファイル構成
今回関係あるファイル
- route.js
- main.js
- App.vue
- pages
- A.vue
- B.vue
- components
- PageContent.vue
App.js
template内にrouter-linkを設置
<template> <div id="app"> <router-link to="/a">go to A</router-link> <span>|</span> <router-link to="/b">go to B</router-link> <router-view></router-view> </div> </template>PageContent.vue
<template> <div class="hello"> <h1>{{ msg }}</h1> <p>ページの内容をそれぞれ表示するためのコンポーネント</p> </div> </template> <script> export default { name: "PageContent", props: { msg: String } }; </script>A.vue
<template> <div id="app"> <PageContent msg="A" /> </div> </template> <script> import PageContent from "@/components/PageContent"; export default { name: "A", components: { PageContent } }; </script>B.vue
<template> <div id="app"> <PageContent msg="B" /> </div> </template> <script> import PageContent from "@/components/PageContent"; export default { name: "B", components: { PageContent } }; </script>route.js
import Vue from "vue"; import VueRouter from "vue-router"; import A from "@/pages/A"; import B from "@/pages/B"; Vue.use(VueRouter); const routes = [ { path: "/", component: A, }, { path: "/a", component: A, }, { path: "/b", component: B, }, ]; const router = new VueRouter({ routes: routes, }); export default router;main.js
import Vue from "vue"; import App from "./App.vue"; import router from "./route.js"; Vue.config.productionTip = false; new Vue({ router, render: (h) => h(App), }).$mount("#app");参考?:
Vue Routerの書き方、使い方について解説
https://www.e-loop.jp/knowledges/14/
- 投稿日:2021-01-13T09:11:42+09:00
9割以上が知らない!?初心者におススメ!Vue.jsでパンくずリストを作ろう!!
皆さんこんにちは!
今回は初心者さんに必見!パンくずリストの作り方をお教えします!!
HTML、CSSではなくコンポーネントを利用して作成していきます。
パンくずリストはSEO内部対策にも非常に有効で、コンポーネントを使うことでその都度HTML、CSSを記述しなくて済むのでぜひご利用ください!
また、今回の記事を書くにあたって、下記の記事を参考にさせて頂きました。
ご協力誠にありがとうございます。
それでは、順を追って一緒に説明を見ていきましょう!
コンポーネントの作成
componentsディレクトリに
Breadcrumb.vue
を作成し、以下のテンプレートを作成します。components/Breadcrumb.vue<template> <div class="l-breadcrumb"> <div class="l-breadcrumb__inner"> <ul class="l-breadcrumb__items"> <li class="l-breadcrumb__item" v-for="breadcrumb in breadcrumbs.data" :key="breadcrumb.name"> <a v-if="breadcrumb.path" :href="breadcrumb.path" class="l-breadcrumb__text"> {{ breadcrumb.name }} <b-icon pack="fas" icon="chevron-right" class="bread-icon"></b-icon> </a> <span v-else class="l-breadcrumb__text">{{ breadcrumb.name }}</span> </li> </ul> </div> </div> </template> <script> export default { props: ['breadcrumbs'] } </script> <style scoped> .l-breadcrumb__items li { display: inline-block; margin-top: 1rem; } .l-breadcrumb__items a { color: var(--link-color) !important; } .l-breadcrumb__items a:hover { text-decoration: underline !important; } .l-breadcrumb__items .bread-icon { margin: 0 5px; color: black; opacity: 0.5; display: inline; } </style>
<b-icon>
はCSSフレームワークBuefyを使うことで、実装できます。詳しくは、僕が書いたこちらの記事を見て下さい。
初心者必見!サイト制作は楽してなんぼ。CSSフレームワークBuefyの紹介!!
効率的にサイト作り!Buefyでアイコンを表示しよう!!また、パンくずリストのCSSも載せておくので、ぜひ使ってください!
コンポーネントの読み込み
コンポーネントを読み込み、propsでnameとpathと言う名のプロパティを用いて、オブジェクト作成します。
App.vue<breadcrumb :breadcrumbs="breadcrumbs" /> <script> import Breadcrumb from '~/components/Breadcrumb.vue' export default { computed: { breadcrumbs: function() { return { data: [ { name: 'ホーム', path: '/' }, { name: 'ユーザー情報', path: '/user' }, { name: 'アカウント情報' } ] } } } } </script>すると、以下のような結果になります。
いかがだったでしょうか?
パンくずリストはUXの部分でも非常に有効なので、僕は使えるなら使った方がいいかなと思います。
今使わなくても、いずれは使うときが必ず来るので、その時は今回書いた記事をぜひ参考にして使ってください!!
以上、「9割以上が知らない!?初心者におススメ!Vue.jsでパンくずリストを作ろう!!」でした!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading
- 投稿日:2021-01-13T00:42:38+09:00
ふと、aタグの機能を無くしたいと思った
今サイト制作を行っているのですが、パンくずリストを作る際にaタグの機能(リンク、cursor)を無くしたいと思ったので、その備忘録として書いていきたいと思います。
まず、
cursor: none,cursor: default
にしてもaタグの機能を消すことができません。なので、ここで出てくるのが
pointer-events
です。ただ、このCSSは
CSS3
なので、IEでの動きはあまりよくないそうです。style.cssa { pointer-events: none; }これで、aタグの機能を無くすことができました!
良ければ、LGTM、コメントお願いします。
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
Thank you for reading