LoginSignup
4
5

More than 3 years have passed since last update.

Vuejsのプロジェクトをリファクタリングしたから気を付けたことを書く

Last updated at Posted at 2019-05-19

リファクタリングの動機

機能を追加したかったけど、このまま追加していくとすぐに変更が難しくなりそうだったから、
小さいうちに変更を加えやすいものにしておきたかった。

あと機能を作ることに集中していたけど、これからUIをきれいにしていきたい。
現状は似ているエレメントをコピペとかしているのでこのままだと統一的なUIにするのが難しそうだと思った

リファクタリング対象

GitHub: https://github.com/sterashima78/vue-webpage-builder
これ自体ついては別の記事に書いている。(これこれ)

リファクタリング前は v0.4.1 である程度リファクタリングしたのが v0.5.1

意識したこと

  • 役割を適切に分ける
  • コンポーネントから大きめのロジックを取り除く
    • コンポーネントはあくまでもViewなのでリアクティブなデータを保持する必要があるけどロジックは持たなくてもいい

Atomic Design

  • atoms・molecules・organisms・templates・pagesにコンポーネントを分ける
  • atoms・molecules・organismsの分け方はあまり厳密に考えすぎない
    • 最初のリファクタリングなのでそれなりの大きさのコンポーネントに分けることを重視
  • Vuexに依存するのはpagesだけ
    • イベントをパスしまくるのはめんどいけど、いったんこれで
  • pagesは自分のViewを持たずにtemplatesを表示するだけ
    • templatesに値を注入してイベントハンドラを設定するだけ
    • .vueファイルで記述したけど結局HOCなので.ts書いてもよかった

ディレクトリ構成

主要なもののみ

変更前

VueCLI の create で生成した所からスタートしているので割と一般的な構成だと思う

v0.4.1
src
├── App.vue
├── components
│   ├── Ace.vue
│   ├── ComponentEditor.vue
│   ├── ComponentTree.vue
│   ├── ComponentsList.vue
│   ├── ExternalResource.vue
│   ├── Viewer.vue
│   └── tags.ts
├── main.ts
├── observer #Rx
├── store #Vuex
├── types.ts
├── util
│   ├── LocalVue.ts
│   ├── NodeUtils.ts
│   └── toString.ts
└── views
    └── Home.vue

変更後

少し移行が完了していない箇所がある

v0.5.1
src
├── App.vue
├── application
│   └── components
│       ├── atoms
│       ├── molecules
│       ├── organisms
│       ├── pages
│       │   └── ViewerPage.vue
│       └── templates
│           └── ViewerTemplate.vue
├── domain # ドメインロジックが入る
│   ├── model
│   └── service
├── main.ts
├── store #Vuex
├── types.ts
├── util
│   ├── LocalVue.ts
│   └── NodeUtils.ts
└── views
    └── Home.vue

進める手順

あらかじめテストを書いてあればそれをガイドに進めたけど、まずテストが書きにくかったので無い。

  1. コンポーネントを分ける
    • コピペエレメントが減った
  2. ステートの更新と、更新するためのデータを作ったりするロジックを分ける
    • この辺でテストを書こうと思えるようになってきた
  3. データを作成するロジックを外に出す
    • この辺で無駄な処理とかの整理ができた

Vuexに依存している (Nodesモジュール)

ComponentsList.vue(v0.4.1)
<template>
  <v-card flat style="overflow-y: scroll; height: calc(100vh - 50px);">
    <v-card-text>
      <v-select :items="['html', 'component']" v-model="type"/>
      <v-text-field v-model="filter" label="filter"/>
      <v-container grid-list-xl>
        <v-layout wrap>
          <v-flex xs6 v-for="name in filteredComponents" :key="name">
            <v-sheet
              draggable="true"
              height="75"
              :elevation="20"
              style="word-break: break-all;cursor: move;"
              @dragstart.native.stop="cmpDragStart(name)"
              @dragend.native.stop="cmpDragEnd(name)"
              @dragover="$event.preventDefault()"
            >{{name}}</v-sheet>
          </v-flex>
        </v-layout>
      </v-container>
    </v-card-text>
  </v-card>
</template>

<script lang="ts">
import { Component, Vue, Watch, Prop } from "vue-property-decorator";
import Nodes from "../store/modules/nodes";
import HTMLTags from "./tags";
@Component
export default class ComponentsList extends Vue {
  private filter = "";
  private type = "html";
  public get filteredComponents(): string[] {
    switch (this.type) {
      case "html":
        return HTMLTags.filter(c =>
          new RegExp(this.filter, "i").test(c)
        ).sort();
      case "component":
        return Nodes.components.filter(c =>
          new RegExp(this.filter, "i").test(c)
        );
      default:
        return [];
    }
  }
  public cmpDragStart(name: string) {
    Nodes.SET_NEW_COMPONENT_NAME(name);
  }
  public cmpDragEnd(name: string) {
    Nodes.REMOVE_NEW_COMPONENT_NAME(name);
  }
}
</script>

コンポーネントを分ける
小さいコンポーネントにわけることで重複がなくなる。
タブアイテムとして配置するという興味 (MenuTabItem) と コンポーネントを選ばせるという興味 (ComponentSelector) が分かれる
更新処理部分をevent発行に置き換えて上位コンポーネントに移譲する

ComponentsList.vue(v0.5.1)
<template>
<MenuTabItem>
  <ComponentSelector 
    :components="components" 
    @dragStart="dragStart"
    @dragEnd="dragEnd"
  />
</MenuTabItem>
</template>

<script lang="ts">
import { Component, Vue, Prop } from "vue-property-decorator";
import ComponentSelector from "@/application/components/molecules/ComponentSelector.vue";
import MenuTabItem from "@/application/components/atoms/MenuTabItem.vue";
@Component({
  components: {
    ComponentSelector,
    MenuTabItem
  }
})
export default class ComponentsList extends Vue {
  @Prop({ default: () => [] })
  private components!: string[];
  private dragStart(name: string) {
    this.$emit("dragStart", name);
  }
  private dragEnd(name: string) {
    this.$emit("dragEnd", name);
  }
}
</script>

おわりに

ロジックを分離したから単体試験が書きやすくなる。
状態は外から注入して、操作の結果はイベント発行なのでStoryBookも書きやすくなる。

4
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
4
5