20201020のvue.jsに関する記事は11件です。

【Vue】The template root disallows 'v-for' directives. 解決法

テンプレートルートは、'v-for'ディレクティブを許可しません。

分割したコンポーネントでv-forを使ったところ、このようなエラーが、、、:point_down:

The template root disallows ‘v-for’ directives.
(翻訳にかけると「テンプレートルートは、'v-for'ディレクティブを許可しません」と言う意味)

スクリーンショット 2020-10-20 21.00.42.jpg

なんじゃこりゃ??

コードはこんな感じ

<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を囲いました。。

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

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の中でデータベースに関わる部分は以下の通りです。

.env
DB_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で作成したものを記入します。

スクリーンショット 2020-10-19 12.57.42.png

.env
DB_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)
.env
DB_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 migratephp 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ファイルにて

.env
APP_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_DEBUGtrueなら、作成中のアプリケーションでエラーが起きると、エラーが起きるまでの過程とエラーメッセージを画面に表示してくれます。
falseなら、シンプルなエラーページが表示されるだけです。
サービスを公開する時には、falseにするのを忘れないようにしましょう。

スタックトレース (stack trace)とは

APP_DEBUG=trueの場合
スクリーンショット 2020-10-20 20.52.14.png

APP_DEBUG=falseの場合
スクリーンショット 2020-10-20 20.55.27.png

