20190701のvue.jsに関する記事は13件です。

Vue.jsで連想配列の要素を削除するには

Vue.jsで配列要素をレンダリングに反映されるよう削除するには

Vue.obj.splice(インデックス番号, 削除数)

を使いますが、連想配列ではspliceは使えません。

Vue.$delete(配列, キー)

を使います。
要素追加の際の

Vue.$set(配列, キー, 値)

と似てますね。

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

Vuex の使い方を勉強してみた

はじめに

Qiita 初投稿です:relaxed:

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.js
import 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.js
const 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.js
const moduleA = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleA;
store/moduleB.js
const moduleB = {
  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

export default moduleB;
store/index.js
import 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.js
const moduleA = {
  namespaced = true,

  state: { ... },
  getters: { ... },
  mutations: { ... },
  actions: { ... }
};

モジュール分割した Store をコンポーネントから参照する

以下のようなモジュール分割された Store を定義し、コンポーネントから参照してみる。

store/moduleA.js
const 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.js
const 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;

moduleAmoduleB はそれぞれが

  • データ項目 result
  • データ操作用のアクション setResultclearResult

を持つ。

以下のコンポーネントでは、 moduleAmoduleB の データ項目 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 を使うと状態管理がだいぶ楽になることが分かりました。
また、データの扱いがある程度ルール化されているので、初心者にはありがたいです。
最後までご覧いただきありがとうございました!

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

vue-multiselect を CDN でつかってみる

Vue.jsで、こんな感じのいけてる入力フォームのUIを探してたのですが

  1. 選択肢からフィルタしながら入力
  2. 入力した内容はタグになる
  3. 複数の値を選択できる

vue-multiselectっていうのを見つけて、これが良さそう。

image.png

さっと確認するには、JSはCDNで読み込んで試したいのですが、多くのVueのライブラリはCDNの時の使い方の例をちゃんと書いてないですね...

幸い、今回のvue-multiselectcomponents: { 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,

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

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

Firebase
バックエンドの処理を代わりにしてくれるサービス
本来は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 //enter

nmpに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-fns

VSCode
フォルダーを開くを押下
srcに
repositoly
firebaseConfig

export 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 serve

command + クリックで選択し、画面を表示

 App running at:
  - Local:   http://localhost:8080/ 
  - Network: http://192.168.11.15:8080/

 
 
 

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

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

Vuexのモジュールを使用してストアを管理する

Vuex で状態管理を行っているアプリケーションでは、基本的にストアは1つだと思います。
アプリケーションが大きくなるにつれストアで管理する必要がある状態は多くなった場合、沢山のステートやミューテーションなどを一つのオブジェクトで管理する事になってしまうでしょう。
そういった事態を防ぐ為に、Vuex にはモジュールオプションが用意されています。
これに加えて、個人的にモジュールオプションを使用する事のメリットとして、管理するステートに紐づくオプションをまとめてグループ化できる事だと感じています。

私がVuexのモジュールオプションについて調べた時、分割したモジュールのステートへのアクセスや更新を行う方法に混乱して、なかなか理解が進みませんでした。
個人的な振り返りも含めて、簡単な「TODOアプリ」の作成を通して Vuex のモジュールの使用について記事にします。

Jul-01-2019 12-17-41.gif

この記事では、以下の環境で作業を進めていきます。

  • node -> 10.15.3
  • npm -> 6.4.1
  • @vue/cli -> 3.8.4

プロジェクトの作成

まずは、適当なディレクトリでプロジェクトを作成します。

vue create vue-todo-app

Vuexを使用する為、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.jssrc/store/modules/配下に作成します。

touch src/store/modules/tasks.js src/store/modules/persons.js

最後にsrc/main.jsでストアの読み込み先を変更します。

src/main.js
import 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.js
export 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.js
export default {
  state: {
    persons: [
      {id: 1, name: '一郎'},
      {id: 2, name: '次郎'}
    ]
  }
}

modulesのオブジェクトの中でインポートしたモジュールを読み込みます、

src/store/store.js
import 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
  }
})

スクリーンショット 2019-07-01 13.50.05.png

ストアに登録したモジュールのステートをコンポーネント側で読み込む

モジュールとして分割した場合でもステートを読み込む方法は変わりません。
コンポーネント側で読み込んだステートはモジュールの名前が付いたオブジェクトにラップされた状態となります。
これによってステートは、モジュール毎にスコープを分離する事ができます。

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>

これで、ストアにモジュールとして登録したステートを使用する事ができました。

スクリーンショット 2019-07-01 13.55.34.png

ステートの状態を更新する

チェックボックスの状態が変更されたタイミングでタスクのステートを変更できるようにします。
まずは、src/store/modules/tasks.jsにミューテーションとアクションを定義します。

src/store/modules/tasks.js
export 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>

Jul-01-2019 14-09-58.gif

