Nuxt.jsでVuexを使ってみる #nuxtjs #vuejs

複数コンポーネントで状態管理をするのに便利らしいVuexを使ってみた。

Vuexストアに状態管理を任せて、各コンポーネントはストア経由で状態変更することで、状態変更を一元管理できるっぽい。

ja.nuxtjs.org

ストアの作成

store/sample.js を作成する。

store配下にjsファイルを作ると、Nuxt.jsがVuexストアにしてくれる。

  • state
    • 状態管理する要素の管理
  • mutations
    • 状態の操作
  • actions
    • 状態の操作する処理を呼び出し、反映(commit)する
    • commitしない限り、反映されない
    • コンポーネントからは、actionで定義した処理が呼ばれる。
// 状態管理したい要素に名前をつけて、stateとしてexportする
export const state = () => ({
  // 'hogeFromStore' という名前の状態を管理する
  hogeFromStore: 'Hello, Vuex!'
})

// 状態を変更する処理は mutationとしてexportする
export const mutations = {
  // ここでは hogeFromStore の状態(値)を変更する処理を定義
  setHogeFromStore(state, value) {
    state.hogeFromStore = value
  }
}

// 実際に各コンポーネントから呼び出す処理をactionとしてexportする
export const actions = {
  writeHoge(context, value) {
    // コミットすることで状態変更が反映される
    context.commit('setHogeFromStore', value)
  }
}

コンポーネントからの呼び出し

コンポーネントからは、 $store 経由で参照できるようになる

<template>
  <div>
    <!-- $store.state経由で状態の取得 -->
    <p>store: {{this.$store.state.sample.hogeFromStore}}</p>
    <!-- $store.dispatch経由でactionを呼び出す -->
    <button v-on:click="$store.dispatch('sample/writeHoge', '値を書き換えます')">Test</button>
    <!-- コンポーネントのScript経由で利用することも可能 -->
    <button v-on:click="testMethod()">Test2</button>
  </div>
</template>

<script>
export default {
  methods: {
    // Scriptからも参照可能
    testMethod: function() {
      console.log(this.$store.state.sample.hogeFromStore)
      this.$store.dispatch('sample/writeHoge', 'メソッドからの書き換え')
      console.log(this.$store.state.sample.hogeFromStore)
    }
  }
}
</script>

mountedからの利用

mountedからも利用できる。

なので、data変更時の処理として使える。

fetchと組み合わせて、レンダリング前にコンポーネントのデータをセットするといったことも可能。

※ただしmountedはSSRされないので注意。SSRしたいときはpageコンポーネントでasyncDataを使うなどする。

mounted

<template>
  <div>
    <p>store: {{this.$store.state.sample.hogeFromStore}}</p>
  </div>
</template>

<script>
export default {
  mounted: function() {
    this.$store.dispatch('sample/writeHoge', 'mountedからも利用できる')
  }
}
</script>

参考

Requestパラメータを画面に表示する #nuxtjs #vuejs

概要

http://localhost:3000/sample?hoge=abc123

のようにRequestされた場合に、 abc123 を画面に表示してみる

asyncDataを使う

pageコンポーネントの場合、asyncDataというメソッドを使うと、Context経由でリクエストの情報を取得できる。

asyncDataはレンダリング前に呼びだれ、結果はdataとマージされる。

Queryパラメータの場合は context.query で取得できる

pages/sample.vue はこんな感じになる。

<template>
  <div>
    <p>hoge: {{hoge}}</p>
  </div>
</template>

<script>
export default {
  data: function() {
    return {
      // asyncDataで上書きされる
      hoge: 'default'
    }
  },
  asyncData(context) {
    return {
      // asyncDataでreturnすると、dataにマージされる
      hoge: context.query['hoge']
    }
  }
}
</script>

http://localhost:3000/sample だと「default」が表示されるが、 ?hoge=abc123 をつけると、「abc123」が表示されるようになる。

なお、下記のようにも書ける。

// 最初からqueryを使っちゃう
asyncData({ query }) {
  return {
    hoge: query['hoge']
  }
}

propsでサブコンポーネントに値を受け渡す

asyncDataは pageコンポーネントでしか使えないみたい。

propsを使えばサブコンポーネントに値を受け渡すことができる。

サブコンポーネントの作成

サブコンポーネントcomponents/Sample.vue として作成する。

propsを宣言しておくと値を外から受け取れるようになり、dataと同じように扱える。

今回は hogeFromPage という名前で受け渡すようにする。

<template>
  <p>hoge from page: {{hogeFromPage}}</p>
