vuexを使った画面のアラートの表示


# 使てるもの
* Vue3
* Vuex
    * applicationの状態管理(store)してくれる。今回は必須
* Vuetify
    * ボタンとか楽にいい感じにできるやつ 
* vue-router
    * SPAを構築してルーティング制御してくれる 
* vue-axios 
* axios
    * ajax　api叩くときに使う
* axios-mock-adapter
    * mock　apiのダミー作る 



# 作ったもの
APIでエラーが返却されたとき、画面に描画する。
アラート用のコンポーネントにいろんなコンポーネントからimportしたり、レスポンス等のステータスを持ちまわったりするのがめんどくさかったので、そこを解消した。

# デモ
https://suechon.github.io/vue-alert/

# コード
https://github.com/Suechon/vue-alert/

# なんとなくの図
![素材類.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/236151/528ca51b-ee55-d58c-bac8-03b51602b62e.jpeg)

# コードの説明
## API叩いてるコンポーネント
* Page1.vue/Page2.vue

```vuejs

<template>
  <div>
    <p>ページ1</p>
    <v-btn color="success" @click="clickSuccess">成功</v-btn>
    <v-btn color="error" @click="clickError">失敗</v-btn>
    {{ success }}
    <v-col>
      <v-btn @click="jump">ページ２へ</v-btn>
    </v-col>
  </div>
</template>
<script>
export default {
  data() {
    return { success: "" };
  },
  methods: {
    jump() {
      this.$router.push({ path: "page2" });
    },
    clickSuccess() {
      this.axios
        .get("/page1")
        .then((response) => {
          this.success = "成功" + response.data;
        })
        .catch((e) => {
          this.store_setAlert({
            action: true,
            message: "error \n" + e,
            detail: "[page1]成功ボタンで失敗しました'",
          });
        });
    },
    clickError() {
      this.axios
        .get("/hoge")
        .then((response) => {
          this.success = "失敗" + response.data;
        })
        .catch((e) => {
          this.store_setAlert({
            action: true,
            message: e,
            detail: "[page1]失敗ボタンで失敗しました'",
          });
        });
    },
    store_setAlert({ action, message, detail }) {
      this.$store.dispatch("alert/setAlert", {
        action: action,
        message: message,
        detail: detail,
      });
    },
  },
};
</script>
```
 clickSuccess()とclickError()でリクエスト成功するAPIと失敗するAPIを叩いてます。
それぞれcatchしたあとにstore_setAlert()を呼んで、` this.$store.dispatch("alert/setAlert", {...`でstoreに状態を設定していきます。
actionの型はbooleanで、trueの時アラートを表示する。falseの時表示しない。
messageはとりあえず返却されたエラーを設定。
detailは返却されたエラーを見て、表示したい文言を変えたい場合にそれを設定します。

## アラート表示するコンポーネント
* Alert.vue

```vuejs

<template >
  <div v-if="getAction">
    <v-alert dense outlined type="error" class="multi-line">
      <v-row>
        <v-col cols="12" class="pb-1">
          <div v-html="getMessage"></div>

          <v-row>
            <div v-html="'\n'+getDetail"></div>

            <v-spacer />
            <v-btn
              color="error"
              text
              @click="store_setAlert({action:false,message:null,detail:null})"
            >Close</v-btn>
          </v-row>
        </v-col>
      </v-row>
      <v-divider class="my-4 error" style="opacity: 0.92" v-if="this.getDetail !=null" />
      <p>Please reload the page.</p>
    </v-alert>
  </div>
</template>
<script>
export default {
  data() {
    return { newLine: "\n" };
  },
  computed: {
    getAction() {
      return this.$store.getters["alert/action"];
    },
    getMessage() {
      return this.$store.getters["alert/message"];
    },
    getDetail() {
      return this.$store.getters["alert/detail"];
    },
  },
  mounted() {},
  methods: {
    store_setAlert({ action: action, message: message, detail: detail }) {
      this.$store.dispatch("alert/setAlert", {
        action: action,
        message: message,
        detail: detail,
      });
    },
  },
};
</script>

<style scoped>
.multi-line {
  white-space: pre-line;
}
</style>
```
デザイン？はvuetifyのv-alert使ってます。
v-alert内は改行がきかなかったのでstyleと `<div v-html="'\n'+getDetail" />`あたりで無理やり改行してます。
`<div v-if="getAction">`
 getAction()を呼んでて、 getAction()はstoreのactionを参照してます。
actionはbooleanを取るので、`v-if`でこのコンポーネントを表示するかどうかを制御してます。

` <div v-html="getMessage"></div>`
` <div v-html="'\n'+getDetail"></div>`
これらもそれぞれgetMessage()とgetDetail()からstoreで管理されてるメッセージを参照してきてます。

`@click="store_setAlert...`
closeのボタン押したときはactionをfalseにしてアラートを非表示にします。

## main.js

```js
//省略...
// interceptors
axios.interceptors.request.use(request => {
  store_setAlert({
    action: false,
    message: null,
    detail: null,
  }).then(function (change) {
  }).catch(function (error) {
  });

  return request
})
function store_setAlert({ action: action, message: message, detail: detail }) {
  return new Promise(function (resolve, reject) {
    store.dispatch("alert/setAlert", {
      action: action,
      message: message,
      detail: detail,
    });
    resolve();
  })
}
//省略...
```
アラート出て、更新等するとき、actionがtrueのままだと、2回目のリクエストで成功してもアラートが出続けるので、リクエスト投げるま前は必ずfalseにするようにしてます。


## router.js

```js

//省略...

router.beforeEach((to, from, next) => {
    store.commit('alert/setAlert', { action: false, message: null, detail: null });
    next();
}
```
beforeEachで画面遷移するときもfalseで初期化してます。
これもpage1でアラート出て、page2に遷移してAPI叩かない又は正常だった場合にアラートが出続けてしまうので。

## App.vue

```vue
<template>
  <v-app id="app">
    <v-main>
      <v-container fluid>
        <Alert />
        <router-view />
      </v-container>
    </v-main>
  </v-app>
</template>
//省略...
```

Alertのコンポーネントどこで読み込んでもいいけど毎回importするのめんどくさいので全ページで表示できるようApp.vueでimportしてます。


## まとめ
値をあちこち持って回るのが好きじゃないのでだいぶすっきりしたかなと思います。
api呼ぶところとかもまとめればもう少しましになりそう