20200124のvue.jsに関する記事は7件です。

初心者によるプログラミング学習ログ 219日目

100日チャレンジの219日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。

100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。

219日目は

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

【備忘録】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? No
Vue 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-24_12h42_02.png

まとめ

あまり頻繁にやらないので、ふとやろうと思った時に忘れてしまっていた。
こんなことを避けるためにも備忘録として残すように習慣づけたい。。。

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

【Django】Django+MySQL+Vue.jsな環境を作るメモ【Python】

システム構成

・Django
・Vue.js
・MySQL

django-admin startproject config .
python manage.py startapp accounts

最初の設定

APPSに追加

config/setting.py
INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    "accounts.apps.AccountsConfig",
]

言語とタイムゾーンを日本仕様にする

config/setting.py
LANGUAGE_CODE = 'ja'

TIME_ZONE = 'Asia/Tokyo'

DjangoのデータベースにMySQLを設定する

pip install pymysql
pip install python-dotenv

manage.pyの編集(MySQLの追加)

manage.py
import pymysql
pymysql.install_as_MySQLdb()

configの中に.envファイルの作成

config/.env
DB_NAME = XXXXX
DB_PWD = XXXXX
DB_USER = XXXXX
DB_HOST = XXXXX

setting.pyの編集

config/setting.py
from os.path import join, dirname
from dotenv import load_dotenv
config/setting.py
dotenv_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.py
from 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.py
from 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 runserver

Bootstrapテンプレートを反映させる

staticフォルダの追加

Bootstrapをダウンロード
(https://startbootstrap.com/themes/one-page-wonder)

プロジェクト直下にstaticフォルダを新規作成

ダウンロードしたBootStrapをstaticフォルダに入れる

├── accounts
├── config
├── static
|       ├── css
|       ├── img
|       └── vender
├── manage.py

静的フォルダが配置されている場所を設定する

config/settings.py
STATICFILES_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">&copy;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 &amp; 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.js
var app = new Vue({
    delimiters: ['[[', ']]'],
    el: '#indexapp',
    data: {
        title: "TestTitle"
    },
})

実行

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

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() //レンダリング部分

だいたいはこのように分類するとわかりやすいです。そして、その作業の流れを文章化してみると

  1. Appという部品を作成する命令を出す(1)
  2. 命令を受けたコンポネントはコンストラクタから部品を用意してレンダリング領域にある通り、タグを作成し、外側に返す(2)(4)
  3. onChangeイベントが発火したら、用意していた関数を使って処理を行う(3)
  4. 処理を行った状態で再度レンダリングを行い、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が消えてかなり明白になったと思いますが、基本的な動作は変わっていません。ただ、変数定義部分、処理部分、レンダリング部分が一つの関数に収まったので、すごくすっきりします。

ただ、注意することとして

html
    const App =()=>{....}
    const.elem = <App />; //ワンクッション置く
    ReactDOM.render(
        elem,
        document.getElementById("root")
    );
    //ダイレクトに呼び出す以下の書き方は推奨されていない(警告が出ます)
    const App =()=>{....}
    ReactDOM.render(
        <App />,
        document.getElementById("root")
    );

この部分で、このようにrenderメソッドに対して、ワンクッション置かないと警告メッセージが表示されることになります。

演習2:プルダウンメニューの制御

以下作成中

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

【Vue/Nuxt】VeeValidateを導入してバリデーションを実装してみた

VeeValidateの概要

VeeValidate is a validation framework built specifically for Vue.js
引用:https://logaretm.github.io/vee-validate/guide/basics.html#validation-provider

VeeValidateはVue.js専用のバリデーションフレームワークみたいですね。

バリデーションの実装や言語のローカライズを簡単に出来るため導入を決めました。

以下の記事を見ながら実装しました。

参考:https://www.virment.com/how-to-use-veevalidate-nuxtjs/

バリデーション実装の要件

今回は、お問い合わせフォームを実装する前提で話を進めていきます。

お問い合わせフォームは以下の項目を用意します。

  • 名前
  • メールアドレス
  • お問い合わせ内容

また、バリデーションの要件は以下です。

  • バリデーションメッセージを日本語にローカライズ
  • メールアドレスの入力形式
  • 必須項目指定
  • 最大文字数指定
  • バリデーションが通ってない場合は送信不可

VeeValidateの下準備

  • VeeValidateをインストール
  • VeeValidateのプラグイン作成
  • nuxt.config.jsの設定

VeeValidateをインストール

$ yarn add vee-validate

VeeValidateのプラグイン作成

plugin/vee-validate.js
import 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.js
export 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/#transpile

Babelについての補足

原文

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.json

Nuxt.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

inputtextarea等にラップして、バリデーションメッセージを表示してくれます。

まずは、名前とメールアドレスの入力に利用するフォームを用意しました。

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>

同じinputtextarea等のタグであれば、それぞれのバリデーションルールや属性を変えて、propsで渡せば使いまわせるな〜と思ったため、Validation Providerの実装は共通化をしています。

isを利用することでinputtextarea等のタグを動的に切り替えることは出来たのですが、それぞれで違う属性(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#rendering

Validation 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については、以下の記事が本気で分かりやすかったため、共有だけしておきます。

参考:https://blog.cloud-acct.com/posts/spa-signup-form/#%E3%82%B3%E3%83%A9%E3%83%A0%E3%82%B3%E3%83%B3%E3%83%9D%E3%83%BC%E3%83%8D%E3%83%B3%E3%83%88%E3%81%AE%E5%8F%8C%E6%96%B9%E5%90%91%E3%83%87%E3%83%BC%E3%82%BF%E3%83%90%E3%82%A4%E3%83%B3%E3%83%87%E3%82%A3%E3%83%B3%E3%82%B0

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

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 | MDN

var 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 ストア - NuxtJS

export 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 · GitHub

MDN公式のメソッド定義(Method definitions)リファレンス
メソッド定義 - JavaScript | MDN

本記事は個人ブログに投稿したものをベースにリライトしました
https://pisolino.hatenablog.com/entry/2020/01/24/020059

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

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