๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

Project

[ํ”„๋กœ์ ํŠธ ๊ธฐ๋ณธ ์„ธํŒ…] ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๊ตฌํ˜„ (vuex, localStorage)

ํ”„๋กœ์ ํŠธ ๊ธฐ๋ณธ ์„ธํŒ…

6. [ํ”„๋ก ํŠธ์—”๋“œ] ํšŒ์› ๊ฐ€์ž…, ๋กœ๊ทธ์ธ ๊ตฌํ˜„ (vuex, localStorage)


ํšŒ์› ๊ฐ€์ž… ํ™”๋ฉด ๋ฐ ๊ธฐ๋Šฅ, resister ๊ฐ์ฒด, vuex ์ƒ์„ฑ

vuex ์ƒ์„ฑ

store/index.js

import Vue from "vue"
import Vuex from "vuex"
import axios from "axios"

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    userInfo: null,
    isLogin: false
  },
  mutations: {
    loginSuccess(state, payload) {
      state.isLogin = true
      state.userInfo = payload
    },
    logout(state) {
      state.isLogin = false
      state.userInfo = null
      localStorage.removeItem("access_token")
    }
  },
  actions: {
    getAccountInfo({ commit }) {
      let token = localStorage.getItem("access_token")
      axios
        .get("/userinfo", {
          headers: {
            "X-AUTH-TOKEN": token
          }
        })
        .then((response) => {
          commit("loginSuccess", response.data.data)
        })
        .catch((error) => {
          console.log(error)
        })
    }
  },
  modules: {}
})

src ํ•˜์œ„์— store ํด๋”๋ฅผ ๋งŒ๋“ค๊ณ  index.js๋ฅผ ๋‹ค์Œ๊ณผ ๊ฐ™์ด ์ƒ์„ฑํ•œ๋‹ค.

state์—๋Š” user์˜ ์ •๋ณด์™€ ๋กœ๊ทธ์ธ์˜ ์—ฌ๋ถ€๋ฅผ ์ €์žฅํ•˜๋ฉฐ ๋กœ๊ทธ์ธ๊ณผ ๋กœ๊ทธ์•„์›ƒ์— ๋Œ€ํ•œ mutations๋ฅผ ๋งŒ๋“ค์—ˆ๋‹ค.

getAccountInfo์€ localStorage์— ์ €์žฅ๋œ jwt ํ† ํฐ์„ ํ™•์ธ ํ›„ ์ด๋ฅผ api์— ์ „๋‹ฌํ•˜์—ฌ ์œ ์ €์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๋Š” actions์ด๋‹ค.

ํ›„์— cookie๊ฐ€ web storage ๋ณด๋‹ค ๋” ๋‚˜์€ ๋ฐฉ์•ˆ์ด๋ผ๊ณ  ์ƒ๊ฐ์ด ๋˜๋ฉด js-cookie๋ฅผ ์ด์šฉํ•ด์„œ ์ฟ ํ‚ค์— ํšŒ์›์ •๋ณด์™€ ๋กœ๊ทธ์ธ ์œ ๋ฌด๋ฅผ ๋‹ด๋Š” ๊ฒƒ์œผ๋กœ ์ˆ˜์ •ํ•  ๊ณ„ํš์ด๋‹ค.

resisterObj.js, loginObj.js

export default class ResisterObj {
  constructor(email, password, nickname) {
    this.email = email
    this.password = password
    this.nickname = nickname
  }
}
export default class LoginObj {
  constructor(email, password) {
    this.email = email
    this.password = password
  }
}

ํด๋ผ์ด์–ธํŠธ์—๊ฒŒ ๋ฐ›์€ ์ •๋ณด๋ฅผ ๋‹ด์„ ResisterObj์™€ LoginObj์ด๋‹ค.

ํšŒ์›๊ฐ€์ž… view

ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด

account-register.vue

