- 投稿日:2020-01-24T20:03:54+09:00
初心者によるプログラミング学習ログ 219日目
100日チャレンジの219日目
twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
219日目は
おはようございます
— ぱぺまぺ@webエンジニアを目指したい社畜 (@yudapinokio) January 23, 2020
219日目
・Vue.jsでアプリ作成講座
・javascript#早起きチャレンジ#駆け出しエンジニアと繋がりたい#100DaysOfCode
- 投稿日:2020-01-24T12:56:08+09:00
【備忘録】vue-cliインストールしてベースプロジェクトを作る
概要
久しぶりにVue.jsで新しくアプリを作ろうと思い立って、
vue-cli
使ってベースのアプリを作ろうと思ったら手順を完全に忘れていた。今後のためにも、備忘録を作っておこうと思う。
環境
- vue-cli 4.1.2
vue-cliインストール(グローバル)
# インストール実行 npm install -g @vue/cli # インストール成功したか確認 vue --version # @vue/cli 4.1.2プロジェクト作成
# プロジェクト作成実行 vue create gtd_mock # https://npm.taobao.org/ というnpmのミラーサーバーを利用してよいか?という内容 # 調べるとこれが原因でこける事例もあるので、使わない設定にした ? Your connection to the default npm registry seems to be slow. Use https://registry.npm.taobao.org for faster installation? NoVue CLI v4.1.2 # プラグイン設定 # babel,eslint のみのデフォルト設定かマニュアル設定をするか?という内容 # Vue-Routerなど利用したかったので、マニュアル設定 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, Router, Vuex, CSS Pre-processors, Linter # TypeScript 利用時に、コンポーネントをクラスで定義する構文を利用する設定 ? Use class-style component syntax? Yes # BabelをTypeScriptにも適用 ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)? Yes # Vue-Router の historyモードは利用しない ? Use history mode for router? (Requires proper server setup for index fallback in production) No # 使用するCSSプリプロセッサの指定 ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with dart-sass) # lint と formatter の指定(ESLint + Prettier) ? Pick a linter / formatter config: Prettier # ESLintの追加設定 ? Pick additional lint features: Lint on save # 設定内容をpackage.jsonにまとめるか、別ファイルに分けるか ? Where do you prefer placing config for Babel, ESLint, etc.? In dedicated config files # 今回の設定を保存するか(次回作成時のため) ? Save this as a preset for future projects? Yes作成時ログ(正常終了)Vue CLI v4.1.2 ✨ Creating project in C:\develop\repo\gtd_mock\gtd_mock. ⚙ Installing CLI plugins. This might take a while... > yorkie@2.0.0 install C:\develop\repo\gtd_mock\gtd_mock\node_modules\yorkie > node bin/install.js setting up Git hooks can't find .git directory, skipping Git hooks installation > core-js@3.6.4 postinstall C:\develop\repo\gtd_mock\gtd_mock\node_modules\core-js > node -e "try{require('./postinstall')}catch(e){}" > ejs@2.7.4 postinstall C:\develop\repo\gtd_mock\gtd_mock\node_modules\ejs > node ./postinstall.js added 1155 packages from 836 contributors and audited 32980 packages in 123.389s found 0 vulnerabilities ? Invoking generators... ? Installing additional dependencies... added 84 packages from 69 contributors and audited 35404 packages in 31.178s found 0 vulnerabilities ⚓ Running completion hooks... ? Generating README.md... ? Successfully created project gtd_mock. ? Get started with the following commands: $ cd gtd_mock $ npm run serveプロジェクト起動
# プロジェクト作成時のログの最後に出力されるコマンド cd gtd_mock npm run serve起動後の画面
まとめ
あまり頻繁にやらないので、ふとやろうと思った時に忘れてしまっていた。
こんなことを避けるためにも備忘録として残すように習慣づけたい。。。
- 投稿日:2020-01-24T11:34:41+09:00
【Django】Django+MySQL+Vue.jsな環境を作るメモ【Python】
システム構成
・Django
・Vue.js
・MySQLdjango-admin startproject config . python manage.py startapp accounts最初の設定
APPSに追加
config/setting.pyINSTALLED_APPS = [ 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', "accounts.apps.AccountsConfig", ]言語とタイムゾーンを日本仕様にする
config/setting.pyLANGUAGE_CODE = 'ja' TIME_ZONE = 'Asia/Tokyo'DjangoのデータベースにMySQLを設定する
pip install pymysql pip install python-dotenvmanage.pyの編集(MySQLの追加)
manage.pyimport pymysql pymysql.install_as_MySQLdb()configの中に.envファイルの作成
config/.envDB_NAME = XXXXX DB_PWD = XXXXX DB_USER = XXXXX DB_HOST = XXXXXsetting.pyの編集
config/setting.pyfrom os.path import join, dirname from dotenv import load_dotenvconfig/setting.pydotenv_path = join(dirname(__file__), '.env') load_dotenv(dotenv_path) DB_NAME = os.environ.get("DB_NAME") DB_PWD = os.environ.get("DB_PWD") DB_USER = os.environ.get("DB_USER") DB_HOST = os.environ.get("DB_HOST") DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': DB_NAME,#データベース名 'USER': DB_USER,#ユーザー名 'PASSWORD': DB_PWD,#パスワード 'HOST': DB_HOST,#ローカルホスト等 'PORT': '3306',#接続ポート } }ロガーの設定をする
config/settings.py#ロギング設定 LOGGING = { "version": 1, "disavle_existing_loggers": False, #ロガーの設定 "logger":{ "django": { "handlers": ["console"], "lebel": "INFO", } }, #accountsのアプリ利用するロガー "diary": { "handlers": ["console"], "level": "DEBUG", }, #ハンドラの設定 "handlers": { "console": { "level": "DEBUG", "class": "logging.StreamHandler", "formatter": "dev" }, }, #フォーマッターの設定 "formatters": { "dev": { "format": "\t".join([ "%(asctime)s", "[%(levelname)s]", "%(pathname)s(Line:%(lineno)d)", "%(message)s" ]) }, }, }ルーティングの設定し、index.htmlへアクセスさせる
config/urls.pyfrom django.contrib import admin from django.urls import path, include urlpatterns = [ path('admin/', admin.site.urls), path("", include("accounts.urls")) ]新規ファイル作成:accounts/urls.py
accounts/urls.pyfrom django.urls import path from . import views app_name = "accounts" urlpatterns = [ path("", views.IndexView.as_view(), name="index") ]新しいディレクトリ『templates』を作成
accounts/templatesに作成新規ファイルindex.htmlを作成
accouts/templates/index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>トップページ</title> </head> <body> <h1>Hello World</h1> </body> </html>確認
python manage.py runserverBootstrapテンプレートを反映させる
staticフォルダの追加
Bootstrapをダウンロード
(https://startbootstrap.com/themes/one-page-wonder)プロジェクト直下にstaticフォルダを新規作成
ダウンロードしたBootStrapをstaticフォルダに入れる
├── accounts ├── config ├── static | ├── css | ├── img | └── vender ├── manage.py静的フォルダが配置されている場所を設定する
config/settings.pySTATICFILES_DIRS = ( os.path.join(BASE_DIR, "static"), )各ページで共通するbase.htmlを作る
accounts/templates/base.html{% load static %} <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no"> <meta name="description" content=""> <meta name="author" content=""> <title>{% block title %}{% endblock %}</title> <!-- Bootstrap core CSS --> <link href="{% static 'vendor/bootstrap/css/bootstrap.min.css' %}" rel="stylesheet"> <!-- Custom fonts for this template --> <link href="https://fonts.googleapis.com/css?family=Catamaran:100,200,300,400,500,600,700,800,900" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Lato:100,100i,300,300i,400,400i,700,700i,900,900i" rel="stylesheet"> <!-- Custom styles for this template --> <link href="{% static 'css/one-page-wonder.min.css' %}" rel="stylesheet"> <!-- My style --> <link rel="stylesheet" type="text/css" href="{% static 'css/mystyle.css' %}"> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script src="https://unpkg.com/vue"></script> {% block head %}{% endblock %} </head> <body> <div id="wrapper"> <!-- ナビヘッダー --> <nav class="navbar navbar-expand-lg navbar-dark navbar-custom fixed-top"> <div class="container"> <a class="navbar-brand" href="{% url 'accounts:index' %}">TITLE</a> <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"> <span class="navbar-toggler-icon"></span> </button> <div class="collapse navbar-collapse" id="navbarResponsive"> <ul class="navbar-nav mr-auto"> <li class="nav-item {% block active_inquiry %}{% endblock %}"> <a class="nav-link" href="#">INQUIRY</a> </li> </ul> <ul class="navbar-nav ml-auto"> <li class="nav-item"> <a class="nav-link" href="#">Sign Up</a> </li> <li class="nav-item"> <a class="nav-link" href="#">Log In</a> </li> </ul> </div> </div> </nav> {% block contents%}{% endblock %} <!-- Footer --> <footer class="py-5 bg-black"> <div class="container"> <p class="m-0 text-center text-white small">©Foa TemplatesCode 2020</p> </div> <!-- /.container --> </footer> <!-- Vue.js JavaScript --> {% block scripts%}{% endblock %} <!-- Bootstrap core JavaScript --> <script src="{% static 'vendor/jquery/jquery.min.js' %}"></script> <script src="{% static 'vendor/bootstrap/js/bootstrap.bundle.min.js' %}"></script> </div> </body> </html>合わせてindex.htmlもそれっぽく編集する
accounts/templates/index.html{% extends 'base.html' %} {% load static %} {% block title %}TITLE --Subtitle{% endblock %} {% block contents %} <div id="indexapp"> <header class="masthead text-center text-white"> <div class="masthead-content"> <div class="container"> <h1 class="masthead-heading mb-0">[[ title ]]</h1> <h2 class="masthead-subheading mb-0">SubTitle</h2> <a href="#" class="btn btn-primary btn-xl rounded-pill mt-5">LOG IN</a> </div> </div> <div class="bg-circle-1 bg-circle"></div> <div class="bg-circle-2 bg-circle"></div> <div class="bg-circle-3 bg-circle"></div> <div class="bg-circle-4 bg-circle"></div> </header> <div class="py-5 text-white" style="background-image: linear-gradient(to bottom, rgba(0, 0, 0, .75), rgba(0, 0, 0, .75)), url(https://static.pingendo.com/cover-bubble-dark.svg); background-position: center center, center center; background-size: cover, cover; background-repeat: repeat, repeat;"> <div class="container"> <div class="row"> <div class="p-3 col-md-8 col-lg-6 ml-auto text-right text-white"> <p class="lead">"I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks."</p> <p><b>Johann Goethe</b><br><small>CEO and founder</small></p> </div> </div> </div> </div> <div class="py-5" style="background-image: linear-gradient(to left bottom, rgba(189, 195, 199, .75), rgba(44, 62, 80, .75)); background-size: 100%;"> <div class="container"> <div class="row"> <div class="text-center mx-auto col-md-12"> <h1 class="text-white mb-4">Testimonials</h1> </div> </div> <div class="row"> <div class="col-lg-4 col-md-6 p-3"> <div class="card"> <div class="card-body p-4"> <div class="row"> <div class="col-md-4 col-3"> <img class="img-fluid d-block rounded-circle" src="https://static.pingendo.com/img-placeholder-2.svg"> </div> <div class="d-flex col-md-8 flex-column justify-content-center align-items-start col-9"> <p class="mb-0 lead"> <b>J. W. Goethe</b> </p> <p class="mb-0">Co-founder</p> </div> </div> <p class="mt-3 mb-0">I throw myself down among the tall grass by the trickling stream; and, as I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world.</p> </div> </div> </div> <div class="col-lg-4 col-md-6 p-3"> <div class="card"> <div class="card-body p-4"> <div class="row"> <div class="col-md-4 col-3"> <img class="img-fluid d-block rounded-circle" src="https://static.pingendo.com/img-placeholder-1.svg"> </div> <div class="d-flex col-md-8 flex-column justify-content-center align-items-start col-9"> <p class="mb-0 lead"> <b>G. W. John</b> </p> <p class="mb-0">CEO & founder</p> </div> </div> <p class="mt-3 mb-0" >I lie close to the earth, a thousand unknown plants are noticed by me: when I hear the buzz of the little world among the stalks, and grow familiar with the countless indescribable forms of the insects and flies.</p> </div> </div> </div> <div class="col-lg-4 p-3"> <div class="card"> <div class="card-body p-4"> <div class="row"> <div class="col-md-4 col-3"> <img class="img-fluid d-block rounded-circle" src="https://static.pingendo.com/img-placeholder-3.svg"> </div> <div class="d-flex col-md-8 flex-column justify-content-center align-items-start col-9"> <p class="mb-0 lead"> <b>J. G. Wolf</b> </p> <p class="mb-0">CFO</p> </div> </div> <p class="mt-3 mb-0">Oh, would I could describe these conceptions, could impress upon paper all that is living so full and warm within me, that it might be the mirror of my soul, as my soul is the mirror of the infinite God!</p> </div> </div> </div> </div> </div> </div> <div class="py-5 text-center"> <div class="container"> <div class="row"> <div class="mx-auto col-md-8"> <h1>{{ this.title }}</h1> <p class="mb-4"> Oh, would I could describe these conceptions, could impress upon paper all that is living so full and warm within me, that it might be the mirror of my soul, as my soul is the mirror of the infinite God! O my friend -- but it is too much for my strength -- I sink under the weight of the splendour of these visions!</p> <div class="row text-muted"> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-angellist fa-3x"></i> </div> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-cc-visa fa-3x"></i> </div> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-empire fa-3x"></i> </div> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-paypal fa-3x"></i> </div> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-rebel fa-3x"></i> </div> <div class="col-md-2 col-4 p-2"> <i class="d-block fa fa-first-order fa-3x"></i> </div> </div> </div> </div> </div> </div> </div> {% endblock %} {% block scripts %} <script src="{% static 'js/index_vue.js' %}"></script> {% endblock %}Vue.jsを組み込む
ファイルを作成し、Titleを動的に変化させる
static内に新規フォルダ『js』に作成
js内にindex.jsを作成
Djangoのシステムと混同させないため、
delimiters: ['[[', ']]']が大事static/js/index.jsvar app = new Vue({ delimiters: ['[[', ']]'], el: '#indexapp', data: { title: "TestTitle" }, })実行
python manage.py runserver
- 投稿日:2020-01-24T11:20:43+09:00
jQuery愛好家のためのVue.js、React入門(いずれAngularも)
まず、ことわっておきますが、jQueryは非常に優秀なライブラリです。自分がメインとするWEBシステムの世界ではかなり重宝していますので、時代の潮流だからといって理由もなくjQueryを切り捨てろとは一言も言いませんし、もっと技術は培養すべきです。
ですが、使用できる選択肢を増やす、武器を増やすためにVue.js、Reactを学習するのは非常に有効です。ただ、これらの活用は学習コストが高いといわれています。その原因はフロントエンドありきで話が進みすぎているからだと考えています。したがって、自分の投稿記事は、jQueryを多用するWEBシステムエンジニアに向けた、フォーム操作をメインに置いた半備忘録兼自分なりに解釈した解説です。
ちなみに自分はサーバ構築からバックエンドまでこなしているワンオペエンジニア(フリーランス、非正規雇用に非ず)です。
§1:Vue.jsとReact、そしてAngular
その前に、vue.jsとReactとはどういったもので、どんな意図で開発されたものかを知っておく必要があります。そして、その理念を知っていたら、スクリプト記法の理屈もわかりやすいからです。
Vue.js
Vue.jsはもともとGoogleが開発したAngularJSの開発者の一人が、個人で開発を始めたJSフレームワークです。そのため、小規模の開発に向いた柔軟な利用が可能です(デフォルトでjQueryとの混用も可能)。そのVue.jsとは一言でどんな技術かというと
html内の部品に対して、Vueディレクティブという魔法をかける
こういうものです。ディレクティブとは双方向という意味で、データのインプットとアウトプットをリアルタイムで行うことを意味しています。これをVue.jsやAngularJSなどでは双方向バインディングと呼んでおり、今のAngularでもその技術はある程度継承されています。そして、AngularJSはhtml側でngというプロパティやメソッドを使ってバックエンドの処理をしており、かなり明解だった反面、開発が進むとかなりhtmlソースが汚されてしまうので、技術をある程度継承し、Angularというパッケージ単位のフレームワークを作ったわけです。しかし、このAngularJSの双方向バインディングと簡潔さを捨てるのはもったいないと、飛躍的に発展させたのがVue.jsでありVueディレクティブというもので、これはソースが汚れる原因となったhtml側でのバックエンド処理を、スクリプト側(コンポーネントを作成して管理)で処理させるようにしました。つまり
- AngularJS: ディレクティブ処理をhtml側で処理できたために、ソースが汚れてしまい、敬遠されてしまった。
- Vue.js : ディレクティブ処理をスクリプト側に記載させるようにし、html側は基本、メソッドとプロパティだけ入出力させるようにした(つまり、JavaSrciptの基本に返った)
この理念を覚えておけば、今後の学習にも役立ちます。
React
ReactはFacebookが開発したコンポーネント型のJSフレームワークで、その鍵となるのは
JSX
という記法です。そのJSXとはなにかというと、スクリプトの中に直接htmlタグを記述できてしまうものです。ですが、その独特の記法によって技術者や入門者に違和感を与え敬遠されてしまっていたのも事実で、そのために何度も記法が変更されてきており、今日ではだいぶ見やすく、そして記述しやすくなっています。それでもReactの基本的な理念と動きは変わっておらず、一言でいうとhtml内に魔法結社(JSXの拠点)を作り、そこでディレクティブな部品(htmlタグ)を錬金する
こういうものです。つまり、Vue.jsだとディレクティブとなる部品自体はhtml上にあったのに対し、Reactの場合は、ディレクティブな部品はhtmlになく、バックエンド上のコンポーネントで作成されることになります。そして、ディレクティブな処理を行う際もそのバックエンド内だけで処理するので、非常に動きが高速で、部品もバックエンドにしか存在しないので、開発を分担しやすく中規模の開発に向いています。
- React :部品の管理を徹底するために、ディレクティブ処理をhtml側で一切できないようにしている。部品の調達と管理はすべてJSXに則ったコンポーネント内で行う。
これが基本です。あと、コンポーネントの記法もいろいろあって、しかもしょっちゅう変更されているので、それが却って敬遠させている(初心者がどこから手を付けたらいいのかわからない)気もしますが、基本となる部分は全くぶれていないことを踏まえておいてください。
Angular(参考)
AngularはAngularJSでの失敗を教訓に、その失敗の原因となったソースの汚れを解消させたものです。そしてvue.js、Reactと違い、RubyのRailsやPHPのLaravel、PythonのDjangoのような、それ一つでパッケージとなっているフルスタックフレームワークとなっており、TypeScriptがベースです。そして、これも一言で表すと
プロジェクト自体がAngularという魔法世界(フレームワーク)である
なので、そのパッケージ内では自由自在にAngularの技術を利用できます。ですが、前述したようにソースの汚れを反省して作ったフレームワークなので、ディレクティブな部品は各アーキテクチャ内で実行するようになっています。ただ、基本はAngularJS時代とそこまで変わっていないためにあまり難しくなく、どちらかというとVue.jsに近いですが、その正直Vue.jsより理屈は解りやすいので、フレームワーク開発に慣れているバックエンドエンジニアなら、上記2つより学習は楽かもしれないです。ただ、けっこう容量があるので、大規模開発向きです(これを省力化、小規模化したJSフレームワークも存在します)。
- Augular:ディレクティブ処理を行う部品はhtml上にあるが、処理は外部のアーキテクチャ内で行う。
なお、現在もAngularJSはマイナーチェンジを続けていますが、本来のAngularJSの目的はVue.jsが担っていると考えていますので、本記事でAngularJSは採り上げません。
§2:バックエンドのための基本文法
前述したように、これらのJSフレームワークの学習コストが肥大してしまった原因は、フロントエンドありきで解説していることが多いためでしょう。そのため、バックエンドエンジニアが基礎の基礎である文法もわからずに、そっちばかりに目が行ってしまっていることで混乱を招き、これらの技術に手を出す気力を与えず、比較的記述が簡単なjQuery依存から脱皮できないと考えています。
したがって、自分はjQueryで操作してきたことを、Vue.js、Reactではどう記述するのかを重点において説明したいと思います。
演習1:フォーム操作:テキスト文字を表示させる
フォーム操作の基本の基本です。ですが、この基本だけでかなり記法の根本が理解できるのも事実です。ただ、単純に表示させるだけなのも面白くないので、jQueryでフォームに対し、キーの打鍵ごとに値を表示させるようにしましょう。
html<body> <input type="text" id="f_inp"> <p>入力された文字<span id="mes"></span></p> <script> $(function(){ $('#f_inp').on("keyup",function(){ let mes = $(this).val(); //値を取得する $("#mes").text(mes); //打ち込んだ値を反映させる }) }) </script> </body>これで、打ち込んだ文字をそのまま下の#mesに表示させることができます。これとほぼ同じ挙動をVue.js、Reactで再現してみます。
Vue.jsで再現
Vue.jsで記述するとこうなります。そしてVueですが、こっちはコンテンツの後に制御部分を記述してください。
vue-lesson.html<script src="https://cdn.jsdelivr.net/npm/vue"></script> </head> <body> <div id="app"> <input type="text" v-model="mes"> <p>入力された文字<span>{{ mes }}</span></p> </div> <!-- 制御部分はコンテンツの後 --> <script> new Vue({ el: '#app', data:{ mes: '', }, }); </script> </body>jQueryより更に簡潔になっているのは一旦置いといて、目の付け所はelとv-modelいうプロパティであり、これこそがまさしくVue.jsの双方向バインディング技術になります。そして、それが適用される部品は#appに囲んだ単一のブロック要素のみとなります。これをエレメントと呼び、el(elementの略)プロパティで指定した範囲のみ、ディレクティブになるわけです。
もっと詳しく文法を解説する
Vue.jsの場合は、html部分とスクリプト部分は区別しない方が説明もしやすいです。もう一度さっきのhtmlファイルを確認してみましょう。そして、コメントを付与します。
vue-lesson.html<body> <div id="app"><!--#app内のブロック要素にVueディレクティブを適用(1) --> <input type="text" v-model="mes"><!-- v:modelはフォームの値を監視するVueディレクティブ(2) --> <p>入力された文字<span>{{ mes }}</span></p><!-- {{hoge}}はマスタッシュ --> </div> <script> new Vue({ el: '#app', //適用対象となる要素名(1) //dataはVue.jsの操作に必要な変数を格納するオブジェクト data:{ mes: '', //今回は変数mesを使うので定義しておく(中は空白)。(2) }, }); </script> </body>このようになります。まずは、部品をディレクティブにするために(1)のように、どこまで適用するのか、その場所を定義し、そこから具体的な動作(2)が行われます。v-modelプロパティはフォーム部品の動きを監視する働きを持っているので、inputの動きに変化(新たに文字が入力された)があると即座に反応し、そしてその結果を{{ mes }}に返すようになっています。
補足1:dataプロパティ
dataプロパティはVueディレクティブで作業するための変数置き場で、これを定義しておかないと、hoge is not definedと処理中に未定義のエラーが起きます。今回はmesという変数を使用しているので、これを定義しておき、そして初期値を空っぽにしておきます。
補足2:マスタッシュ
マスタッシュとは英語で口髭のことで、{{ }}という記号です。そしてVue.jsではVueディレクティブの外側(v-modelなどv-xxxxというプロパティ)に値を適用する場合は{{変数名}}とすることで、その値を受けとることができます。ここでは{{mes}}はVueディレクティブの外側なのでマスタッシュで記述しています(エレメントの外側ではないので注意。エレメントの外側にマスタッシュを記述しても、そこはVue.jsの適用外なので、ただの文字列として認識されるだけです)。
Reactで再現
では、全く同じ動作をReactでも再現してみます。ただ、Reactは少し説明をわかりやすくするために、敢えて最短の記述にしていません(もっとスリム化した記法も使えるのですが、今日、一番見ることが多い記法です)。
react-lesson1.html<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script> <script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/babel-standalone/6.10.3/babel.min.js"></script> <script type="text/babel"> class App extends React.Component{ constructor(){ super(); this.state = { mes: '' }; this.changeText = this.changeText.bind(this); } changeText(e){ this.setState({ mes :e.target.value }); } render(){ return( <div> <input type="text" onChange={this.changeText} /> <p>入力された文字<span>{ this.state.mes }</span></p> </div> ); } } ReactDOM.render( <App />, document.getElementById("root") ); </script> </head> <body> <div id="root"></div> </body>初見の人が見たら、「なんだこりゃ?」となること請け合いです。スクリプトの中にhtmlタグが入っているのが生理的に受け付けないのでしょう。しかし、Reactも初期と比較すると、だいぶ記述はスリム化しており、そして解りやすくなっています。では、この処理の流れがわかるようにコメントを付与してみます。なお、Vue.jsでは、スクリプトをbodyタグの内側、コンテンツの後に書くのがセオリーでしたが、Reactはコンテンツの前、headタグの中に記述するのがセオリーです(普通は外部ファイル化します)。
react-lesson1.html<script type="text/babel"> //Reactコンポネントを作成するクラス(1) class App extends React.Component{ //A:定義部分。簡単にいえば、処理前に準備する変数やメソッドなど。 constructor(){ super(); this.state = { mes: '' }; this.changeText = this.changeText.bind(this); //(3)値をバインドさせる準備 } //B:処理関数部分。renderの外側に書く方が見やすい。 //入力された文字を返す関数(2) changeText(e){ this.setState({ mes :e.target.value }); //setStateは値をバインドさせる処理(3) } //C:レンダリング部分。JSX記法のhtmlタグを描画する。 render(){ //JSX記法で返す部品(2) return( //onChangeハンドラ以下はReactでバインディング処理を行う関数(3) <div> <input type="text" onChange={this.changeText} /> <p>入力された文字<span>{ this.state.mes }</span></p> </div> ); } } ReactDOM.render( <App />, //コンポネント作成を行う部品(1)。記述方法にルールがある。 document.getElementById("root") //外側に部品を表示させる(4) ); </script> </head> <body> <div id="root"><!-- ここに処理部品が入る(4)--></div> </body>だいたいこのような流れになっています。16.8からはもっと簡略化した記法も使えるのですが、一般的に知られている記法をマスターしてからの方がいいでしょう。見た目は複雑に見えますが、まずは次のように分類しましょう。
- React.Component() //コンポネントを作成します
- ReactDOM.render() //作成したコンポネントを反映させます
renderとはいろいろな意味がありますが、レンダリングという言葉通り、描写という言葉で覚えればいいでしょう。そして、その名の通り、ReactDOM.render内ではJSX記法に則ったhtmlタグを描写しているのです。そしてその部品を作成するのがReactDOM.Componentであり、いわば、前者がシステムの納入、後者がシステムの開発や保守管理だと思えばいいでしょう。したがって、htmlタグ側にはリアクティブ処理された部品しか存在していなく、スクリプトの処理部分は、Vue.jsと違って部品すら見えません。
では、今度はReact.Componentの中身を3つに分類してみます。
- constructer() //定義部分
- 処理用の関数 //処理部分
- render() //レンダリング部分
だいたいはこのように分類するとわかりやすいです。そして、その作業の流れを文章化してみると
- Appという部品を作成する命令を出す(1)
- 命令を受けたコンポネントはコンストラクタから部品を用意してレンダリング領域にある通り、タグを作成し、外側に返す(2)(4)
- onChangeイベントが発火したら、用意していた関数を使って処理を行う(3)
- 処理を行った状態で再度レンダリングを行い、ReactDOM.renderを使用して処理の外側に返す(4)
それを踏まえた上で処理を追ってみるとやっていることはJavaScriptとそこまで相違ないと気づくはずです。
ただ、決定的な違いとして処理関数内にreturnが存在しない(レンダリング部分ではない)、そしてreturn処理を行う代わりにsetStateで制御することです。そして、これによってバインディングが可能になります。
オブジェクト、変数の利用
Reactでもう一つ躓きがちなのが、オブジェクトや変数の利用に際しては、同じコンポーネント内であっても、外部の存在として扱わないといけないということです。具体的に言うと、thisという代名詞を置いているのがそれで、それぞれ、定義部分、レンダリング部分、関数部分で変数や関数をやりとりする際には、thisという代名詞を使っているのがわかると思います。
- 関数部分のchangeTextをレンダリング部分で使用する場合、this.changeTextとして呼び出し
- レンダリング部分でmesを使用するために、定義部分のstate.mesをthis.state.mesとして呼び出し
このような具合です。またsetStateのようにコンポーネントで用意された部品を使用する際も、外部から借用することになるので、this.setStateと代名詞を付与しています。
補足:バインディングのルール
定義部分で
this.changeText = this.changeText.bind(this);とあると思いますが、これは別におまじないではなく、利用する関数において値をバインド(同期)したいときに記述するルールで、これを記述しないと値をバインドできません。そして、基本は同じオブジェクト名にしておくだけで、左側は変数を代入しているだけですので、定義さえしておけば、別名でも大丈夫です(したところでメリットが薄いので同名にしおくべきですが)。
また、setStateはReactにおいて極めて重要性の高いメソッドですが、簡単にいえば、元の値と新しい値をバインドさせたいときに設定するものです。具体的には先程入力した文字と新たに入力した文字が異なる場合、随時setStateメソッドが実行されることになります。
また、e.target.valueはJSX内にある部品の、任意のフォームに対して取得した値(value)を受けとるもので、e(jQueryではelemと書くことが多い)とは、任意のオブジェクト変数に過ぎません。そして、受け取った値を先程のsetStateメソッドを使って、値の変化を処理しているわけです。
最新の書き方(16.8以降)
上記の方法でもかなり簡潔になりました(ECMA5時代はコンポネントも逐一作成する必要がありました)が、それでも至るところにthisばかりあったりと、色々と無駄があるようにも見えました。それをスリム化させるとこのようになります。
react-renew.html<script type="text/babel"> //定義部分 const { useState } = React; //ローカルの場合で、useStateを使用するための定義 const App =()=>{ //処理部分 const [mes,setMes] = useState(true); //バインディング処理 //一度メソッドで準備する const changeText = (e)=>{ setMes( e.target.value ); //値の比較 } //レンダー部分 return( <div> <input onChange={changeText} /> <p>入力された文字<span>{ mes }</span></p> </div> ); } const elem = <App />; //ワンクッション置かないと警告が出る //レンダリング処理 ReactDOM.render( elem, document.getElementById("root") ); </script>代名詞のthisが消えてかなり明白になったと思いますが、基本的な動作は変わっていません。ただ、変数定義部分、処理部分、レンダリング部分が一つの関数に収まったので、すごくすっきりします。
ただ、注意することとして
htmlconst App =()=>{....} const.elem = <App />; //ワンクッション置く ReactDOM.render( elem, document.getElementById("root") ); //ダイレクトに呼び出す以下の書き方は推奨されていない(警告が出ます) const App =()=>{....} ReactDOM.render( <App />, document.getElementById("root") );この部分で、このようにrenderメソッドに対して、ワンクッション置かないと警告メッセージが表示されることになります。
演習2:プルダウンメニューの制御
以下作成中
- 投稿日:2020-01-24T03:49:50+09:00
【Vue/Nuxt】VeeValidateを導入してバリデーションを実装してみた
VeeValidateの概要
VeeValidate is a validation framework built specifically for Vue.js
引用:https://logaretm.github.io/vee-validate/guide/basics.html#validation-providerVeeValidateはVue.js専用のバリデーションフレームワークみたいですね。
バリデーションの実装や言語のローカライズを簡単に出来るため導入を決めました。
以下の記事を見ながら実装しました。
参考:https://www.virment.com/how-to-use-veevalidate-nuxtjs/
バリデーション実装の要件
今回は、お問い合わせフォームを実装する前提で話を進めていきます。
お問い合わせフォームは以下の項目を用意します。
- 名前
- メールアドレス
- お問い合わせ内容
また、バリデーションの要件は以下です。
- バリデーションメッセージを日本語にローカライズ
- メールアドレスの入力形式
- 必須項目指定
- 最大文字数指定
- バリデーションが通ってない場合は送信不可
VeeValidateの下準備
- VeeValidateをインストール
- VeeValidateのプラグイン作成
- nuxt.config.jsの設定
VeeValidateをインストール
$ yarn add vee-validateVeeValidateのプラグイン作成
plugin/vee-validate.jsimport Vue from 'vue' import { extend, ValidationProvider, ValidationObserver, localize } from 'vee-validate' import ja from 'vee-validate/dist/locale/ja.json' import { required, email, max } from 'vee-validate/dist/rules' extend('required', required) extend('email', email) extend('max', max) Vue.component('validation-provider', ValidationProvider) Vue.component('validation-observer', ValidationObserver) localize('ja', ja)他にどのlocaleがあるかは、VeeValidateのGitHubを見ました。
参考:https://github.com/logaretm/vee-validate/tree/master/locale
nuxt.config.jsの設定
nuxt.config.jsexport default { plugins: [ { src: '@/plugins/vee-validate' } ], build: { transpile: ["vee-validate/dist/rules"] } }上記のように、
transpile
の設定をしていない場合は、「Unexpected token export」のエラーが出ました。Nuxt.jsのtranspileについての補足
特定の依存関係を Babel で変換したい場合、build.transpile を追加することができます。
引用:https://ja.nuxtjs.org/api/configuration-build/#transpileBabelについての補足
原文
Babel is a toolchain that is mainly used to convert ECMAScript 2015+ code into a backwards compatible version of JavaScript in current and older browsers or environments.
直訳
Babelは、主に現在および以前のブラウザーまたは環境でECMAScript 2015+コードをJavaScriptの下位互換バージョンに変換するために使用されるツールチェーンです。
新しい書き方を古い書き方でも動くように変換してくれてるようです。
また、transpileについては、以下のドキュメントも参考にしました。
参考:https://ja.nuxtjs.org/guide/plugins/#es6-%E3%83%97%E3%83%A9%E3%82%B0%E3%82%A4%E3%83%B3
VeeValidateの実装
以下の手順でVeeValidateを実装します。
- Validation Providerの実装
- Validation Observerの実装
以下は、簡略化させたディレクトリ構成です。
project_name ├──app/ │ ├──components/ │ │ └──validation/ │ │ ├──FormValidationInput.vue │ │ └──FormValidationTextarea.vue │ └──pages/ │ ├──contact/ │ │ ├──thanks/ │ │ │ └──index.vue //メール送信後に飛ばすサンクスページ │ │ └──index.vue │ └──index.vue ├──nuxt.config.js └──package.jsonNuxt.jsのディレクトリ構成は、いつも以下のドキュメントのようにしています。
参考:https://qiita.com/arthur_foreign/items/637f2976e9f5e7a89727
Validation Providerの実装
Validation Providerの概要は以下です。
The ValidationProvider component is a regular component that wraps your inputs and provides validation state using scoped slots.
引用:http://vee-validate.logaretm.com/v2/guide/components/validation-provider.html#rendering
input
やtextarea
等にラップして、バリデーションメッセージを表示してくれます。まずは、名前とメールアドレスの入力に利用するフォームを用意しました。
component/validation/FormValidationInput.vue<template> <validation-provider v-slot="{ errors }" :rules="rules" :name="labelMessage" > <div class="container"> <label :for="formComponentName"> {{ labelMessage }}: </label> <input :id="formComponentName" v-model="inputValueModel" :type="inputType" :name="formComponentName" :maxlength="maxLength" :placeholder="placeHolderMessage" /> <!-- 入力値と最大文字数を親Componentで扱えるようにする --> <slot :inputValue="inputValue" :maxLength="maxLength" /> <p v-show="errors.length"> {{ errors[0] }} </p> </div> </validation-provider> </template> <script> export default { props: { rules: { type: String, required: true }, labelMessage: { type: String, required: true }, inputType: { type: String, required: true }, formComponentName: { type: String, required: true }, maxLength: { type: String, required: true }, placeHolderMessage: { type: String, required: true }, inputValue: { type: String, required: true } }, computed: { inputValueModel: { get() { return this.$props.inputValue }, set(val) { this.$emit('update:inputValue', val) } } } } </script>次に、お問い合わせ内容の入力に利用するフォームを用意しました。
component/validation/FormValidationTextarea.vue<template> <validation-provider v-slot="{ errors }" :rules="rules" :name="labelMessage" > <div class="container"> <label :for="formComponentName"> {{ labelMessage }}: </label> <textarea :id="formComponentName" v-model="inputValueModel" :cols="textareaCols" :rows="textareaRows" :name="formComponentName" :maxlength="maxLength" :placeholder="placeHolderMessage" /> <!-- 入力値と最大文字数を親Componentで扱えるようにする --> <slot :inputValue="inputValue" :maxLength="maxLength" /> <p v-show="errors.length"> {{ errors[0] }} </p> </div> </validation-provider> </template> <script> export default { props: { rules: { type: String, required: true }, labelMessage: { type: String, required: true }, textareaCols: { type: String, required: true }, textareaRows: { type: String, required: true }, formComponentName: { type: String, required: true }, maxLength: { type: String, required: true }, placeHolderMessage: { type: String, required: true }, inputValue: { type: String, required: true } }, computed: { inputValueModel: { get() { return this.$props.inputValue }, set(val) { this.$emit('update:inputValue', val) } } } } </script>同じ
input
やtextarea
等のタグであれば、それぞれのバリデーションルールや属性を変えて、propsで渡せば使いまわせるな〜と思ったため、Validation Providerの実装は共通化をしています。
is
を利用することでinput
やtextarea
等のタグを動的に切り替えることは出来たのですが、それぞれで違う属性(textareaならrowとかcol)とかをpropsで渡した際に、Componentが肥大化してしまうよな〜と思ったため、タグごとにComponentを分けました。もう少し共通化出来そうな部分はあるのですが、パパッと実装したものをそのままコピペしてます。
※リファクタしたら記事の更新します。また、公式ドキュメントがかなり分かりやすいので、以下を参考にして実装しました。
参考:http://vee-validate.logaretm.com/v2/guide/components/validation-provider.html#rendering
それと、Vueの実装については、以下のドキュメント参考にしています。
参考1(v-modelについて):https://jp.vuejs.org/v2/guide/forms.html
呼び出し先(子)Componentの描画内容を、呼び出し元(親)で決めるためにスロットを利用しました。
また、スコープ付きスロットで、親からpropsで渡した子のデータにアクセスしています。
これは、Validation Observerの実装時に役に立ちました。
具体的には、名前とメールアドレスのバリデーションで、同じComponentを使い回しつつも、名前には入力文字数を表示するが、メールアドレスの項目には入力文字数を表示しないという実装です。
参考2(スロットについて):https://jp.vuejs.org/v2/guide/components-slots.html
また、スロットについての解説は、以下の記事が本気で参考になりました。
参考:https://www.hypertextcandy.com/vuejs-scoped-slots
Validation Observerの実装
The ValidationObserver is a convenient component that also uses the scoped slots feature to communicate the current state of your inputs as a whole.
引用:http://vee-validate.logaretm.com/v2/guide/components/validation-observer.html#renderingValidation ProviderをValidation Observerでラップすることで、全てのバリデーションを通らない場合は送信をさせないといった実装にすることが出来ました。
pages/contact/index.vue<template> <div class="contact-form"> <p>Contact us</p> <validation-observer ref="observer" v-slot="{ invalid }" tag="form" @submit.prevent="submit()" > <FormValidationInput rules="required|max:50" labelMessage="Name" inputType="text" formComponentName="name" maxLength="50" placeHolderMessage="名前を入力してください" :inputValue.sync="inputName" > <template v-slot="inputProps"> <p> {{ inputProps.inputValue.length + '/' + inputProps.maxLength }} </p> </template> </FormValidationInput> <!-- メールアドレスには入力文字数を表示しない --> <FormValidationInput rules="required|email|max:256" labelMessage="E-Mail" inputType="email" formComponentName="email" maxLength="256" placeHolderMessage="メールアドレスを入力してください" :inputValue.sync="inputEmail" /> <FormValidationTextarea rules="required|max:1000" labelMessage="Message" textareaCols="20" textareaRows="10" formComponentName="message" maxLength="1000" placeHolderMessage="お問い合わせ内容を入力してください" :inputValue.sync="inputMessage" > <template v-slot="inputProps"> <p> {{ inputProps.inputValue.length + '/' + inputProps.maxLength }} </p> </template> </FormValidationTextarea> <button type="submit" :disabled="invalid"> Submit </button> </validation-observer> </div> </template> <script> export default { components: { ContactSubmitButton: () => import('@/components/contact/ContactSubmitButton'), FormValidationInput: () => import('@/components/validation/FormValidationInput'), FormValidationTextarea: () => import('@/components/validation/FormValidationTextarea') }, data() { return { inputName: '', inputEmail: '', inputMessage: '' } }, methods: { async submit() { const isValid = await this.$refs.observer.validate() if (isValid) { // バリデーション通過時の処理(例:サーバーに値を送信する等) // サンクスページに遷移 this.$router.push('/contact/thanks') } } } } </script>実装はほとんど公式ドキュメントと変わりません。
Validation ObserverでValidation Providerをラップしました。
また、バリデーションが通ってない場合に、送信ボタンは非活性になっています。
参考:http://vee-validate.logaretm.com/v2/guide/components/validation-observer.html#rendering
上には載せてないのですが、入力した値をリセットした場合は、Validate Observerのバリデーションに引っかかって、エラーメッセージがいっぺんに表示されました。
そのため、Validation Observerにリセットをかけることで回避します。
contact/index.vue<script> //省略 methods: { async submit() { const isValid = await this.$refs.observer.validate() if (isValid) { this.inputName = '' this.inputEmail = '' this.inputMessage = '' requestAnimationFrame(() => { this.$refs.observer.reset(); }); } } } //省略 </script>この実装についても、公式ドキュメントに詳しく書かれています。
参考:http://vee-validate.logaretm.com/v2/guide/components/validation-observer.html#examples
requestAnimationFrame()
については、以下を参考にしました。参考:https://developer.mozilla.org/ja/docs/Web/API/Window/requestAnimationFrame
syncについてちゃんと書くとなると別記事にした方がいいと思ったため詳しく書きません。
ただ、
sync
については、以下の記事が本気で分かりやすかったため、共有だけしておきます。
- 投稿日:2020-01-24T02:59:48+09:00
VueコンポーネントのfunctionをES6スタイルで書く
はじめに
Vueでは関数定義をES6のアロー関数(Arrow Function)を使わずにES5以前からの書き方のfunction式(function expression)を使う方法が推奨されています。
メソッド(例 plus: () => this.a++) を定義するためにアロー関数を使用すべきではないことに注意してください。
アロー関数は、this が期待する Vue インスタンスではなく、this.a が undefined になるため、親コンテキストに束縛できないことが理由です。var vm = new Vue({ data: { a: 1 }, methods: { plus: function () { this.a++ // this = VueComponent } } }) vm.plus() vm.a // 2例えばmethods内部でfunctionを定義した場合、thisに呼び出し元のVueインスタンスが格納されます。
var vm = new Vue({ data: { a: 1 }, methods: { plus: () => { this.a++ // this = undefined } } }) vm.plus() vm.a一方でアロー関数で定義した場合、thisの内容はundefinedとなります。
そして上記の例でのthis.aの呼び出しはundefinedのプロパティを参照しようとするのでエラーとなってしまうのです。これは従来のfunction expressionが呼び出し元のコンテキストをthisに格納する挙動に対して、アロー関数ではthisが呼び出し元ではなくレキシカルスコープのthisにバインドされるという違いによるものです。
私も当初アロー関数に書き直そうとして、このthis参照問題に当たってしまいました。
GoogleやQiitaで検索してみるとアロー関数を諦めてfunctionで関数定義しなさいと解説している記事がいくつかヒットするので同じ悩みの人が一定数いるようです。しかし別の記法でES6スタイルで記述されている方のgistを参考に、functionを撤廃することができたので具体的な方法を紹介します。
要約
ES6からObjectのpropertyにfunctionを定義する場合の短縮構文である、メソッド定義構文(Method Definition)が実装されました。
この記法を使うことでVueのcomputed, methodsなどの内部で定義したfunctionを置き換えていきます。
メソッド定義 - JavaScript | MDNvar obj = { foo: function() { /* コード */ }, bar: function() { /* コード */ } }; var obj = { foo() { /* コード */ }, bar() { /* コード */ } };なぜアロー関数ではなくこの記法にするのかは後述します。
それでは個別の事例ごとにNGパターンを踏まえつつES6スタイルに書き直してみましょう。
props
propsなどの内部のプロパティとして値を返すfunctionが定義されている場合
props: { colorCodes: { type: Array, default: function() { return ['#FFFFFF', '#F0F0F0']; } } }これは単純にアロー関数にできそうですね?
props: { colorCodes: { type: Array, default: () => { return ['#FFFFFF', '#F0F0F0']; }, }, },できました!しかし…
functionの内部でthisが参照されている時はどうでしょうか?props: { colorCodes: { type: Array, default: function() { console.log(this); // VueComponentが参照できる return this.colorArray; }, }, }NGな書き方
前述のように単純にアロー関数に変換すると内部にthisが記述された時に参照できなくなってしまいます
props: { colorCodes: { type: Array, default: () => { console.log(this); // undefined return this.colorArray; // undefinedのプロパティを参照しようとしてエラーになる }, }, }OKな書き方
メソッド定義形式で書けば内部でthisが参照できます
props: { colorCodes: { type: Array, default() { console.log(this); // VueComponentが参照できる return this.colorArray; }, }, }computed, methodsなど
computedの内部でfunctionで定義されている場合
computed: { mySomething: function() { const something = this.getSomething(); ... }, },NGな書き方
単純にアロー関数に書き換えると、内部にthisが記述された時に参照できなくなります
computed: { mySomething: () => { const something = this.getSomething(); // undefinedのプロパティを参照しようとしてエラーになる ... }, },OKな書き方
メソッド定義形式で書けば内部でthisが参照できます
computed: { mySomething() { const something = this.getSomething(); // thisでVueComponentが参照できる ... }, },computedに限らずmethodsなどの中身も同様にメソッド定義形式に書き換えられます
methods: { getColorCodes: function() { return { startColor: this.colorCodes[0], endColor: this.colorCodes[1], }; } },OKな書き方
methods: { getColorCodes() { return { startColor: this.colorCodes[0], endColor: this.colorCodes[1], }; } },実例
Nuxtのサイトのサンプルがメソッド定義形式で書かれていました
Vuex ストア - NuxtJSexport default { fetch ({ store }) { store.commit('increment') }, computed: mapState([ 'counter' ]), methods: { increment () { this.$store.commit('increment') } } }実例2
element-uiのサンプルもメソッド定義形式で書かれていました
Element - The world's most popular Vue UI framework<el-collapse v-model="activeNames" @change="handleChange"> ... 省略 ... </el-collapse> <script> export default { data() { return { activeNames: ['1'] }; }, methods: { handleChange(val) { console.log(val); } } } </script>参考サイト
超素晴らしい解説のgistです。ほぼこれを参考にしています。
Clean up your Vue modules with ES6 Arrow Functions · GitHubMDN公式のメソッド定義(Method definitions)リファレンス
メソッド定義 - JavaScript | MDN本記事は個人ブログに投稿したものをベースにリライトしました
https://pisolino.hatenablog.com/entry/2020/01/24/020059
- 投稿日:2020-01-24T01:28:19+09:00
Vue.jsかNuxtJSでassetsに入れた画像を動的に表示させる
環境
- NuxtJS v2.11.0
- TypeScript v3.7.5
問題
リストで管理している画像ファイルを動的に書いてもうまく表示されない
コンポーネント部分
以下のように画像のファイル名リストを文字列で持っていたとする。
import { Vue, Component, Prop } from 'vue-property-decorator' @Component({}) export default class ImageUnit extends Vue { @Prop() imageNames: Array<string> }テンプレート部分
<template> <dl> <dt>Image: </dt> <dd v-for="(imageName, index) in imageNames" :key="`image-${index}`"> <img :src="`@/assets/${imageName}`" alt="`image-index`"> </dd> </dl> </template>このように書いても、HTMLに直接上記のURLが表示され、画像は出なかった。
staticディレクトリに入れるという手もあるが、Nuxtで管理できないのは困るので別の方法を考えた。解決策
以下のURLを参考にし、一度モジュールをバインドすればOK。
正常に/_nuxt/assets/hogehoge.png
画像が表示された。https://ja.nuxtjs.org/guide/assets/#webpack
<template> <dl> <dt>Image: </dt> <dd v-for="(imageName, index) in imageNames" :key="`image-${index}`"> <img :src="require(`@/assets/${imageName}`)" alt="`in-${index}`"> </dd> </dl> </template>