- 投稿日:2021-03-03T23:01:32+09:00
Vue + Vuetify + Storybook v6の環境を構築する
Vuetify環境にStorybookを導入する際、vue-cli-plugin-vuetify-storybookで素直にインストールするとStorybook v5が導入されてしまいます。
「どうしてもStorybook v6が使いたい.......! (新しいもの好き)」
とチャレンジしてみたところ苦戦したのでまとめてみました。構築した環境
パッケージ バージョン @vue/cli 4.5.11 vue 2.6.12 vuetify 2.4.5 @storybook/vue 6.1.20 Vue + Vuetify環境のプロジェクトの作成
Vuetifyのサイト(https://vuetifyjs.com/ja/getting-started/installation/ )を参考にVueプロジェクトを作成し、Vuetifyをインストールします。
# Vueプロジェクトの作成 (プリセットは「Default ([Vue 2] babel, eslint)」を選択しました) $ vue create my-app # 作成したプロジェクトへ移動 $ cd my-app # Vuetifyをインストール (プリセットは「Default (recommended)」を選択しました) $ vue add vuetify現時点でVuetifyはVue 3には対応していません。
Vueプロジェクト作成の際にはVue 2を選択してください。以下のコマンドを実行し、ブラウザで http://localhost:8080/ にアクセスし確認します。
$ npm run serve
無事Vue + Vuetifyの環境が構築できました。
Storybook v6のインストール
Storybookの公式サイト(https://storybook.js.org/docs/vue/get-started/install )を参考にStorybook v6をインストールします。
$ npx sb init
以下のコマンドを実行し、ブラウザで http://localhost:6006/ にアクセスし、Storybookが起動できることを確認します。
$ npm run storybook
Storybookの設定
まず、StorybookのWebpackを設定します。
Storybook v5ではwebpack.config.jsに設定を書いていたと思いますが、v6ではmain.jsに記載します。.storybook/main.jsconst path = require('path') module.exports = { "stories": [ "../src/**/*.stories.mdx", "../src/**/*.stories.@(js|jsx|ts|tsx)" ], "addons": [ "@storybook/addon-links", "@storybook/addon-essentials" ], // webpackの設定 webpackFinal: async (config, { configType }) => { // @がsrcディレクトリをさすように設定 config.resolve.alias['@'] = path.resolve(__dirname, '..', 'src') // sass-loaderを設定 config.module.rules.push({ test: /\.sass$/, use: [ 'style-loader', 'css-loader', 'sass-loader', ], }) return config }, }次にVuetifyプラグインファイルを少し書き換えて、optionsをexportするようにします。
(本番と同じvuetifyオプションをStorybookでも使用できるようにするためです)src/plugins/vuetify.jsimport Vue from 'vue'; import Vuetify from 'vuetify/lib/framework'; Vue.use(Vuetify); // オプションをエクスポートします。といっても、今回は特にオプション設定していないので空オブジェクトです。 export const options = {} export default new Vuetify(options);Storybookの設定ファイルにvuetifyの設定を記載します。
Storybook v5ではconfig.jsに設定を記載していましたが、v6からはpreview.jsに記載することになりました。.storybook/preview.jsimport Vue from 'vue' import Vuetify from 'vuetify' import 'vuetify/dist/vuetify.min.css' import { options } from '@/plugins/vuetify.js' // Vuetifyを設定し、vuetifyインスタンスを作成 Vue.use(Vuetify) const vuetify = new Vuetify(options) export const parameters = { actions: { argTypesRegex: "^on[A-Z].*" }, } // decoratorsでvuetifyインスタンスをVueのオプションに登録 export const decorators = [ (story, context) => { const wrapped = story(context) return Vue.extend({ vuetify, components: { wrapped }, template: ` <v-app> <v-container fluid> <wrapped /> </v-container> </v-app> ` }) }, ]Storyを書いてみる!
まずコンポーネントファイルを作ります。
src/components/MyButton/MyButton.vue<template> <v-btn color="success" @click="$emit('click')"> {{ text }} </v-btn> </template> <script> export default { name: 'MyButton', props: { text: { type: String, default: 'ボタン', } } } </script>次にストーリーを書きます。
src/components/MyButton/MyButton.stories.jsimport vuetify from '@/plugins/vuetify' import MyButton from './MyButton' export default { component: MyButton, title: 'MyComponents/MyButton', argTypes: { click: { action: 'click '}, } } const Template = (args, { argTypes }) => ({ components: { MyButton }, props: Object.keys(argTypes), // vuetifyインスタンスを登録する vuetify, template: '<my-button v-bind="$props" @click="click" />', }) export const Default = Template.bind({}) Default.args = { text: 'できた!' }これらのファイルを作り、Storybookを起動し、http://localhost:6006/?path=/story/mycomponents-mybutton--default にアクセスすると、
できました!
参考文献
以下のWebサイトにとても助けられました。
最後に
今回.storybook/preview.jsだけでなく、MyButton.stories.jsのTemplateでもvuetifyインスタンスを与えています。
このようにしないと$vuetifyを使った際にエラーになる箇所があったためです。
もっとスマートな方法があればぜひ教えてください。
- 投稿日:2021-03-03T20:47:08+09:00
webpack-dev-serverにドメインでアクセスする
やりたいこと
Dockerコンテナ上で
npm run serve
したときに任意のドメインでアクセスしたい設定
nginx-proxyを利用して設定したドメインで各コンテナにアクセスできる状態にする。
nginx-proxyを利用した環境の構築はここ等を参考にしてください。
docker-compose.ymlはこんな感じ
docker-compose.ymlversion: '3.5' services: node: build: ./infra/node environment: VIRTUAL_HOST: "任意のドメイン" HTTPS_METHOD: "noredirect" expose: - 3000 networks: - app-network - local-network command: sh -c "npm run serve" networks: app-network: name: app-network local-network: external: true name: local-network
vue.config.js
に設定を記述します。vue.config.jsmodule.exports = { devServer: { host: '0.0.0.0', port: 3000, disableHostCheck: true, watchOptions: { poll: true } }以上の設定で
npm run serve
した後に任意のドメインでアクセスが可能になります。所感
今回やりたいことを実現するにあたって正直理解できていないところがたくさんある(host: '0.0.0.0'やnginx-proxyの仕組み等)ので、一つ一つ調べて理解して使うことが大事だと痛感しました。
参考
nginx-proxyを利用した環境の構築
【超便利】DockerでPHPローカル開発環境の最強構築方法
- 投稿日:2021-03-03T20:38:30+09:00
【Vue.js】v-modelを使った双方向データバインディングについて
v-model、双方向データバインディングについて少しだけまとめ。
データバインディングとは?
データを結びつけること
双方向とは?
vueの記述を変えると、HTML側も書き変わるような仕組み。
簡単なコード
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <div id="app"> <input type="text" v-model="message"> <h1>{{message}}</h1> </div>new Vue({ el: '#app', data: { message: 'がんばれ' } })これで下のような表示になるが、ここで文字を入力すると、リアルタイムでh1タグで囲んでいるmessageの文字も変わる。
これがv-model。
- 投稿日:2021-03-03T20:22:00+09:00
【Vue.js】v-onディレクティブでボタンを押したら数字が増える
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script> <div id="app"> <p>{{ number }}回目のクリックです</p> <button @click="countUp">ボタン</button> </div>new Vue({ el: '#app', data: { number: 0, }, methods: { countUp: function() { this.number += 1 }, } })ボタンを押すと、数字が増えていくようなものが出来あがる。
<button @click="countUp">ボタン</button>
この@clickは本当は,v-on:clickと記述するが、このように@で省略する記法もある。
- 投稿日:2021-03-03T20:13:09+09:00
爆速で検索できる顔文字サイトを開発した話
きっかけ
某S●mejiに対抗するため、えりんぎという顔文字サイトを2020年1月に公開
志半ばで中途半端な状態のまま放置してドメインを失効しそうになる
閉鎖しようと思ったが友人がずっと使ってくれてたのを知るというわけでバグだらけの「えりんぎ」をリニューアルすることに
作ったもの
顔文字をカテゴリやキーワードで検索できるサイト
えもしぇあ https://emoshare.net製作期間は3週間で60時間くらい
※顔文字を登録する作業が10時間以上占めている( ;꒳; )LighthouseでPerformanceが100にならなかったけど爆速
※Google Analytics入れたら92に下がった( ;꒳; )
技術
Laravel 6.x (普段使っている安心安定のLaravel)
Vue3 (Composition API 使ってみたかった)
Tailwind CSS (Laravel8で採用されたらしいので使ってみたかった)実装方針
実装に時間が掛かりそうなものはできるだけ除外
コンポーネントはあまり細かく分けない
新しいことはあまりしない(躓いたら諦める)実装したい機能
検索したときにページ遷移せずいい感じで表示したい
タップやクリックでクリップボードにコピー
コピーした履歴を持ちたい
できるだけ爆速で表示したい
いいね機能
顔文字にキーワードを持たせて検索しやすくする(管理画面で設定するのめんどくさくなって途中から設定してない)
投稿(めんどくさくなってやめた)
ダークモード(めんどくさくなってやめた)
多言語化する(顔文字に日本語を含めなくなるのでやめた)画面設計
DB設計
実装
いいねボタン
これ参考にした
タグクラウド
チェックボックスの状態でON/OFFを切り替える
vue.js// 一部省略 <label :for="'category' + category.id"> <input :id="'category' + category.id" type="checkbox" class="category-input hidden" /> <div class="category-button-wrapper"> <span class="category-button"> #{{ category.name }} </span> </div> </label>style.scss.category-input { &:checked { + .category-button-wrapper { .category-button { color: #fff; background: #f59e0b; border-color: #f59e0b; } } } &:active { + .category-button-wrapper{ .category-button { color: #fff; background: #fbbf24; border-color: #fbbf24; } } } }
:checked
と:hover
で実装したところスマホでうまく動かなかったが:hover
を:active
に変えたら動いたコピー履歴
Local Storageでコピーした顔文字を保持
エラー画面
地味に 401, 403, 404, 405, 419, 429, 500, 503 エラーに対応した
gzip圧縮
cssとjsを圧縮する
zopfli使ったら結構小さくなったtailwind.config.jsmodule.exports = { purge: [ './resources/js/components/**/*.vue', './resources/views/**/*php' ], darkMode: false, // or 'media' or 'class' theme: { extend: {}, }, variants: { extend: {}, }, plugins: [], }webpack.mix.jsconst mix = require('laravel-mix') const tailwindcss = require('tailwindcss') const CompressionPlugin = require('compression-webpack-plugin') const zopfli = require('node-zopfli') mix.js('resources/js/app.js', 'public/js').vue() .sass('resources/sass/app.scss', 'public/css') .options({ processCssUrls: false, postCss: [ tailwindcss('./tailwind.config.js') ], }).webpackConfig({ plugins: [ new CompressionPlugin({ test: /\.(css)|(js)$/, algorithm: (content, options, fn) => { zopfli.gzip(content, options, fn); }, }) ] }).htaccess# gzip対応 RewriteCond %{HTTP:Accept-Encoding} gzip RewriteCond %{REQUEST_FILENAME}\.gz -s RewriteRule ^(.+) $1.gz <FilesMatch "\.css\.gz$"> ForceType text/css AddEncoding x-gzip .gz </FilesMatch> <FilesMatch "\.js\.gz$"> ForceType application/x-javascript AddEncoding x-gzip .gz </FilesMatch>デプロイ
レンタルサーバーにデプロイする方法忘れたのでこれを参考にした
ヘテムルにLaravelをデプロイする方法 (Laravel5.8)最後に
Tailwind CSSのおかげで命名する時間を節約できたのはよかった
あとレスポンシブ対応も楽だった
Google Analytics入れたらBest Practicesが100にならないんだけどどうしたらいいの?
- 投稿日:2021-03-03T20:07:44+09:00
【Vue.js】Composition-APIで簡単なタイピングゲームを作ってみました
以前Vue.jsで作った奴をComposition-APIに書き換えて+αしてみました。
ロジックとかは大体同じです。
環境
version macOS Catalina 10.15.7 npm 6.9.0 node 10.16.0 vue 2.6.11 @vue/composition-api 1.0.0-rc.2 完成品
AppVue<template> <div id="app"> <div v-if="!playing">Enterキーでスタート</div> <div v-else> <div>残り{{ time }}秒</div> <div>単語数:{{ typedWord }} / ミス:{{ miss.num }}</div> <br /> <div> <div v-if="miss.status">?</div> <div v-else><br /></div> <span style="opacity: 0.3;">{{ typeChar }}</span> <span>{{ nowWord }}</span> </div> </div> <br /> <div v-if="!time">ゲーム終了!おつかれさまでした!?</div> </div> </template> <script> import { ref, reactive, toRefs } from '@vue/composition-api'; export default { setup() { // 単語関係ーーーーーーーーーーーーーーーーーーーーーーー const wordStore = reactive({ nowWord: '', // 出題されている単語 typeChar: '', // 入力した単語の一文字 typedWord: 0, // 入力し終えた単語の数 words: [ // 出題単語 'dog', 'cat', 'monkey', 'elephant', 'giraffe', 'penguin', 'lion', 'tiger', 'pig', 'cow', ], }); // wordsからランダムな単語を1つ取り出す const setWord = () => { wordStore.nowWord = wordStore.words[Math.floor(Math.random() * wordStore.words.length)]; }; // タイピング関係ーーーーーーーーーーーーーーーーーーーー const miss = reactive({ // ミスの回数とミスしたかどうか num: 0, status: false, }); const keydown = (e) => { // 間違えたときの処理 if (wordStore.nowWord[0] !== e.key) { miss.num++; miss.status = true; return; } miss.status = false; // typeCharに入力できた文字を追加していく wordStore.typeChar += e.key; // 入力できたらnowWordの頭文字を削除していく wordStore.nowWord = wordStore.nowWord.slice(1); // 単語を入力し終えたときの処理 if (!wordStore.nowWord) { wordStore.typedWord++; wordStore.typeChar = ''; setWord(); } }; // カウントダウン関係ーーーーーーーーーーーーーーーーーー const time = ref(10); // カウントダウンのタイム const countDownTimer = () => { const id = setInterval(() => { time.value--; if (!time.value) { // ゲーム終了。keydown関数のキーイベントを削除 removeEventListener('keydown', keydown); clearInterval(id); } }, 1000); }; // ゲーム開始ーーーーーーーーーーーーーーーーーーーーーー const playing = ref(false); // ゲームが始まっているかどうか const startGame = (e) => { // 入力がEnterキーでなければゲームが始まらない if (e.key !== 'Enter') return; playing.value = true; // 各関数実行 // startGame関数のキーイベントを削除 removeEventListener('keydown', startGame); // keydown関数のキーイベントを発火 addEventListener('keydown', keydown); setWord(); countDownTimer(); }; // startGame関数のキーイベント発火 addEventListener('keydown', startGame); return { ...toRefs(wordStore), playing, miss, time, setWord, }; }, }; </script> <style> #app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50; font-size: 20px; margin-top: 100px; } </style>
ロジックとそれに関連する変数ごとに分けてまとめるように意識しました。
素のjavascriptで書いてる感じが強かったです。
もっとCompotision-APIの要素を入れたかったですが、これが限界でした笑
- 投稿日:2021-03-03T19:27:02+09:00
[Vue]ページ遷移時にページを画面のトップに戻したい
概要
Vue Routerでページ遷移する時、前のページのスクロール位置のまま遷移してしまうので画面が中途半端な位置になってしまいます。
ページ遷移した時に画面トップに戻る処理を実装できるのが
scrollBehavior関数
です。実装
const router = new VueRouter({ mode: 'history', routes: [ { path: '/', name: 'home', component: HomeComponent, props: true, meta: {isPublic: true}, children: [ { path: '/trend', name: 'trend', components: { trend: TrendTimeline, }, props: true, meta: {isPublic: true}, }, { path: '/item', name: 'item', components: { item: TrendItem, }, props: true, meta: {isPublic: true}, } ] }, ], scrollBehavior (to, from, savedPosition) { if (savedPosition) { return savedPosition } else { return { x: 0, y: 0 } } } })to: 遷移先のルート情報
from: 遷移元のルート情報
savedPosition: ブラウザの戻る/進むボタンがトリガーされた時にのみ使う上記だとブラウザ戻る、進むボタンが押された時以外はページトップに戻るようになっております。
特定のルートだけページ位置を戻したくない場合は、
scrollBehavior (to, from, savedPosition) { if (savedPosition || to.name == 'home' ||to.name == 'trend' || to.name == 'item') { return savedPosition } else { return { x: 0, y: 0 } } }のようにto.nameで指定することで実装することができます。
- 投稿日:2021-03-03T11:50:25+09:00
Userモデルの変更、Vuexの導入
今日やること
1. apiのUserモデルの'first_name', 'last_name'をフロントエンド側で編集できるようにする。
2.Vuexの使い方1ではまずapi上で情報が編集できるようにしてからフロントエンド側でも編集できるように変える。
api上でUserモデルを編集する
Userモデルにpermissionを与える
まず、Userを編集するためにはpermissionを得る必要があるが、現在設定されているものは
settings.py
で確認できる。settings.pyREST_FRAMEWORK = { # Use Django's standard `django.contrib.auth` permissions, # or allow read-only access for unauthenticated users. 'DEFAULT_PERMISSION_CLASSES': [ 'rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly' ], 'DEFAULT_AUTHENTICATION_CLASSES': [ 'rest_framework.authentication.TokenAuthentication' ] }どうやら標準のパーミッションの設定が追加されているようだ。この
rest_framework.permissions.DjangoModelPermissionsOrAnonReadOnly
の意味は「もし、ログインしていない状態ではreadonly」ということだ。つまり、編集するためにはトークンを指定してログインする必要がある。だが、request headersにトークンを指定してもまだ編集することができない、、、これは
UserViewset
にpermissionクラスが設定されていないためである。なのでviewsets.py
に追加しよう。viewsets.pyclass UserViewSet(viewsets.ModelViewSet): queryset = User.objects.all() serializer_class = UserSerializer + permission_classes = [permissions.IsAuthenticated]するとこのような画面になる。
無事にapi上での編集を許可することができた。
フロントエンド側で編集した内容をapiへ送る
次にフロントエンド側で変更した内容を
axios
を使ってdjangoのapiに送信していこう。axiosのPATCHメソッドを利用して、データの更新方法を確認します。PATCHでは既存のデータの上書きを行うので、更新したい項目と値の組を指定する必要があります。また更新するユーザを識別するIDも必須となります。
patchの使い方は以下のようになる。
ex.pyaxios.patch('/user/id',{ firstName: 'Jone', lastName : 'Dow' }) .then(function (response) { // handle success console.log(response); })今回は
Profile.py
でユーザーが['first_name']['last_name']を変更した際に情報を渡したいので次のようになる。Profile.py/VUE<form class="form"> <div class="form-row"> <div class="form-group col-md-6"> <label>First Name:</label> <input type="text" class="form-control" v-model="account['user']['first_name']"> </div> <div class="form-group col-md-6"> <label>Last Name: </label> <input type="text" class="form-control" v-model="account['user']['last_name']"> </div> <v-btn @click="updateProfile">Update</v-btn> </div> </form>Profile.py/JSmethods: { updateProfile(){ axios .patch('/users/' + this.account.user.id + '/',{ first_name: this.account['user']['first_name'], last_name: this.account['user']['last_name'] } ) .then(resp => { console.log(resp.data) this.account.user = resp.data this.$store.commit('setAccount', this.account) }) }ここで
.patch('/users/' + this.account.user.id + '/'
があるが、これはアカウントのUserIdを指定している。また、
first_name
とlast_name
の編集内容はv-modelの内容からとってくる。v-modelとは?
Vue.jsのディレクティブの1種。
dataオブジェクトのプロパティの値とinputタブの入力値や選択値のデータを連動することができる。
簡単に言うと、画面上でボックスに入力した内容が、jsファイルのデータに反映できる便利な機能。
jsファイル側のデータを更新した場合も、画面上の入力値に連動させることができるため、双方向データバインディング(データ結合)と呼ぶ。▼使い方
タグの属性に記述する。
v-model="プロパティ名"
┗ プロパティ名:dataオブジェクトの中の連動させてい値をもつプロパティ名を記述。Vuexを使う
Vuexとは?
VuexとはVue.jsのためのデータ保存ツールです。
すべてのコンポーネントからアクセスでき、さらに意図しないデータの変更を防げます。
例えば、ログイン中のユーザーに関するデータはどのコンポーネントからも参照できたほうが便利ですよね。
Vuexはデータを1箇所に集中させて管理を容易にします。
今回の場合であればaccountの情報を管理します。Vuexのインストール方法
では、実際にインストールをしてみましょう。 以下のコマンドでvueにインストールできます。
vue add vuexVuexの使い方
では、実際にVuexを使ってみましょう。
ストアの作成
ストアとは、データを保持しておく場所です。今回は、store.jsという名前のファイルを作成します。store.jsimport Vue from ‘vue’ import Vuex from ‘vuex’ Vue.use(Vuex) export default new Vuex.Store({ state: { + account: null }, mutations: { + setAccount(state, account) { + state.account = account } }, actions: { }, modules: { } })
state
を使うことで”account”というデータはどこからでも参照できるようになりました。(グローバルにする)次にstateで定義したaccountは直接書き込むことはできないのでmutationsにaccountの値を設定して、、、
ん〜〜、なんだか難しい
Vue.js + Vuexでデータが循環する全体像
一度、vuexについてしっかりと学ぶ必要がありそうなのでここでまとめておく。
vuexの図解
- アプリ1つに対してStoreも1つ
これはvuexをインストールした際に自動生成されるフォルダーである。(
store/index.js
)
データの変更は必ずmutationを経由
mutationsに登録した関数を呼ぶために使うのが「$store.commit()」
です。
storeのmutationsに登録した関数以外の場所でstateを変更してはいけません。storeに保存したデータはstateから読み取る
あらゆるコンポーネントが$storeにアクセスできます。
データの読み取りは$store.stateで。
②で説明した通り、読み取ったstateを直接書き換えてはいけません。Vuexの使い方(改めて)
ここまで理解した時点で改めて実際のコードを確認しよう
store.jsimport Vue from ‘vue’ import Vuex from ‘vuex’ Vue.use(Vuex) export default new Vuex.Store({ state: { + account: null }, mutations: { + setAccount(state, account) { + state.account = account } }, actions: { }, modules: { } })Nabar.vue/VUEul.navbar-nav li.nav-item(v-if="!account") a(href="/login/") Login li.nav-item(v-if="account") a(href="/" @click="logout") Sign OutNavbar.vue/JS<script> export default { name: 'Navbar', data(){ return { } },computed: { account() { return this.$store.state.account } }, methods:{ logout(){ alert("are you sure?") localStorage.removeItem("account") this.$store.commit('setAccount', null) this.$router.push('/') } } } </script>ストアに保存されているデータを変更するときは、毎回コミットして保存します。 流れは以下の通りです。
変更ボタンを押すとmethodsの”logout”ファンクションが実行される
第1引数のsetAccuntが実行され、store.jsのmutationsのAccountの第2引数に’null’が渡される
state.accountがnullになる。
これでデータの変更が完了しました。今日の課題
・ vuetifyを使ってUI部分を整える
・ Web Scrapingを自分で学習しインスタの投稿写真のリンクURLをpython上で取得する(ヒント:beautifulライブラリーを使う)
- 投稿日:2021-03-03T10:55:29+09:00
[Vue/Nuxt]client-onlyのコンポーネントはrefで取得できない
client-only
はSSRさせるアプリケーションにおいて、SSRさせずにクライアント側で展開させたい場合に使うタグです。<template> <div> <client-only> no-ssr </client-only> <p>ssr</p> </div> </template>この
client-only
内に展開したコンポーネントをref
で取得するケースで、まず通常の取得ケースは以下のようになります。<template> <div> <client-only> <my-component ref="myComponentRef" /> </client-only> </div> </template> <script> import { ref, onMounted } from 'vue' export default { setup() { const myComponentRef = ref(null) onMounted(() => { console.log(myComponentRef.value) }) return { myComponentRef } } } </script>
ref
で空の変数を用意しておいて、テンプレートのref
に代入、onMounted
後であればVue v2のthis.$refs
の様にスクリプト内でコンポーネントにアクセスできます。
ですが、client-only
内に展開したコンポーネントではこの方法ではアクセスできません。
このコードを実行するとmyComponentRef.value
はnull
となります。nextTickを使う
この場合はnextTickを利用すると解決できます。
<script> import { ref, onMounted, nextTick } from 'vue' export default { setup() { const myComponentRef = ref(null) onMounted(() => { nextTick(() => { console.log(myComponentRef.value) }) }) return { myComponentRef } } } </script>おわり
- 投稿日:2021-03-03T10:47:05+09:00
【部分的SPA】初心者なりにRuby on Rails, Vue.jsでポートフォリオを作成してみた【タイピングアプリ 】
初めに
プログラミング学習を初めて2ヶ月程度の初心者ですが、自分なりにあれこれ工夫をしながらrailsアプリに部分的にSPAを導入したポートフォリオを作成してみました。本記事ではアプリの概要について記述し、その後いくつかの記事に分けて工夫した箇所を投稿していきたいと思います。
皆様のポートフォリオ作りに少しでもお役に立てると幸いです。
また、所々GIF画像を挿入していますが、Qiita上だと動きが悪いかもしれません。画像をクリックして頂けるとGyazoのページに遷移致しますので、そちらでしたらヌルヌル動くかと思います。関連記事
このポートフォリオを作成するにあたって使用した技術を順次記事にしていきます。
アプリの概要
「エンジニア初学者用の英単語タイピングアプリ」です。
日本語のタイピングならそれなりの速さで打てても、英単語のタイピングになった瞬間にガクッと速度が落ちますよね?また、コードは誤字脱字があるとエラーになってしまうので正確に打てているか確認が必要ですが、それもなかなか時間が掛かりますよね?(私だけ?)
そんな悩みを解消すべく作ったのがこのアプリ「Typie」です。
Typieはプログラミングに使用する英単語に絞ってタイピングの練習が出来るアプリです。URLは下記になります。PCで使用することが前提のアプリなので、レスポンシブ非対応になります。ご了承ください。
Typie_URL: http://54.95.229.185/Github_URL:
開発環境
- フロントエンド
- HTML/CSS/JavaScript
- Vue.js 2.6.12
- Vuetify 2.4.4
- バックエンド
- Ruby 2.6.5
- Ruby on Rails 6.0.3.5
- インフラ
- Mysql/MariaDB
- AWS(EC2)/Nginx/Unicorn/Capistrano
- その他
- Visual Studio Code
使用イメージ
新規投稿
フォームに「タイトル」「言語名」「ソースコード」「色」を入力して送信すると、ソースコードから英単語を抽出して問題を作成する仕組みになっています。ソースコードに登場する英単語なら、それは「プログラミングに使用する言語」と言えるという発想です。詳細表示・編集・削除など
マイページを弄り倒しているGIFです。詳細表示や編集・削除は全てダイアログを表示して行うようにしました。編集や削除結果は非同期通信で反映され、地味に詳細表示ページのヘッダーの色を問題集(今後"Book"と呼びます)の色と同期させています。(この辺は半分遊んでます)絞り込み検索
Bookが増えてくると目当てのものが探し辛くなるので、絞り込み検索機能も実装しました。機能一覧
機能 概要 アプリ使用方法紹介機能 ランディングページにてアプリの使用方法をカルーセル形式で紹介しています ユーザー管理機能 新規登録・ログイン・ログアウトが出来ます 入力補佐機能 フォームは入力内容が条件を満たしていないと枠が赤色に、満たすと緑色に変化します Book一覧表示機能 Bookのタイトル、言語名、単語数、ハイスコアが表示されます Book詳細表示機能 Bookのタイトル、言語名、スコアTOP5が表示される他、タイピング・編集・削除もここから行えます Book編集機能 Bookのタイトル、言語名、色を編集出来ます Bookの削除機能 Bookを削除出来ます Bookの絞り込み検索機能 言語名から絞り込み検索を行えます カウントダウン機能 いきなりタイピングが始まらないよう、3秒数えます タイピング機能 登録された英単語を出題し、正解・不正解文字数をカウントします 残りの単語数表示機能 あとどれくらいで終わるのかが分かる機能です 途中退出機能 途中で退出してもスコアが算出されます スコア算出機能 タイピング技能検定が使用している計算式(下記リンク)を用いてスコアを算出します
- タイピング技能検定URL
工夫した点
とにかく「英単語のタイピングに慣れていないエンジニア初学者向け」であることを主軸に、以下の課題をどう解決するかを考えました。
既存のエンジニア向け英単語タイピングアプリで感じた課題
- 投稿した範囲全てが問題として出題されるタイプ
- 1つ1つの英単語のタイピングに時間が掛かると全然進んでいる気がしない
- 終わりが見えないストレスがある
- 途中で抜けるとスコアは出ない
- 入力すべき文字がハイライトされていて見づらい
- タイマーが気になる
- 単語単位で出題されるタイプ
- 出題される単語を、一度全て自力で設定するのはキツい
DB設計
各テーブルについて
テーブル名 説明 Users ユーザー情報 Books Bookの情報 Words 英単語情報、初出単語のみ保存 Book_words Book/Wordの中間テーブル Languages 言語名情報、初出言語のみ保存 Book_languages Book/Languageの中間テーブル Scores スコア情報 苦労した点
フロントエンド
- 思った通りにならない
「このプロパティを追加すればこうなるはず」…と思って記述しても想像と違う結果になる。何も変化していないことが最も多い。バックエンドと違ってエラーが出ないからどこがどう間違っているのか分かりづらい。あとセンスが無い。- Vue.jsとのデータの受け渡し
当然ながらインスタンス変数が無い。JSON形式に変換しようとするも、狙った通りの連想配列にならない。本当に表示するデータに限定して受け渡しをしないと、表示されるまで時間が掛かる。バックエンド
- 6つのテーブルの値が変動するフォームオブジェクト
BookとWord/Languageは多対多の関係。そしてWordは追加される単語の数だけ増え、Languageは増えるとしても1つだけなので処理が全く違う。そしてBook一覧表示ではベストスコアが表示されるので、スコアが空のままだとnilclassのエラーになってしまう。- フォームオブジェクトの編集
レコードの編集は簡単だと思っていた。…それは全部railsがやってくれていたからで、1から自力で実装するのはなかなかの難易度だった。参考にした学習教材・記事
本当に色んなものを参考にさせて頂きました。
ここでは特に助けていただいたものを一部抜粋して紹介します。Vue.js
- 【書籍】『Vue.jsのツボとコツがゼッタイにわかる本』(中田亨, 秀和システム, 2019/3/28)
Vue.jsの基礎はこの書籍から学びました。ミニアプリの作成を通して解説を読んでいく形式なのでとても分かりやすく、これからVue.jsの学習を始める方におすすめの一冊です。
- 【書籍】『Vue.js&Nuxt.js超入門』(掌田津耶乃, 秀和システム, 2019/2/11)
こちらは「アプリを作る」ことを意識して書かれている一冊です。Nuxt.jsやFirebaseについても解説があるので、最低限の基本は既に抑えているという方にはこちらがおすすめです。
- 【有料動画】『超Vue.js2完全パック』(よしぴー, Udemy, 最終更新 2020/12/20)
Vue.js界隈では有名な動画です。Vue.jsの基礎から応用まで解説している動画になります。話し手の方がとても早口なのですが、ちゃんと当倍速で聞き続けているとだんだんクセになっていきます。
Ruby on Railsへの組み込み
- 【有料記事】『Rails × Vue.js でメモアプリを作成しながらモダンな開発を学ぼう!』(Daijiro, Techpit, 最終更新 2021/1)
Vue.jsの基礎的な説明もされているのですが、どちらかというと「アプリ開発過程を追体験出来る」教材になっているので、Vue.js/Ruby on Railsの基礎は定着させた上で受講するのがよろしいかと思います。axiosやjbuilderを使って実際にアプリを作成する過程を学ぶことが出来ます。
- 【有料記事】『【Rails / Vue.js】Snippet アプリを作ってみよう!』(kozzy, Techpit, 最終更新 2020/9)
Vue.jsについての解説は少ないのですが、関数を使ったダイアログの表示/非表示切り替え、CRUDの実装、Vuetifyの使い方を学ぶことが出来ます。
開発に詰まった際の解決方法
Vue.jsをRuby on Railsアプリに組み込む際に直面した課題の解決方法に関する記事です。詳細は今後違う記事にまとめようと思いますが、ここでは簡単な紹介をさせて頂きます。
- 【無料記事】『【Rails】foremanでrails serverとwebpack-dev-serverを一度に起動する』(五十川洋平, 最終更新 2019/4/2)
- 【無料記事】『【Rails】jbuilderの使い方辞典〜メソッドの文法と使い方がすぐわかる』(Pikawaka, 最終更新 2020/4/3)
- 【無料記事】『【Vue】axiosで、デフォルトでCSRFトークンを設定できるようにする』(@ngron, Qiita, 最終更新 2019/10/14)
今後の課題
- テストコードの記述
- ドメイン取得
- SSL取得
- 完全なSPA化
- ユーザー間交流
- 絞り込み検索機能の充実化
- dockerの学習と導入
- CircleCIの学習と導入
まだまだやることが沢山ありますね。今後の転職活動の状況も考えながら少しずつ進めていきたいと思います。
- 投稿日:2021-03-03T08:22:11+09:00
デコレーターを使おうとしたら怒られた
背景
vuex-module-decoratorsを使ってストアを宣言したら、
クラス名にて怒られたimport { Module, VuexModule, Mutation, Action } from 'vuex-module-decorators'; @Module export default class AuthStore extends VuexModule { }デコレーターの実験的なサポートは将来のリリースで変更になる可能性がある機能です。'experimentalDecorators' オプションを設定してこの警告を削除します
対処
VSCodeの設定にてExperimental Decoratorsの設定を変更。
- 基本設定を開く
- 設定を開く
- 検索窓にexperimentalと入力
JS/TS › Implicit Project Config: Experimental Decorators
の設定にチェックを入れる参考記事
- 投稿日:2021-03-03T01:16:38+09:00
Vue.js URLからidを取得
例えば以下の?がつくURLで1を取得したい場合
http://localhost:3000/maps?tweet_id=1クエリパラメーターと言うみたいです
これで1が取得できます<template> <div id="app"> {{$route.query.tweet_id}} </div> </template>詳細ページでよく見る以下のURLでは
http://localhost:3000/tweets/1これで良いと思います
<template> <div id="app"> {{$route.params.id}} </div> </template>おまけ
axiosでJSONデータを取得するときなどはthisをつけると取得できます
axios .get(`/api/v1/tweets/${this.$route.params.id}/index.json`) .then(response =>{ this.analyses = response.data; })
- 投稿日:2021-03-03T00:26:59+09:00
初心者必見!!再利用するコンポーネントは「Vue.component」で定義しよう!
皆さんこんにちは!
何回も使うコンポーネントって毎回
import
するの面倒くさくないですか??それに
import
した後にcomponents
で定義しなきゃいけない。。。僕の場合、ユーザーに分かりやすいようにパンくずリストのコンポーネントをページごとに必ず設置します。
このように何回も使うコンポーネントは再利用できるように
Vue.component
で定義しちゃいましょう!!main.jsimport Vue from 'vue' // パンくずリストコンポーネント import Breadcrumb from './components/Breadcrumb' Vue.component('Breadcrumb', Breadcrumb)一応簡単に説明しますね。
Vue.component
は、第1引数に呼び出すときの名前、第2引数に対象のコンポーネントをそれぞれ引数にします。作業はこれだけです!
後はいつものように呼び出してあげてください。
作業効率アップ♪
以上、「初心者必見!!再利用するコンポーネントは「Vue.component」で定義しよう!」でした!
また、何か間違っていることがあればご指摘頂けると幸いです。
他にも初心者さん向けに記事を投稿しているので、時間があれば他の記事も見て下さい!!
あと、最近「ココナラ」で環境構築のお手伝いをするサービスを始めました。
気になる方はぜひ一度ご相談ください!
Thank you for reading