[ํ๋ก์ ํธ ๊ธฐ๋ณธ ์ธํ ] ํ์ ๊ฐ์ , ๋ก๊ทธ์ธ ๊ตฌํ (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" })
์ฌ์ฉ์์ ์ ๋ณด๋ฅผ ๋ฐ์ ๋ค ๋ฉ์ธ ํ์ด์ง๋ก ์ด๋ํ๋ค.