Vue.js+Vuexの行儀の悪い脱法コード3つ


これは「[「はじめに」の Advent Calendar 2021](https://adventar.org/calendars/7037)」13日目の記事です。

※コンポーネントの種類、用語は [Atomic design](https://qiita.com/kahirokunn/items/f188b2362a4d3cdf0204) をベースにしています。

https://qiita.com/kahirokunn/items/f188b2362a4d3cdf0204

## 脱法1. Atom/MoleculesからActionを呼び出す

* 理由
  * Atom/MoleculesとVuexのstateが強い結合をもつ。
  * 挙動が一緒で読み出すstateが違うパーツが大量生産される

```FooPage.vue
<template>
  <ButtonFoo />
  <ButtonBar />
  <ButtonBaz />
</template>
<script>
import ButtonFoo from '@/components/ButtonFoo.vue'
import ButtonBar from '@/components/ButtonBar.vue'
import ButtonBaz from '@/components/ButtonBaz.vue'

  components: {
    ButtonFoo, ButtonBar, ButtonBaz
  }
</script>
```

```ButtonFoo.vue
<template>
  <button @click="onClick">{{ name }}</button>
</template>
<script>

export default {
  computed: {
    name() {
      return this.$store.getters.foo.name;
    }
  },
  methods: {
    ..mapActions('foo', ['getName'])
    onClick() {
      this.getName()
    }
  }
}
</script>
```

```store/modules/foo.js
export default {
  namespaced: true,
  mutations: {
    FOO_NAME: (state, name) => {
      state.name = name
    }
  },
  actions: {
    getName({ commit }) {
      asyncApi().then(response => {
        commit('FOO_NAME', response.data)
      }
    }
  }
}
```

* 合法化するには
  * Atom/Moleculesへ値を渡すときは`props`
  * Atom/Moleculesでの操作やイベントは`$emit`
  * Actionの呼び出しはPagesに集約させる


```FooPage.vue
<template>
  <ButtonFoo :name="foo" @foo-click="getFoo()" />
  <ButtonFoo :name="bar" @foo-click="getBar()" />
  <ButtonFoo :name="baz" @foo-click="getBaz()" />
</template>
<script>
import ButtonFoo from '@/components/ButtonFoo.vue'

export default {
  computed: {
    foo(): { return this.$store.getters.foo.name; },
    bar(): { return this.$store.getters.bar.name; },
    baz(): { return this.$store.getters.baz.name; },
  },
  methods: {
    ..mapActions('foo', ['getName'])
    getFoo() {
      foo.getName()
    }
    // (略)
  }
}
</script>
```

```Button.vue
<template>
  <button @click="onClick">{{ name }}</button>
</template>
<script>

export default {
  props: {
    name: {
      type: String,
      default: "init"
    }
  },
  methods: {
    onClick() {
      this.$emit()
    }
  }
}
</script>
```

## 脱法2: actionの戻り値をHookして自コンポーネント内のdataに保存する

* 理由
  * Vuexの`単方向データフロー`コンセプト違反。
  * SSOT(Single Source Of Truthe)違反。
  * storeとコンポーネント内のdataの整合性の保証がない

https://vuex.vuejs.org/#what-is-a-state-management-pattern

https://vuex.vuejs.org/guide/state.html#single-state-tree


```FooPage.vue
<template>
  <ButtonFoo :name="name" @foo-click="getFoo()" />
</template>
<script>
import ButtonFoo from '@/components/ButtonFoo.vue'

export default {
  data() {
    return {
      name: ""
    }
  }
  computed: {
    foo() { return this.$store.getters.foo.name; },
  },
  methods: {
    ..mapActions('foo', ['getName'])
    getFoo().then {
      foo.getName().then((data) => {
        this.name = data
      })
    }
  }
}
</script>
```

```store/modules/foo.js
import { getName } from '@/api/foo'

export default {
  namespaced: true,
  mutations: {
    FOO_NAME: (state, name) => {
      state.name = name
    }
  },
  actions: {
    return new Promise((resolve, reject) => {
      getName({ commit }): {
        asyncApi().then(response => {
          commit('FOO_NAME', response.data)
          resolve(response.data)
        }.catch((e) => {
          reject(e)
        })
      }
    }
  }
}
```

* 合法化するには
  * `actions`は、`mutations` にわたすデータを返り値にしない。
  * 同じ内容を保存するのは1箇所にする

※コードは1と同様なので割愛

## 脱法3: Atom/Moleculesがapiをダイレクトコール

* 理由
  * アプリケーションデータを一元管理できない(どこで保存されているかわからなくなる)
  * APIとコンポーネントの結合が強くなると変更に弱い
    * APIのスキーマ変更の追従が困難になる

スキーマをそのままstateに利用しているコードも行儀は良くない。
パット見、独立したデータフローサイクルが構築されているので、問題ないように見えるコトがあるのが厄介。

```Button.vue
<template>
  <button @click="onClick">{{ name }}</button>
</template>
<script>
import { getName } from '@/api/foo'

export default {
  data() {
    return {
      name: ""
    }
  },
  methods: {
    onClick() {
      getName().then((response) => {
        this.name = response.data
      })
    }
  }
}
</script>
```

* 合法化するには
  * `api`呼び出しは`actions`経由に集約する。
      * APIのスキーマ変更に対しては、Vue.jsアプリケーション内のデータモデルとAPIのレスポンスモデルを別に定義しておくこと。OpenAPIを利用した自動生成を使うことで管理コストは削減観光。
  * (例外)アプリケーションのメインデータではないもの、ライブラリとして切り離せるもの。
      * 郵便番号から住所文字列をサジェストする再利用可能なOrganizmsなど