<template>
  <v-container style="width: 450px">
    <v-layout align-center row wrap>
      <v-flex xs12>
        <v-alert v-if="isError" type="error">
          {{ errorMsg }}
        </v-alert>
        <v-card>
          <v-toolbar flat color="indigo">
            <v-toolbar-title
              ><span class="white--text">ํšŒ์›๊ฐ€์ž…</span></v-toolbar-title
            >
          </v-toolbar>
          <div class="pa-5">
            <v-form ref="form" v-model="valid" lazy-validation>
              <v-text-field
                v-model="formData.email"
                :rules="emailRules"
                label="Enter E-mail"
                required
              ></v-text-field>

              <v-text-field
                v-model="formData.name"
                :counter="10"
                :rules="nameRules"
                label="Name"
                required
              ></v-text-field>

              <v-text-field
                v-model="formData.password"
                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
                :rules="[rules.required, rules.min]"
                :type="show ? 'text' : 'password'"
                label="Enter Password"
                hint="At least 8 characters"
                counter
                @click:append="show = !show"
              ></v-text-field>

              <v-text-field
                v-model="chkPassword"
                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
                :rules="[rules.required, rules.min]"
                :type="show ? 'text' : 'password'"
                label="Enter Password Again"
                hint="At least 8 characters"
                counter
                @click:append="show = !show"
              ></v-text-field>

              <h6 v-if="sameChk(chkPassword)" class="mb-5 teal--text accent-3">
                Please create the two passwords identical.
              </h6>
              <h6 v-else class="mb-5 red--text lighten-2">
                Please create the two passwords identical.
              </h6>

              <div class="mt-3 d-flex flex-row-reverse">
                <v-btn color="error" class="mr-4" @click="reset"> ๋ฆฌ์…‹ </v-btn>

                <v-btn
                  :disabled="!valid"
                  color="blue"
                  class="mr-4"
                  @click="register(formData)"
                >
                  ํšŒ์›๊ฐ€์ž…
                </v-btn>
              </div>
            </v-form>
          </div>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import RegisterObj from "../models/resisterObj"
import axios from "axios"
export default {
  data: () => ({
    formData: new RegisterObj("", "", ""),
    valid: false,
    nameRules: [
      (v) => !!v || "Name is required",
      (v) => (v && v.length <= 10) || "Name must be less than 10 characters"
    ],
    isError: false,
    errorMsg: "",
    emailRules: [
      (v) => !!v || "E-mail is required",
      (v) => /.+@.+\..+/.test(v) || "E-mail must be valid"
    ],
    show: false,
    chkPassword: "",
    rules: {
      required: (value) => !!value || "Required.",
      min: (v) => v.length >= 8 || "Min 8 characters"
    }
  }),
  methods: {
    goToMain() {
      this.$router.push({
        name: "login"
      })
    },
    sameChk(password) {
      if (this.formData.password == password) return true
      else {
        this.valid = false
        return false
      }
    },
    register(RegisterObj) {
      if (
        !this.formData.email ||
        !this.formData.name ||
        !this.formData.password
      ) {
        this.isError = true
        this.errorMsg = "์ด๋ฉ”์ผ๊ณผ ๋‹‰๋„ค์ž„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
        return
      }
      axios
        .post("/signup", RegisterObj)
        .then(() => {
          this.goToMain()
        })
        .catch((err) => {
          if (err.response) {
            this.isError = true
            this.errorMsg = err.response.data.message
          }
        })
    },
    validate() {
      this.$refs.form.validate()
    },
    reset() {
      this.$refs.form.reset()
    },
    resetValidation() {
      this.$refs.form.resetValidation()
    }
  }
}
</script>

ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด์˜ view๋ฅผ ๊ตฌ์ƒํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์ด๋‹ค.

์ž…๋ ฅ ํผ์€ vuetify ๋ฌธ์„œ์˜ forms๋ฅผ ์ฐธ๊ณ ํ•ด์„œ ๋งŒ๋“ค์—ˆ๋‹ค.vuetify ํ•œ๊ธ€๋ฌธ์„œ - forms

// ...
<v-text-field
                v-model="formData.password"
                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
                :rules="[rules.required, rules.min]"
                :type="show ? 'text' : 'password'"
                label="Enter Password"
                hint="At least 8 characters"
                counter
                @click:append="show = !show"
              ></v-text-field>

              <v-text-field
                v-model="chkPassword"
                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
                :rules="[rules.required, rules.min]"
                :type="show ? 'text' : 'password'"
                label="Enter Password Again"
                hint="At least 8 characters"
                counter
                @click:append="show = !show"
              ></v-text-field>

<h6 v-if="sameChk(chkPassword)" class="mb-5 teal--text accent-3">
                Please create the two passwords identical.
              </h6>
              <h6 v-else class="mb-5 red--text lighten-2">
                Please create the two passwords identical.
              </h6>
// ...

// ...
sameChk(password) {
      if (this.formData.password == password) return true
      else {
        this.valid = false
        return false
      }
    },
// ...