</template>

<script>
export default {
  
  props: ['hogeFromPage']
}
</script>

Pageコンポーネントから値を受け渡す。

propsは <sample hogeFromPage='hello!' /> のような形式で渡せるようになるのだけど、VueComponentの値を渡すときは v-bind を使えばよさそう。

<template>
  <div>
    <!-- v-bindで値を渡す -->
    <!-- <sample hogeFromPage='hello!' /> -->
    <sample v-bind="{'hogeFromPage': hoge}" />
  </div>
</template>

<script>
// サブコンポーネントをimport
import sample from '~/components/Sample'

export default {
  data: function() {
    return {
      hoge: 'default'
    }
  },
  // サブコンポーネントの利用
  components: { sample },
  asyncData(context) {
    return {
      hoge: context.query['hoge']
    }
  }
}
</script>

これでhogeの値をサブコンポーネントでも使えるようになるはず。

注意点としては コンポーネントからサブコンポーネントへの依存が発生してしまう こと。

親が子のことを意識しなければいけなくなるので、利用は慎重にした方がよさそう。

参考

ja.nuxtjs.org

Nuxt.jsプロジェクトでjestにpower-assertを導入 #nuxtjs #powerassert

Nuxt.jsのテストをjestで書くというのを先日やってみたが、Assertionにpower-assertを使うようにする。

su-kun1899.hatenablog.com

power-assertの導入

babel連携が必要になるので、 power-assertに加えてbabel-preset-power-assertをインストールする。

github.com

github.com

$ npm install --save-dev babel-preset-power-assert power-assert

.babelrc のpresetsを追記する。

{
  "presets": [
    "power-assert"
  ]
}

Assertを書き換える

importして、assertを書き換えるだけ。

// assertをimportする
import assert from 'assert'
import { mount } from 'vue-test-utils'
import Mikan from '../../components/Mikan'

describe('Mikan', () => {

  test('name is みかん', () => {
    let wrapper = mount(Mikan)

    // Assertをpower-assertに置き換える
    // expect(wrapper.vm.name).toEqual('みかん')
    assert.equal(wrapper.vm.name, 'みかん')
  })
})

power-assertを書くと、失敗時の出力がとても分かりやすくなる。

assert(wrapper.vm.name === 'みかん')
        |       |  |    |
        |       |  |    false
        |       |  "りんご"

メソッドの時もいい感じ。

assert(wrapper.vm.calcAmount(count) === 108)
        |       |  |          |      |
        |       |  300        3      false

nuxt generateで生成した静的ファイルをhttp-serverでホスティングする #nuxtjs

Nuxt.jsには静的ファイルの生成がサポートされており、成果物を静的ファイルとしてホスティングできるようになる。

開発中など、この静的ファイルをNuxt.jsに依存させないで確認したいときに、http-serverを使うと便利。

静的ファイルの生成

vue-cliから作成したNuxtプロジェクトであれば、package.json にscriptが記載されていると思う。

