※スマホ対応はしてません。

Vueでフォームの値を取得する方法をまとめておくので、コピペでご利用ください。(Vue.js始めるおれおれアドベントカレンダー2016 – 11日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 11日目

昔書いたjQuery版の真似をして一通りやってみます。と言ってもVueはまだよくわかってない感じがあるので何か違う感じあったら教えて欲しい感じです。

前提

  • Vue v2.1.4
  • Vuex v2.0.0

Vuexの state で情報を持ちます。ただの data でもいいよね。

  • state.js
const Vuex = require('vuex')

module.exports = new Vuex.Store({
  state: {
    // ここに値を記述
  }
})
  • App.vue
<form @submit.prevent="form_submit">
  <!-- ここにコントロールを記述 -->
</form>
var store = require('./store.js')

module.exports = {
  data: function () {
    return store.state
  },
  methods: {
    form_submit (event) {
      // …
    }
  }
}

なんか状況によってはめんどくさい場合もあるみたいだけど、今回はそういう状況ではないとします。

凡例

これから記述する各項は

  1. Store
  2. HTML
  3. 情報受け取り

の順にコードを記載します。受け取りはだいたいただ = でもらうだけだけど。

選択肢の持ち方

値はもちろんStoreに持つんだけど、選択肢の方も一緒に管理したいなあと思って、HTMLじゃなくてStoreの方に書いた。(各項参照。)

これってどうだろう? 大丈夫かな?

何かあれば追記します。

(さらに…)

Vueで「スクロールに合わせて何かする」のってどうするんだろ。(Vue.js始めるおれおれアドベントカレンダー2016 – 10日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 10日目

作れないことはないんだけど、どうするのが最良なんだろうか。とりあえず作ってみた。

スクロールを拾う

方々でスクロールを監視する必要はないので、偉い部品に任せる。それが main.js なのか App.vue なのか。今回は試しに main.js でやってみた。

  • main.js
const app = new Vue({
  el: '#app',
  render: function (createElement) {
    return createElement(App)
  },
  methods: {
    startWatchingScroll: function () {
      document.addEventListener('scroll', () => {
        const scrollTop = document.body.scrollTop || document.documentElement.scrollTop
        console.log(scrollTop)
        store.commit('updateScrollTop', scrollTop)
      })
    }
  }
})
app.startWatchingScroll()

removeEventListener() の事は今回はさておく。

  • App.vue
<style>
  …
</style>

<template>
  <div class="scrollTop">Top: {{scrollTop}}</div>
</template>

<script>
  var store = require('./store.js')

  module.exports = {
    data: function () {
      return store.state
    }
  }
</script>

もらって表示するだけ。

よくわからないこと

スクロール監視を行うひと

main.js か App.vue か?

App.vue の方が実質的な本体感があるのでこっちらでやるべきなような。あるいは App.vue は各コンポーネントの取りまとめ役だけになってもらって、その外側に触れるのは main.js の方にするか。今回は後者の方が良いかなーと思ったんだけどあまり自信がない。

他の選択肢として、 main.js 内に app の関数ではなくて直接イベント監視する方法もありそう。

new Vue({
    …
})

document.addEventListener('scroll', () => {
    const scrollTop = document.body.scrollTop || document.documentElement.scrollTop
    store.commit('updateScrollTop', scrollTop)
})

こっちの方が良いかな。あーなんかこっちの方が良い気がしてきた。いやどうなんだろ。

scrollTop を格納する場所

これはたぶんStoreで良いんだと思う。

ただの文字列をコンポーネントに渡す話。(Vue.js始めるおれおれアドベントカレンダー2016 – 9日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 9日目

今まで二重引用符 "…" の中に一重引用符 '…' を書かないと駄目かなって思ってたんだけど。

<my-component v-bind:message="'Hello World!'"></my-component>
or
<my-component :message="'Hello World!'"></my-component>
MyMessage = {
    template: '<p>{{message}}</p>',
    props: ['message']
}

v-bind ではないHTMLの属性の書き方でいけることに気が付いた。

<my-component message="Hello World!"></my-component>

JS側は一緒。

そうだったのか!

デモ

よくわからないこと

仕様なの?

どこに書いてあるのかよくわからない。あるならここら辺だと思うんだけど。

いや考え方の順序が違うのかな。 props のところに『親コンポーネントからデータを受け取るためにエクスポートされた属性のリスト/ハッシュです。』とあるので、そもそもこれは「属性」を受け取るものであると。一方の v-bind もコンポーネントに渡す値を云々じゃなくて、あくまで「属性」を束縛するものであると。結果的にコンポーネントに情報を渡せるけど。

あ、合点がいったわ。

ESLintプラグインでVueの書式に合わせて書こう。(Vue.js始めるおれおれアドベントカレンダー2016 – 8日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 8日目

例示されるコードと自分が書くコードの書き方が違うのが気になってきたので、lintツールを入れてみようと思いました。

eslint-plugin-vue

Vue公式でESLintのプラグインが公開されていた。

これを導入してみる。

$ npm i -S eslint eslint-plugin-vue eslint-config-vue

で .eslintrc.js を用意する。

  • .eslintrc.js
module.exports = {
    "extends": "vue",
};

あ、こんだけで良いんだ。さてlint実行。

対象を src/ で指定するとJSファイルしか見てくれないので、 src/* にして *.vue も対象に含める。

$ ./node_modules/.bin/eslint src/*

/my/impressive/path/08-vue-lint/src/App.vue
   8:40  error  Extra semicolon                                    semi
  11:3   error  Expected indentation of 2 spaces but found 1 tab   indent
  12:4   error  Expected indentation of 2 spaces but found 2 tabs  indent
  13:3   error  Expected indentation of 0 spaces but found 1 tab   indent
  13:4   error  Unexpected trailing comma                          comma-dangle
  14:3   error  Extra semicolon                                    semi

/my/impressive/path/08-vue-lint/src/main.js
   1:27  error  Extra semicolon                                    semi
   2:29  error  Extra semicolon                                    semi
   3:14  error  Extra semicolon                                    semi
   5:33  error  Extra semicolon                                    semi
…

✖ 70 problems (70 errors, 0 warnings)

うは、なんかいっぱい言われてる。

自動修復

ESLintの機能だけど、 --fix を付けると簡単な(機械的に変換できる)書式エラーは自動的に修正してくれる。ファイルはGitで管理してるから機械変換しても怖くはないでしょう?

$ $ ./node_modules/.bin/eslint src/* --fix

/my/impressive/path/08-vue-lint/src/App.vue
   8:40  error  Extra semicolon                                    semi
  11:3   error  Expected indentation of 2 spaces but found 1 tab   indent
  12:4   error  Expected indentation of 2 spaces but found 2 tabs  indent
  13:3   error  Expected indentation of 0 spaces but found 1 tab   indent
  13:4   error  Unexpected trailing comma                          comma-dangle
  14:3   error  Extra semicolon                                    semi

/my/impressive/path/08-vue-lint/src/MyTitle.vue
   6:35  error  Extra semicolon                                    semi
   9:3   error  Expected indentation of 2 spaces but found 1 tab   indent
   9:17  error  Missing space before function parentheses          space-before-function-paren
…

✖ 51 problems (51 errors, 0 warnings)

エラーが70件から51件に減った。 *.js は全部自動で直してくれたけど、 *.vue はまるまる残ってる感じだ。

ESLintとしてはJSファイルじゃないものは対応しない方針の様子。まあそうだね。Stack Overflowでも「無理だよ☆」という回答だ……。

さてどうしたものか

JS部分だけ取り出してうまいことやれば自動変換で全て丸く収まりそうなんだけどなー。手作業で直して確認するより、手作業で自動変換できる形にして変換して戻す、みたいな形が好み。

自分の場合はVim使ってるので、こんな感じでJS部以外をコメントアウトしました。 <style> の中身には対応できないけど、今回はまだ書いてないからこれで良いや。

:g/^\s*</norm 0i///

で拡張子を変えまして。

  • App.js
///<template>
/// <div class="container">
///     <my-title></my-title>
/// </div>
///</template>

///<script>
    var MyTitle = require('./MyTitle.vue');

    module.exports = {
        components: {
            MyTitle
        },
    };
///</script>

さあどうだ。

$ ./node_modules/.bin/eslint src/* --fix
  • App.js
// /<template>
// /    <div class="container">
// /        <my-title></my-title>
// /    </div>
// /</template>

// /<script>
var MyTitle = require('./MyTitle.vue')

module.exports = {
  components: {
    MyTitle
  }
}
// /</script>

あーインデント変わっちゃったな。まあこんなもんか。ではコメントアウトを解除してファイル名を戻して。

:g/^\/\/ \//norm 4x

別にインデントはこれで怒られないけど、手動で戻しました。結局手動かあ……。

開発環境へ組み込む

ファイル変更を検知して、ビルドの前に検証するようにします。

  • package.json
{
  "scripts": {
    "start": "npm run vue-watch",
    "vue-build": "browserify -t vueify -e src/main.js -o out/app.js ",
    "vue-lint": "eslint src/*",
    "vue-watch": "chokidar src/* -c 'npm run vue-lint && npm run vue-build'"
  },
  "dependencies": {
    "browserify": "^13.1.1",
    "chokidar-cli": "^1.2.0",
    "eslint": "^3.11.1",
    "eslint-config-vue": "^2.0.1",
    "eslint-plugin-vue": "^1.0.0",
    "vue": "^2.1.4",
    "vueify": "^9.3.0",
    "vuex": "^2.0.0"
  }
}

ESLintを直接呼んだ方が、失敗時にnpmの出力が出なくて良いようにも思う。長いんだよなーあれ。

よくわからないこと

自動修正をもっとうまくやりたい

まあ仕方ないかこんなもんか。lintがある状態で書いてれば自動修正いらないし。

lint失敗時の出力を減らしたい

さっき言ったnpmのやつ。こういうの。

$ npm run vue-lint

> @ vue-lint /my/impressive/path/08-vue-lint
> eslint src/*


/my/impressive/path/08-vue-lint/src/App.vue
  45:4  error  Extra semicolon  semi

✖ 1 problem (1 error, 0 warnings)


npm ERR! Linux 4.8.4-200.fc24.x86_64
npm ERR! argv "/my/impressive/path "/my/impressive/path"run" "vue-lint"
npm ERR! node v5.7.1
npm ERR! npm  v3.8.2
npm ERR! code ELIFECYCLE
npm ERR! @ vue-lint: `eslint src/*`
npm ERR! Exit status 1
npm ERR! 
npm ERR! Failed at the @ vue-lint script 'eslint src/*'.
npm ERR! Make sure you have the latest version of node.js and npm installed.
npm ERR! If you do, this is most likely a problem with the  package,
npm ERR! not with npm itself.
npm ERR! Tell the author that this fails on your system:
npm ERR!     eslint src/*
npm ERR! You can get information on how to open an issue for this project with:
npm ERR!     npm bugs 
npm ERR! Or if that isn't available, you can get their info via:
npm ERR!     npm owner ls 
npm ERR! There is likely additional logging output above.

npm ERR! Please include the following file with any support request:
npm ERR!     /my/impressive/path/08-vue-lint/npm-debug.log

npm通さずに普通に呼ぶと簡単。

$ ./node_modules/.bin/eslint src/*

/my/impressive/path/08-vue-lint/src/App.vue
  45:4  error  Extra semicolon  semi

✖ 1 problem (1 error, 0 warnings)

うーんでもなー。lintだけ呼びたいこともあろうし、同じこと二度書きたくないし。

おしまい

これでだいぶ開発環境が整ってきた気がする。

コード

vueifyを使って良い感じにコンポーネントを扱えるようになったよ。(Vue.js始めるおれおれアドベントカレンダー2016 – 7日目)

カテゴリー: JavaScript

Vue.js始めるおれおれアドベントカレンダー2016 – 7日目

色々やろうとしたらやっぱりファイルの管理とかが手間なので、ぼちぼちツールを導入します。

なにそれ

Browserify transform for Vue.js components, with scoped CSS and component hot-reloading.

(Broserifyを使って)Vueのコンパイル的なアレをしてくれるツールです。対象のファイルはJSではなく、HTMLテンプレートとJSの両方を含む *.vue という拡張子のファイルだそうだ。

hot-reloading は自分の環境だと特にそれっぽい動きはしてくれてない。ウェブブラウザが別マシンだから?

ところでこういうのをコンパイルと呼んで良いんだろうか。

試行錯誤の記録

色々エラー出たりしたので置いておきます。

まず今まで使ってたひとつしかないJSファイルを単純にvueifyに突っ込んだら、ちゃんと動いてくれた。変換なしで通してくれたみたい。

続いてVueとVuexを require() で読み込むようにしたら、エラー。

Error: [vuex] must call Vue.use(Vuex) before creating a store instance.

あ、まあそうですね。さくっと Vue.use(Vuex) を追加。

で次。

[Vue warn]: Failed to mount component: template or render function not defined.
(found in root instance)

んーコンポーネントの template をHTMLに書いてちゃ駄目か。そりゃそうだ。

でJS側に持ってきても駄目だった。あれ? あーそうか本体(コンポーネントにしてない部分)もこっちに持ってこないとか。どうやら main.app から全体を司る App を render() で出力するのが定石らしい。(合ってる?)

でやったら、何故か画面が空っぽになる、何も出てこない……。と思ったら return してなかった。ああーそっか!

よしまあじゃあ一度全部消して、READMEの例の通りにやってみよう。

Error: Parsing file /path/to/you/work/dir/App.vue: 'import' and 'export' may only appear at the top level (23:0)

えー。

export default {} を module.exports = {} にしたら解決。よくわかってない。

ともかくこれで動いた!

ただ、最終的な出力ファイルの位置でエラーが出てくるのでわかりづらい。browserifyの方のオプションで --debug を与えるとソースマップを付けてくれるんだけど、うーんわかりづらい。

まとめ

環境

こんな感じになりました。

{
  "scripts": {
    "start": "npm run vue-watch",
    "vue-build": "browserify -t vueify -e src/main.js -o out/app.js ",
    "vue-watch": "chokidar src/* -c 'npm run vue-build'"
  },
  "dependencies": {
    "browserify": "^13.1.1",
    "chokidar-cli": "^1.2.0",
    "vue": "^2.1.4",
    "vueify": "^9.3.0",
    "vuex": "^2.0.0"
  },
}
  • src/ に元のファイルを、 out/ に出力ファイルを置くものとします。
  • npm run vue-build でビルドします。
  • npm run vue-watch でファイル監視してビルドします。
  • chokidar はファイル監視してコマンド実行するやつです。べんり!

Hello World!

  • src/main.js
const Vue = require('vue');
const Vuex = require('vuex');  // 今回使ってないけど
Vue.use(Vuex);

const App = require('./App.vue');

new Vue({
  el: '#app',
  render: function(createElement) {
    return createElement(App);
  },
});
  • src/App.vue
<style>
  h1 {
    font-style: italic;
  }
</style>

<template>
  <div class="container">
    <h1>{{message}}</h1>
  </div>
</template>

<script>
  module.exports = {
    data: function() {
      return {
        message: 'Hello World!',
      },
    },
  };
</script>

hello-vueify-world

はあー、なるほどこんな感じですか!

コンポーネント

別のファイルにして require() する感じ。無理やり感あるけど分割して、ついでにVuexも突っ込みます。

  • App.js
<template>
  <div class="container">
    <my-title></my-title>
  </div>
</template>

<script>
  var MyTitle = require('./MyTitle.vue');

  module.exports = {
    components: {
      MyTitle: MyTitle,
    },
  };
</script>
  • MyTitle.js
<template>
  <h1>{{title}}</h1>
</template>

<script>
  var store = require('./store.js');

  module.exports = {
    data: function() {
      return store.state;
    },
  };
</script>
  • store.js
const Vuex = require('vuex');

module.exports = new Vuex.Store({
  state: {
    title: 'Hello World!',
  },
});

見た目はさっきと一緒。

よくわからないこと

store.state を直接 data に指定しない?

data: store.state と書いたらこんなん言われた。

[Vue warn]: The "data" option should be a function that returns a per-instance value in component definitions.

毎回 function() 書いて return しないといけない?

export default を使うには?

これも未来JavaScriptの機能だよね。 import するやつ。例の通りに書いたのに駄目だった。コンパイル時にエラーになる。どうしたら良かったんだろうか。バベる? いつ?

hot-reloading

使えたら便利そうだけど。

lint

そろそろVue的な書き方に合わせたい。セミコロン欲しい派だけど、しばらく書いてれば慣れるだろう……。

おしまい

ビルドなしで強力なのが導入できて便利~というのがVueの最初の印象だったんだけど、結局ビルドするようになりました。まあ「ちゃんと」やろうとしたらそうなるよね。例えjQueryでも。とはいえ簡単だし、jQueryでちゃんと整えるよりやっぱり簡単じゃないだろうか。