- 投稿日:2020-10-20T22:16:24+09:00
【Vue】The template root disallows 'v-for' directives. 解決法
テンプレートルートは、'v-for'ディレクティブを許可しません。
分割したコンポーネントで
v-for
を使ったところ、このようなエラーが、、、The template root disallows ‘v-for’ directives.
(翻訳にかけると「テンプレートルートは、'v-for'ディレクティブを許可しません」と言う意味)なんじゃこりゃ??
コードはこんな感じ
<template> <app-stock v-for="stock in stocks" :key="stock.id"></app-stock> </template> <script> import Stock from './Stock.vue'; export default { data() { return { stocks: [ { id: 1, name: 'BMW', price: 110 }, { id: 2, name: 'Google', price: 210 }, { id: 3, name: 'Amazon', price: 250 }, { id: 4, name: 'Sony', price: 260 }, ] } }, components: { appStock: Stock, } } </script>Componentの中の処理はすべてsingle root elementで囲わなければならない
なんだか難しそうな言葉ですが、要するに「何か別のタグで囲え」という事
<template> <!-- 適当にdivタグで囲った --> <div> <app-stock v-for="stock in stocks" :key="stock.id"></app-stock> </div> </template> <script> import Stock from './Stock.vue'; export default { data() { return { stocks: [ { id: 1, name: 'BMW', price: 110 }, { id: 2, name: 'Google', price: 210 }, { id: 3, name: 'Amazon', price: 250 }, { id: 4, name: 'Sony', price: 260 }, ] } }, components: { appStock: Stock, } } </script>というわけで、
<div>
でv-for
を囲いました。。
- 投稿日:2020-10-20T21:08:15+09:00
Laravel+vue.jsでサービス開発する際の一連の流れ(1)〜環境構築編〜
LaravelとVue.jsを使ってサービスを開発しました。その過程でやったことと、参考にした記事などを書いていきます。備忘録兼、私と同じような初心者の道標になるような記事になれば良いなと思います。
環境
・Laravel(5.8.38)
・Vue.js
・MAMPプロジェクト作成
開発に必要なファイル一式が揃ったフォルダを作成します。プロジェクト作成には2種類の方法があります。
- laravel new
- composerコマンド
laravel new
を使うと最新バージョンのLaravelがインストールされるようです。僕は5.8系を使いたかったので、composerコマンドを使いました。ターミナル% composer create-project "laravel/laravel=(Laravelのバージョン)" (プロジェクト名)
僕の場合、実際のコマンドは下のようになりました。
MAMPを使用しているので、cd /Applications/MAMP/htdocs
でhtdocsディレクトリに移動し、実行します。プロジェクト名は、laravel_exampleとします。ターミナル% composer create-project "laravel/laravel=5.8.*" laravel_example
すると、パッケージがズラーッとインストールされ、laravel_exampleディレクトリが作成されました。
laravel_exampleディレクトリに移り、Laravelのバージョンを確認します。ターミナル% php artisan -v Laravel Framework 5.8.38
ちゃんと5.8系のLaravelがインストールされました。
参考記事:【Laravel入門】プロジェクト作成から起動まで
ディレクトリ構造
先ほどインストールしたディレクトリの配下に、appディレクトリとかconfigディレクトリとか、いろいろ入ってます。
最初はあまり気にしなくていいと思うし、覚える必要もないと思います。開発をしていくと勝手にこの辺には何が入ってる、というのはわかってきます。一応、公式の解説ページを載せときます。
Laravel 5.8 ディレクトリ構造
ちなみに、この公式のドキュメント、開発するにあたってめちゃくちゃお世話になります。簡潔にまとまっている分初心者にはちょっと難しく感じられ、読むのを避けがちです。
しかし、ググっても解決できず、気が進まないながらも公式ドキュメントを読んだら簡単に解決できた、ということが少なからずありました。データベース(mysql)の設定
データベースの作成
まず、今回の開発で使うデータベースを作成しましょう。
プロジェクトのrootディレクトリ直下にて(ここでなくても良いかもしれないが)ターミナル% mysql -u root -p Enter password:
すると、パスワードの入力を求められます。僕の場合、特に何も設定していなかってのでパスワードはrootです(ユーザー名もroot)。入力するとmysqlが起動し、ターミナルの表示が
%
からmysql>
に変わります。
既に存在するデータベース一覧はSHOW DATABASES;
で表示できます。最後の「 ; 」を忘れないようにしましょう。ターミナルmysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | performance_schema | | mysql | | php_sample01 | | sample | | sample2 | | sys | | wordpress | +--------------------+ 8 rows in set (0.03 sec)データベースを作成するコマンドは
ターミナルmysql> CREATE DATABASE (データベース名);僕の場合、下のようになりました。データベース名もlaravel_exampleにしました。なんでも良いと思います。
ターミナルmysql> CREATE DATABASE laravel_example; Query OK, 1 row affected (0.00 sec)もう一度データベースの一覧を見てみると、laravel_exampleが作成されていることが確認できます。
ターミナルmysql> SHOW DATABASES; +--------------------+ | Database | +--------------------+ | information_schema | | laravel_example | | mysql | | performance_schema | | php_sample01 | | sample | | sample2 | | sys | | wordpress | +--------------------+ 9 rows in set (0.00 sec)参考記事:MySQLをMacのターミナルで操作するときのメモ
Laravelのデータベース設定
作成したデータベースをLaravelで使えるように設定します。先ほど作ったディレクトリ(僕ならlaravel_exampleディレクトリ)の中にあるファイルを修正します。
.env
(プロジェクトのルートディレクトリ直下にあります。)database.php
(configディレクトリにあります。).env
.env
ファイルとは?.envファイルを使って開発環境と本番環境を切り替えたり、データベースなどの接続情報の変更を行うことができます。つまりLaravelにとって重要な設定変更このは.envファイルを介することで簡単に行うことができます。例えば開発環境では簡易に利用できるsqliteのデータベースに接続し、本番環境ではmysqlのDBに接続といった変更が可能です。
参考記事:入門者のためのLaravel .envファイルの基礎と理解とりあえずは、この
.env
ファイルに情報を入力していけば、開発環境の設定ができます。
.env
の中でデータベースに関わる部分は以下の通りです。.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel (データベース名) DB_USERNAME=root (ユーザー名) DB_PASSWORD= (パスワード)これを修正していきましょう。
DB_CONNECTION、DB_HOST等の項目の意味は下の記事を参考に。
参考記事:保存版!Laravelの.envでできること大全僕の場合、mysqlの設定は変更していないので、ユーザー名もパスワードもrootのままでした。一応、MAMPを使っている場合はサーバーをオンにした後に自動で表示されるページにも載っています。(下の画像の赤枠で囲んだところです)
データベース名はさっきCREATE DATABASE
で作成したものを記入します。.envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel_example DB_USERNAME=root DB_PASSWORD=root画像をよく見ると、
Socket /Applications/MAMP/tmp/mysql/mysql.sock
とも書いてありますね。これも.env
に追加しておきましょう。
ちなみに、下記のsqlコマンドを打っても、socketを取得できます。ターミナルmysql> SHOW VARIABLES LIKE '%sock%'; +-----------------------------------------+-----------------------------------------+ | Variable_name | Value | +-----------------------------------------+-----------------------------------------+ | performance_schema_max_socket_classes | 10 | | performance_schema_max_socket_instances | -1 | | socket | /Applications/MAMP/tmp/mysql/mysql.sock | +-----------------------------------------+-----------------------------------------+ 3 rows in set (0.00 sec).envDB_CONNECTION=mysql DB_HOST=127.0.0.1 DB_PORT=3306 DB_DATABASE=laravel_example DB_USERNAME=root DB_PASSWORD=root DB_SOCKET=/Applications/MAMP/tmp/mysql/mysql.sockこれで
.env
ファイルの設定、つまり、開発環境の設定がほぼ完了しました。database.php
開発環境の設定が「ほぼ」完了した、と言いましたが、それは
.env
に設定した内容をLaravelに反映させる必要があるからです。
config
ディレクトリ内にあるdatabase.php
ファイルで、.env
に書いたデータベース情報を読み込んでいます。※ちなみに、
database.php
が.env
のデータベース情報を読み込んでいるのと同様に、mail.php
ではメールの情報を、cache.php
ではキャッシュの情報を.env
から読み込むといった感じになっています。今回使うのはmysqlなので、
database.php
内のmysqlの部分を修正していきましょう。database.php'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', '127.0.0.1'), 'port' => env('DB_PORT', '3306'), 'database' => env('DB_DATABASE', 'forge'), 'username' => env('DB_USERNAME', 'forge'), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],
env()
はヘルパ関数(Laravelが用意してくれている便利メソッド)で、第1引数で.env
の値を取得、第2引数は.env
内に値がなかった場合のデフォルトの値を設定します。個人的には、第2引数は全て
''
で良いのでは?と思います。
なぜなら、サービスを作り終えてサーバーにデプロイする際(開発環境から本番環境に切り替える際)、デフォルト値を下手に設定していたせいでエラー地獄に陥ったからです。具体的には、本番環境では
unix_socket
を設定する必要がなかったのですが、database.php'unix_socket' => env('DB_SOCKET', '/Applications/MAMP/tmp/mysql/mysql.sock'),と書いていたためにデフォルト値の
/Applications/MAMP/tmp/mysql/mysql.sock
が設定されてしまいエラーになるなど。
デプロイ時にこうなることを避けるために、僕はデフォルト値(第2引数)は全部空にしておこうと思います。
(複数人で開発する際には、環境を合わせるためにデフォルト値を設定しておいた方が良いのかも?)database.php'connections' => [ 'mysql' => [ 'driver' => 'mysql', 'url' => env('DATABASE_URL'), 'host' => env('DB_HOST', ''), 'port' => env('DB_PORT', ''), 'database' => env('DB_DATABASE', ''), 'username' => env('DB_USERNAME', ''), 'password' => env('DB_PASSWORD', ''), 'unix_socket' => env('DB_SOCKET', ''), 'charset' => 'utf8mb4', 'collation' => 'utf8mb4_unicode_ci', 'prefix' => '', 'prefix_indexes' => true, 'strict' => true, 'engine' => null, 'options' => extension_loaded('pdo_mysql') ? array_filter([ PDO::MYSQL_ATTR_SSL_CA => env('MYSQL_ATTR_SSL_CA'), ]) : [], ],これで開発環境の設定は完了です。
動作確認
実際に正しくテーブルが作成できるか試してみましょう。
プロジェクトのルートディレクトリに移動し、ターミナル% php artisan migrate
Laravelは
users
テーブルとpassword_resets
テーブルを作成するためのファイルを予め準備してくれており、このコマンドを打つことでテーブルが作成されます。ターミナルlaravel_example % php artisan migrate Migration table created successfully. Migrating: 2014_10_12_000000_create_users_table Migrated: 2014_10_12_000000_create_users_table (0.03 seconds) Migrating: 2014_10_12_100000_create_password_resets_table Migrated: 2014_10_12_100000_create_password_resets_table (0.03 seconds)
mysqlを確認しましょう。
use (データベース名);
で使用するデータベースを選択し、show tables;
でそのデータベースに登録されているテーブルを表示できます。ターミナルmysql>use laravel_example; Reading table information for completion of table and column names You can turn off this feature to get a quicker startup with -A Database changed mysql> show tables; +---------------------------+ | Tables_in_laravel_example | +---------------------------+ | migrations | | password_resets | | users | +---------------------------+ 3 rows in set (0.00 sec)
users
テーブルとpassword_resets
テーブルがしっかり作成されています!ちなみに、Laravelにはテーブル作成の巻き戻しをするコマンドもあります。
ターミナルphp artisan migrate:rollback
php artisan migrate
とphp artisan migrate:rollback
はセットで覚えときましょう。後々たくさん使います。ターミナルlaravel_example % php artisan migrate:rollback Rolling back: 2014_10_12_100000_create_password_resets_table Rolled back: 2014_10_12_100000_create_password_resets_table (0.02 seconds) Rolling back: 2014_10_12_000000_create_users_table Rolled back: 2014_10_12_000000_create_users_table (0 seconds)
コマンド入力後、テーブルを確認してみると・・・
ターミナルmysql> show tables; +---------------------------+ | Tables_in_laravel_example | +---------------------------+ | migrations | +---------------------------+ 1 row in set (0.00 sec)
users
テーブルとpassword_resets
テーブルが削除されています。
ちゃんとデータベースを操作できているようですね。デバッグ
最後にデバッグの設定です。LarabelではデバッグのON,OFFも簡単に設定できます。
デバッグをONにするには.envファイルにて.envAPP_DEBUG=true
とするだけです。これは
config
ディレクトリのapp.php
で読み込まれています。app.php/* |-------------------------------------------------------------------------- | Application Debug Mode |-------------------------------------------------------------------------- | | When your application is in debug mode, detailed error messages with | stack traces will be shown on every error that occurs within your | application. If disabled, a simple generic error page is shown. | */ 'debug' => env('APP_DEBUG', false),ここに書かれている通りで、
APP_DEBUG
がtrue
なら、作成中のアプリケーションでエラーが起きると、エラーが起きるまでの過程とエラーメッセージを画面に表示してくれます。
false
なら、シンプルなエラーページが表示されるだけです。
サービスを公開する時には、falseにするのを忘れないようにしましょう。以上で環境構築編は終了です。あとはジャンジャン開発していきましょう!
続く
- 投稿日:2020-10-20T20:26:05+09:00
Nuxt.jsのloading indicatorでカスタムコンポーネントを使いたかった
Nuxt.jsのloading indicatorでカスタムプロパティを使いたかった
背景
Nuxt.jsで、ロード中であることを表すアイコンを実装することになったのでNuxt.jsに実装されているloadingプロパティを使うことに。
元々ローディングに使うコンポーネントがvue-loadingを使用して作られており、それにloadingとloading indicatorで合わせたかった。そもそもloadingとloading indicatorとは
Nuxt.jsに実装されているロード中のアニメーションを流せる優れものです。
デフォルトで実装されており、かつ設定も簡単でカスタマイズも簡単なのでとても重宝しました。loadingプロパティとloading indicatorプロパティの違い
loading
画面遷移時にロード中であることを示すloading indicator
初期表示など、vueコンポーネントがロード中で表示するコンテンツがない状態でロード中であることを示すなお、デフォルトの状態ではloadingは黒いプログレスバーが表示され、loading indicatorは何も表示されません。
loading・loding indicatorの設定
loadingに関わる設定は
nuxt.config.js
で設定できます。
以下は例です。nuxt.config.jsloading: { color: 'red', height: '5px', }, loadingIndicator: { name: 'circle', color: 'red', background: 'white', },詳細はこちらから
https://ja.nuxtjs.org/guides/configuration-glossary/configuration-loading-indicator/
https://ja.nuxtjs.org/api/configuration-loading/困ったこと
前置きが長くなりましたが本題です。上記の例ではloadingは色と高さを指定しており、プログレスバーが表示されています。それを他のコンポーネントで使われているくるくると合わせることになりました。
それがこれ
これはvue-loadingを使って実装されています。
loadingの方は公式ドキュメントのカスタムコンポーネントの項目を参考にして実装し、すんなりといけたのですがloading indicatorのほうで問題発生。
最初に同じようにloading indicatorでvueコンポーネントを指定してみました。
nuxt.config.jsloadingIndicator: '使いたいカスタムコンポーネント',すると更新した時にエラーが発生
Cannot use import statement outside a module
ぐぐってもいまいち出ない・・・
原因
エラーメッセージを読んだところ、「モジュールの外ではインポートはできないよ」でした
・・・
初期表示など、vueコンポーネントがロード中で表示するコンテンツがない状態でロード中であることを示す
loading indicatorはvueコンポーネントがロードされている最中に表示されるものなので、vueコンポーネントが使えるハズがありませんでした。
その時点では存在しないものを使おうとしていたわけですね。
試しにhtmlファイルを作成して読み込んでみたところうまくいきました。
nuxt.config.jsと同じ階層、componentやassetsなんかに置いても問題なく読み込まれます。ぐぐる前にちゃんと読まないから・・・
解決策
どうしてもという場合はカスタムコンポーネントをHTMLで実装してloading indicatorに指定することで解決できます。
HTMLを指定する場合はカスタムコンポーネントを指定するのと同じように書けば読み込まれます。nuxt.config.jsloadingIndicator: '~/CustomLoading.html',ただ今回は一から作るほど時間的余裕がないのでNuxt.jsの実装してくれたSpinkitで実装することにしました。vue-loadingを使って実装されている部分もSpinkitを使用するようにしました。
Nuxt.jsではloading indicatorで使えるようにSpinkitをHTMLで実装してくれており、とても簡単に実装できます。
使えるSpinkitの一覧はこちらnuxt.config.jsloadingIndicator: { name: 'chasing-dots', color: '#f24e1e', background: 'white', }vue-loadingの眩しい笑顔が刺さる・・・
- 投稿日:2020-10-20T20:15:47+09:00
Vue.js始め方~ちゃんとインストールする方法~
1000番煎じくらいだけど社内用に15分で作ったやつをとりあえず残します
Node.js をインストール
ここからインストーラーをダウンロード
自分の OS のインストーラーを選択
LTS の方を選んだ方がいい!
Mac の場合ターミナルで以下のコマンドを実行
brew install nodevue-cli をインストール
コマンドプロンプト、ターミナルを起動して以下のコマンドを実行
これ以降はコマンドプロンプト、ターミナルで手順を行う
npm i -g @vue/cli早速プロジェクト作成!
適当なフォルダ/ディレクトリに移動して以下のコマンドを実行
vue create new-projectnew-project は適当な名前で OK
なんか色々質問されるけど適当に Enter 押しとけば OK!
ローカルサーバーをうごかす
作成されたフォルダに移動
cd new-projectサーバー起動!
npm run serveアクセスしてみる
なんか適当にいじってみる
HelloWorld.vue を開く
new-project/src/App.vue
4 行目の msg の中身を書き換える
- <HelloWorld msg="Welcome to Your Vue.js App"/> + <HelloWorld msg="Vue.js始めてみた!!"/>勝手にブラウザが変更される!
おわり
- 投稿日:2020-10-20T16:13:52+09:00
laravel8系でVue.jsを連携するまで 備忘録
前提知識
Laravel6.0以降は自前でVue.jsをインストールし、resources/js/app.jsを編集する必要がある。(前は標準搭載
インストール→コンパイル→VueComponentの作成→app.jsで接続設定→bladeのテコ入れ→連携の確認
インストール
npm:パッケージマネージャー
package.jsonで使用しているパッケージの名前とバージョンに関する情報が書かれていて、それに基づきパッケージがinstallされる。
あと古いモノをアップデートしてくれるらしい。ルートディレクトリで
npm install以上でインストール完了。
ただし同じ環境を作る際は以下の視点が必要
https://blog.minimalcorp.com/users/jigen/posts/6f325dc9b8a00370b6aedf47a34cb3cevuejsは別にコンポーネントのコンパイルが必要
npm run devnpm run hothotの場合、Hot Module Replacement(HMR)有効な状態でビルドされ、リアルタイムで編集が反映される
Vueコンポーネントを作成
resources/jsディレクトリ配下にcomponentsフォルダ作成しそこにVueコンポーネントを作成
以下例resources/js/components/Sample.vue<template> <div> <h1>Sample Component</h1> </div> </template> <script> export default { mounted() { console.log('Component mounted.') } } </script>app.jsの編集
これまで 参考:https://www.larajapan.com/2019/10/14/app-jsとbootstrap-js/
https://qiita.com/shonansurvivors/items/1715a483ac4298162ccdVue.jsとVueコンポーネント(Sample.vue)を使用するよう定義。
resources/js/app.jsimport './bootstrap' import Vue from 'vue' import Sample from './components/Sample' Vue.component('sample-component',require('./components/Sample.vue').default); const app = new Vue({ el: '#app', components: { Sample } });memo:ネットで参考にしたrequireとwindowの方法では繋がらなかった
blade
例を見た方が早いのでwelcome.blade.phpを編集
welcome.blade.php<!DOCTYPE html> <html lang="{{ str_replace('_', '-', app()->getLocale()) }}"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="csrf-token" content="{{ csrf_token() }}"> <title>Laravel</title> </head> <body> <div id="app"> <sample-component></sample-component> </div> <script src="{{ mix('/js/app.js') }}"></script> </body> </html>ポイント
・<meta name="csrf-token" content="{{ csrf_token() }}">
・<script src="{{ asset('js/app.js')}}">
・<div id="app"><sample-component></example-component></div>
それぞれ、
・head内metaタグにcsrf対策のコードの埋め込み
・Vueで描画する領域としてdivタグを設置
・laravel mixがコンパイルする先のファイルの指定を行っている参考:https://www.ritolab.com/entry/171
トップページにSample.Vueの内容が表示されているか確認。
これでLaravelとVueが連携できたことが確認できる
- 投稿日:2020-10-20T14:08:24+09:00
子コンポーネントのデザインオプションを親コンポーネントから切り替える[Vue.js, Vuetify]
Vuetifyのデザインコンポーネントはとても便利で、オプションをちょっと書くだけでUIのデザインや機能まで簡単にカスタマイズできます。
さらに、少し工夫するだけで親コンポーネントからもオプション類を操作できるため、その手順をまとめます。やりたいこと
こんな感じで、バレーボールのポジションを選択する
v-select
だけのコンポーネントを作ります。
(なぜバレーボールなのかは無視してください)
子コンポーネント<template> <v-select prepend-icon=" mdi-volleyball " :items="positionList" label="ポジション" ></v-select> </template> <script> export default { data() { return { positionList: [ 'ウィングスパイカー', 'ミドルブロッカー', 'セッター', 'リベロ', ], } }, } </script>※本来は子コンポーネントで選択された値を親に返すために
v-model
でバインドしたり$emit
を用いた処理を書いたりしないといけないですが、本題から外れるため省略しています。この入力フォームを開くと以下のように選択リストが展開され、一つのポジションを選択できます。
ここで、
v-select
はmultiple
というオプションを与えると複数選択できるフォームになります。子コンポーネント(抜粋)<template> <v-select prepend-icon=" mdi-volleyball " :items="positionList" label="ポジション" multiple ></v-select> </template>「親Aからこのコンポーネントを呼ぶときは単一選択でよいが親Bから呼ぶときは複数選択したい」ということもあるかと思います。例えば、「私はこのポジションです」という使い方なら単一でよいですが、「経験したことがあるポジション」を選択するときは、複数選択したいでしょう。
この
multiple
を、親コンポーネントから切り替えます。propsでBoolean値を渡す
この
multiple
、Vuetify公式を見るとBoolean型でコントロールされていることがわかります。したがって、
multiple
を何らかのBoolean型の変数でバインドし、かつその変数を親から与えることで、マルチとシングルを切り替えることができそうです。よって、親・子のコードは以下の通りになります。
親A(single)<position-selecer> </position-selecer>親B(multiple)<position-selecer :multiple-flg="true" > </position-selecer>子(position-selecer)<template> <v-select prepend-icon=" mdi-volleyball " :items="positionList" label="ポジション" :multiple="multipleFlg" ></v-select> </template> <script> export default { props: { multipleFlg: { type: Boolean, default: false, }, }, data() { return { positionList: [ 'ウィングスパイカー', 'ミドルブロッカー', 'セッター', 'リベロ', ], } }, } </script>まず、子の
v-select
のmultiple
を任意のBoolean型の変数にバインドします。これを親から受け取りたいのでpropsに指定するのですが、普通に記述するだけだと子はすべてString型として受け取ってしまい、true/falseとして解釈できません。これは、propsをオブジェクト形式で記述し、type
をBoolean
と指定することで解決します。また、今回は明示的な指定があったときだけマルチにしたいので、デフォルト値をfalseにしています。
次に親側の記述についてです。
上述のとおり子のデフォルトはmultiple: false
であるため、何もしなければシングルで表示されます。なので親Aは特に何か指定する必要はありません。他方、親Bはマルチで呼びたいため
multiple-flg
にtrue
を設定しています。このとき、普通は静的な値をpropsで渡す際に:multiple
のようにバインドする必要はないのですが、今回のように子がオブジェクト形式で型を指定してpropsを受け取るような場合は、バインドした状態で渡す必要があります。(特にscript部で何かする必要はなく、template部で形式的にバインドするだけOKです)以上のようにすることで、親からの呼び出しに応じて子のオプションを切り替えて使うことができました!
細かいデザイン的な要素も親から与えることで決定できるため、子コンポーネントの再利用性が高まると思います。参考
- 投稿日:2020-10-20T11:44:07+09:00
[メモ] error: Unexpected console statement (no-console) と言われるとき
Vue.js+Electronでbuildしたときに、console.logで
error: Unexpected console statement (no-console) at src\components\**.vue
と叱られるときのメモ。// eslint-disable-line no-console
をconsole.logの書いてある行に追加すれば解消できる。
ただ、何もしなくてももう一度buildすると問題なく通る。
下のような関数を作って、console.logの代わりに呼んでもよいかもしれない。function consoleLog(...theArgs) { console.log(theArgs); // eslint-disable-line no-console }もしもコンソールログが配列になるのが気になるなら展開してもよいかもしれない。
function consoleLog(...theArgs) { const len = theArgs.length; switch (len) { case 1: console.log(theArgs[0]); // eslint-disable-line no-console break; case 2: console.log(theArgs[0], theArgs[1]); // eslint-disable-line no-console break; default: console.log(theArgs); // eslint-disable-line no-console break; } }
- 投稿日:2020-10-20T11:44:07+09:00
error: Unexpected console statement (no-console) と言われるとき
Vue.js+Electronでbuildしたときに、console.logで
error: Unexpected console statement (no-console) at src\components\**.vue
と叱られるときのメモ。// eslint-disable-line no-console
をconsole.logの書いてある行に追加すれば解消できる。
ただ、何もしなくてももう一度buildすると問題なく通る。
下のような関数を作って、console.logの代わりに呼んでもよいかもしれない。function consoleLog(...theArgs) { console.log(theArgs); // eslint-disable-line no-console }もしもコンソールログが配列になるのが気になるなら展開してもよいかもしれない。
function consoleLog(...theArgs) { const len = theArgs.length; switch (len) { case 1: console.log(theArgs[0]); // eslint-disable-line no-console break; case 2: console.log(theArgs[0], theArgs[1]); // eslint-disable-line no-console break; default: console.log(theArgs); // eslint-disable-line no-console break; } }
- 投稿日:2020-10-20T10:52:26+09:00
vue.jsにおけるリファクタリングのお話
前書き
某web系受託会社で
一年半ほどvue.js/firebaseで開発アルバイト(長期インターン)をしています(大学生)。
私が開発始めるよりも前に動いてたサービスに対してずっと開発してきましたが、ついにリファクタリングをごりごりやる流れになっており、その流れでこの手法良くない?という共有(備忘録)と、質問でもっと良い方法/改善案募集したく記事を書こうと思います。今回のモノでは、
・可読性向上のためファイル分離
・コンポーネント指向を生かし共通部分を極力排除
・保守性向上のためのルール,mixin化を目標にやっていきます
コンポーネント指向
コンポーネント指向参考記事
アトミックデザイン参考記事
まず前提としてコンポーネント指向/アトミックデザインを目指していこうね。
というのがこのサービス開発における方向性です。私も社員から教えられ実際この考えに賛同し、趣味プロの中でも時間があればこういう方向性で開発/リファクタリングを進めてます。ざっくり説明すると(間違ってたらスイマセン)、
ソースコード(ex:HogeHogeButton.vue)を入力と出力を依存なく作成しそれをそれのみでも動くように作り、可能な範囲で小さく作成することにより、atom(原子)を目指しもう依存なく動くパーツを作成したあと組み合わせていくとコスパ良いコードだよねーって感じです。オブジェクト指向
java,C++はオブジェクト指向だ。とかjsは関数型だ、
オブジェクト指向だとか話したいわけではなく、オブジェクト指向の継承や木構造で表されるその様子についてフワフワとした理解があれば大丈夫です。実際の開発されているコードの現場
人の入れ替わりも多少激しい会社ですので最初からみんなが、
そのような開発をしていたわけではありません。
アトミックデザインを最初から目指してコーディングしていたわけではないので、
一つのファイルにどでかい部品(決してatomでない)が2~3個搭載されており、
またそのファイル以外にも似たようなどでかい部品が存在しているが、似ているが,決して同様なコードではないことが多々あると思います。(私のところはそうでした)
また、リファクタリングの工程を区切り区切りで進捗を出し、長い作業をし手が離せないというのはリファクタリングの現場ではあまりよろしくないなーともありここからそのままのatomicなコンポーネント指向にもっていくのは少々ミスマッチな気がします。では実際にここから、私が行った工程を順を追ってやっていきます。
擬似ファイル構成(以下、hogehogeproject)
hogehogeProjectview/ |-hoge1.vue |-hoge2.vue |-hoge3.vue components/ | ... |部品の分離
一つのファイルにどでかい部品があるわけですので、可読性が著しく悪いです。
せっかくvue.jsというコンポーネント指向なフレームワークを使っているので、どうにか分けて動作していくことを目指していきたいです。
どうやら今回のhogehogeprojectではbigHoge
という部品がhoge1,hoge2,hoge3に存在してそうだなというのが分かりました。(実際には名称がついてないこともあると思います。共通したコード群としてbigHoge
とします)
依存を取り除き、一つのコンポーネントとして分けることが可能そうなので、props等を使いhoge1,hoge2,hoge3からbigHoge1,bigHoge2,bigHoge3を分けます。
(propsや$emit等で頑張りましょう)hogehogeProjectview/ |-hoge1.vue |-hoge2.vue |-hoge3.vue components/ |-bigHoge1.vue |-bigHoge2.vue |-bigHoge3.vue部品の共通部分排除
ここが肝です。
共通部分を持っているのに分けられている部品はかなり厄介です。
そこの共通部分が修正対象になった時に、n倍コード量かかりそうで、厄介です。
そこで共通部分を出来るだけ排除したいとみなさん思うでしょうが、
どうすれば良いでしょうか?
アトミックデザインにし、共通部分は共通のコンポーネントのみでしか記入されないとすると、
上手く改善は出来そうですが、今からするには全てに対して行う必要がありかなり重たいタスク(手が止められない)になりそうです。
ここで3つの共通部分を持ったコンポーネントを作ったら、そこに対しては改善出来そうです。
ただこの方法には弱点があり、bigHoge1とbigHoge2の方がbigHoge1とbigHoge3より共通部分が多そうな場合共通部分を残したまま共通コンポーネントを作成することになり、もしこれが3つではなくn個の場合、かなり余剰な共通部分は残ってしまいそうです。似ているコンポーネントの共通Wrapperを作成する
ここで、共通部分を排除することを目的とした木構造を考えます。
wrapperBigHoge |-bigHoge1 |-bigHoge2 |-bigHoge3の恩恵は受けたいですが、
wrapperBigHoge |-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 |-bigHoge3このように、オブジェクト指向の継承のように、
入れ子の構造を守った場合、wrapperBigHogeの恩恵はそのまま受けいられそうです。
そこで下記のような構造を考えます。wrapperBigHoge |-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 |-wrapperBigHoge2 |-bigHoge3このようにしても、
bigHoge1,bigHoge2,bigHoge3は、wrapperBigHogeの恩恵を受けいられますし、
先の問題だった余剰な共通部分が存在する問題も、この親子関係を作っていくとした場合階層の違いは存在しても、マクロでの共通部分、ミクロでの共通部分と分けて考えることができそうです。また、この方法の一番の利点は共通部分排除の段階では、
自分の親と対象にする子供のみを考えれば良いという点にあると思います。|-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 views/ |-hoge3.vue |-hoge4.vueこのように、とりあえず、bigHoge1,bigHoge2をhoge1,hoge2から分離し
その共通コンポーネント"wrapperBigHoge1"を作成することができました。
また、次に|-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 |-wrapperBigHoge2 |-bigHoge3 |-bigHoge4として、既存のwrapperBigHoge1に将来的に統合できそうなことを意識して、
wrapperBigHoge2を作成します。
次に、wrapperBigHoge |-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 |-wrapperBigHoge2 |-bigHoge3 |-bigHoge4とし、wrapperBigHogeを作成とすることができ、
小さな領域で開発パフォーマンスを改善することができ、また如何様な拡張性ももっており
自由度高くリファクタリングをしていくことが可能なので、
コンポーネント指向で大事なそれが部品として動くという要素を残しつつ、いずれ時がきた時に上位(親)のwrapperを作成することも視野に入れる
ことを考え開発していくとスムーズにできますmixin化
先の段階で、
将来的に木構造のような深さがn(ある程度大きい値)になることが、予想されます。
いま開発している段階で、深さが最大2ぐらいだからemit
で貰ったものを、親にごちゃごちゃして返せばよいか...
という発想だといずれそれをリファクタリングする工数が増えるでしょう
現状のvueで親子関係の疎通はrefとemit,props
が代表的なのであると思います。
個人的にはemit
とprops
で作成していくことをおすすめしますが、
propsに関しては親から貰った要素をそのまま子にpropsとして渡すことは容易だと思いますが、emitに関してはそうはいきません。
emitは親に命令(以降order)とvalueを渡すことで、親が特定のイベントを発火させることができます。
ただこの場合親から貰ったemitをそのまま子に発火させにいくのは多少手間が必要(methodsに記入して,そこに発火させてそのmethodsの中に命令とvalueを渡してー...という感じです)
(一行で済ませようとすると、valueは取れても、orderはとれません
ex: @hoge="emit("hoge", value)", @hoge="emit" 等もできない)そこで以下のことを考えます。
・普段コンポーネントを作成しているように、emitを呼び出したい
・普段コンポーネントから貰ってるvalueをそのまま受け取りたい
・emitの受け流しは極力コードを記入せずに済ませたいこれを満たすようにfunctionを作成していくと,
start (order, value) { let pack = {} pack.order = order pack.value = value this.propagate(pack) } propagate (value) { if (value.order) { this.$emit(value.order, value) } } end (value) { this.$emit(value.order, value.value) }これをすることで、いつもemitしてたところを、
startに、伝播をpropagateに、最後通常通りに使用したいので伝播されたものをendでemitすると、
考えていたことを実現することができ、
これをmixin化することで、全ての木構造のコンポーネントに対して適用することができます。
mixinについての詳しい話は参考記事を見ていただきたいですが、今回のだと例えばHogeHogemixin.jsexport { methods: { start (order, value) { let pack = {} pack.order = order pack.value = value this.propagate(pack) } propagate (value) { if (value.order) { this.$emit(value.order, value) } } end (value) { this.$emit(value.order, value.value) } } }とし、全ての木構造コンポーネントに
mixins:[HogeHogemixin]
とすれば動くと思われます。ルール決め
木構造なコンポーネントを作成してきましたが、
ある程度ルールを作成するとスムーズにこれからの開発ライフを楽しむことができます。
ここで属性の定義をします。
wrapper: 子供、親と自由に持つことが出来るが通常のコンポーネントとのように、子供以外から自由に呼び出されたりしてはいけない
instance: 子供を持つことを推奨しない(禁止ではない)がこの木構造に含まれていないvueファイルから呼び出されることを前提に作られる
この二つを定義することで、前にやったwrapper化したファイル等は属性で表すとこのようになります。
wrapperBigHoge |-wrapperBigHoge1 |-bigHoge1 |-bigHoge2 |-wrapperBigHoge2 |-bigHoge3 |-bigHoge4 | V wrapper |-wrapper |-instance |-instance |-wrapper |-instance |-instanceとなります。
このように分けることで、
wrapperはstart,propagateのみを持つ
instanceはendのみを持っている状態が好ましい
と出来るので、どこかで伝播が止まっているな?という事例が少なくなります。後書き
私もこのリファクタリングをして運用過程まではなかなか行けてないので、
そこで出てきた問題点などがあった場合随時記入していこうと思います。
また今回やりたい事象/解決した問題点をもっと良い方法で解決できるようでしたら、コメント等で教えてくださると助かります。
- 投稿日:2020-10-20T01:07:34+09:00
ブラウザ側で画像をリサイズしてFirebase Storageにアップロードする(Vue.js)
browser-image-compressionを使って、簡単に画像のリサイズをすることができます。
あまりの手軽さに感動したのでまとめます。
browser-image-compression
の導入と利用については既に素晴らしい記事がありますので、ぜひご参考ください。
当記事でもbrowser-image-compression
の使い方はさらっと紹介しつつ、Firebase Storageへのアップロードまで一気通貫でまとめます。なお、以下がプロジェクトに導入、設定済みであることを前提としています
- Vuetify
- Firebase
browser-image-compressionの導入
npmもしくはyarnで導入します。
yarn add browser-image-compressionリサイズ・アップロード
(準備)リサイズ用モジュールの作成
imageCompression.jsimport imageCompression from 'browser-image-compression' export default { async getCompressImageFile(file) { const options = { maxSizeMB: 0.8, //最大ファイルサイズ maxWidthOrHeight: 600, //最大縦横値 } return await imageCompression(file, options) }, }リサイズしたい目的によって、単にファイルサイズを小さくしたいこともあれば、画像のサイズを一定以下にしないといけないこともあると思います。
今回私が利用したケースにおいては画像サイズが重要だったため、maxWidthOrHeightを指定しています。maxSizeMBは念の為に指定していましたが、なくても良かったかもです。
もっと言うとこれぐらいの記述量なら外出ししなくてもよかったかも……(本題)アップロード処理
先ほどのリサイズ処理をアップロード前に呼んでリサイズを行い、リサイズ後画像をアップロードします。
流れを大きく分けると以下の3段階です。
- フォーム上で選択された画像をscriptで受け取る
- 受け取った画像をリサイズする
- リサイズ後の画像をFirebase Storageにアップロードする
先にコード全体を掲載し、そのうえで順に触れていきます。
template部分<v-file-input accept="image/*" label="写真" prepend-icon="mdi-camera" @change="uploadImage" ></v-file-input>script部分import { storage } from '@/plugins/firebase' import imageCompression from '@/imageCompression.js' //中略 async uploadImage(fileInfo) { //選択された画像の情報を取得 this.imageInfo = fileInfo //画像をリサイズする console.log('start compress') this.compressedImage = await imageCompression.getCompressImageFile( this.imageInfo ) console.log('finished compress') //storageへの参照 const storageRef = storage.ref() const xxxImagesRef = storageRef.child('xxx/' + uniqueImageName) //アップロードしてURLを取得 //コールバック関数内からthisへ参照できないため、selfに退避する let self = this xxxImagesRef.put(this.compressedImage).then(async function (snapshot) { self.imageUrl = await snapshot.ref.getDownloadURL() }) },(参考)firebase.jsimport Vue from 'vue' import { firestorePlugin } from 'vuefire' import firebase from 'firebase/app' import 'firebase/firestore' import 'firebase/storage' Vue.use(firestorePlugin) const firebaseApp = firebase.initializeApp({ //xxx部分は各々の内容で置換 apiKey: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', authDomain: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', databaseURL: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', projectId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', storageBucket: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', messagingSenderId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', appId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', measurementId: 'xxxxxxxxxxxxxxxxxxxxxxxxxxx', }) export const db = firebaseApp.firestore() export const storage = firebaseApp.storage()※私はfirebase.jsを切り出していますが、main.jsに同内容を記述している方はよしなに読み替えてください。
1.フォーム上で選択された画像をscriptで受け取る
まずは画像アップロードフォームを作ります。Vuetifyでさくさく作ります。
template部分<v-file-input accept="image/*" label="写真" prepend-icon="mdi-camera" @change="uploadImage" ></v-file-input>
v-file-input
は、特に複雑なことをしなくても、このような記述だけで任意のメソッドにファイルの情報を渡すことができます。今回の例であれば、ファイルが選択された(あるいは変更された)ときにそのファイルをuploadImage
に渡し、リサイズとアップロードを行います。
※@change
だとファイルを変更するたびにアップロードが発生するので、Storage上にゴミが溜まります。ユースケース次第で、@change
ではリサイズのみに留めてもよいでしょう。続いて、
v-file-input
で選択された画像ファイルを受け取るための記述は以下のみです。引数としてファイルを受け取ります。uploadImage_ファイル受け取りasync uploadImage(fileInfo) { //選択された画像の情報を取得 this.imageInfo = fileInfoここの概念がまだ理解できていません。単にファイルの属性情報などだけを受け取るのではなく、まさにファイルとして振る舞うモノを変数として受け取っているということに違和感があります……
2.受け取った画像をリサイズする
uploadImage_リサイズ処理//画像をリサイズする this.compressedImage = await imageCompression.getCompressImageFile( this.imageInfo )script上部でimportした
imageCompression
の中のgetCompressImageFile
を動かします。
1.で受け取った画像ファイル(this.imageInfo
)を引数にし、戻り値(=リサイズ後の画像)を新しい変数に格納しています。3.リサイズ後の画像をFirebase Storageにアップロードする
最後は2.で得られたリサイズ後の画像をアップロードします。
uploadImage_アップロード処理//storageへの参照 const storageRef = storage.ref() const xxxImagesRef = storageRef.child('xxx/' + uniqueImageName) //アップロードしてURLを取得 //コールバック関数内からthisへ参照できないため、selfに退避する let self = this xxxImagesRef.put(this.compressedImage).then(async function (snapshot) { self.imageUrl = await snapshot.ref.getDownloadURL() })まずはともあれStorageへの参照を作ります。Storage内部の任意の子ディレクトリに保存したい場合、既存の参照からさらに
child()
メソッドでもう一段階深い参照を作ることができます。(xxxの部分は任意のディレクトリ名に読み替えてください)適当な参照が作れたら、その参照の
put()
メソッドでファイルをアップロードします。
単にアップロードするだけでなく、アップロードしたファイルのダウンロード用URLも欲しかったので、コールバック関数内でgetDownloadURL()
によってURLを取得しています。一連の処理が完了すると、Storageは以下のようになっています。
もとの画像が17MBほどだったので、かなりの容量を節約することができました!
参考
- 投稿日:2020-10-20T00:30:32+09:00
Vuetifyのcodeタグの装飾を止める
Nuxt/contentとVuetifyを使ってブログを作ろうとしていたけど、
codeタグの文字が勝手に装飾されてしまってprismのコードハイライトが大変見えにくくなっていた。解決法
nuxt.config.jsに以下の設定を追加
nuxtconfig.js... vuetify:{ customVariables: ['~/assets/variables.scss'], treeShake: true }次に/assets/variables.scssに(なければ作成)以下を追加
~/assets/variables.scss$code-background-color:initia; $code-color:initia;以上で多分直る
解説
公式ドキュメントにはパッと見た感じ
Vuetifyには、複数の標準要素のカスタムスタイルがあります。
https://vuetifyjs.com/en/styles/content/としか書かれておらず、無効化する方法なんて一切書かれていない
しかし、公式ドキュメントと半日にらめっこしていると、
sass変数一覧にしれっとSASS Variables
Name Defalt $code-background-color #FBE5E1 !default; $code-color #C0341D !default; って書いてあった。
自分の読解力とかエンジニア力が足りなかっただけだと思うが非常にわかりにくいと感じた。あとは
https://vuetifyjs.com/en/features/sass-variables/
に従って適当にしてやったらできた。