๐ฏ ํ์ต ๋ชฉํ
- ๊ธฐ์กด Vuex store๋ฅผ
auth
,theme
๋ชจ๋๋ก ๋๋๋ค - ๊ฐ ๋ชจ๋์์ state, mutations, actions, getters๋ฅผ ๋ ๋ฆฝ์ ์ผ๋ก ๊ด๋ฆฌํ๋ค
- ๋ชจ๋ ๊ตฌ์กฐ๋ฅผ ํตํด ํ์ฅ์ฑ๊ณผ ์ ์ง๋ณด์์ฑ์ ๋์ธ๋ค
โ 1๋จ๊ณ: ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ ๋ง๋ค๊ธฐ
src/
โโโ store/
โโโ index.js โ ๋ฃจํธ ์คํ ์ด
โโโ modules/
โ โโโ auth.js โ ๋ก๊ทธ์ธ/์ฌ์ฉ์ ๊ด๋ จ ์ํ
โ โโโ theme.js โ ๋คํฌ๋ชจ๋ ๊ด๋ จ ์ํ
โ
2๋จ๊ณ: auth.js
๋ชจ๋ ์์ฑ
// store/modules/auth.js
import { isTokenExpired } from '@/utils/jwt';
const state = {
isLoggedIn: false,
username: localStorage.getItem('username') || '',
};
const mutations = {
login(state, token) {
localStorage.setItem('token', token);
state.isLoggedIn = true;
},
setUsername(state, name) {
state.username = name;
localStorage.setItem('username', name);
},
logout(state) {
localStorage.removeItem('token');
localStorage.removeItem('username');
state.isLoggedIn = false;
state.username = '';
},
syncLoginState(state) {
const token = localStorage.getItem('token');
const name = localStorage.getItem('username');
if (!token || isTokenExpired(token)) {
localStorage.removeItem('token');
localStorage.removeItem('username');
state.isLoggedIn = false;
state.username = '';
} else {
state.isLoggedIn = true;
state.username = name || '';
}
},
};
export default {
namespaced: true,
state,
mutations,
};
โ
3๋จ๊ณ: theme.js
๋ชจ๋ ์์ฑ
// store/modules/theme.js
const state = {
isDark: localStorage.getItem('theme') === 'dark',
};
const mutations = {
toggleDark(state) {
state.isDark = !state.isDark;
localStorage.setItem('theme', state.isDark ? 'dark' : 'light');
},
};
export default {
namespaced: true,
state,
mutations,
};
โ
4๋จ๊ณ: ๋ฃจํธ ์คํ ์ด index.js
์์ ๋ชจ๋ ๋ฑ๋ก
// store/index.js
import Vue from 'vue';
import Vuex from 'vuex';
import auth from './modules/auth';
import theme from './modules/theme';
Vue.use(Vuex);
export default new Vuex.Store({
modules: {
auth,
theme,
},
});
โ 5๋จ๊ณ: ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ๋ ๋ฐฉ์ ๋ณ๊ฒฝ
๋ชจ๋์ namespaced: true
๋ก ๋ฑ๋กํ๊ธฐ ๋๋ฌธ์ ์ ๊ทผ ๋ฐฉ์์ด ๋ฐ๋๋๋ค.
App.vue
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState('auth', ['isLoggedIn', 'username']),
...mapState('theme', ['isDark']),
},
created() {
this.syncLoginState();
},
methods: {
...mapMutations('auth', ['logout', 'syncLoginState']),
...mapMutations('theme', ['toggleDark']),
handleLogout() {
this.logout();
this.$router.push('/login');
},
},
};
๐ ๋ชจ๋ ์ด๋ฆ ('auth', 'theme')์ ์ฒซ ๋ฒ์งธ ์ธ์๋ก ๋ฐ๋์ ๋ฃ์ด์ผ ํฉ๋๋ค!
๐ง 1. data()
์ ๊ฑฐ
data() {
return {}; // ๋๋ ์๋ต
},
๐ง 2. watch: $route()
์ ๊ฑฐ
Vuex๊ฐ ์ ์ญ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ฏ๋ก route ๋ณ๊ฒฝ ์ ๋ณ๋๋ก localStorage์์ ์ฝ์ด์ฌ ํ์ ์์.
๐ 3. ์ต์ข ๋ฆฌํฉํ ๋ง๋ ์คํฌ๋ฆฝํธ ๋ถ๋ถ
<script>
import { mapState, mapMutations } from 'vuex';
export default {
computed: {
...mapState('auth', ['isLoggedIn', 'username']),
...mapState('theme', ['isDark']),
},
created() {
this.syncLoginState(); // ์๋ก๊ณ ์นจ ์ ๋ก๊ทธ์ธ ์ํ ๋ณต์
},
methods: {
...mapMutations('auth', ['logout', 'syncLoginState']),
...mapMutations('theme', ['toggleDark']),
handleLogout() {
this.logout();
this.$router.push('/login');
},
},
};
</script>
โ 6๋จ๊ณ: Login.vue๋ ์์
this.$store.commit('auth/login', res.data.token);
this.$store.commit('auth/setUsername', res.data.username);
โ
7๋จ๊ณ: router.js
์์
import store from './store';
// ๊ธฐ์กด๊ณผ ๋์ผ
router.beforeEach((to, from, next) => {
store.commit('auth/syncLoginState');
const isAuthRequired = to.matched.some((record) => record.meta.requiresAuth);
const isLoggedIn = store.state.auth.isLoggedIn;
if (isAuthRequired && !isLoggedIn) {
if (to.path !== '/login') {
next('/login');
} else {
next();
}
} else if ((to.path === '/login' || to.path === '/register') && isLoggedIn) {
if (from.path !== '/todos') {
next('/todos');
} else {
next(false); // ํ์ฌ ํ์ด์ง์์ stay
}
} else {
next(); // ์ ์ ๋ผ์ฐํ
}
});
router.beforeEach
์์ ์ ํํ ์กฐ๊ฑด ๋ถ๊ธฐ +next()
๋ฅผ ์ค๋ณต ํธ์ถํ์ง ์๋๋ก ์กฐ์
๐ฆ ์์ฝ
์ด ๊ฐ์์์๋ Vuex ์คํ ์ด๋ฅผ auth, theme ๋ชจ๋๋ก ๋๋์ด ์ ์ญ ์ํ๋ฅผ ์ญํ ๋ณ๋ก ๋ถ๋ฆฌํ๋ค. ๋ชจ๋ํ๋ฅผ ํตํด ๋ ๊ตฌ์กฐ์ ์ด๊ณ ์ ์ง๋ณด์์ ๊ฐํ ์ํ ๊ด๋ฆฌ ์ฒด๊ณ๋ฅผ ๊ตฌ์ถํ๋ค.
FE ์ ์ฅ์
๐ฌ ๋๊ธ
โป ๋ก๊ทธ์ธ ํ ๋๊ธ์ ์์ฑํ ์ ์์ต๋๋ค.