オブジェクトベース設計

WEB+DB PRESS(vol.107) 速攻改善UIデザイン

多くの人が陥りがちなタスクベースの設計でなく、オブジェクトベースの設計の方が画面数も操作性も上がるという話。 自分が経験した中でも、まず何をやるべきかが要件定義に上がってくるような、業務アプリを作るときなどは例に挙げられているようなタスクベースの画面構成になりがちだったけれど、 機能追加のたびにかなり画面数が増えていくような印象だった。 こちらの特集だと実際に、タスクベースをオブジェクトベースにするとどうなるか?という事例がいくつか挙げられていてわかりやすかった。

Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders

  • Nuxt.js 2.8.1
  • TypeScript 3.5.3
  • Vuetify

ドロワーのコンポーネントを追加した際に上記エラー。

vuetifyjs.com

layout.vue(親)

<template>
  <v-app>
    <v-toolbar>
      <v-toolbar-side-icon @click.stop="toggleDrawer" />
    </v-toolbar>
    <Sidebar :drawer="drawer" />
  </v-app>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

@Component({
  components: {
    Sidebar: () => import('@/components/Sidebar.vue')
  }
})
export default class DefaultLayouts extends Vue {

  drawer: boolean = false

  toggleDrawer() {
    this.drawer = !this.drawer
  }
}
</script>

Sidebar.vue

# Sidebar.vue

<template>
  <v-navigation-drawer
    v-model="sidebar"
    app
  >
    <v-list dense>
      <v-list-tile to="/" class="home">
        <v-list-tile-action>
          <v-icon>home</v-icon>
        </v-list-tile-action>
        <v-list-tile-content>
          <v-list-tile-title>Home</v-list-tile-title>
        </v-list-tile-content>
      </v-list-tile>
    </v-list>
  </v-navigation-drawer>
</template>

<script lang="ts">
import { Component, Vue, Prop, Emit } from 'vue-property-decorator'

@Component
export default class Sidebar extends Vue {
  @Prop(Boolean)
  drawer!: boolean
}
</script>

これだと、ドロワー表示中にドロワーを非表示にした場合、親から受け取ったpropを直接変更することになるのがよくないため、 子コンポーネントから親にemitするようにする

コンポーネントの基本 — Vue.js

vue-property-decoratorを利用してemitさせるには、@Emitを利用する

Sidebar.vue

<script lang="ts">
import { Component, Vue, Prop, Emit } from 'vue-property-decorator'

@Component
export default class Sidebar extends Vue {
  @Prop(Boolean)
  drawer!: boolean

  get sidebar(): boolean {
    return this.drawer
  }

  set sidebar(value: boolean): void {
    this.changeDrawer(value)
  }

  @Emit()
  changeDrawer(value: boolean) {
    return value
  }
}
</script>

親の方でもイベントを購読させることで、エラーが消えた。

unknown mutation type: test/update

テスト作成中に上記エラー。

  • Nuxt.js 2.4.0
  • jest 23.6.0
  • node v11.2.0
  • Vuex 3.1.1

これは、Vuexを使用する時、モジュールモード を使用したんだけど、そうすると名前空間が分割されるというのがテスト時に再現できていなかったので発生したみたい。

vuex.vuejs.org

# store/test.js
export const state = () => {}

export const mutations = {
  update(state) {
    ...
  }
}
# test/TestForm.spec.js

const localVue = createLocalVue()

localVue.use(Vuex)

beforeEach(() => {
    const state = {}
    const actions = {
      testAction: jest.fn()
    }
   # ここを変える
    const store = new Vuex.Store({
         state: state
         actions
    })
    wrapper = shallowMount(TestForm, {
      store,
      localVue
    })
  })

なので、上記の箇所を変更して、modules内に収めてnamespacedをtrueにセットする

    store = new Vuex.Store({
      modules: {
        test: {
          namespaced: true,
          state: {},
          actions
        }
      }
    })

これで、stateやmutationsの名前空間が使用できるはず。

Multiple instances of Vue detected

Nuxt.jsを使ったアプリでComponentのテストを作成したところ、表題のエラーとなった。

  • Nuxt.js 2.4.0
  • jest 23.6.0
  • node v11.2.0
  • vuetify1.3.14
import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import Test from '@/components/Test.vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'


const localVue = createLocalVue()

localVue.use(Vuex)
localVue.use(Vuetify)

describe('Test', () => {
    let actions
    let store

    beforeEach(() => {
        actions = {
          testAction: jest.fn()
        }
        store = new Vuex.Store({
          state: {},
          actions
        })
    })

  test('is a Vue instance', () => {
    const wrapper = shallowMount(Test, { store, localVue })
    expect(wrapper.isVueInstance()).toBeTruthy()
  })
})

localVue.use(Vuetify)をVue.use(Vuetify)に変更すると動くようになる

github.com

github.com

import { mount, shallowMount, createLocalVue } from '@vue/test-utils'
import Test from '@/components/Test.vue'
import Vue from 'vue'
import Vuex from 'vuex'
import Vuetify from 'vuetify'

Vue.use(Vuetify)

const localVue = createLocalVue()

localVue.use(Vuex)

 ...

名前付きインポートの場合は分割代入しなければならない

下記のようなテストを書いたら、undefinedとなった

import mutations from '@/store/resume.js'

test('object check', () => {
    expect(mutations).toBeTruthy(); // undefined
})

これは、importしているmutationsを分割代入していないのが原因だった

jsprimer.net

そもそもESモジュールのexportが二種類あるのをちゃんと認識できていなかった。 モジュールは名前付きエクスポートと、デフォルトエクスポートの二種類がある。

// 名前付きエクスポート
export function fn() { }
export class ClassName { }

// デフォルトエクスポート
export default function () {}
export default class {}

大きな差としては、名前付きエクスポートはモジュールにつきいくつも作成できるのだけど、 デフォルトエクスポートはモジュールに対して一つまでしか作成できない。

そうなると、受け側となるimportの方も、デフォルトインポートの場合は分割代入しなくて良いが、 名前付きインポートの場合は分割代入しなくてはならなくなる。

developer.mozilla.org

なので、下記のようにすればよかった。

import { mutations } from '@/store/resume.js'