オブジェクトベース設計
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
ドロワーのコンポーネントを追加した際に上記エラー。
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-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>
親の方でもイベントを購読させることで、エラーが消えた。
Vue.jsでテスト書く時の参考にしているドキュメント
Jest
Jestを使用しているのでJest
Vue Test Util
Vue.jsのテスト用ライブラリ。
後で読む
unknown mutation type: test/update
テスト作成中に上記エラー。
- Nuxt.js 2.4.0
- jest 23.6.0
- node v11.2.0
- Vuex 3.1.1
これは、Vuexを使用する時、モジュールモード を使用したんだけど、そうすると名前空間が分割されるというのがテスト時に再現できていなかったので発生したみたい。
# 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)に変更すると動くようになる
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を分割代入していないのが原因だった
そもそもESモジュールのexportが二種類あるのをちゃんと認識できていなかった。 モジュールは名前付きエクスポートと、デフォルトエクスポートの二種類がある。
// 名前付きエクスポート export function fn() { } export class ClassName { } // デフォルトエクスポート export default function () {} export default class {}
大きな差としては、名前付きエクスポートはモジュールにつきいくつも作成できるのだけど、 デフォルトエクスポートはモジュールに対して一つまでしか作成できない。
そうなると、受け側となるimportの方も、デフォルトインポートの場合は分割代入しなくて良いが、 名前付きインポートの場合は分割代入しなくてはならなくなる。
なので、下記のようにすればよかった。
import { mutations } from '@/store/resume.js'