- 投稿日:2020-08-02T23:52:37+09:00
文章から単語を取り出して数えるサービスを作成してみた
概要
「単語を数えるプログラミング」をテーマにお勉強してみました。
単語を数えるということで形態素解析を用いて文章から単語を取り出して単語数を数えてみるサービスを作ることにしました。お勉強した結果ですが以下のサービスとして公開しておりますので、以下の 作ったモノ からご確認いただければ幸いです。
使用しているフレームワークとライブラリ
今回は
Glitch
というNode.js
のアプリを公開するためのサービスを使用して公開しています。Glitch
フレームワークとライブラリについては以下を使用しております。
フレームワーク
ライブラリ
- 形態素解析
- ネガポジ判定(前向き・後ろ向き判断)
- グラフ
作ったモノ
公開しているサイトとコードは以下のアドレスでご確認いただけます。
公開先(作ったモノの動作を確認する)
https://morphological-analysis.glitch.me/
コード
https://glitch.com/edit/#!/morphological-analysis
作ったモノの説明
コードについては上記のコードをご確認いただければ幸いです。
以下に特記したい内容を記載します。バックエンド
const express = require("express"); var compression = require('compression') const app = express(); const kuromoji = require("kuromoji"); const analyze = require("negaposi-analyzer-ja"); app.use(compression()) app.use(express.static("public")); app.set("json spaces", 2); const builder = kuromoji.builder({ dicPath: "node_modules/kuromoji/dict" }); app.get("/", (request, response) => { response.sendFile(__dirname + "/views/index.html"); }); app.get("/morphological", (request, response) => { builder.build(function(err, tokenizer) { if (err) { throw err; } let reqMsg = request.query.msg || "形態素解析とは文章を意味を持つ最小の単位に分けるイメージです"; let tokenizedResult = tokenizer.tokenize(reqMsg) let result = { send_msg: reqMsg, morphological_result: tokenizedResult, negaposi_score: analyze(tokenizedResult) }; console.log("tokened:" + result); response.json({ result }); }); }); app.get("/negaposi", (request, response) => { builder.build(function(err, tokenizer) { if (err) { throw err; } let reqMsg = request.query.msg || "形態素解析とは文章を意味を持つ最小の単位に分けるイメージです"; const score = analyze(tokenizer.tokenize(reqMsg)); builder = null; response.json({ message: reqMsg, negaposi_score: score }); }); }); // listen for requests :) const listener = app.listen(process.env.PORT, () => { console.log("Your app is listening on port " + listener.address().port); });
Express
によるAPI
サーバーです。文章を単語ずつに区切る形態素解析するためのAPI
として実装されています。ウェブサーバーとしてURLのトップにアクセスされた場合は、views/index.html
を表示するようにしています。形態素解析
https://ja.wikipedia.org/wiki/形態素解析
上記のWikipediaから抜粋させていただきます。
辞書と呼ばれる単語の品詞等の情報にもとづき、形態素(Morpheme, おおまかにいえば、言語で意味を持つ最小単位)の列に分割し、それぞれの形態素の品詞等を判別する処理です。今回の場合は
kuromoji.js
という形態素解析のライブラリを使用しています。
kuromoji.builder
で辞書を読み込むことで形態素解析の処理を行っています。なお、形態素解析の品質は辞書に依存することがほとんどで、今回はデフォルトの辞書を使用しているため、流行語などで単語を区切ったりすることはできません。
オマケ:ネガポジ判定
kuromoji.js
で形態素解析された結果を使ってネガポジ判定もできるようなので実装してみました。ネガポジ判定とは、主に人の発言や発想などが、前向き(ポジティブ)か後ろ向き(ネガティブ)かを判定するモノになります。使用させていただいた negaposi-analyzer-ja では、形態素解析された結果を 単語感情極性対応表を使用してネガポジ判定をしているようです。こちらも単語感情極性対応表に依存することになります。
ネガポジ判定自体はPythonなどの機械学習などの方が向いていると思います。BertやWord2Vec などを使えば、自分だけのネガポジなどを作れそうですね。
フロントエンド
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>morphological-analysis</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" /> <link href="https://fonts.googleapis.com/css?family=Open+Sans:300,400,700" rel="stylesheet" /> <link rel="stylesheet" href="https://unpkg.com/bulma@0.8.2/css/bulma.min.css" /> </head> <body> <section class="hero is-info is-fullheight" id="app"> <div class="hero-body"> <div class="container has-text-centered"> <div class="column"> <div class="box"> <div class="field is-grouped"> <p class="control is-expanded"> <input class="input" v-model="sendMsg" type="text" placeholder="形態素解析する文章を入力してください" /> </p> <p class="control"> <a class="button is-info" @click="morphoExec"> 実行する </a> </p> </div> </div> <div class="subtitle" v-if="isLoading"> <progress class="progress is-large is-warning" max="100" ></progress> </div> <h2 class="subtitle has-text-left" v-if="sendMsg"> {{sendMsg}} </h2> <h2 v-if="dataset.length > 0" class="bar-graph"> <chartjs-horizontal-bar :labels="labels" :data="dataset" :bind="true" :datalabel="dataLabel" :option="chartOption" :height="chartHeight" ></chartjs-horizontal-bar> </h2> <h1 class="subtitle" v-if="negaposiScore"> ネガポジスコア: {{negaposiScore}} </h1> <h3 class="subtitle has-text-left" v-if="meishiCountList"> <p class="subtitle has-text-left">単語一覧</p> <li v-for="(item, key) in meishiCountList" class="has-text-left"> {{ key }} : {{ item }} </li> </h3> </div> </div> </div> </section> <script src="https://cdn.jsdelivr.net/npm/vue@2.5.16/dist/vue.js"></script> <script src="https://unpkg.com/axios/dist/axios.min.js"></script> <script src="https://unpkg.com/vue-chartjs@2.6.0/dist/vue-chartjs.full.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/Chart.js/2.3.0/Chart.js"></script> <script src="https://unpkg.com/hchs-vue-charts@1.2.8"></script> <script> "use strict"; Vue.use(VueCharts); new Vue({ el: "#app", data: { sendMsg: null, meishiCountList: null, negaposiScore: null, isLoading: false, labels: [], dataset: [], dataLabel: "名詞 カウント", chartOption: { legend: { display: false }, responsive: true, maintainAspectRatio: false, scales: { xAxes: [ { ticks: { max: 5, min: 0, stepSize: 1 } } ] } }, chartHeight: 500 }, methods: { morphoExec() { this.negaposiScore = null; this.labels = []; this.dataset = []; this.sendMsg = this.sendMsg || "何も文字が入力されなかったのでサンプルの文章で形態素解析を実行します。何か文字を入力することで単語をカウントします。"; const sendMsg = this.sendMsg; this.isLoading = true; const vueThis = this; axios .get("/morphological?msg=" + sendMsg) .then(function(response) { const morphoResult = response.data.result.morphological_result; let meishiList = []; morphoResult.filter(function(item, index) { if (item.pos == "名詞") { meishiList.push(item.surface_form); return true; } }); let meishiCount = {}; for (let i = 0; i < meishiList.length; i++) { let key = meishiList[i]; meishiCount[key] = meishiCount[key] ? meishiCount[key] + 1 : 1; } vueThis.labels = Object.keys(meishiCount); vueThis.dataset = Object.values(meishiCount); vueThis.negaposiScore = response.data.result.negaposi_score; vueThis.meishiCountList = meishiCount; vueThis.chartOption.scales.xAxes[0].ticks.max = Math.max.apply( null, Object.values(meishiCount) ); vueThis.chartHeight = 30 * meishiList.length; console.log( "最大値:" + Math.max.apply(null, Object.values(meishiCount)) ); console.log(vueThis.meishiCountList); }) .finally(() => (this.isLoading = false)); } } }); </script> <style> .bar-graph { margin-left: auto; margin-right: auto; padding-top: 10%; padding-bottom: 10%; background-color: #f5f5f5; } </style> </body> </html>Vue.js
Express
でもテンプレートエンジンとして使用できるのですが、今回のフロントエンドのフレームワークはVue.js
を使用しています。単純にVue.js
が書きやすかったので選択(いろいろとダメですが・・・)しました。バックエンドの
API
への接続から、形態素解析された後のデータの処理をVue.js
のmethods
で処理しています。返ってくるデータを整理するために、いろいろと試行錯誤していますが、もう少しイイカンジに書けそうな気がします。Bulma
CSSや見た目のデザイン面では
Bulma
を使用しています。今回は以下のテンプレートから参考させていただきました。https://bulmatemplates.github.io/bulma-templates/templates/landing.html
Charts.js
以下のサイトを参考にグラフを追加しました。
https://vuejsexamples.net/vue-bar-chart/
当初はなかなか
Chart.js
のプロパティ(設定)の理解ができませんでした。
しかしながら元のライブラリのコードを読んだり、ドキュメントをちゃんと読むことで少しずつ理解することできました。具体的にやったことはOption
を設定する(上書きする)ことでグラフの設定をカスタマイズすることができました。課題
形態素解析するときに辞書を読み込むので極端にメモリを消費しています。
今後辞書を良いものに変えると辞書の容量も増えることになり、もっとメモリを消費することになります。もし品質を向上させる場合は、
Glitch
をアップグレードするか他のサービスを使うことを検討する必要がありそうです。ちなみに当初は Netlify Functions を使おうとしていて、辞書ファイルの読み込みが上手くいきませんでした。これはファイルの読み込み(
fs.readFile
)に工夫がいるとのことで、今回はGlitch
を選択しています。コードも改善余地があり形態素解析した結果の処理などはもっとイイカンジにできると思います。そもそも
Express
に統一した方がよいかもしれません。ただ、今回はお勉強が目的でどんどん動かしていくモノを作っていけたので一旦は良い結果が得られたと思います。
- 投稿日:2020-08-02T22:18:41+09:00
『パクってOK』transformプロパティで圧倒的に動くCSSアニメーション28選
こちらの記事に記載のデザイン・コードは全てオリジナルなので自由に使っていただいて大丈夫です(筆者が作成したため)
Web制作で使える。コピペOK。HTML, CSSだけ
まずは動きを確認してみてください(これがコピペOKなのかと驚くはず)
コードの説明や実際の動きも詳しくまとめてみたので自由にお使いください。
【rotate×scale】画像が回転・拡大するCSSアニメーション3選(解説あり)
動きは下の画像のような感じになります
1. rotate×box-shadowでリアルすぎる画像回転アニメーション
2. rotate×scale(拡大)で失敗しない画像回転アニメーション
3. rotate×scale(拡大)×filterで想像を上回る画像回転アニメーション
コードを確認する
【transform rotate(360deg)×flexbox】画像が回転+伸縮するCSSアニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateX(360deg)×flexbox画像アニメーション
2. 【横回転】transform rotateY(360deg)×flexbox画像アニメーション
3. 【平面回転】transform rotateZ(360deg)×flexbox画像アニメーション
コードを確認する
【真似したくなる】transform:rotate×scaleでCSS画像アニメーション3選
動きは下の画像のような感じになります
1. transform rotateX×scaleで【縦回転】CSS画像アニメーション
2. transform rotateY×scaleで【横回転】CSS画像アニメーション
3. transform rotateY/Z×scaleで【3D回転】画像アニメーション
コードを確認する
transform:translateで絵画デザインのスライドショーを実現!CSSスライダーアニメーション3選
動きは下の画像のような感じになります
1. クリックで右方向からスライドしてくるアニメーション
2. クリックで上からスライドしてくるアニメーション
3. クリックで斜め上からスライドしてくるアニメーション
コードを確認する
【transform:rotate×scale】回転×拡大するCSS画像アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateXで画像アニメーション
2. 【横回転】transform rotateYで画像アニメーション
3. 【3D回転】transform rotateYZで画像アニメーション
コードを確認する
filterとtransformでリアルな絵画デザインのCSSスライダーアニメーション3選
動きは下の画像のような感じになります
1. クリックで横幅が伸び縮みしながらスライドするアニメーション
2. クリックで縦幅が伸び縮みしながらスライドするアニメーション
3. クリックで思いっきり伸び縮みしながらスライドするアニメーション
コードを確認する
filterとtransformで超動くCSSスライダーアニメーション4選【Web制作者必見】
動きは下の画像のような感じになります
1. 縦回転するシンプルなスライダーアニメーション
2. 滑らかに横回転するスライダーアニメーション
3. 平面回転するスタイリッシュなスライダーアニメーション
コードを確認する
【transform×回転『rotate』】CSS画像アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateXで画像アニメーション
2. 【横回転】transform rotateYで画像アニメーション
3. 【3D回転】transform rotateYZで画像アニメーション
コードを確認する
【transform rotate×画像背景】CSS回転アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateX画像背景アニメーション
2. 【横回転】transform rotateY画像背景アニメーション
3. 【3D回転】transform rotateYZ画像背景アニメーション
コードを確認する
- 投稿日:2020-08-02T22:18:41+09:00
『パクってOK』transformプロパティで圧倒的に動くCSSアニメーション28選【Vueでも使える】
こちらの記事に記載のデザイン・コードは全てオリジナルなので自由に使っていただいて大丈夫です(筆者が作成したため)
Web制作で使える。コピペOK。HTML, CSSだけ
まずは動きを確認してみてください(これがコピペOKなのかと驚くはず)
コードの説明や実際の動きも詳しくまとめてみたので自由にお使いください。
【rotate×scale】画像が回転・拡大するCSSアニメーション3選(解説あり)
動きは下の画像のような感じになります
1. rotate×box-shadowでリアルすぎる画像回転アニメーション
2. rotate×scale(拡大)で失敗しない画像回転アニメーション
3. rotate×scale(拡大)×filterで想像を上回る画像回転アニメーション
コードを確認する
【transform rotate(360deg)×flexbox】画像が回転+伸縮するCSSアニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateX(360deg)×flexbox画像アニメーション
2. 【横回転】transform rotateY(360deg)×flexbox画像アニメーション
3. 【平面回転】transform rotateZ(360deg)×flexbox画像アニメーション
コードを確認する
【真似したくなる】transform:rotate×scaleでCSS画像アニメーション3選
動きは下の画像のような感じになります
1. transform rotateX×scaleで【縦回転】CSS画像アニメーション
2. transform rotateY×scaleで【横回転】CSS画像アニメーション
3. transform rotateY/Z×scaleで【3D回転】画像アニメーション
コードを確認する
transform:translateで絵画デザインのスライドショーを実現!CSSスライダーアニメーション3選
動きは下の画像のような感じになります
1. クリックで右方向からスライドしてくるアニメーション
2. クリックで上からスライドしてくるアニメーション
3. クリックで斜め上からスライドしてくるアニメーション
コードを確認する
【transform:rotate×scale】回転×拡大するCSS画像アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateXで画像アニメーション
2. 【横回転】transform rotateYで画像アニメーション
3. 【3D回転】transform rotateYZで画像アニメーション
コードを確認する
filterとtransformでリアルな絵画デザインのCSSスライダーアニメーション3選
動きは下の画像のような感じになります
1. クリックで横幅が伸び縮みしながらスライドするアニメーション
2. クリックで縦幅が伸び縮みしながらスライドするアニメーション
3. クリックで思いっきり伸び縮みしながらスライドするアニメーション
コードを確認する
filterとtransformで超動くCSSスライダーアニメーション4選【Web制作者必見】
動きは下の画像のような感じになります
1. 縦回転するシンプルなスライダーアニメーション
2. 滑らかに横回転するスライダーアニメーション
3. 平面回転するスタイリッシュなスライダーアニメーション
コードを確認する
【transform×回転『rotate』】CSS画像アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateXで画像アニメーション
2. 【横回転】transform rotateYで画像アニメーション
3. 【3D回転】transform rotateYZで画像アニメーション
コードを確認する
【transform rotate×画像背景】CSS回転アニメーション3選
動きは下の画像のような感じになります
1. 【縦回転】transform rotateX画像背景アニメーション
2. 【横回転】transform rotateY画像背景アニメーション
3. 【3D回転】transform rotateYZ画像背景アニメーション
コードを確認する
- 投稿日:2020-08-02T18:43:16+09:00
【Vue.js】CDN版でも単一ファイルコンポーネント(.vue)を使いたい!
こんにちは。
CDN版ライブラリの使用については皆さん賛否両論あると思いますが、私は容認派です。
手軽に使える便利さは正義!そんなわけで、ビルド不要なCDN版Vue.jsでも単一ファイルコンポーネントシステム(
.vue
ファイル)を実現したくね!?と思ったので色々と試してみたお話です。そもそもそんなことできるの?
できました。
しかし一筋縄では行かず、クライアントサイドで多少のゴリ押しが必要となります。
ゴリ押すためには、以下のキーポイントを押さえる必要があります。非同期通信
いわゆるAjaxと呼ばれるやつです。
XMLHTTPRequest
やfetch
など色々ありますが、ここではaxios
という便利な非同期通信ライブラリを使用します。非同期コンポーネントとミックスイン
Vue.jsは、非同期でコンポーネントを追加できる、非常に柔軟な機構を持っています。
これらを使用し、非同期通信で取得した.vue
ファイルをパースし、既存のコンポーネントへ追加します。VueRouter
Vue.jsで仮想ルーティングを実装するためのプラグインです。
実はVueRouterもCDN版が存在し、ビルド不要で仮想ルーティングを実装できます。
(更に言うとステート管理プラグインのVuexもCDN版が存在します!)VueRouterと単一ファイルコンポーネントを組み合わせ、ビルドシステムさながらのページを構築することができます。
サンプルコード
/index.html
: メインページ/component.vue
: Vueコンポーネントファイル/paths.json
: 仮想パスとコンポーネントファイルの物理パスを列挙したマッピングデータこれら3個のファイルのみで、Vue.jsアプリケーションを構築してみます。
UIはVuetifyを使用しました。なぜマッピングデータが必要かというと、静的配信においてクライアントはサーバーがどのファイルを配信しているかを知る術がないからです。
マッピングデータを用意してやることにより、クライアントがコンポーネントファイルの場所を解決できるようになります。最初のうちはマッピングデータをメインページのJavaScript内で記述しても問題ないと思いますが、拡張性を考えると外へ書き出しておいたほうが良いかなと思います。
index.html
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="utf-8"> <meta name="viewport" content="height=device-height, width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=no, minimal-ui"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/vuetify@latest/dist/vuetify.min.css"> <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"> <script src="https://cdn.jsdelivr.net/npm/vue@latest/dist/vue.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vue-router@latest/dist/vue-router.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/vuetify@latest/dist/vuetify.min.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@latest/dist/axios.min.js"></script> <title>Vue</title> </head> <body> <div id="vue"> <v-app> <v-app-bar app dark height="52" color="secondary"> <v-toolbar-title>Vue</v-toolbar-title> </v-app-bar> <v-main> <router-view></router-view> </v-main> </v-app> </div> </body> <script> const loadComponent = axios.create({ method: "get", baseURL: ".", responseType: "text", transformResponse(data){ const doc = new DOMParser().parseFromString(data, "text/html"); const template = doc.head.getElementsByTagName("template")[0]?.innerHTML; const script = doc.head.getElementsByTagName("script")[0]?.innerText const style = doc.head.getElementsByTagName("style")[0]; if(style){ document.head.appendChild(style); } return { template(){ return template ? template : ""; }, script(){ return script ? new Function(script.trim().replace(/^(export\s+default|module\.exports\s*?=)\s*?/i, "return"))() : {}; } }; } }); const loadMap = axios.create({ method: "get", baseURL: "./components", responseType: "json", transformResponse({parents, paths}){ return Object.entries(paths).map(([virtual, physical]) => [`${parents.virtual}${virtual}`, `${parents.physical}${physical}`]); } }); const vue = (async()=>{ const maps = [ "/paths.json" ]; const paths = []; for await(const {data} of maps.map(map => loadMap(map))){ paths.push(...data); } return new Vue({ el: "#vue", vuetify: new Vuetify(), router: new VueRouter({ routes: paths.map(([virtual, physical])=>{ return { path: virtual, async component(res){ const {data} = await loadComponent(`${physical}.vue`); res({ template: data.template(), mixins: [data.script()] }); } }; }) }) }); })(); </script> </html>コンポーネントローダー
最初に
axios.create()
で、コンポーネントファイルをロードするためのaxiosインスタンスを作ります。
以後、このインスタンスをコンポーネントローダーと呼称します。レスポンスデータは
transformResponse()
でVueコンポーネントとして使えるよう加工します。加工する内容は、まずVueコンポーネントファイルをテキスト形式で取得し、DOMツリーへと変換します。
これは、Vueコンポーネント内の<template>
/<script>
/<style>
を分離するためで、正規表現マッチによるタグ検出も不可能ではないですが、より簡単で正確なDOMParser
を使用します。なお、VueコンポーネントはHTMLとして解析されるため
<template>
/<script>
/<style>
は自動的にDOMツリーの<head>
内へ移動されます。テンプレート文字列
<template>
はそのままinnerHTML
で中身を返します。
問題はJavaScript文字列の<script>
で、これは少しテクニカルです。JavaScript文字列の評価にはしばしば
eval()
が用いられますが、このeval()
は速度面でも安全面でも良くないという話は周知の事実かと思います。そこで、同じくJavaScript文字列から評価を行える、関数コンストラクタ
new Function()
を使います。
関数を作成する場合、通常であればfunction xxx(){}
と書きますが、関数コンストラクタを用いることでも作成できます。関数コンストラクタは、中身となるJavaScript文字列を引数として受け取り、それを評価し関数オブジェクトとして返します。
eval()
とは違い、評価結果が関数スコープ内へ封じ込められるため安全という訳です。関数コンストラクタは関数を返すため
new Function()()
としてやれば、評価した関数をそのまま即時実行することもできます。なお、評価の直前でJavaScript文字列の先頭
export default
またはmodule.exports
をreturn
へと書き換えています。
これは、コンポーネントオブジェクトをexport
へ出力するのではなく、関数の返り値とするためです。この時の動作を表すと、以下のようになります。
// "return" へ書き換えた後に関数コンストラクタの引数へ代入 // "export default{data(){},...}" => "return{data(){},...}" return new Function("return{data(){},...}")(); // => {data(){},...} // 上記の関数コンストラクタの評価結果(等価) return (function(){ return { data(){ }, ... }; })(); // => {data(){},...}この、評価後に返されるコンポーネントオブジェクトを後述のミックスインとして追加している訳です。
もし書き換えなければ、コンポーネントオブジェクトは正体不明の
export
へ出力されることになってしまいます。なお、テンプレートやスクリプトの処理結果を、オブジェクトプロパティではなく関数として返しているのは、参照渡しではなく値渡しするためです。
<style>
は問答無用でドキュメント内へ挿入しています。
(将来的に乱数もしくはハッシュ値を取ってscoped
対応もできれば良いなと考えています)マップローダー
再び
axios.create()
を使い、マッピングデータをロードするためのaxiosインスタンスを作成します。
以後、このインスタンスをマップローダーと呼称します。このレスポンスデータも
transformResponse()
でマッピングデータとして使えるよう加工します。マッピングデータは 後述 のようなデータ構造となっています。
parents
プロパティで仮想パスと物理パスそれぞれに親パスを宣言できるため、親パスとpaths
オブジェクト内の子パスを結合してあげます。返り値は
Array<["仮想パス", "物理パス"]>
のような連想配列とすることで、その後のイテレーション処理を簡単にしています。非同期コンポーネント
まず、VueRouterのパスが非同期で取得されるマッピングデータへ依存しているため、Vueインスタンス全体を
async
即時関数でラップします。なお、複雑なパスの構築へ備え、複数のマッピングデータファイルを結合できるような作りになっています。
マップローダーでマッピングデータを取得し終えたら、Vueインスタンスの構築へ移ります。VueRouterの
path
プロパティへ仮想パスを、コンポーネントローダーへ物理パスを渡すことで、それぞれマッピングの整合をとっています。Vue(VueRouter)の
component
プロパティは、コンポーネント内容を直接記載する場合はオブジェクトとしますが、コンポーネント内容が非同期な場合は、引数resolve
で解決されるPromise
を返すcomponent()
関数となります。
template
プロパティには、コンポーネントローダーの返り値template()
でテンプレート文字列を渡します。
mixins
プロパティには、コンポーネントローダーの返り値script()
でコンポーネントオブジェクトを渡します。component.vue
component.vue<template> <div> <v-btn>foo</v-btn> <v-btn>{{test}}</v-btn> </div> </template> <script> export default { data(){ return { test: "aaa" }; } }; </script>普通のコンポーネントファイルと同様に記述していきます。
ただ実際にロードされるときは、上述の通り先頭がexport default
またはmodule.exports
からreturn
へ自動で書き換えられます。paths.json
paths.json{ "parents": { "virtual": "", "physical": "" }, "paths": { "/": "/component" } }
paths
プロパティで"仮想パス":"物理パス"
のようにマッピングしていきます。
なおparents
プロパティで一括して親パスを設定できるため、実際のパスはparents
+paths
となります。仮想パスについては
:id
のようなVueRouterの動的ルートマッチング機能も使えます。
物理パスに拡張子を含めるか含めないかは好みですが、今回は実質.vue
以外には成り得ないため、ここでは記述しないほうがミスは減るかなと思います。まとめ
だいぶパワープレイになりましたが、無事CDN版Vue.jsでも単一コンポーネントシステムを実現することができました。
と言うかこれもう完全に人力ビルドシステムだなおいもっと良い方法があるよ!とか参考になった!とかありましたらコメント頂ければ幸いです!
- 投稿日:2020-08-02T04:15:55+09:00
なんでVue.jsではコンポーネント内のデータは関数で書くのか?
こんにちは、Yuiです。
私はVue.jsを使って業務委託で開発を行っているのですが、最初ナニモワカランという状態から参入させてもらったため、Vue.jsを雰囲気で書いてました。(今もだいぶ雰囲気で書いちゃってるので、徐々に精進していきます。)
コンポーネント内ではデータは関数で定義する。
Vueを書く人からしたら当たり前かもしれませんが、そのへんも理屈を全くわからないままそういうもんだとおもって開発してました。
ようやく最近そのへんの理屈がわかるようになってきたので、今回はそのことについて書きます。参考:超Vue.js 完全パック - もう他の教材は買わなくてOK! (Vue Router, Vuex含む)
そもそもコンポーネントとは?
コンポーネントとは、何回も使える要素のことを指します。
コーディングをしていると、再利用したい要素というのが出てくるかと思うのですが、その度に新しくコーディングするのは大変です。
例えば、下記のコードを見てください。
<div id="app"></div> new Vue({ el:'#app', data: { hello: 'hello' }, template: '<p>{{hello}}</p>' })この場合、出力は
hello
と出力されますが、もしこれを複数回表示させたい場合、どうなるでしょうか?
つまり、'hello'を何回も表示させたい場合です。通常で考えれば、
<div id="app"></div> <div id="app"></div> <div id="app"></div> new Vue({ el:'#app', data: { hello: 'hello' }, template: '<p>{{hello}}</p>' })このように、
<div id="app"></div>
の部分を何回も書けばいいと思うかもしれません。
ただ、Vue.jsでは実はこれでは一回しか表示がされないのです。なぜかというと、Vueインスタンスでは同じものは表示しないという性質があるからです。
そこでコンポーネントの出番です。というわけで、下記のように書き直します。
<div id="app"> <component></component> <component></component> <component></component> </div> Vue.component('component', { data: function() { return { hello:'hello' } }, template: '<p>{{hello}}</p>' }) new Vue({ el:'#app' })こうすることで、出力結果は
hello hello helloとなります。
コンポーネントでは、最初に第一引数でコンポーネント名を定義する必要があります。
今回は簡単にcomponent
としたので、そのコンポーネントを表示するときは<component></component>
と書きました。これを例えば
Vue.component('hello_component', { data: function() { return { hello:'hello' } }, template: '<p>{{hello}}</p>' })このようにした場合は、出力する際に
<hello_component></hello_component>
と書くことができます。コンポーネント内のデータを無理やりオブジェクト形式で書いてみる
それでは本題の、なぜコンポーネント内の
data
部分は関数で定義するのかに関して。そもそも、オブジェクト形式では書けないのでしょうか。
書いてみましょう。Vue.component('component', { data: { hello:'hello' }, template: '<p>{{hello}}</p>' }) new Vue({ el:'#app' })出力しても何も表示されません。
vueではコンポーネント内では関数を使わないとエラーになるみたいですね。
ただ、data部分をオブジェクト形式で無理やり書くことは出来るので、書いてみます。var data = { hello: 'hello' } Vue.component('component', { data: function() { return data }, template: '<p>{{hello}}</p>' }) new Vue({ el:'#app' })無事出力されました!
何が問題なのか
上記のように、書いた場合、何が問題なのでしょうか?
表示自体は問題なくできているように感じます。ただ上記の場合だと
var data
で中身となるデータ部分を一定のものに定義してしまっています。
つまり、今後データが変更されたらすべての部分で変更が起こってしまうということです。具体的なコードを出します。
Vue.component('component', { data: function() { return data }, template: '<p>{{hello}}<button @click="bye">bye</button></p>', methods: { bye: function() { this.hello = 'bye'; } } }) new Vue({ el:'#app' })こんな感じで、全部のデータ(hello)が
bye
に変わってしまうんです。ただ、できればボタンを押したところだけのデータを変えたいですよね。
というわけで前置きが長くなりましたが、やはりきちんとデータは関数で書いてあげます。Vue.component('component', { data: function() { return { hello:'hello' } }, template: '<p>{{hello}}<button @click="bye">bye</button></p>', methods: { bye: function() { this.hello = 'bye'; } } }) new Vue({ el:'#app' })こうすることで、きちんとデータが独立して変化するようになりました!
まとめ
というわけで、なぜVue.jsではコンポーネント内のデータは関数で書くのか?に対する答えとしては、
レイアウトなどは同じでも、データをそれぞれで変化させる必要があるから
ということになります。そもそも、いつコンポーネントを使うのかと考えた際に、レイアウトは同じでも、中に入ってるデータまで全く同じ表示をすることをないですよね。データだけを変更できるように再利用可能にしたものがコンポーネントです。
当たり前のようですが、あまり意識せずに使っていたのでまとめてみました。
- 投稿日:2020-08-02T00:25:44+09:00
Vuetify v-comboboxをアクティブ状態で送信ボタン押すと以前の値が取得される
再現手順
①v-comboboxで入力または項目を選択
②v-comboboxをアクティブ状態のまま送信ボタン押下
→以前の入力データが取得されてしまう問題のコード
.vue<div id="app"> <v-app> <v-combobox v-model="userValue" :items="userItems" ref="userComboBox" > </v-combobox> <v-btn color="primary" dark large @click="submit()">送信</v-btn> </v-app> </div>.jsnew Vue({ el: '#app', vuetify: new Vuetify(), data () { return { userValue: "", userItems: [ "A","B","C" ], } }, methods:{ submit(){ alert(this.userValue); } }, })submit関数内のconsole.logで出力されるデータは現在入力されているデータではなく、
以前のデータが取得されてしまう。
v-comboboxは非アクティブにならないと入力情報がv-modelに伝わらない為だと思われる。。。解決法はコンボボックスを非アクティブ(blur)してnextTickを使用する
.jssubmit(){ this.$refs.userComboBox.blur(); this.$nextTick(() => { console.log(this.userValue); }) }
- 投稿日:2020-08-02T00:25:44+09:00
【Vuetify】v-comboboxをアクティブ状態で送信ボタン押すと以前の値が取得される問題
再現手順
①v-comboboxで手入力する
②v-comboboxをアクティブ状態のまま送信ボタン押下
→以前の入力データが取得されてしまう問題のコード
上記でコンボボックスに何か入力(項目選択ではない)してアクティブ状態のまま送信ボタンを押してみてください
アラートに何も表示されませんが、2回目送信ボタンを押すと表示されます。.vue<div id="app"> <v-app> <v-combobox v-model="userValue" :items="userItems" ref="userComboBox" > </v-combobox> <v-btn color="primary" dark large @click="submit()">送信</v-btn> </v-app> </div>.jsnew Vue({ el: '#app', vuetify: new Vuetify(), data () { return { userValue: "", userItems: [ "A","B","C" ], } }, methods:{ submit(){ alert(this.userValue); } }, })submit関数内のconsole.logで出力されるデータは現在入力されているデータではなく、
以前のデータが取得されてしまう。
v-comboboxは非アクティブにならないと入力情報がv-modelに伝わらない。
v-text-fieldではこの現象はもちろん発生しない。v-modelへの反映は入力されたら即時行われる為。解決法はコンボボックスを非アクティブ(blur)してnextTickを使用する
.jssubmit(){ this.$refs.userComboBox.blur(); this.$nextTick(() => { console.log(this.userValue); }) }