- 投稿日:2019-07-01T19:09:46+09:00
Vue.jsで連想配列の要素を削除するには
Vue.jsで配列要素をレンダリングに反映されるよう削除するには
Vue.obj.splice(インデックス番号, 削除数)を使いますが、連想配列ではspliceは使えません。
Vue.$delete(配列, キー)を使います。
要素追加の際のVue.$set(配列, キー, 値)と似てますね。
- 投稿日:2019-07-01T17:34:01+09:00
Vuex の使い方を勉強してみた
はじめに
Qiita 初投稿です
Vuex について勉強した際のメモをまとめました。
まだWeb開発初心者のため、単語や言い回しなどおかしなところがあるかもしれません。間違った記載がありましたらご教授いただけると嬉しいです!
Vuex とは
Vue を用いたアプリケーションの開発では、コンポーネント間でのデータのやりとりが頻繁に発生する。
コンポーネント間でデータの整合性を保つためには 各コンポーネントで値渡しの処理を記述する必要があり、ソースの可読性とデバッグ効率が低下しやすい。Vuex は、Vueアプリケーションにおけるデータの状態管理を一元化して開発効率を上げることを目的としたライブラリである。
Vue アプリケーションで扱うデータセットを Store と呼ばれる領域で一元管理することで、各コンポーネントは Store にアクセスすれば常に共通の値を参照することができるようになる。
また、Store のデータに対する操作を予め定義しておけるので、予期しない操作の防止や保守性・可読性の向上が見込める。Vuex / Store の定義
Vuex を Vue アプリケーションで使用する際は以下のように宣言し、Store を定義する。
store/index.js"use strict" import { Vue } from "vue" import { Vuex } from "vuex" Vue.use(Vuex); // Storeを生成 const store new Vuex.Store({ state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }); export default store;main.jsimport Vue from "vue" import store from "store" // Vueインスタンスの定義時に、Store情報を組み込む new Vue({ el: '#app', store, render: h => h(App) });
store
の宣言でstate
,getters
,mutations
,actions
という項目があるが、これらは Store が保持するデータ項目や、Store 上のデータを外部(コンポーネント等)から操作するための関数を定義する項目である。
state
,getters
,mutations
,actions
Store の作成時に定義できる項目は下記の4つである。
項目名 概要 state Store で管理するデータ項目の定義 getters state 内のデータの状態から算出される値(≒算出プロパティ) mutations state のデータを直接操作するための関数(非同期処理は定義不可) actions mutations の操作を各コンポーネントから呼び出すために使用する関数(非同期処理を定義可) ↓ 定義のイメージ
const store new Vuex.Store({ state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } });それぞれの項目は用途によって使い分けされるので、順番に説明する。
state
( Store で管理するデータ項目の定義 )Vuex の Store で管理するデータ項目を定義する。
ここに定義したデータは Vueアプリケーション内の各コンポーネントから適宜取得・更新することができる。state 定義の例
store/index.js// Store 定義 const store = new Vuex.Store({ state: { count: 0 }, // ... });次のように定義することもできる。
store/index.jsconst state = { count: 0 }; // Store 定義 const store = new Vuex.Store({ state, // ... });コンポーネントから state の値を使用
this.$store.state
の値をcomputed
で監視する。components/Counter.vue<template> <div>{{ count }}</div> </template> <script> export default { name: "Counter", computed: { count () { return this.$store.state.count } } }; </script>
mapState
ヘルパーを使用した方が簡潔に書ける。store/index.js// Store 定義 const store = new Vuex.Store({ state: { count1: 0, count2: 0 }, // ... });components/Counter.vue<template> <div> <div>{{ count1 }}</div> <div>{{ count2 }}</div> </div> </template> <script> import { mapState } from "vuex" export default { name: "Counter", computed: { ...mapState([ "count1", // 注意)プロパティ名は ' または " でくくる必要がある "count2" ]) } }; </script>state データの更新・削除について
state
は 直接更新・削除を行なってはいけない 。基本的に Store 内のデータ操作は、後述する mutations に定義する。
getters
( state 内のデータの状態から算出される値(≒算出プロパティ))
getters
ではstate
のデータに対する算出プロパティを定義し、各コンポーネントで利用できる。例えば、TODOリストの未完了のデータ数を取得する関数
doneTodoCount
が、以下のように定義されるとする。components/TodoList.vue<template> <div>{{ doneTodoCount }}</div> </template> <script> export default { name: "TodoList", computed: { doneTodoCount () { return this.$store.state.todos.filter(todo => todo.done).length } } }; </script>上記の書き方で目的は果たせるが、他のコンポーネントでこの関数を利用したい場合にはこの関数をコピーするか、共通処理として外部モジュールに切り出してインポートする必要がある。
getters
を使用することで、Store 経由で共通の算出プロパティとして使用できるようになる。getters の定義
getters
に定義する関数は第1引数にstate
をもち、ここから Store のデータにアクセスできる。store/index.js// Store 定義 const store = new Vuex.Store({ state: { todos: [ { id: 1, label: '...', done: true }, { id: 2, label: '...', done: false } ] }, getters: { // 第1引数に state をもつ doneTodoCount: (state) => { return state.todos.filter(todo => todo.done).length } } // ... });コンポーネントから getters の関数を使用
state
と同様の形でthis.$store.getters
に含まれるゲッター関数をcomputed
で監視する。components/TodoList.vue<template> <div>{{ doneTodoCount }}</div> </template> <script> export default { name: "TodoList", computed: { doneTodoCount () { return this.$store.getters.doneTodoCount } } }; </script>
mapGetters
ヘルパー関数によって参照することもできる。components/TodoList.vue<template> <div>{{ doneTodoCount }}</div> </template> <script> import { mapGetters } from "vuex" export default { name: "TodoList", computed: { ...mapGetters([ "doneTodoCount" ]) } }; </script>※getters では同期的な処理のみを記述する。Ajax等の非同期処理を実行したい場合は、後述する actions で定義する。
mutations
( state のデータを直接操作する関数 )記述中
actions
( mutations の操作 + 非同期処理する関数 )記述中
モジュール分割による store の切り分け
Store は以下のようにモジュール分割して定義することもできる。
store/moduleA.jsconst moduleA = { state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }; export default moduleA;store/moduleB.jsconst moduleB = { state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } }; export default moduleB;store/index.jsimport moduleA from "moduleA.js" import moduleB from "moduleB.js" const store = new Vuex.Store({ modules: { A: moduleA, B: moduleB } });モジュール分割することで Store が肥大化することを防ぎ、またカテゴリ等によって Store を分けて管理できる。
デフォルトでは各モジュールで宣言したgetters
,mutations
,actions
はグローバル名前空間に登録されるため、複数のモジュールが同じミューテーション/アクションタイプに反応することになる。名前空間をモジュール単位で登録したい場合は、モジュールの宣言時に
namespaced = true
を設定する。store/moduleA.jsconst moduleA = { namespaced = true, state: { ... }, getters: { ... }, mutations: { ... }, actions: { ... } };モジュール分割した Store をコンポーネントから参照する
以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。
store/moduleA.jsconst moduleA = { namespaced = true, state: { result: undefined }, mutations: { setResult(state, data) { state.result = data }, clearResult({ commit }) { state.result = undefined } }, actions: { setResult({ commit }, data) { commit("setResult", data) }, clearResult({ commit }) { commit("clearResult") } } }; export default moduleA;store/moduleB.jsconst moduleB = { // 以下、moduleA と同じ内容 namespaced = true, state: { result: undefined }, mutations: { setResult(state, data) { state.result = data }, clearResult({ commit }) { state.result = undefined } }, actions: { setResult({ commit }, data) { commit("setResult", data) }, clearResult({ commit }) { commit("clearResult") } } }; export default moduleB;
moduleA
、moduleB
はそれぞれが
- データ項目
result
- データ操作用のアクション
setResult
、clearResult
を持つ。
以下のコンポーネントでは、
moduleA
、moduleB
の データ項目result
に対して表示・更新・クリアができる。components/Sample.vue<template> <div> <div>{{ result_A }}</div> <div>{{ result_B }}</div> <input type="text" v-model="input_A"> <button @click="setResult_A">更新</button> <button @click="clearResult_A">クリア</button> <input type="text" v-model="input_B"> <button @click="setResult_B">更新</button> <button @click="clearResult_B">クリア</button> </div> </template> <script> import { mapState, mapActions } from "vuex" export default { name: "Sample", data: { input_A: "", input_B: "" }, computed: { // 第1引数に名前空間(moduleA, moduleB)を指定し、 // それぞれの "state.result" を別名で取得 ...mapState("moduleA", { result_A: state => state.result }), ...mapState("moduleB", { result_B: state => state.result }), }, methods: { // 第1引数に名前空間(moduleA, moduleB)を指定し、 // それぞれのアクションを別名で取得 ..mapActions("moduleA", { setResult_A (dispatch) { dispatch("setResult", this.input_A) }, clearResult_A: "clearResult" }), ..mapActions("moduleB", { setResult_B (dispatch) { dispatch("setResult", this.input_B) }, clearResult_B: "clearResult" }) } }; </script>上記の
..mapActions("moduleA", [ ... ])
のように、ヘルパー関数の第1引数に名前空間(moduleA, moduleB)を指定することで、指定したモジュールの store を操作できる。まとめ
Vuex を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!
- 投稿日:2019-07-01T16:56:13+09:00
vue-multiselect を CDN でつかってみる
Vue.jsで、こんな感じのいけてる入力フォームのUIを探してたのですが
- 選択肢からフィルタしながら入力
- 入力した内容はタグになる
- 複数の値を選択できる
vue-multiselect
っていうのを見つけて、これが良さそう。さっと確認するには、JSはCDNで読み込んで試したいのですが、多くのVueのライブラリはCDNの時の使い方の例をちゃんと書いてないですね...
幸い、今回の
vue-multiselect
はcomponents: { Multiselect: window.VueMultiselect.default }
ってやるだけでいけましたvue-multiselect.html<!DOCTYPE HTML> <html> <head> <title>Timeline</title> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://unpkg.com/vue-multiselect@2.1.0"></script> <link rel="stylesheet" href="https://unpkg.com/vue-multiselect@2.1.0/dist/vue-multiselect.min.css"> <script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script> </head> <style> body { font-family: 'Arial' } </style> <body> <div id="app"> <div> <multiselect v-model="value" placeholder="city name?" label="city" track-by="city_ascii" :options="options" :multiple="true" :taggable="true" ></multiselect> </div> <pre class="language-json"><code>{{ value }}</code></pre> </div> </body> <script> var app = new Vue({ el: '#app', components: { Multiselect: window.VueMultiselect.default }, data () { return { value: [], options: [ { "city": "San Martin", "city_ascii": "San Martin", "lat": -33.06998533, "lng": -68.49001612, "pop": 99974, "country": "Argentina", "iso2": "AR", "iso3": "ARG", "province": "Mendoza", "timezone": "America/Argentina/Mendoza" }, { "city": "San Nicolas", "city_ascii": "San Nicolas", "lat": -33.33002114, "lng": -60.24000289, "pop": 117123.5, "country": "Argentina", "iso2": "AR", "iso3": "ARG", "province": "Ciudad de Buenos Aires", "timezone": "America/Argentina/Buenos_Aires" }, { "city": "San Francisco", "city_ascii": "San Francisco", "lat": -31.43003375, "lng": -62.08996749, "pop": 43231, "country": "Argentina", "iso2": "AR", "iso3": "ARG", "province": "Córdoba", "timezone": "America/Argentina/Cordoba" } ] } } }) </script>作者に感謝。誰かの時間の節約になればと思い、CodePenをそっと置いておく。
Cheers,
- 投稿日:2019-07-01T15:44:01+09:00
Vue.jsの環境構築
Node.jsをインストール
最新版は不具合が多いので推奨版をインストール
https://nodejs.org/ja/
VSCode
以下のプラグインをインストール
Vetur:Vue.jsのサポート
VueHelper:Vue.jsのコード補完
HTML Snippets:HTML5のコード補完
language-stylus:stylusのサポート
HTML CSS Support:CSSのサポート
ESLint:JavaScriptのlint
TSLint:TypeScriptのlint
vueをインストール
ターミナルで以下を入力$ npm install -g @vue/cli //インストールできたかの確認は以下 $ vue --versionFirebase
バックエンドの処理を代わりにしてくれるサービス
本来はPHPなどのサーバーサイド言語で、行うことを代わりにしてくれる
https://console.firebase.google.com/u/0/?hl=ja&pli=1
VSCode→デバッグボタンを押下→ターミナル
①下記を入力してVueにディレクトリ作成$ vue creat ディレクトリ名
②Vueディレクトリの設定
↓キーでManually select features 選択し Enter? Please pick a preset: (Use arrow keys) ❯ default (babel, eslint) Manually select features
③↓キーで選択し、スペースでlint以外チェックし、Enter
④コメントの通り行うVue CLI v3.7.0 ? Please pick a preset: Manually select features ? Check the features needed for your project: (Press <space> to select, <a> to toggle all, <i> to invert se ? Check the features needed for your project: TS, PWA, Router, Vuex, CSS Pre-processors, Linter ? Use class-style component syntax? Yes //enter ? Use Babel alongside TypeScript for auto-detected polyfills? No //enter ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes //ernter ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Stylus //Stylus選択 ? Pick a linter / formatter config: TSLint //TSLint選択 ? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i> to invert selection)Lint on save ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedicated config files ? Save this as a preset for future projects? No //enternmpにfirebaseのツールパッケージをインストール
※npm (Node.js Package Manager)
Node.jsをパッケージ(Package)を管理(Manager)するツール
パッケージは、予め用意された便利な機能をまとめたもの
①以下のコマンド入力し作ったディレクトリに移動$ cd ディレクトリ名②以下のコマンド入力し、ディレクトリの中にnpmを作成
$ npm run serve③Ctr + c で止める
④下記を入力しnmpをコンパイルする$ npm run build
⑤下記を入力しnmpにfirebaseのツールパッケージをインストールする$ npm install -g firebase-tools※ -g をつけることで、使用しているコンピュータ内のパッケージを全て表示する
⑥firebaseのツールが使えるようになったかの確認は以下を入力$ firebase --version※build
大規模プロジェクトを作成するときなどは、ソースコードのファイルがたくさん作られる
そのそれぞれのソースコードをそれぞれの機械語に翻訳した後、1つの実行ファイルにまとめることを指す
ソースコードをそれぞれ翻訳するのがコンパイル
コンパイルされた複数のファイルを1つの実行ファイルにまとめるのがリンク
ビルドはこのコンパイルとリンクをまとめて行なっている
deploy設定①下記を入力しfirebaseのアカウントにログイン
$ firebase login※loginで来なかったら下記を入力し、再度ログイン処理を行う
$ firebase logout下記が表示されたら成功
✔ Success! Logged in as アカウントアドレス matsuihidenori-no-MacBook-Pro:firebase-training matsuihidenori$ firebase login Already logged in as アカウントアドレス
②下記を入力$ firebase init hosting
③プロジェクト選択しEnter
④コメントの通り入力? What do you want to use as your public directory? dist //distと入力 ? Configure as a single-page app (rewrite all urls to /index.html)? No //enter ✔ Wrote dist/404.html ✔ Wrote dist/index.html
⑤下記を入力$ firebase deploy
⑥以下が表示されるので、command + クリックでURL選択Project Console: https://console.firebase.google.com/project/プロジェクト名/overview Hosting URL: https://プロジェクト名.firebaseapp.com※以後は、$ firebase deployと入力すればいつでもdeployできる
※deploy
ビルドしてできたファイルを実行する環境にあわせて、実際に実行できるようにすること開発者がデプロイして「準備おっけーだよ」ってあげたやつはネットワーク上で利用できるようになる
例えば ブラウザでできるゲームとか
ライブラリの導入$ cd ディレクトリ名$ npm install firebase vuetify axios date-fnsVSCode
フォルダーを開くを押下
srcに
repositoly
firebaseConfigexport const firebaseConfig = { apiKey: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', authDomain: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', databaseURL: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', projectId: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', storageBucket: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX', messagingSenderId: 'XXXXXXXX', }firebaseの初期化コード
import { firebaseConfig } from '@/repository/firebaseConfig'; import firebase from 'firebase/app'; import 'firebase/firestore'; firebase.initializeApp(firebaseConfig); firebase.firestore.FieldValue.serverTimestamp();Vuetifyの初期化コード
import Vuetify from 'vuetify'; import colors from 'vuetify/es5/util/colors’; //追加 themeはお好みで Vue.use(Vuetify, { theme: { original: colors.purple.base, theme: '#5982EE', twitter: '#00aced', facebook: '#305097', line: '#5ae628', error: '#F26964', succcess: '#698FF0', }, options: { themeVariations: ['original', 'secondary'], }, })ターミナルで下記入力でプレビュー確認
$ npm run servecommand + クリックで選択し、画面を表示
App running at: - Local: http://localhost:8080/ - Network: http://192.168.11.15:8080/
- 投稿日:2019-07-01T15:27:09+09:00
vue-routerでのレイアウトの出し分け
したいこと
ログイン画面とその他の画面で表示するレイアウトを変えたい
環境
laravel vue.js 5.8.26 2.9.6 実装
index.blade.php<body> <div id="app"> <app></app> </div> <script src="{{ mix('js/app.js') }}"></script> </body>
resources/js/router.jsimport VueRouter from 'vue-router'; import Login from './components/Login.vue' import Top from './components/Top.vue' const router = new VueRouter({ mode: 'history', base: '/', routes: [{ path: '/login', name: 'login', // metaにlayoutを追加 meta: { layout: 'none'}, component: Login }, { path: '/', name: 'top', component: Top } ] }); export default router;
- デフォルトレイアウトのcomponent作成
- ヘッダーフッターのないログイン用レイアウトのcomponent作成
resources/js/components/layout/DefaultLayout.vue<template> <div> <custom-header/> <router-view></router-view> <custom-footer/> </div> </template> <script> // ヘッダー、フッターコンポーネントをimport // コンポーネントの詳細は省略 import CustomHeader from '../common/Header' import CustomFooter from '../common/Footer' export default { name: 'login', components: { CustomHeader, CustomFooter, }, } </script>resources/js/components/layout/NoneLayout.vue<template> <div> <router-view></router-view> </div> </template>
resources/js/components/App.vue<template> <div> <!-- v-bind:isでタブインタフェースのコンポーネントを切り替えられる --> <component v-bind:is="layout()"></component> </div> </template> <script> // レイアウトコンポーネントをインポート import DefaultLayout from './layout/DefaultLayout.vue'; import NoneLayout from './layout/NoneLayout.vue'; export default { components: { DefaultLayout, NoneLayout }, methods: { layout() { // router.jsのroutesにmeta.layoutの存在確認 // セットするレイアウトを判断する let layout = this.$route.meta.layout ? this.$route.meta.layout + '-layout' : 'default-layout'; return layout; } } } </script>
- 投稿日:2019-07-01T14:54:08+09:00
Vuexのモジュールを使用してストアを管理する
Vuex で状態管理を行っているアプリケーションでは、基本的にストアは1つだと思います。
アプリケーションが大きくなるにつれストアで管理する必要がある状態は多くなった場合、沢山のステートやミューテーションなどを一つのオブジェクトで管理する事になってしまうでしょう。
そういった事態を防ぐ為に、Vuex にはモジュールオプションが用意されています。
これに加えて、個人的にモジュールオプションを使用する事のメリットとして、管理するステートに紐づくオプションをまとめてグループ化できる事だと感じています。私がVuexのモジュールオプションについて調べた時、分割したモジュールのステートへのアクセスや更新を行う方法に混乱して、なかなか理解が進みませんでした。
個人的な振り返りも含めて、簡単な「TODOアプリ」の作成を通して Vuex のモジュールの使用について記事にします。この記事では、以下の環境で作業を進めていきます。
- node -> 10.15.3
- npm -> 6.4.1
- @vue/cli -> 3.8.4
プロジェクトの作成
まずは、適当なディレクトリでプロジェクトを作成します。
vue create vue-todo-appVuexを使用する為、
Manually select features
を選択し、その後Vuexを選びます。? Please pick a preset: default (babel, eslint) ❯ Manually select features? Check the features needed for your project: ◉ Babel ◯ TypeScript ◯ Progressive Web App (PWA) Support ◯ Router ❯◉ Vuex ◯ CSS Pre-processors ◉ Linter / Formatter ◯ Unit Testing ◯ E2E Testingその他、いくつかの質問が表示されるので回答していきます。
今回は以下のように選択を進めました。? Pick a linter / formatter config: (Use arrow keys) ❯ ESLint with error prevention only ESLint + Airbnb config ESLint + Standard config ESLint + Prettier? Pick additional lint features: (Press <space> to select, <a> to toggle all, <i > to invert selection) ❯◉ Lint on save ◯ Lint and fix on commit? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? (Use arro w keys) ❯ In dedicated config files In package.json? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In dedica ted config files ? Save this as a preset for future projects? (y/N) Nインストールが完了したら、開発環境を実行します。
cd vue-todo-app yarn serve
続いて、
src/App.vue
を編集します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> </div> </template> <script> export default { name: 'app' } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>ストアの作成
@vue/cli でプロジェクトを作成した場合は、
src/store.js
にストアを定義したファイルが配置されています。
モジュールオプションを使用する際に ES Modules を使用する為にディレクトリの構成を変更していきます。mkdir src/store mkdir src/store/modules mv src/store.js src/store/store.js続いて、タスクを管理するモジュールとして
tasks.js
と、担当者を管理するモジュールとしてpersons.js
をsrc/store/modules/配下
に作成します。touch src/store/modules/tasks.js src/store/modules/persons.js
最後に
src/main.js
でストアの読み込み先を変更します。src/main.jsimport Vue from 'vue' import App from './App.vue' import store from './store/store' Vue.config.productionTip = false new Vue({ store, render: h => h(App) }).$mount('#app')モジュールにステートを定義をして読み込む
src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 1', status: false, personId: 2}, ], }, }src/store/modules/persons.jsexport default { state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'} ] } }modulesのオブジェクトの中でインポートしたモジュールを読み込みます、
src/store/store.jsimport Vue from 'vue' import Vuex from 'vuex' import tasks from './modules/tasks' import persons from './modules/persons' Vue.use(Vuex) export default new Vuex.Store({ modules: { tasks, persons } })ストアに登録したモジュールのステートをコンポーネント側で読み込む
モジュールとして分割した場合でもステートを読み込む方法は変わりません。
コンポーネント側で読み込んだステートはモジュールの名前が付いたオブジェクトにラップされた状態となります。
これによってステートは、モジュール毎にスコープを分離する事ができます。
tasks
というモジュールをmapState
で読み込んだ場合は、コンポーネント側では以下のようなオブジェクトとして保持しています。{ // ~~~ 省略 ~~~~ tasks: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ] }, // ~~~ 省略 ~~~~ }この点を踏まえた上で、
src/App.vue
を編集します。
src/App.vue
で読み込んだステートを TaskList コンポーネントに props として渡します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), } } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>
src/components/配下
に TaskList コンポーネントを作成します。src/components/TaskList.vue<template> <ul class="task-list"> <li v-for="task in taskList" :key="task.id"> <label> <input type="checkbox" :checked="task.status"> <span>{{task.name}}</span> <span> / </span> <span>担当者: {{getPersonName(task.personId)}}</span> </label> </li> </ul> </template> <script> export default { name: 'TaskList', props: { taskList: { type: Array, default: () => [], }, personList: { type: Array, default: () => [], }, }, methods: { getPersonName (id) { const person = this.personList.find((person) => { return person.id === id }) return person ? person.name : '未設定' }, }, } </script> <style scoped> .task-list { list-style: none; padding-left: 0; } </style>これで、ストアにモジュールとして登録したステートを使用する事ができました。
ステートの状態を更新する
チェックボックスの状態が変更されたタイミングでタスクのステートを変更できるようにします。
まずは、src/store/modules/tasks.js
にミューテーションとアクションを定義します。src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) }, }, }次に、
src/App.vue
側で先程定義したアクションを読み込みます。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="changeCheckStatus" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>最後に TaskList コンポーネント側から、input タグで change イベントが発生するタイミングで
$emit()
を実行します。src/components/TaskList.vue<template> <ul class="task-list"> <li v-for="task in taskList" :key="task.id"> <label> <input type="checkbox" :checked="task.status" @change="handleCheck($event, task.id)"> <span>{{task.name}}</span> <span> / </span> <span>担当者: {{getPersonName(task.personId)}}</span> </label> </li> </ul> </template> <script> export default { name: 'TaskList', props: { taskList: { type: Array, default: () => [], }, personList: { type: Array, default: () => [], }, }, methods: { getPersonName (id) { const person = this.personList.find((person) => { return person.id === id }) return person ? person.name : '未設定' }, handleCheck (e, id) { this.$emit('changeCheckStatus', { id: id, checked: e.currentTarget.checked, }) }, }, } </script> <style scoped> .task-list { list-style: none; padding-left: 0; } </style>モジュールとして登録されているミューテーション・アクション・ゲッターは、モジュールを使用しない場合と同じようにコンポーネント側での読み込み・使用が可能です。
しかし、モジュールを分けていても同じスコープ上に各ミューテーション・アクション・ゲッターが登録される為、モジュール間で使用している名前が競合する場合があります。試しに tasks モジュールと persons モジュールに test というミューテーションを定義してみます。
src/store/modules/tasks.jsexport default { state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, test () { window.alert('task のアラート') } }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) } }, }src/store/modules/tasks.jsexport default { state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'}, ], }, mutations: { test () { window.alert('person のアラート') }, } }TaskList コンポーネントの各チェックボックスにおいてチェンジイベントが発生した時に、先程登録したミューテーション test をコミットしてみます。
src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="test" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), test () { this.$store.commit('test') }, }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>モジュール間において同じ名前で登録されたミューテーションは、コミットで呼び出される時に一致する名前の処理がすべて実行されます。
(アクションも同様の挙動・ゲッターはエラーが発生します。)名前空間の指定
モジュールとして分割するだけでは、ゲッター・ミューテーション・アクションも同一のスコープ上に登録されてしまいます。それらを、モジュール内に閉じ込める場合は
namespaced
オプションにtrue
を渡して名前空間を指定します。src/store/modules/tasks.jsexport default { namespaced: true, // 名前空間の指定 state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, test () { window.alert('task のアラート') } }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) } }, }src/store/modules/tasks.jsexport default { namespaced: true, // 名前空間の指定 state: { persons: [ {id: 1, name: '一郎'}, {id: 2, name: '次郎'}, ], }, mutations: { test () { window.alert('person のアラート') }, } }名前空間を指定した事によって、モジュールのミューテーションへのアクセス方法が若干変更になります。名前空間が有効な場合は、接頭辞にモジュール名を指定します。
src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <task-list :taskList="tasks.tasks" :personList="persons.persons" @changeCheckStatus="test" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, computed: { ...Vuex.mapState(['tasks', 'persons']), }, methods: { ...Vuex.mapActions(['changeCheckStatus']), test () { this.$store.commit('tasks/test') // 接頭辞としてモジュール名を指定して`/`で繋ぎます。 }, }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>それでは、各モジュールの名前空間が有効な状態でタスクの新規登録機能を作成していきます。
task モジュールにミューテーションとアクションを定義します。src/store/modules/tasks.jsexport default { namespaced: true, state: { tasks: [ {id: 1, name: 'sample task 1', status: true, personId: 1}, {id: 2, name: 'sample task 2', status: false, personId: 2}, ], }, mutations: { changeCheckStatus (state, {id, checked}) { const tasks = state.tasks.slice() const task = tasks.find((task) => { return task.id === id }) task.status = checked state.tasks = tasks }, addTask (state, {name, personId}) { state.tasks = [ ...state.tasks, { id: new Date().getTime(), name: name, status: false, personId: personId, }, ] }, }, actions: { changeCheckStatus ({commit}, payload) { commit('changeCheckStatus', payload) }, addTask ({commit}, payload) { commit('addTask', payload) }, }, }
src/App.vue
にフォームを設置します。src/App.vue<template> <div id="app"> <h1>Vuex module option demo</h1> <form @submit.prevent="addTask(newTask)"> <table> <tr> <th>タスク名</th> <td><input type="text" placeholder="taskName" v-model="newTask.name"></td> </tr> <tr> <th>担当者</th> <td> <select v-model.number="newTask.personId"> <option value="0">未選択</option> <option v-for="person in persons" :value="person.id" :key="person.id" > {{person.name}} </option> </select> </td> </tr> </table> <button type="submit">タスク追加</button> </form> <hr> <task-list :taskList="tasks" :personList="persons" @changeCheckStatus="changeCheckStatus" /> </div> </template> <script> import Vuex from 'vuex' import TaskList from '@/components/TaskList' export default { name: 'app', components: { TaskList, }, data() { return { newTask: { name: name, personId: 0, } } }, computed: { // ヘルパー関数でモジュール名を指定してステートの読み込み ...Vuex.mapState('tasks', ['tasks']), ...Vuex.mapState('persons', ['persons']), }, methods: { // ヘルパー関数でモジュール名を指定してアクションの読み込み ...Vuex.mapActions('tasks', ['changeCheckStatus', 'addTask']), }, } </script> <style> #app { font-family: 'Avenir', Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: left; color: #2c3e50; margin-top: 60px; } </style>まとめ
- モジュールとして分割する事でコードの見通しがよくなる
- モジュールとして分割する事でモジュール間のステートのスコープを分ける事ができる
- モジュールとして分割 + 名前空間を指定する事でゲッター・ミューテーション・アクションもモジュール内に閉じ込める事ができる
名前空間の指定をしない場合は、コミットやディスパッチで指定した名前の処理がすべてのモジュールに対して実行される為、どのモジュールにどのような処理が定義されているか把握している必要がありそうです。しかし、チームで開発している場合や規模の大きなアプリケーションだと全て把握する事は難しいので、基本的にモジュールを分ける場合は名前空間を指定した方が良いかなと思いました。
- 投稿日:2019-07-01T12:24:17+09:00
vue.js component 非同期で 部品化
vue.js で component を使いまわしたい時がある。
しかも非同期で。概要
・各vueから読み込む
・<my-component :message="message"></my-component>
の中に Com.vue の内容を表示する。
・top.vue から message 池田を渡す。必ず data の中に入れて渡す。top.vue<my-component :message="message"></my-component> <script> export default { components: { 'my-component': () => import('./Com.vue') }, data () { return { //子要素にわたす message:'池田' }; }, } </script>com.vue<template> <div> ラッキー {{message}} </div> </template> <script> export default { props: ['message'] } </script>すると ラッキー 池田 と表示される。
- 投稿日:2019-07-01T11:51:58+09:00
[Vue]便利なnuxt-linkや$routerだが、外部サイトへのページ遷移はaタグを使うしかない件
薄めの内容ですが、調べて「はえ〜」と思ったので、共有しておきます。
nuxt-linkや$routerは、外部サイトへのリンクとしては使えない
nuxt-linkと$routerについて
ここでは、簡単に書きます。サンプルは以下の通りです。
サンプルコード
nuxt-linkのサンプル<nuxt-link to="/home">ホームへ</nuxt-link>$routerのサンプル<button @click="onClick">ホームへ</button> ~省略~ <script> new Vue({ el: "#app", methods: { onClick(){ this.$router.push('/home'); } } }) </script>すごいざっくり特徴を解説
箇条書きにすると、以下の感じです。
ちょっとざっくり過ぎますが、ご容赦ください。
- 両方ともページ遷移で便利
- あくまで主観的だが、aタグでのリンクと比べて、非同期的な操作感がる。サクサクとページ遷移される。
- nuxt-linkの方は、レンダリングされてブラウザで表示される際には、aタグとして描画される。
- $routerはnuxt-linkをスクリプト的な表現にしたもの。
外部サイトへのリンクでは使えない
nuxt-linkや$routerですが、以下の様な、外部サイトへのリンクとしては使えません
挙動してくれないサンプル<nuxt-link to="https://qiita.com">Qiitaへ</nuxt-link> <!-- ※ページ遷移されない -->エラー等は出ないですが、クリックしてもページ遷移はしてくれません。
対応策案
今の所、シンプルにaタグにして
対応策<a href="https://qiita.com">Qiitaへ</a>とすることぐらいでしょうか。
何か他に良い方法があれば、どなたか教えていただければ嬉しいです。さいごに
今日は短い内容ですが、以上です。
最後まで、ありがとうございました。
- 投稿日:2019-07-01T10:49:37+09:00
Webpacker 3 → 4
Rails5.2で使っている
webpacker
を3系から4系にアップデートしようとしたところ、ほとんどデフォルト環境で使っているにも関わらずいくつか作業が必要だったのでメモしておきます。Gemfile
まずgemのバージョンを上げます。2019/7/1現在だと4.0.7が最新でした。
gem 'webpacker', '~> 4.0'$ bundle installWebpackerを再インストール
環境を綺麗にするため、一度インストールし直します。
色々聞かれますが、全部上書きしちゃいます。$ bundle exec rails webpacker:installVueも入れ直します。
$ bundle exec rails webpacker:install:vue設定ファイル変更
- .babelrc
- .postcssrc.yml
これらのファイルはそれぞれ
babel.config.js
とpostcss.config.js
に置き換えられたようなので削除します。$ rm .babelrc .postcssrc.ymlPug
テンプレートエンジンとしてPugを使っていたんですが、webpackのloaderが置き換わったようなので対応します。
$ yarn remove pug-loader $ yarn add pug-plain-loaderconfig/webpack/loaders/pug.jsmodule.exports = { test: /\.pug$/, use: [{ loader: 'pug-plain-loader' }] }config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const { VueLoaderPlugin } = require('vue-loader') const vue = require('./loaders/vue') const pug = require('./loaders/pug') environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin()) environment.loaders.prepend('vue', vue) environment.loaders.prepend('pug', pug) module.exports = environment動作確認
$ bin/webpack
- 投稿日:2019-07-01T10:49:37+09:00
Webpacker 3 -> 4
Rails5.2で使っている
webpacker
を3系から4系にアップデートしようとしたところ、ほとんどデフォルト環境で使っているにも関わらずいくつか作業が必要だったのでメモしておきます。Gemfile
まずgemのバージョンを上げます。2019/7/1現在だと4.0.7が最新でした。
gem 'webpacker', '~> 4.0'$ bundle installWebpackerを再インストール
環境を綺麗にするため、一度インストールし直します。
色々聞かれますが、全部上書きしちゃいます。$ bundle exec rails webpacker:installVueも入れ直します。
$ bundle exec rails webpacker:install:vue設定ファイル変更
- .babelrc
- .postcssrc.yml
これらのファイルはそれぞれ
babel.config.js
とpostcss.config.js
に置き換えられたようなので削除します。$ rm .babelrc .postcssrc.ymlPug
テンプレートエンジンとしてPugを使っていたんですが、webpackのloaderが置き換わったようなので対応します。
$ yarn remove pug-loader $ yarn add pug-plain-loaderconfig/webpack/loaders/pug.jsmodule.exports = { test: /\.pug$/, use: [{ loader: 'pug-plain-loader' }] }config/webpack/environment.jsconst { environment } = require('@rails/webpacker') const { VueLoaderPlugin } = require('vue-loader') const vue = require('./loaders/vue') const pug = require('./loaders/pug') environment.plugins.prepend('VueLoaderPlugin', new VueLoaderPlugin()) environment.loaders.prepend('vue', vue) environment.loaders.prepend('pug', pug) module.exports = environment動作確認
$ bin/webpack
- 投稿日:2019-07-01T04:47:25+09:00
来るべきVue3.0に備える
v-sendai #2での発表内容です
自己紹介
tanshio(@_tanshio)
フロントエンドエンジニア、ウェブデザイナー
好きなものはVue・Nuxt、TypeScript、WebGLです
いま挑戦中のものはRustです
VTuberが好きです
基本的には、https://github.com/vuejs/rfcs に書いている情報になります。
Class APIの破棄
クラスベースのAPIが提案されていましたが、破棄されました。
<template> <div @click="increment"> {{ count }} {{ plusOne }} <Foo /> </div> </template> <script> import Vue from 'vue' import Foo from './Foo.vue' export default class App extends Vue { static components = { Foo } count = 0 created() { console.log(this.count) } get plusOne() { return this.count + 1 } increment() { this.count++ } } </script>https://github.com/vuejs/rfcs/pull/17#issuecomment-494242121
僕的にはシンプルかつ継承などもしやすいので残念。
rfcsのVuexがこれベースっぽかったので今後注目。
Function-based Component API
import { value, computed, watch, onMounted, inject } from 'vue' const App = { // same as before props: { a: String, b: Number }, // same as before components: { // ... }, setup(props) { // data const count = value(1) // computed const plusOne = computed(() => count.value + 1) // methods function inc() { count.value++ } // watch watch(() => props.b + count.value, val => { console.log('changed: ', val) }) // lifecycle onMounted(() => { console.log('mounted!') }) // dependency injection const injected = inject(SomeSymbol) // other options like el, extends and mixins are no longer necessary // expose bindings on render context // any value containers will be unwrapped when exposed // any non-containers will be exposed as-is, including functions return { count, plusOne, inc, injected } }, // template: `same as before`, render({ state, props, slots }) { // `this` points to the render context and works same as before (exposes everything) // `state` exposes bindings returned from `setup()` (with value wrappers unwrapped) } }
React Hooksのようなイメージ。
今からでも使える
https://github.com/vuejs/vue-function-api
- setup
- value
- プリミティブ型で使う
- state
- 配列やオブジェクトなど。(Vue.observable)
- computed
- watch
ポータルが来る
body直下などにVueコンポーネントがおけるようになる
- モーダル・トーストの管理が非常に楽になる
z-indexでの管理が楽になる
Nuxtはどうなる?
VueFes 2018の質疑応答の限りでは、Vue 3.0がリリースされ次第早めに対応したNuxtもリリースしたいとのこと。
準備しておく
非推奨、動作が変わるものを見直す
- slot
- slot-scopeが非推奨に
- v-slotを使う
- Vue.observable
- 参照ではなくなるので、使っている場合は注意
バージョンアップはきついか?
V1→V2がそこまでつらくなかったので大丈夫だと思いたい
TypeScriptをやろう
Vue 3.0ではTypeScriptが全面的にサポートされる(はず)なので、今のうちにTypeScriptに慣れておこう。
コンポーネントのpropsがエディタ上で補完出るようになってほしい…(Reactではできるので)まとめ
- Function-based Component APIがくるぞ!
- TypeScriptをやろう
ありがとうございました
- 投稿日:2019-07-01T03:17:20+09:00
Nuxt.jsビギナーズガイド ハマったこと
CHAPTER4はじめでのこと。
事象
projectを作成して、appフォルダに関連ディレクトリを移した。その後少し進めると、さっきまでできていたyarn devが通らなくなった。
yarn installを叩いて依存関係を修正するも、今度はelement-uiがうまく動かない。
SSRでレンダリングされたと思われる最初のうちはelement-uiが当たっているけど、読み込みが終わるとスタイルが崩れて、プレーンなテキストに戻ってしまう。element-uiのel-menu-itemあたりでエラーが起きている。
よく見ると、SSRとクライアントで描画したDOMが不一致になっているとのエラーも混ざっている。単純なタイポでもなさそうで、よくわからずにyarn create nuxt-appを数回試した。
解決策と原因
よく考えずに.nuxtフォルダまでappディレクトリに移していたことが原因っぽい。
依存関係が崩れて、yarn installで修正したと思っても、よく見るとelement-uiのバージョンがどうのでwarning吐いてた。.nuxtフォルダに触らない手順だと、特にエラー発生せず。
P106ページ周辺でのこと。
事象:
・ユーザー登録を実行すると、失敗のnotificationがでる。
・しかし、firebaseのDBを見てみると、ユーザ情報は登録されている。
・devtoolsでhttpのpatchに対して200[OK]のレスポンスが返ってきていることも見える
・登録したユーザー名でログインに成功する。
・ユーザ登録は実際にできているのに、失敗のnotificationしかでない。考察:
ユーザー登録まではできているのだから、patchを投げたところまでは良いはずと判断。
じゃあその次らへんか・・・?そうしたらstoreに書いたregisterのロジックでtypo発見。$axios.$patchの結果を変数userに格納し忘れる痛恨のミスを発見。
これだと思って修正するも事象はなおらず。
あとは何があるんだろ。この手のデバッグを効率よくやる方法を知りたい。。。
- 投稿日:2019-07-01T00:58:21+09:00
vueプロジェクトの`npm run build`がエラーになる
vue cli で作成したプロジェクトのbuildができない
2019/06/30 12時頃、
vue create
で作成したプロジェクトのnpm run build
コマンドができなくなった。以下のようなエラーが発生。
$ npm run build ⠙ Building for production...Starting type checking and linting service... Using 1 worker with 2048MB memory limit ⠸ Building for production...Unhandled rejection Error: original.line and original.column are not numbers -- you probably meant to omit the original mapping entirely and only map the generated position. If so, pass null for the original mapping instead of an object with empty or null values. at SourceMapGenerator_validateMapping [as _validateMapping] (/Users/user_name/spring-sec-sample-app/src/main/resources/client-app/node_modules/webpack-sources/node_modules/source-map/lib/source-map-generator.js:276:15) ...エラーをみる限り、
source map
の作成時にコケている?よう。暫定対応1
そもそもビルド時に、
source map
を作成しないようにする。vueプロジェクトの設定は、
vue.config.js
に書き込めば、vueがそのファイルを自動的に検知して読み込んでくれる。デフォルトでは作成されないので、存在しない場合は自分で作成する。
階層はプロジェクトディレクトリのルートに配置する。$ vi vue.config.js
./vue.config.jsmodule.exports = { // other config productionSourceMap: false }これでひとまず、エラーは回避できる。
暫定対応2
node modules の依存関係を自分で定義する。
package.json{ "dependencies": { "core-js": "^2.6.5", "vue": "^2.6.10", "vue-class-component": "^7.0.2", "vue-property-decorator": "^8.1.0", "vue-router": "^3.0.3", "terser": "4.0.0" // 追加 }, }上記のように、
terser
のバージョンを4.0.0
に指定する。原因
vueプロジェクトは
source map
作成時、内部的に依存しているterser
というライブラリを利用している。
そのterser
がv4.0.1を2019/06/30にリリースした模様。https://www.npmjs.com/package/terser
そのv4.0.1でバグが紛れ込んだそうです。
issueが上がっていました。
https://github.com/terser-js/terser/issues/380クライアントってモジュールの依存関係が複雑すぎて、こういう小さな変化がエラーを生み出すし、サーバに比べて極めて大変。。
依存する全てのモジュール理解している人とかいるのかな、、難しい。