{
  "scripts": {
    "generate": "nuxt generate",
  },

なので、これを実行するだけ。

$ npm run generate

そうすると dist/ 配下に静的ファイルが生成される。

しかしこのままだとHTMLから参照しているJSファイルのパス等が /_nuxt/pages/index.5a196feafb5f24fea7d5.js のようになっており気軽には確認できない。
(ブラウザ等でそのままファイルとして開いてもJSが404になってしまったり)

http-serverの導入

http-serverを使うと、簡単にホスティングできそう。

www.npmjs.com

npmで開発用にインストール

$ npm install http-server --save-dev

scriptを package.json に追記

{
  "scripts": {
    "start-static": "http-server ./dist"
  },

起動する。

$ npm run start-static

http://127.0.0.1:8080ホスティングされる。

ポート等はオプションで差し替えたりできるみたい。

おまけ

プロジェクト内(npm)で完結したかったのでhttp-serverを使ったけど、ただ静的ファイルをホスティングするだけならphpコマンドが簡単だと教わった。

$ php -S localhost:8000 -t dist/

参考

ja.nuxtjs.org qiita.com qiita.com

jestでNuxt.jsのテストを書く #jest #nuxtjs #vuejs

概要

Nuxt.jsで、jestというテスティングフレームワークを使って、コンポーネントのテストを書いてみました。

facebook.github.io

準備

vue-cli でNuxt.jsのプロジェクトは作成済とします。

jest-vue-preprocessorをインストールします。

たぶんVueとjestを連携するためのもの。。

$ npm i -D jest jest-vue-preprocessor babel-jest

vue-test-utilsをインストールします。

きっとテストのUtilでしょう。。

npm i -D vue-test-utils

package.json に、jest用の設定を追記します。

"jest": {
  "moduleNameMapper": {
    "^vue$": "vue/dist/vue.common.js"
  },
  "moduleFileExtensions": [
    "js",
    "vue"
  ],
  "transform": {
    "^.+\\.js$": "<rootDir>/node_modules/babel-jest",
    ".*\\.(vue)$": "<rootDir>/node_modules/jest-vue-preprocessor"
  }
}

テストがjestで実行されるように、scriptsも package.json に追記します。

"scripts": {
  "test": "jest",
  ...
},

プロジェクトルートに、 .babelrc を作成します。

babel用の設定だと思うんですが、babelは知識不足でイマイチよく分かってないです。。
ただしこれがないとテストはエラーになって動きませんでした。

{
  "presets": ["env"]
}

これで、テストがjestで実行できるようになります。

$ npm test

テスト対象のコンポーネント

下記のようなコンポーネントcomponents/Mikan.vue として作成します。

<template>
  <p>{{ name }}{{ priceWithTax }}円</p>
</template>

<script>
export default {
  data: function() {
    return {
      name: 'みかん',
      price: 100
    }
  },
  computed: {
    priceWithTax: function() {
      return this.price * 1.08
    }
  },
  methods: {
    calcAmount: function(count) {
      return this.price * count
    },

    calcAmountWithTax: function(count) {
      return this.calcAmount(count) * 1.08
    }
  }
}
</script>

プロパティのテスト

名前や値段のテストをします。

テスト用のファイルは test/components/Mikan.test.js として作成します。

import { mount } from 'vue-test-utils'
import Mikan from '../../components/Mikan'

// 直接test()を書くこともできるが、
// テストの大まかな単位でdescribeして囲っておくとよさげ
describe('Mikan', () => {
  let wrapper

  // beforeEachは各test毎の実行前に呼び出される
  beforeEach(() => {
    // mountすると、コンポーネントをテストで扱いやすくしたようなラッパーオブジェクトが作られる
    // プロパティやメソッドにアクセス可能になる
    // またDOMにも仮想DOMとしてアクセスできるようになる模様
    wrapper = mount(Mikan)
  })

  test('name is みかん', () => {
    // wrapper.vm でコンポーネントのプロパティにアクセスできる
    expect(wrapper.vm.name).toEqual('みかん')
  })

  test('price is 100', () => {
    expect(wrapper.vm.price).toEqual(100)
  })
})

computedのテスト

プロパティと同じようにテストできます。

  test('priceWithTax is 108', () => {
    expect(wrapper.vm.priceWithTax).toEqual(108)
  })

メソッドのテスト

メソッドもvm経由で呼び出せばOKです。

describe('3 Mikans', () => {
  const count = 3
  let wrapper

  beforeEach(() => {
    wrapper = mount(Mikan)
  })

  test('calcAmount is 300', () => {
    expect(wrapper.vm.calcAmount(count)).toEqual(300)
  })

  test('calcAmountWithTax is 300', () => {
    expect(wrapper.vm.calcAmountWithTax(count)).toEqual(324)
  })
})

テスト結果

npm test すると下記のように結果が表示されます。

  Mikan
    ✓ name is みかん (12ms)
    ✓ price is 100 (1ms)
    ✓ priceWithTax is 108 (1ms)
  3 Mikans
    ✓ calcAmount is 300 (1ms)
    ✓ calcAmountWithTax is 300 (1ms)

scriptを "test": "jest --coverage" に書き換えると、カバレッジも見られるようになります。

-----------|----------|----------|----------|----------|----------------|
File       |  % Stmts | % Branch |  % Funcs |  % Lines |Uncovered Lines |
-----------|----------|----------|----------|----------|----------------|
All files  |      100 |       50 |      100 |      100 |                |
 Mikan.vue |      100 |       50 |      100 |      100 |          29,36 |
-----------|----------|----------|----------|----------|----------------|

参考

VSCodeでVue.jsのフォーマットがうまくいかない #vscode #vuejs #nuxtjs

概要

とりあえずNuxt.jsはVSCodeで書いてみてるんだけど、フォーマットをかけるとエラーになってしまう。

前提

Veturというプラグインを入れています。

github.com

結論

Prettierプラグインを入れる。

github.com

VSCodeの設定に下記を追加する。

"prettier.singleQuote": true,
"prettier.semi":false,

.eslintrc.js のrulesに下記を追加する。

rules: {
    // 追加する
    'space-before-function-paren': 0
  },

詳細

VSCodeでフォーマットかけると、エラーになってしまった。

フォーマット書けると下記のようになる。

<script>
export default {
  data: function() {
    return {
      message: "hello world!"
    };
  }
};
</script>

Nuxt.jsを npm run dev で起動しようとするとエラーになる

  10:17  error  Missing space before function parentheses  space-before-function-paren
  12:16  error  Strings must use singlequote               quotes
  13:6   error  Extra semicolon                            semi
  15:2   error  Extra semicolon                            semi

✖ 4 problems (4 errors, 0 warnings)
  4 errors, 0 warnings potentially fixable with the `--fix` option.

--fix オプションは効果なかった(´・ω・`)

どうも

  • functionの括弧はスペースあけろ
  • シングルクォート使え
  • いらんセミコロンつけるな

ということのようだ。

とりあえずPrettierプラグインを追加

どうもVeturのフォーマットがPrettierをデフォルトにしているっぽい?のでVSCodeに追加する。

github.com

いろいろ調べたけど

github.com

上記のIssueが見つかった。

効果があったのが前述のVSCodeの設定。

"prettier.singleQuote": true,
"prettier.semi":false,

これでシングルクォートとセミコロンは解決された。

しかしfunctionのスペースが解消されない。

javascript.format.insertSpaceBeforeFunctionParenthesis とかすごくそれっぽいのに。。

スペース入れるのを諦めた

結局スペースを入れるのをあきらめて、lintの方のルールでスペースがないものを正しいとするようにした。

.eslintrc.js に追記。

rules: {
    // 追加する
    'space-before-function-paren': 0
  },

なーんか納得行かないし気持ち悪いなぁ。。

参考

Nuxt.jsのディレクトリ構成 #nuxtjs #vuejs

vue-cliからテンプレートプロジェクトを作ると、ディレクトリがいくつも作られるのだが、それぞれの役割についてわかったこと。

基本的には↓に書いてあるけど。

ja.nuxtjs.org

テンプレートプロジェクトを作る

$ vue init nuxt-community/starter-template nuxt-example

ディレクトリ構成

nuxt-example
├── assets
├── components
├── layouts
├── middleware
├── node_modules
├── pages
├── plugins
├── static
└── store

assets

Webpackで扱う静的ファイルを配置する。

CSS系(LESS/SASS)やJavaScript、画像ファイルなんかが置かれるっぽい。

WebpackはCSSやJSファイルをひとまとめにしてくれるやつ。

画像ファイルはBase64形式にしてくれるみたい。

ja.nuxtjs.org

components

部品的なvueファイル(コンポーネント)を配置する。

例えば MenuBar.vue みたいなやつになるだろうか。

layoutやpageでimportして使うようになると思われる。

とうもVue.js(Nuxt.js)ではtemplateとscriptを同じファイルに書くようだ。

hoge.jsとhoge.htmlのように、スクリプトとテンプレートを分けるイメージがあったのでカルチャーショック。。

layouts

default.vueを用意しておくと、pageのレイアウトのベースになる。

layoutといいつつ、bodyタグの中身だけのイメージ。

htmlレベルのテンプレートはルートディレクトリに app.html を用意するらしい。

デフォルト以外のlayoutを別名で用意した場合は、個別にpage側で使用するlayoutを指定してあげる。

ja.nuxtjs.org

middleware

あるページ(のグループ)がレンダリングされる前に実行されるような処理を定義することができるらしい。

AOP的なことをやりたいときはここを使う模様。

(例ではauthが挙げられていた)

node_modules

npm install した色々が入るところ。

pages

実際にアプリケーションのビューになるようなvueファイルを配置する。

ファイルの名前や階層がそのままURLのルーティングになる。

なおElementUIというVue.js用のコンポーネント集みたいなものがあるらしい。

それを使えばBootstrapみたいに簡単にそれなりのデザインで画面が作れそう?

Element

plugins

ここは正直良くわからない。。横断的に使うようなライブラリとかパッケージが必要になった場合に使うのかなぁ。

static

Webpack で扱わない静的ファイルを配置する。

robots.txtとか、sitemap.xmlとか、faviconだったりが該当すると思われる。

store

Vuexとやらを使う時に必要になるらしい。

Vuexは複数のコンポーネントの状態管理を楽にしてくれるもののよう。

例えばあるアクション(操作)をした場合に、コンポーネントAとコンポーネントBの状態が変化する、みたいなときに有効みたい。

参考

qiita.com

qiita.com

qiita.com