๋‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ฉ”์†Œ๋“œ๋กœ ๊ฐ’์ด ๋ณ€ํ•  ๋•Œ๋งˆ๋‹ค ํ™•์ธํ•˜์—ฌ ๋‘ ๊ฐœ๊ฐ€ ๊ฐ™์ง€ ์•Š์„ ๋• valid๋ฅผ false๋กœ ๋ฐ”๊ฟ”์ค€๋‹ค.

// ...

register(RegisterObj) {
      if (
        !this.formData.email ||
        !this.formData.nickname ||
        !this.formData.password
      ) {
        this.isError = true
        this.errorMsg = "์ด๋ฉ”์ผ๊ณผ ๋‹‰๋„ค์ž„๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ๋ชจ๋‘ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
        return
      } else if (!this.sameChk) {
        this.isError = true
        this.errorMsg = "๋‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ๊ฐ™์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค."
        return
      }
      axios
        .post("/sign-up", RegisterObj)
        .then(() => {
          this.goToMain()
        })
        .catch((err) => {
          if (err.response) {
            this.isError = true
            this.errorMsg = err.response.data.messager
          }
        })
    },

// ...

valid๊ฐ€ true ์ผ ๋•Œ ํšŒ์›๊ฐ€์ž… ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด register ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

ํ•„์š”ํ•œ ๋ชจ๋“  ๊ฐ’์„ ๋‹ค ์ž…๋ ฅํ–ˆ๋Š”์ง€ ํ™•์ธํ•ด ์ฃผ๊ณ , sameChk ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ๋‘ ๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ–ˆ๋Š”์ง€๋„ ํ™•์ธํ•ด ์ค€๋‹ค.

์ด์ƒ์ด ์—†์œผ๋ฉด axios๋ฅผ ์ด์šฉํ•ด baseURL์˜ "/sign-up" ๊ฒฝ๋กœ์— ๋“ฑ๋ก ๊ฐ์ฒด๋ฅผ post ์š”์ฒญ์œผ๋กœ ๋ณด๋‚ด์ค€๋‹ค.

this.goToMain()๋Š” ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ๋ณด๋‚ด์ฃผ๋Š” ๋ฉ”์†Œ๋“œ์ด๋‹ค.

๋กœ๊ทธ์ธ ๊ฐ์ฒด ๋ฐ ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€ ์ƒ์„ฑ

๋กœ๊ทธ์ธ ํ™”๋ฉด

๋กœ๊ทธ์ธ ํ™”๋ฉด

account-login.vue

<template>
  <v-container style="width: 450px">
    <v-layout align-center row wrap>
      <v-flex xs12>
        <v-alert v-if="isError" type="error">
          {{ errorMsg }}
        </v-alert>
        <v-card>
          <v-toolbar flat color="indigo">
            <v-toolbar-title
              ><span class="white--text">๋กœ๊ทธ์ธ</span></v-toolbar-title
            >
          </v-toolbar>
          <div class="pa-5">
            <v-form ref="form" v-model="valid" lazy-validation>
              <v-text-field
                v-model="formData.email"
                :rules="emailRules"
                label="Enter E-mail"
                required
              ></v-text-field>

              <v-text-field
                v-model="formData.password"
                :append-icon="show ? 'mdi-eye' : 'mdi-eye-off'"
                :rules="[rules.required, rules.min]"
                :type="show ? 'text' : 'password'"
                label="Enter Password"
                hint="At least 8 characters"
                counter
                @click:append="show = !show"
              ></v-text-field>

              <div class="mt-3 d-flex flex-row-reverse">
                <v-btn color="error" class="mr-4" @click="reset"> ๋ฆฌ์…‹ </v-btn>

                <v-btn
                  color="primary"
                  class="mr-4"
                  link
                  router
                  :to="{ name: 'register' }"
                >
                  ํšŒ์›๊ฐ€์ž…
                </v-btn>

                <v-btn
                  :disabled="!valid"
                  color="success"
                  class="mr-4"
                  @click="login(formData)"
                >
                  ๋กœ๊ทธ์ธ
                </v-btn>
              </div>
            </v-form>
          </div>
        </v-card>
      </v-flex>
    </v-layout>
  </v-container>
</template>

