$ npm i -g @vue/cli
$ vue create {project_name}
module.exports = {
devServer: {
overlay: false
}
}
module.exports = {
...
rules: {
...
"prettier/prettier": ['error', {
singleQuote: true,
semi: true,
useTabs: true,
tabWidth: 2,
trailingComma: 'all',
printWidth: 80,
bracketSpacing: true,
arrowParens: 'avoid',
}]
},
...
}
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"~/*": [
"./*"
],
"@/*": [
"./src/*"
],
}
},
"exclude": [
"node_modules",
"dist"
]
}
import Vue from 'vue';
import VueRouter from 'vue-router';
// 플러그인을 실행하기 위해서 필요한 코드
Vue.use(VueRouter);
// VueRouter로 인스턴스를 생성하고 export default 로 꺼냄.
export default new VueRouter();
...
import router from '@/router/index';
...
new Vue({
...
router,
}).$mount('#app');
...
import LoginPage from '@/view/LoginPage.vue';
import SignupPage from '@/view/SignupPage.vue';
...
export default new VueRouter({
// routes: Vue Router에 의해서 컨트롤되는 페이지의 정보를 담는 것
routes: [
{
path: '/login',
component: LoginPage,
},
{
path: '/signup',
component: SignupPage,
},
]
});
...
routes: [
{
path: '/login',
component: () => import ('@/views/LoginPage.vue'),
},
{
path: '/signup',
component: () => import ('@/views/SignupPage.vue'),
},
]
...
...
routes: [
{
path: '/',
redirect: '/login',
},
{
path: '/login',
component: () => import ('@/views/LoginPage.vue'),
},
{
path: '/signup',
component: () => import ('@/views/SignupPage.vue'),
},
]
...
...
routes: [
...
{
path: '*',
component: () => import ('@/views/NotFoundPage.vue'),
},
]
...
...
export default new VueRouter({
mode: 'history',
routes: [
...
]
});
$ npm i axios
<script>
import axios from 'axios';
export default {
...
methods: {
submitForm() {
console.log('폼 제출');
axios.post();
}
},
}
</script>
import axios from 'axios';
function registerUser(userData) {
const url = 'http://localhost:3000/signup';
return axios.post(url, userData);
}
export { registerUser };
<script>
import { registerUser } from '@/api/index';
export default {
...
methods: {
submitForm() {
console.log('폼 제출');
const userData = {
username: this.username,
password: this.password,
nickname: this.nickname
}
registerUser(userData);
}
},
}
</script>
<script>
import { registerUser } from '@/api/index';
export default {
...
methods: {
async submitForm() {
console.log('폼 제출');
const userData = {
username: this.username,
password: this.password,
nickname: this.nickname
}
const { data } = await registerUser(userData);
console.log(data.username);
this.initForm();
},
initForm() {
this.username = '';
this.password = '';
this.nickname = '';
}
},
}
</script>
import axios from 'axios';
const axiosService = axios.create({
baseURL: 'http://localhost:3000/',
});
function registerUser(userData) {
return axiosService.post('signup', userData);
}
export { registerUser };
VUE_APP_API_URL=http://localhost:3030/
import axios from 'axios';
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
function registerUser(userData) {
return axiosService.post('signup', userData);
}
export { registerUser };
'use strict'
const merge = require('webpack-merge')
const prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"',
VUE_APP_API_URL:'"http://localhost:3030/"',
})
<script>
import { registerUser } from '@/api/index';
export default {
...
methods: {
async submitForm() {
try {
// 비즈니스 로직
const userData = {
username: this.username,
password: this.password,
nickname: this.nickname
}
const { data } = await registerUser(userData);
this.logMessage = `${data.user.username}님 회원 가입이 완료되었습니다.`;
} catch (error) {
// 에러 핸들링할 코드
console.log(error.response);
this.logMessage = error.response.data;
} finally {
this.initForm();
}
},
...
},
}
</script>
function validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
export { validateEmail };
<template>
<form>
...
<button :disabled="!isUsernameValid || !password" type="submit">회원가입</button>
</form>
</template>
<script>
...
import { validateEmail } from '@utils/validation';
export default {
...
computed: {
isUsernameValid() {
return validateEmail(this.username);
}
},
...
}
</script>
$ npm i vuex
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
username: '',
},
mutations: {
setUsername(state, username) {
state.username = username;
}
}
});
...
import store from '@/store/index';
new Vue({
...
store,
}).$mount('#app');
<script>
...
export default {
...
methods: {
...
async submitForm() {
const userData = {
username: this.username,
password: this.password,
}
const { data } = await loginUser(userData);
this.$store.commit('setUsername', data.user.username);
this.$router.push('/main');
...
},
...
},
}
</script>
<template>
...
<span>{{ $store.state.username }}</span>
</template>
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
username: '',
},
getters: {
isLogin(state) {
return state.username !== '';
},
},
mutations: {
setUsername(state, username) {
state.username = username;
}
}
});
<template>
...
<template v-if="$store.getters.isLogin">
<span>{{ $store.state.username }}</span>
</template>
<template>
<router-link to="/login">로그인</router-link>
</template>
</template>
<template>
...
<template v-if="isUserLogin">
<span>{{ $store.state.username }}</span>
</template>
<template>
<router-link to="/login">로그인</router-link>
</template>
</template>
<script>
export default {
computed: {
isUserLogin() {
return this.$store.getters.isLogin;
}
}
}
</script>
import axios from 'axios';
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
headers: {
Authorization: 'token_test',
}
});
function registerUser(userData) {
return axiosService.post('signup', userData);
}
...
export { registerUser };
...
export default new Vuex.Store({
state: {
...
token: '',
},
...
mutations: {
...
setToken(state, token) {
state.token = token;
}
}
});
...
<script>
...
methods: {
async submitForm() {
try {
...
const { data } = await loginUser(userData);
this.$store.commit('setToken', data.token);
...
} catch (error) {
...
} finally {
...
}
}
}
</script>
import axios from 'axios';
import store from '@/store/index';
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
headers: {
Authorization: store.state.token,
}
});
function registerUser(userData) {
return axiosService.post('signup', userData);
}
...
export { registerUser };
import axios from 'axios';
import store from '@/store/index';
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
headers: {
Authorization: store.state.token,
}
});
function loginUser(userData) {
return axiosService.post('login', userData);
}
...
export { loginUser };
// Add a request interceptor
axios.interceptors.request.use(function (config) {
// 요청을 보내기 전에 어떤 처리를 할 수 있다.
return config;
}, function (error) {
// 요청이 잘못되었을 때 에러가 컴포넌트 단으로 오기 전에 어떤 처리를 할 수 있다.
return Promise.reject(error);
});
// Add a response interceptor
axios.interceptors.response.use(function (response) {
// 서버에 요청을 보내고 나서 응답을 받기 전에 어떤 처리를 할 수 있다.
return response;
}, function (error) {
// 응답이 에러인 경우에 미리 전처리할 수 있다.
return Promise.reject(error);
});
export function setInterceptors(axiosService) {
axiosService.interceptors.request.use(function (config) {
// 요청을 보내기 전에 어떤 처리를 할 수 있다.
return config;
}, function (error) {
// 요청이 잘못되었을 때 에러가 컴포넌트 단으로 오기 전에 어떤 처리를 할 수 있다.
return Promise.reject(error);
});
axiosService.interceptors.response.use(function (response) {
// 서버에 요청을 보내고 나서 응답을 받기 전에 어떤 처리를 할 수 있다.
return response;
}, function (error) {
// 응답이 에러인 경우에 미리 전처리할 수 있다.
return Promise.reject(error);
});
return axiosService;
}
import axios from 'axios';
import store from '@/store/index';
import { setInterceptors } from './common/interceptors';
function createAxiosService() {
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
headers: {
Authorization: store.state.token,
}
});
return setInterceptors(axiosService);
}
const axiosService = createAxiosService();
function loginUser(userData) {
return axiosService.post('login', userData);
}
...
export { loginUser };
인터셉터를 사용하는 것은 필수는 아니다. api/index.js에서 Axios 인스턴스를 만들 때 Authorization에 token 값을 설정해줘도 된다.
import axios from 'axios';
import { setInterceptors } from './common/interceptors';
function createAxiosService() {
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
return setInterceptors(axiosService);
}
const axiosService = createAxiosService();
function loginUser(userData) {
return axiosService.post('login', userData);
}
...
export { loginUser };
import store from '@/store/index';
export function setInterceptors(axiosService) {
axiosService.interceptors.request.use(
function (config) {
config.headers.Authorization = store.state.token;
return config;
},
function (error) {
return Promise.reject(error);
}
);
axiosService.interceptors.response.use(
function (response) {
return response;
},
function (error) {
return Promise.reject(error);
}
);
return axiosService;
}
function saveAuthToCookie(value) {
document.cookie = `auth=${value}`;
}
function saveUserToCookie(value) {
document.cookie = `user=${value}`;
}
function getAuthFromCookie() {
return document.cookie.replace(
/(?:(?:^|.*;\s*)auth\s*=\s*([^;]*).*$)|^.*$/,
'$1',
);
}
function getUserFromCookie() {
return document.cookie.replace(
/(?:(?:^|.*;\s*)user\s*=\s*([^;]*).*$)|^.*$/,
'$1',
);
}
function deleteCookie(value) {
document.cookie = `${value}=; expires=Thu, 01 Jan 1970 00:00:01 GMT;`;
}
export {
saveAuthToCookie,
saveUserToCookie,
getAuthFromCookie,
getUserFromCookie,
deleteCookie,
};
<script>
...
import { saveAuthToCookie, saveUserToCookie } from '@/utils/cookies';
export default {
...
methods: {
async submitForm() {
try {
...
const { data } = await loginUser(userData);
this.$store.commit('setToken', data.token);
this.$store.commit('setUsername', data.user.username);
saveAuthToCookie(data.token);
saveUserToCookie(data.user.username);
this.$router.push('/main');
} catch (error) {
...
} finally {
...
}
}
}
}
</script>
...
import { getAuthFromCookie, getUserFromCookie } from '@/utils/cookies';
...
export default new Vuex.Store({
state: {
username: getUserFromCookie() || '',
token: getAuthFromCookie() || '',
},
getters: {
...
},
mutations: {
...
},
});
import { saveAuthToCookie, saveUserToCookie } from '@/utils/cookies';
import { loginUser } from '@/api/index';
...
actions: {
async LOGIN({ commit }, userData) {
const { data } = await loginUser(userData); // api 호출
commit('setToken', data.token);
commit('setUsername', data.user.username);
saveAuthToCookie(data.token);
saveUserToCookie(data.user.username);
return data;
}
}
<script>
...
export default {
...
methods: {
async submitForm() {
try {
...
// const { data } = await loginUser(userData);
// this.$store.commit('setToken', data.token);
// this.$store.commit('setUsername', data.user.username);
// saveAuthToCookie(data.token);
// saveUserToCookie(data.user.username);
await this.$store.dispatch('LOGIN', userData);
this.$router.push('/main');
} catch (error) {
...
} finally {
...
}
}
}
}
</script>
import axios from 'axios';
import { setInterceptors } from './common/interceptors';
function createAxiosService() {
const axiosService = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
return setInterceptors(axiosService);
}
const axiosService = createAxiosService();
function registerUser(userData) {
return axiosService.post('signup', userData);
}
function loginUser(userData) {
return axiosService.post('login', userData);
}
function fetchPosts() {
return instance.get('posts');
}
...
export { registerUser, loginUser, fetchPosts, ... };
import axios from 'axios';
import { setInterceptors } from './common/interceptors';
function createAxiosService() {
return axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
}
function createAxiosServiceWithAuth(url) {
const axiosService = axios.create({
baseURL: `${process.env.VUE_APP_API_URL}/${url}`,
});
return setInterceptors(axiosService);
}
const axiosService = createAxiosService();
const posts = createAxiosServiceWithAuth('posts');
function registerUser(userData) {
return axiosService.post('signup', userData);
}
...
export { registerUser, loginUser, fetchPosts, ... };
import { axiosService } from './index';
function registerUser(userData) {
return axiosService.post('signup', userData);
}
function loginUser(userData) {
return axiosService.post('login', userData);
}
export { registerUser, loginUser };
import { posts } from './index';
function fetchPosts() {
return posts.get('/');
}
function createPosts(postData) {
return posts.post('/', postData);
}
export { fetchPosts, createPosts };
import axios from 'axios';
import { setInterceptors } from './common/interceptors';
function createAxiosService() {
return axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
}
function createAxiosServiceWithAuth(url) {
const axiosService = axios.create({
baseURL: `${process.env.VUE_APP_API_URL}/${url}`,
});
return setInterceptors(axiosService);
}
export const axiosService = createAxiosService();
export const posts = createAxiosServiceWithAuth('posts');
<script>
...
filters: {
capitalize() {
if (!value) return '';
value = value.toString();
return value.charAt(0).toUpperCase() + value.slice(1);
}
}
</script>
<template>
...
<span>{{ postItem.createDT | formatDate }}</span>
...
</template>
<script>
...
filters: {
formatDate(value) {
return new Date(value);
}
}
...
</script>
export function formatDate(value) {
const date = new Date(value);
const year = date.getFullYear();
let month = date.getMonth() + 1;
month = month > 9 ? month : `0${month}`;
const day = date.getDate();
let hours = date.getHours();
hours = hours > 9 ? hours : `0${hours}`;
const minutes = date.getMinutes();
return `${year}-${month}-${day} ${hours}:${minutes}`;
}
...
import { formatDate } from '@/utils/filters';
Vue.filter('formatDate', formatDate);
...
...
{
path: '/post/:id',
component: () => import('@/views/PostEditPage.vue');
}
...
...
methods: {
...
routeEditPage() {
const id = this.postItem.id;
this.$router.push(`/post/${id}`);
}
}
...
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const router = new VueRouter({
routes: [
{
path: '/',
redirect: '/login',
},
{
path: '/login',
component: () => import('@/views/LoginPage.vue'),
},
{
path: '/signup',
component: () => import('@/views/SignupPage.vue'),
},
{
path: '/main',
component: () => import('@/views/MainPage.vue'),
meta: { auth: true },
},
{
path: '/add',
component: () => import('@/views/PostAddPage.vue'),
meta: { auth: true },
},
{
path: '/post/:id',
component: () => import('@/views/PostEditPage.vue'),
meta: { auth: true },
},
{
path: '*',
component: () => import('@/views/NotFoundPage.vue'),
},
],
});
router.beforeEach((to, from, next) => {
if (to.meta.auth && !store.getters.isLogin) {
console.log('인증이 필요합니다');
next('/login');
return;
}
next();
});
export default router;
module.exports = {
preset: '@vue/cli-plugin-unit-jest',
testMatch: [
'<rootDir>/src/**/*.spec.(js|js|ts|tsx)|**/__tests__/*.(js|js|ts|tsx)';
],
}
{
"env": {
"jest": true
},
}
export function sum(a, b) {
return a + b;
}
import { sum } from './math';
describe('math.js', () => {
test('10 + 20 = 30', () => {
const result = sum(10, 20);
expect(result).toBe(30);
});
});
import Vue from 'vue';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('컴포넌트가 마운팅되면 username이 존재하고 초기값으로 설정되어 있어야 한다.', () => {
const instance = new Vue(LoginForm).$mount();
expect(instance.username).toBe('');
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('컴포넌트가 마운팅되면 username이 존재하고 초기값으로 설정되어 있어야 한다.', () => {
const wrapper = shallowMount(LoginForm);
expect(wrapper.vm.username).toBe('');
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('ID는 이메일 형식이어야 한다.', () => {
const wrapper = shallowMount(LoginForm);
const idInput = wrapper.find('#username');
...
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('ID는 이메일 형식이어야 한다.', () => {
const wrapper = shallowMount(LoginForm, {
data() {
return {
username: 'test',
}
}
});
const idInput = wrapper.find('#username');
console.log('인풋 박스의 값', idInput.element.value);
console.log(wrapper.vm.isUsernameValid);
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('ID가 이메일 형식이 아니면 경고 메세지가 출력된다.', () => {
const wrapper = shallowMount(LoginForm, {
data() {
return {
username: 'test',
}
}
});
const warningText = wrapper.find('.warning');
expect(warningText.exists()).toBeTruthy();
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
test('ID가 이메일 형식이 아니면 경고 메세지가 출력된다.', () => {
const wrapper = shallowMount(LoginForm, {
data() {
return {
username: 'test',
}
}
});
const warningText = wrapper.find('.warning');
expect(warningText.exists()).toBeTruthy();
});
test('ID와 PW가 입력되지 않으면 로그인 버튼이 비활성화 된다.', () => {
const wrapper = shallowMount(LoginForm, {
data() {
return {
username: '',
password: '',
}
}
});
const button = wrapper.find('button');
expect(button.element.disabled).toBeTruthy();
});
});
import { shallowMount } from '@vue/test-utils';
import LoginForm from './LoginForm.vue';
describe('LoginForm.vue', () => {
...
test('ID와 PW가 입력되지 않으면 로그인 버튼이 비활성화 된다.', () => {
const wrapper = shallowMount(LoginForm, {
data() {
return {
username: 'a@a.com',
password: '1234',
}
}
});
const button = wrapper.find('button');
expect(button.element.disabled).toBeTruthy(); // 테스트 실패!
});
});