モジュールとして登録されているミューテーション・アクション・ゲッターは、モジュールを使用しない場合と同じようにコンポーネント側での読み込み・使用が可能です。
しかし、モジュールを分けていても同じスコープ上に各ミューテーション・アクション・ゲッターが登録される為、モジュール間で使用している名前が競合する場合があります。

試しに tasks モジュールと persons モジュールに test というミューテーションを定義してみます。

src/store/modules/tasks.js
export 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.js
export 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>

Jul-01-2019 14-16-23.gif

モジュール間において同じ名前で登録されたミューテーションは、コミットで呼び出される時に一致する名前の処理がすべて実行されます。
(アクションも同様の挙動・ゲッターはエラーが発生します。)

名前空間の指定

モジュールとして分割するだけでは、ゲッター・ミューテーション・アクションも同一のスコープ上に登録されてしまいます。それらを、モジュール内に閉じ込める場合はnamespacedオプションにtrueを渡して名前空間を指定します。

src/store/modules/tasks.js
export 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.js
export 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.js
export 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>

まとめ

  • モジュールとして分割する事でコードの見通しがよくなる
  • モジュールとして分割する事でモジュール間のステートのスコープを分ける事ができる
  • モジュールとして分割 + 名前空間を指定する事でゲッター・ミューテーション・アクションもモジュール内に閉じ込める事ができる

名前空間の指定をしない場合は、コミットやディスパッチで指定した名前の処理がすべてのモジュールに対して実行される為、どのモジュールにどのような処理が定義されているか把握している必要がありそうです。しかし、チームで開発している場合や規模の大きなアプリケーションだと全て把握する事は難しいので、基本的にモジュールを分ける場合は名前空間を指定した方が良いかなと思いました。

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

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>


すると ラッキー 池田 と表示される。

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

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

とすることぐらいでしょうか。
何か他に良い方法があれば、どなたか教えていただければ嬉しいです。

さいごに

今日は短い内容ですが、以上です。
最後まで、ありがとうございました。

参考にしたサイト
参考にしたサイト

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

Webpacker 3 → 4

Rails5.2で使っている webpacker を3系から4系にアップデートしようとしたところ、ほとんどデフォルト環境で使っているにも関わらずいくつか作業が必要だったのでメモしておきます。

Gemfile

まずgemのバージョンを上げます。2019/7/1現在だと4.0.7が最新でした。

gem 'webpacker', '~> 4.0'
$ bundle install

Webpackerを再インストール

環境を綺麗にするため、一度インストールし直します。
色々聞かれますが、全部上書きしちゃいます。

$ bundle exec rails webpacker:install

Vueも入れ直します。

$ bundle exec rails webpacker:install:vue

設定ファイル変更

  • .babelrc
  • .postcssrc.yml

これらのファイルはそれぞれ babel.config.jspostcss.config.js に置き換えられたようなので削除します。

$ rm .babelrc .postcssrc.yml 

Pug

テンプレートエンジンとしてPugを使っていたんですが、webpackのloaderが置き換わったようなので対応します。

$ yarn remove pug-loader
$ yarn add pug-plain-loader
config/webpack/loaders/pug.js
module.exports = {
  test: /\.pug$/,
  use: [{
    loader: 'pug-plain-loader'
  }]
}
config/webpack/environment.js
const { 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webpacker 3 -> 4

Rails5.2で使っている webpacker を3系から4系にアップデートしようとしたところ、ほとんどデフォルト環境で使っているにも関わらずいくつか作業が必要だったのでメモしておきます。

Gemfile

まずgemのバージョンを上げます。2019/7/1現在だと4.0.7が最新でした。

gem 'webpacker', '~> 4.0'
$ bundle install

Webpackerを再インストール

環境を綺麗にするため、一度インストールし直します。
色々聞かれますが、全部上書きしちゃいます。

$ bundle exec rails webpacker:install

Vueも入れ直します。

$ bundle exec rails webpacker:install:vue

設定ファイル変更

  • .babelrc
  • .postcssrc.yml

これらのファイルはそれぞれ babel.config.jspostcss.config.js に置き換えられたようなので削除します。

$ rm .babelrc .postcssrc.yml 

Pug

テンプレートエンジンとしてPugを使っていたんですが、webpackのloaderが置き換わったようなので対応します。

$ yarn remove pug-loader
$ yarn add pug-plain-loader
config/webpack/loaders/pug.js
module.exports = {
  test: /\.pug$/,
  use: [{
    loader: 'pug-plain-loader'
  }]
}
config/webpack/environment.js
const { 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
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

来るべき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コンポーネントがおけるようになる

  • モーダル・トーストの管理が非常に楽になる

image.png

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をやろう

ありがとうございました

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

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に格納し忘れる痛恨のミスを発見。
これだと思って修正するも事象はなおらず。
あとは何があるんだろ。

この手のデバッグを効率よくやる方法を知りたい。。。

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

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.js
module.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

クライアントってモジュールの依存関係が複雑すぎて、こういう小さな変化がエラーを生み出すし、サーバに比べて極めて大変。。
依存する全てのモジュール理解している人とかいるのかな、、難しい。

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