<script>
import LoginObj from "../models/loginObj"
import axios from "axios"
export default {
  data: () => ({
    formData: new LoginObj("", ""),
    valid: false,
    isError: false,
    errorMsg: "",
    emailRules: [
      (v) => !!v || "E-mail is required",
      (v) => /.+@.+\..+/.test(v) || "E-mail must be valid"
    ],
    show: false,
    rules: {
      required: (value) => !!value || "Required.",
      min: (v) => v.length >= 8 || "Min 8 characters"
    }
  }),
  methods: {
    login(LoginObj) {
      if (!this.formData.email || !this.formData.password) {
        this.isError = true
        this.errorMsg = "์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
        return
      }
      axios
        .post("/signin", LoginObj)
        .then((res) => {
          let token = res.data.token
          localStorage.setItem("access_token", token)
          this.$store.dispatch("getAccountInfo")
          this.$router.push({ name: "Home" })
        })
        .catch((err) => {
          if (err.response) {
            this.isError = true
            this.errorMsg = err.response.data.message
          }
        })
    },
    validate() {
      this.$refs.form.validate()
    },
    reset() {
      this.$refs.form.reset()
    },
    resetValidation() {
      this.$refs.form.resetValidation()
    }
  }
}
</script>

๋กœ๊ทธ์ธ ํ™”๋ฉด ๊ตฌํ˜„๋„ ํšŒ์›๊ฐ€์ž… ํ™”๋ฉด๊ณผ ํฐ ์ฐจ์ด๋Š” ์—†๋‹ค.

๋‹ค๋งŒ, axios๋กœ api์™€ ์—ฐ๊ฒฐํ•˜๋Š” ๋ถ€๋ถ„์ด ๋‹ค๋ฅด๋‹ค.

login(LoginObj) {
      if (!this.formData.email || !this.formData.password) {
        this.isError = true
        this.errorMsg = "์ด๋ฉ”์ผ๊ณผ ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”."
        return
      }
      axios
        .post("/signin", LoginObj)
        .then((res) => {
          let token = res.data.token
          localStorage.setItem("access_token", token)
          this.$store.dispatch("getAccountInfo")
          this.$router.push({ name: "Home" })
        })
        .catch((err) => {
          if (err.response) {
            this.isError = true
            this.errorMsg = err.response.data.message
          }
        })
    },

์šฐ์„  "/signin" ๊ฒฝ๋กœ์— ๋กœ๊ทธ์ธ ๋ชจ๋ธ๊ณผ ํ•จ๊ป˜ post ์š”์ฒญ์„ ๋ณด๋‚ด์ค€๋‹ค.

๋ฐฑ์—”๋“œ๊ฐ€ DB์—์„œ ์ด๋ฅผ ํ™•์ธํ•˜๊ณ  ์ •๋ณด๊ฐ€ ์ผ์น˜ํ•œ๋‹ค๋ฉด ์‘๋‹ต ๊ฐ’์˜ data.token์— jwt ํ† ํฐ ๊ฐ’์„ ๋ณด๋‚ด์ค€๋‹ค.

ํ”„๋ก ํŠธ์—์„œ๋Š” localStorage.setItem ๋ฉ”์„œ๋“œ๋ฅผ ํ†ตํ•ด ์ด ํ† ํฐ ๊ฐ’์„ localStorage์— ์ €์žฅํ•œ๋‹ค.

์—ฌ๊ธฐ๊นŒ์ง€์˜ ๊ณผ์ •์€ ํ† ํฐ ๊ฐ’๋งŒ ๋ฐ›์€ ๋’ค ์ €์žฅํ•œ ๋‹จ๊ณ„์ด๊ณ  ๋กœ๊ทธ์ธ ํ•œ ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ์•„์ง ๋ถˆ๋Ÿฌ์˜ค์ง€ ์•Š์•˜๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์ €์žฅ ํ›„ vuex์˜ getAccountInfo Action์„ ํ†ตํ•ด localStorage์— ์žˆ๋Š” ํ† ํฐ ๊ฐ’์„ ๋‹ด์•„ api๋ฅผ ํ˜ธ์ถœํ•ด ์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋„ ์–ป์–ด๋‚ธ๋‹ค.

getAccountInfo({ commit }) {
      let token = localStorage.getItem("access_token")
      axios
        .get("/userinfo", {
          headers: {
            "X-AUTH-TOKEN": token
          }
        })
        .then((response) => {
          commit("loginSuccess", response.data.data)
        })
        .catch((error) => {
          console.log(error)
        })
    }

vuex์—์„œ ๊ตฌํ˜„ํ•œ getAccountInfo Action์ด๋‹ค.

this.$router.push({ name: "Home" })

์‚ฌ์šฉ์ž์˜ ์ •๋ณด๋ฅผ ๋ฐ›์€ ๋’ค ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.