以上で環境構築編は終了です。あとはジャンジャン開発していきましょう!
続く

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

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.js
  loading: {
    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は色と高さを指定しており、プログレスバーが表示されています。それを他のコンポーネントで使われているくるくると合わせることになりました。
それがこれ
kurukuru.gif

これはvue-loadingを使って実装されています。

loadingの方は公式ドキュメントのカスタムコンポーネントの項目を参考にして実装し、すんなりといけたのですがloading indicatorのほうで問題発生。

最初に同じようにloading indicatorでvueコンポーネントを指定してみました。

nuxt.config.js
loadingIndicator: '使いたいカスタムコンポーネント',

すると更新した時にエラーが発生

Cannot use import statement outside a module

ぐぐってもいまいち出ない・・・

原因

エラーメッセージを読んだところ、「モジュールの外ではインポートはできないよ」でした

・・・

初期表示など、vueコンポーネントがロード中で表示するコンテンツがない状態でロード中であることを示す

loading indicatorはvueコンポーネントがロードされている最中に表示されるものなので、vueコンポーネントが使えるハズがありませんでした。
その時点では存在しないものを使おうとしていたわけですね。
試しにhtmlファイルを作成して読み込んでみたところうまくいきました。
nuxt.config.jsと同じ階層、componentやassetsなんかに置いても問題なく読み込まれます。

ぐぐる前にちゃんと読まないから・・・

解決策

どうしてもという場合はカスタムコンポーネントをHTMLで実装してloading indicatorに指定することで解決できます。
HTMLを指定する場合はカスタムコンポーネントを指定するのと同じように書けば読み込まれます。

nuxt.config.js
 loadingIndicator: '~/CustomLoading.html',

ただ今回は一から作るほど時間的余裕がないのでNuxt.jsの実装してくれたSpinkitで実装することにしました。vue-loadingを使って実装されている部分もSpinkitを使用するようにしました。
Nuxt.jsではloading indicatorで使えるようにSpinkitをHTMLで実装してくれており、とても簡単に実装できます。
使えるSpinkitの一覧はこちら

nuxt.config.js
loadingIndicator: {
  name: 'chasing-dots',
  color: '#f24e1e',
  background: 'white',
}

タイトルなし2.gif

vue-loadingの眩しい笑顔が刺さる・・・

スクリーンショット 2020-10-20 21.52.00.png

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

Vue.js始め方~ちゃんとインストールする方法~

1000番煎じくらいだけど社内用に15分で作ったやつをとりあえず残します

Node.js をインストール

ここからインストーラーをダウンロード

自分の OS のインストーラーを選択

LTS の方を選んだ方がいい!

install_node.png

Mac の場合ターミナルで以下のコマンドを実行

brew install node

vue-cli をインストール

コマンドプロンプト、ターミナルを起動して以下のコマンドを実行

これ以降はコマンドプロンプト、ターミナルで手順を行う

npm i -g @vue/cli

早速プロジェクト作成!

適当なフォルダ/ディレクトリに移動して以下のコマンドを実行

vue create new-project

new-project は適当な名前で OK

なんか色々質問されるけど適当に Enter 押しとけば OK!

ローカルサーバーをうごかす

作成されたフォルダに移動

cd new-project

サーバー起動!

npm run serve

アクセスしてみる

http://localhost:8080/

vuelocal.png

なんか適当にいじってみる

HelloWorld.vue を開く

new-project/src/App.vue

4 行目の msg の中身を書き換える

- <HelloWorld msg="Welcome to Your Vue.js App"/>
+ <HelloWorld msg="Vue.js始めてみた!!"/>

vuestart.png

勝手にブラウザが変更される!

おわり

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

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/6f325dc9b8a00370b6aedf47a34cb3ce

vuejsは別にコンポーネントのコンパイルが必要

npm run dev
npm run hot

hotの場合、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/1715a483ac4298162ccd

Vue.jsとVueコンポーネント(Sample.vue)を使用するよう定義。

resources/js/app.js
import './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の内容が表示されているか確認。

http://localhost:8000/

これでLaravelとVueが連携できたことが確認できる

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

子コンポーネントのデザインオプションを親コンポーネントから切り替える[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-selectmultipleというオプションを与えると複数選択できるフォームになります。

子コンポーネント(抜粋)
<template>
  <v-select
    prepend-icon=" mdi-volleyball "
    :items="positionList"
    label="ポジション"
    multiple
  ></v-select>
</template>

image.png

「親Aからこのコンポーネントを呼ぶときは単一選択でよいが親Bから呼ぶときは複数選択したい」ということもあるかと思います。例えば、「私はこのポジションです」という使い方なら単一でよいですが、「経験したことがあるポジション」を選択するときは、複数選択したいでしょう。

このmultipleを、親コンポーネントから切り替えます。

propsでBoolean値を渡す

このmultipleVuetify公式を見るとBoolean型でコントロールされていることがわかります。

image.png

したがって、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-selectmultipleを任意のBoolean型の変数にバインドします。これを親から受け取りたいのでpropsに指定するのですが、普通に記述するだけだと子はすべてString型として受け取ってしまい、true/falseとして解釈できません。これは、propsをオブジェクト形式で記述し、typeBooleanと指定することで解決します。

また、今回は明示的な指定があったときだけマルチにしたいので、デフォルト値をfalseにしています。

次に親側の記述についてです。
上述のとおり子のデフォルトはmultiple: falseであるため、何もしなければシングルで表示されます。なので親Aは特に何か指定する必要はありません。

他方、親Bはマルチで呼びたいためmultiple-flgtrueを設定しています。このとき、普通は静的な値をpropsで渡す際に:multipleのようにバインドする必要はないのですが、今回のように子がオブジェクト形式で型を指定してpropsを受け取るような場合は、バインドした状態で渡す必要があります。(特にscript部で何かする必要はなく、template部で形式的にバインドするだけOKです)

以上のようにすることで、親からの呼び出しに応じて子のオプションを切り替えて使うことができました!
細かいデザイン的な要素も親から与えることで決定できるため、子コンポーネントの再利用性が高まると思います。

参考

Vueのpropsの書き方・使い方について解説

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

[メモ] 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;
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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;
  }
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

vue.jsにおけるリファクタリングのお話

前書き

某web系受託会社で
一年半ほどvue.js/firebaseで開発アルバイト(長期インターン)をしています(大学生)。
私が開発始めるよりも前に動いてたサービスに対してずっと開発してきましたが、ついにリファクタリングをごりごりやる流れになっており、その流れでこの手法良くない?という共有(備忘録)と、質問でもっと良い方法/改善案募集したく記事を書こうと思います。

今回のモノでは、

・可読性向上のためファイル分離
・コンポーネント指向を生かし共通部分を極力排除
・保守性向上のためのルール,mixin化

を目標にやっていきます

コンポーネント指向

コンポーネント指向参考記事
アトミックデザイン参考記事
まず前提としてコンポーネント指向/アトミックデザインを目指していこうね。
というのがこのサービス開発における方向性です。私も社員から教えられ実際この考えに賛同し、趣味プロの中でも時間があればこういう方向性で開発/リファクタリングを進めてます。

ざっくり説明すると(間違ってたらスイマセン)、
ソースコード(ex:HogeHogeButton.vue)を入力と出力を依存なく作成しそれをそれのみでも動くように作り、可能な範囲で小さく作成することにより、atom(原子)を目指しもう依存なく動くパーツを作成したあと組み合わせていくとコスパ良いコードだよねーって感じです。

オブジェクト指向

java,C++はオブジェクト指向だ。とかjsは関数型だ、
オブジェクト指向だとか話したいわけではなく、オブジェクト指向の継承や木構造で表されるその様子についてフワフワとした理解があれば大丈夫です。

実際の開発されているコードの現場

人の入れ替わりも多少激しい会社ですので最初からみんなが、
そのような開発をしていたわけではありません。
アトミックデザインを最初から目指してコーディングしていたわけではないので、
一つのファイルにどでかい部品(決してatomでない)が2~3個搭載されており、
またそのファイル以外にも似たようなどでかい部品が存在しているが、似ているが,決して同様なコードではないことが多々あると思います。(私のところはそうでした)
また、リファクタリングの工程を区切り区切りで進捗を出し、長い作業をし手が離せないというのはリファクタリングの現場ではあまりよろしくないなーともありここからそのままのatomicなコンポーネント指向にもっていくのは少々ミスマッチな気がします。

では実際にここから、私が行った工程を順を追ってやっていきます。

擬似ファイル構成(以下、hogehogeproject)

hogehogeProject
view/
 |-hoge1.vue
 |-hoge2.vue
 |-hoge3.vue
components/
 |
 ...
 |

部品の分離

一つのファイルにどでかい部品があるわけですので、可読性が著しく悪いです。
せっかくvue.jsというコンポーネント指向なフレームワークを使っているので、どうにか分けて動作していくことを目指していきたいです。
どうやら今回のhogehogeprojectではbigHogeという部品がhoge1,hoge2,hoge3に存在してそうだなというのが分かりました。(実際には名称がついてないこともあると思います。共通したコード群としてbigHogeとします)
依存を取り除き、一つのコンポーネントとして分けることが可能そうなので、props等を使いhoge1,hoge2,hoge3からbigHoge1,bigHoge2,bigHoge3を分けます。
(propsや$emit等で頑張りましょう)

hogehogeProject
view/
 |-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化

mixin参考記事
mixin公式

先の段階で、
将来的に木構造のような深さがn(ある程度大きい値)になることが、予想されます。
いま開発している段階で、深さが最大2ぐらいだからemitで貰ったものを、親にごちゃごちゃして返せばよいか...
という発想だといずれそれをリファクタリングする工数が増えるでしょう
現状のvueで親子関係の疎通はrefとemit,propsが代表的なのであると思います。
個人的にはemitpropsで作成していくことをおすすめしますが、
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.js
export {
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のみを持っている状態が好ましい
と出来るので、どこかで伝播が止まっているな?という事例が少なくなります。

後書き

私もこのリファクタリングをして運用過程まではなかなか行けてないので、
そこで出てきた問題点などがあった場合随時記入していこうと思います。
また今回やりたい事象/解決した問題点をもっと良い方法で解決できるようでしたら、コメント等で教えてくださると助かります。

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

ブラウザ側で画像をリサイズして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.js
import 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段階です。

  1. フォーム上で選択された画像をscriptで受け取る
  2. 受け取った画像をリサイズする
  3. リサイズ後の画像を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.js
import 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でさくさく作ります。
image.png

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は以下のようになっています。
image.png

もとの画像が17MBほどだったので、かなりの容量を節約することができました!

参考

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

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/api/vuetify/#sass-variables

って書いてあった。
自分の読解力とかエンジニア力が足りなかっただけだと思うが非常にわかりにくいと感じた。

あとは
https://vuetifyjs.com/en/features/sass-variables/
に従って適当にしてやったらできた。

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