속성을 사용해서
v-if / v-else if / v-else를 사용할 수 있다.
또한 router-link 속성안에서도 v-if 속성을 사용할 수 있다.
<template>
<div>
<ul class="news-list">
<li v-for="(item, index) in listItems" :key="index" class="post">
<div class="points">
{{ item.points || 0}}
</div>
<div>
<!-- 타이틀 영역 -->
<p class="news-title">
<template v-if="item.domain">
<a v-bind:href="item.url">
{{ item.title }}
</a>
</template>
<template v-else>
<router-link v-bind:to="`item/${item.id}`">
{{ item.title }}
</router-link>
</template>
</p>
<small class="link-text">
{{ item.time_age }} by
<router-link
v-if="item.user"
v-bind:to="`/user/${item.user}`" class="link-text">{{ item.user }}</router-link>
<a :href="item.url" v-else>
{{ item.domain }}
</a>
</small>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
created() {
console.log(this.$route.path === '/news');
const name = this.$route.name;
if( name === 'news' ) {
this.$store.dispatch('FETCH_NEWS');
} else if( name === 'ask' ) {
this.$store.dispatch('FETCH_ASK');
} else if( name === 'jobs' ) {
this.$store.dispatch('FETCH_JOBS');
// actionName = 'FETCH_JOBS';
}
// this.$route.dispatch.dispatch(actionName);
},
computed: {
// eslint-disable-next-line vue/return-in-computed-property
listItems() {
const name = this.$route.name;
const state = this.$store.state;
if( name === 'news' ) {
return state.news;
} else if( name === 'ask' ) {
return state.ask;
} else if( name === 'jobs' ) {
return state.jobs;
}
}
}
}
</script>
<style scoped>
.news-list {
padding: 0;
margin: 0;
}
.post {
list-style: none;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
}
.points {
width: 80px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #42b883;
}
.news-title {
margin: 0;
}
.link-text {
color: #828282;
}
</style>
디자인 끝
<template>
<div>
<user-profile></user-profile>
<!-- <p>name: {{ userInfo.id}}</p>
<p>karam: {{ userInfo.karma}}</p>
<p>created: {{ userInfo.created}}</p> -->
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue'
export default {
components: {
UserProfile,
},
computed: {
userInfo() {
return this.$store.state.user;
}
},
created() {
console.log(this.$route.params.id);
const userName = this.$route.params.id;
this.$store.dispatch('FETCH_USER', userName);
// axios.get(`https://api.hnpwa.com/v0/user/${userName}.json`);
},
}
</script>
<style>
</style>
📂 components
ItmeView.vue에서 가져옴
<template>
<div class="user-container">
<div>
<!-- 사용자 프로필 -->
<i class="fa-solid fa-user"></i>
</div>
<div class="user-description">
<!-- 사용자 정보 -->
<!-- <router-link :to="`/user/${fetchedItem.user}`">
{{ fetchedItem.user }}
</router-link>
<div class="time">
{{ fetchedItem.time_ago }}
</div> -->
</div>
</div>
</template>
<script>
export default {
}
</script>
<style>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
UserView.vue에서 FETCH_USER를 호출하고 받아온 데이터를 UserView에서 바로 호출하는 것이 아니라 컴포넌트로 저장되어 있는 UserProfile에서 Computed를 한다.
<template>
<div class="user-container">
<div>
<!-- 사용자 프로필 -->
<i class="fa-solid fa-user"></i>
</div>
<div class="user-description">
<div>
{{ userInfo.id }}
</div>
<!-- <router-link :to="`/user/${userInfo.user}`">
{{ userInfo.id }}
</router-link> -->
<div class="time">
{{ userInfo.created }}
</div>
</div>
</div>
</template>
<script>
export default {
computed: {
userInfo() {
return this.$store.state.user;
}
},
}
</script>
<style>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
props 데이터를 info로 보낸다
<template>
<div>
<user-profile :info="userInfo"></user-profile>
<!-- <p>name: {{ userInfo.id}}</p>
<p>karam: {{ userInfo.karma}}</p>
<p>created: {{ userInfo.created}}</p> -->
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue'
export default {
components: {
UserProfile,
},
computed: {
userInfo() {
return this.$store.state.user;
}
},
created() {
console.log(this.$route.params.id);
const userName = this.$route.params.id;
this.$store.dispatch('FETCH_USER', userName);
// axios.get(`https://api.hnpwa.com/v0/user/${userName}.json`);
},
}
</script>
<style>
</style>
<template>
<div class="user-container">
<div>
<!-- 사용자 프로필 -->
<i class="fa-solid fa-user"></i>
</div>
<div class="user-description">
<div>
{{ info.id }}
</div>
<!-- <router-link :to="`/user/${userInfo.user}`">
{{ userInfo.id }}
</router-link> -->
<div class="time">
{{ info.created }}
</div>
</div>
</div>
</template>
<script>
export default {
props: {
info: Object
},
}
</script>
<style>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
슬롯(slot)은 컴포넌트의 재사용성을 높여주는 기능입니다. 특정 컴포넌트에 등록된 하위 컴포넌트의 마크업을 확장하거나 재정의할 수 있습니다.
각각 프로필을 불러오는 부분에서 id를 호출하는 것이 있고 user를 호출하는 것에서 차이가 있기 때문에 slot를 사용한다.
<template>
<div class="user-container">
<div>
<!-- 사용자 프로필 -->
<i class="fa-solid fa-user"></i>
</div>
<div class="user-description">
<!-- <div>{{ info.id }}</div> -->
<slot name="username"></slot>
<!-- <router-link :to="`/user/${userInfo.user}`">
{{ userInfo.id }}
</router-link> -->
<div class="time">
<!-- time: {{ info.created }} -->
<slot name="time">
<!-- 상위 컴포넌트에서 정의할 영역 -->
</slot>
</div>
<slot name="karma"></slot>
</div>
</div>
</template>
<script>
export default {
props: {
info: Object
},
}
</script>
<style>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
<template>
<div>
<section>
<!-- 사용자 상세 정보 -->
<user-profile :info="fetchedItem">
<div slot="username">{{ fetchedItem.user }}</div>
<template slot="time">{{ fetchedItem.time_ago }}</template>
</user-profile>
</section>
<section>
<h2>{{ fetchedItem.title }}</h2>
</section>
<section>
<!-- 질문 댓글 -->
<div v-html="fetchedItem.content">
</div>
</section>
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue';
import { mapGetters } from 'vuex';
export default {
components: {
UserProfile,
},
computed: {
...mapGetters(['fetchedItem']),
},
created() {
console.log(this.$route.params.id);
const itemId = this.$route.params.id;
this.$store.dispatch('FETCH_ITEM', itemId);
}
}
</script>
<style scoped>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
<template>
<div>
<user-profile :info="userInfo">
<div slot="username">{{ userInfo.id }}</div>
<template slot="time">{{ userInfo.created }}</template>
<div slot="karma">{{ userInfo.karma}}</div>
</user-profile>
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue'
export default {
components: {
UserProfile,
},
computed: {
userInfo() {
return this.$store.state.user;
}
},
created() {
console.log(this.$route.params.id);
const userName = this.$route.params.id;
this.$store.dispatch('FETCH_USER', userName);
// axios.get(`https://api.hnpwa.com/v0/user/${userName}.json`);
},
}
</script>
<style>
</style>
<template>
<div>
<section>
<!-- 사용자 상세 정보 -->
<user-profile :info="fetchedItem">
<!-- <div slot="username">{{ fetchedItem.user }}</div> -->
<router-link slot="username" :to="`/user/${fetchedItem.user}`">
{{ fetchedItem.user }}
</router-link>
<template slot="time">{{ 'Posted ' + fetchedItem.time_ago }}</template>
</user-profile>
</section>
<section>
<h2>{{ fetchedItem.title }}</h2>
</section>
<section>
<!-- 질문 댓글 -->
<div v-html="fetchedItem.content">
</div>
</section>
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue';
import { mapGetters } from 'vuex';
export default {
components: {
UserProfile,
},
computed: {
...mapGetters(['fetchedItem']),
},
created() {
console.log(this.$route.params.id);
const itemId = this.$route.params.id;
this.$store.dispatch('FETCH_ITEM', itemId);
}
}
</script>
<style scoped>
.user-container {
display: flex;
align-items: center;
padding: 0.5rem;
}
.fa-user {
font-size: 2.5rem;
}
.user-description {
padding-left: 8px;
}
.time {
font-size: 0.7rem;
}
</style>
<template>
<div>
<user-profile :info="userInfo">
<div slot="username">{{ userInfo.id }}</div>
<span slot="time">{{ 'Joined ' + userInfo.created }}, </span>
<span slot="karma">{{ userInfo.karma}}</span>
</user-profile>
</div>
</template>
<script>
import UserProfile from '../components/UserProfile.vue'
export default {
components: {
UserProfile,
},
computed: {
userInfo() {
return this.$store.state.user;
}
},
created() {
console.log(this.$route.params.id);
const userName = this.$route.params.id;
this.$store.dispatch('FETCH_USER', userName);
// axios.get(`https://api.hnpwa.com/v0/user/${userName}.json`);
},
}
</script>
<style>
</style>
📂components
<template>
<div class="lds-facebook" v-if="loading">
<div>
</div>
<div>
</div>
<div>
</div>
</div>
</template>
<script>
export default {
props: {
loading: {
type: Boolean,
required: true,
},
},
}
</script>
<style>
.lds-facebook {
display: inline-block;
position: absolute;
width: 64px;
height: 64px;
top: 47%;
left: 47%;
}
.lds-facebook div {
display: inline-block;
position: absolute;
left: 6px;
width: 13px;
background: #42b883;
animation: lds-facebook 1.2s cubic-bezier(0, 0.5, 0.5, 1) infinite;
}
.lds-facebook div:nth-child(1) {
left: 6px;
animation-delay: -0.24s;
}
.lds-facebook div:nth-child(2) {
left: 26px;
animation-delay: -0.12s;
}
.lds-facebook div:nth-child(3) {
left: 45px;
animation-delay: 0;
}
@keyframes lds-facebook {
0% {
top: 6px;
height: 51px;
}
50%, 100% {
top: 19px;
height: 26px;
}
}
</style>
📂utils
import Vue from 'vue';
export default new Vue();
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/ListItem.vue';
import bus from '../utils/bus.js';
export default {
components: {
ListItem,
},
created() {
bus.$emit('start:spinner');
this.$store.dispatch('FETCH_NEWS');
bus.$emit('end:spinner');
}
}
</script>
Start / End spinner 메소드를 만들어 created 될 때 on 끝나고 나서 beforeDestroy를 설정해줘야한다.
<template>
<div id="app">
<tool-bar></tool-bar>
<transition name="page">
<router-view></router-view>
</transition>
<spinner :loading="loadingStatus"></spinner>
</div>
</template>
<script>
import ToolBar from './components/ToolBar.vue';
import Spinner from './components/Spinner.vue';
import bus from './utils/bus.js';
export default {
components: {
ToolBar,
Spinner,
},
data() {
return {
loadingStatus: false,
};
},
methods: {
startSpinner() {
this.loadingStatus = true;
},
endSpinner() {
this.loadingStatus = false;
}
},
created() {
bus.$on('start:spinner', this.startSpinner);
bus.$on('end:spinner', this.endSpinner);
},
beforeDestroy() {
bus.$off('start:spinner', this.startSpinner);
bus.$off('end:spinner', this.endSpinner);
}
}
</script>
<style>
#app {
padding: 0;
margin: 0;
}
a {
color: #34495e;
text-decoration: none;
}
a.router-link-exact-active {
text-decoration: underline;
}
a:hover {
color: #42b883;
text-decoration: underline;
}
/* router Transition */
.page-enter-active,
.page-leave-active {
transition: opacity 0.5s ease;
}
.page-enter-from,
.page-leave-to {
opacity: 0;
}
</style>
스피너 작동 확인을 위해 setTimeOut를 설정해준다.
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/ListItem.vue';
import bus from '../utils/bus.js';
export default {
components: {
ListItem,
},
created() {
bus.$emit('start:spinner');
setTimeout(() => {
this.$store.dispatch('FETCH_NEWS')
.then(() => {
console.log('fetched');
bus.$emit('end:spinner');
})
.catch((error) => {
console.log(error);
});
}, 3000);
}
}
</script>
컴포넌트의 로직을 재사용하기 위한 고급 기술
📂router
import Vue from 'vue'
import VueRouter from 'vue-router';
// import NewsView from '../views/NewsView.vue';
// import AskView from '../views/AskView.vue';
// import JobsView from '../views/JobsView.vue';
import ItemView from '../views/ItemView.vue';
import UserView from '../views/UserView.vue';
import createListView from '../views/CreateListView.js';
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
redirect: '/news',
},
{
// path: url 주소
path: '/news',
// component: url 주소로 갔을 때 표시될 컴포넌트
name: 'news',
//component: NewsView,
component: createListView('NewsView'),
},
{
path: '/ask',
name: 'ask',
// component: AskView,
component: createListView('AskView'),
},
{
path: '/jobs',
name: 'jobs',
// component: JobsView,
component: createListView('JobsView'),
},
{
path: '/user/:id',
component: UserView,
},
{
path: '/item/:id',
component: ItemView,
}
]
});
📂views
하이 오더 컴포넌트(news, ask, jobs의 route의 행위가 동일함)
import ListView from './ListView.vue';
import bus from '../utils/bus.js';
export default function createListView(name) {
return {
// 재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
name: name,
created() {
bus.$emit('start:spinner');
setTimeout(() => {
this.$store.dispatch('FETCH_LIST', this.$route.name)
.then(() => {
console.log('fetched');
bus.$emit('end:spinner');
})
.catch((error) => {
console.log(error);
});
}, 3000);
},
render(createElement) {
return createElement(ListView);
},
}
}
📂components
랜더용
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/ListItem.vue'
export default {
components: {
ListItem,
},
}
</script>
<style>
</style>
📂api
import axios from 'axios';
const config = {
baseUrl: 'https://api.hnpwa.com/v0/'
}
function fetchNewsList() {
// return axios.get(config.baseUrl + 'news/1.json');
return axios.get(`${config.baseUrl}news/1.json`);
}
function fetchAskList() {
return axios.get(`${config.baseUrl}ask/1.json`);
}
function fetchJobsList() {
return axios.get(`${config.baseUrl}jobs/1.json`);
}
function fetchUserInfo(username) {
return axios.get(`${config.baseUrl}user/${username}.json`);
}
function fetchCommentInfo(id) {
return axios.get(`${config.baseUrl}item/${id}.json`);
}
function fetchList(pageName) {
return axios.get(`${config.baseUrl}${pageName}/1.json`);
}
export {
fetchNewsList,
fetchAskList,
fetchJobsList,
fetchUserInfo,
fetchCommentInfo,
fetchList,
}
📂store
리스트 선언
import Vue from 'vue';
import Vuex from 'vuex';
import mutations from './mutations.js';
import actions from './actions.js';
Vue.use(Vuex);
export const store = new Vuex.Store({
state: {
news: [],
ask: [],
jobs: [],
user: {},
item: {},
list: [],
},
getters:{
fetchedAsk(state) {
return state.ask;
},
fetchedItem(state) {
return state.item;
}
},
mutations,
actions,
});
import { fetchAskList, fetchCommentInfo, fetchJobsList, fetchList, fetchNewsList, fetchUserInfo } from '../api/index.js';
export default {
FETCH_NEWS(context) {
fetchNewsList()
.then(response=> {
console.log(response.data);
context.commit('SET_NEWS', response.data);
return response;
})
.catch(error=>{
console.log(error);
})
},
FETCH_ASK(context) {
fetchAskList()
.then(response => {
context.commit('SET_ASK', response.data);
})
.catch(error=>{
console.log(error);
})
},
FETCH_JOBS({commit}) {
fetchJobsList()
.then(({ data })=>{
commit('SET_JOBS', data);
})
.catch(error=> {
console.log(error);
})
},
FETCH_USER({commit}, name) {
fetchUserInfo(name)
.then(({data}) => {
commit('SET_USER', data);
})
.catch(error => {
console.log(error);
});
},
FETCH_ITEM({commit}, id) {
fetchCommentInfo(id)
.then(({data}) =>{
commit('SET_ITEM', data);
})
.catch(error=>{
console.log(error);
});
},
FETCH_LIST({commit}, pageName) {
fetchList(pageName)
.then(({data}) => {
commit('SET_LIST', data)
})
.catch(error => {
console.log(error)
});
},
}
export default {
SET_NEWS(state, news) {
state.news = news; // state 변경 가능
},
SET_ASK(state, ask) {
state.ask = ask;
},
SET_JOBS(state, jobs) {
state.jobs = jobs;
},
SET_USER(state, user) {
state.user = user;
},
SET_ITEM(state, item) {
state.item = item;
},
SET_LIST(state, list) {
state.list = list;
},
}
<template>
<div>
<ul class="news-list">
<li v-for="(item, index) in listItems" :key="index" class="post">
<div class="points">
{{ item.points || 0}}
</div>
<div>
<!-- 타이틀 영역 -->
<p class="news-title">
<template v-if="item.domain">
<a v-bind:href="item.url">
{{ item.title }}
</a>
</template>
<template v-else>
<router-link v-bind:to="`item/${item.id}`">
{{ item.title }}
</router-link>
</template>
</p>
<small class="link-text">
{{ item.time_age }} by
<router-link
v-if="item.user"
v-bind:to="`/user/${item.user}`" class="link-text">{{ item.user }}</router-link>
<a :href="item.url" v-else>
{{ item.domain }}
</a>
</small>
</div>
</li>
</ul>
</div>
</template>
<script>
export default {
computed: {
listItems() {
return this.$store.state.list;
}
}
}
</script>
<style scoped>
.news-list {
padding: 0;
margin: 0;
}
.post {
list-style: none;
display: flex;
align-items: center;
border-bottom: 1px solid #eee;
}
.points {
width: 80px;
height: 60px;
display: flex;
align-items: center;
justify-content: center;
color: #42b883;
}
.news-title {
margin: 0;
}
.link-text {
color: #828282;
}
</style>
컴포넌트 레벨이 깊어짐..
컴포넌트 통신에 불편함이 생김
모달이나 스피너를 믹스인으로 빼는 것이 효율적이다.
📂mixins
// mixin
import bus from '../utils/bus.js'
export default {
//재사용할 컴포넌트 옵션
created() {
bus.$emit('start:spinner');
this.$store.dispatch('FETCH_LIST', this.$route.name)
.then(() => {
console.log('fetched');
bus.$emit('end:spinner');
})
.catch((error) => {
console.log(error);
});
}
}
📂router
import Vue from 'vue'
import VueRouter from 'vue-router';
import NewsView from '../views/NewsView.vue';
import AskView from '../views/AskView.vue';
import JobsView from '../views/JobsView.vue';
import ItemView from '../views/ItemView.vue';
import UserView from '../views/UserView.vue';
// import createListView from '../views/CreateListView.js';
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
redirect: '/news',
},
{
// path: url 주소
path: '/news',
// component: url 주소로 갔을 때 표시될 컴포넌트
name: 'news',
component: NewsView,
// component: createListView('NewsView'),
},
{
path: '/ask',
name: 'ask',
component: AskView,
// component: createListView('AskView'),
},
{
path: '/jobs',
name: 'jobs',
component: JobsView,
// component: createListView('JobsView'),
},
{
path: '/user/:id',
component: UserView,
},
{
path: '/item/:id',
component: ItemView,
}
]
});
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/ListItem.vue';
// import bus from '../utils/bus.js';
import ListMixin from '../mixins/ListMixin.js';
export default {
components: {
ListItem,
},
mixins: [ListMixin],
}
</script>
NewsView에서 바로 ListItem에 접근할 수 있다.
mixins가 깊어지는 단점을 보안
라우터 네비게이션 가드(먼저시작)
-> 네비게이션 가드(navigation guard)란 뷰 라우터로 특정 URL에 접근할 때 동작을 정의하는 속성
네이게이션 가드
컴포넌트 라이프 사이클 훅
-> created : 컴포넌트가 생성되자마자 호출되는 로직
라이프 사이클 훅을 이용한 데이터 호출 방법의 문제와 비동기 처리 코드 수정
비동기 처리의 순서를 정의하지 않아서 mixins가 먼저 시작되기 때문에 return을 해서 순서를 보장시켜줘야한다.
이 이유때문에 스피너가 잘 작동을 하지 않았다.
import { fetchAskList, fetchCommentInfo, fetchJobsList, fetchList, fetchNewsList, fetchUserInfo } from '../api/index.js';
export default {
FETCH_NEWS(context) {
return fetchNewsList()
.then(response=> {
console.log(response.data);
context.commit('SET_NEWS', response.data);
return response;
})
.catch(error=>{
console.log(error);
})
},
FETCH_ASK(context) {
return fetchAskList()
.then(response => {
context.commit('SET_ASK', response.data);
})
.catch(error=>{
console.log(error);
})
},
FETCH_JOBS({commit}) {
return fetchJobsList()
.then(({ data })=>{
commit('SET_JOBS', data);
})
.catch(error=> {
console.log(error);
})
},
FETCH_USER({commit}, name) {
return fetchUserInfo(name)
.then(({data}) => {
commit('SET_USER', data);
})
.catch(error => {
console.log(error);
});
},
FETCH_ITEM({commit}, id) {
return fetchCommentInfo(id)
.then(({data}) =>{
commit('SET_ITEM', data);
})
.catch(error=>{
console.log(error);
});
},
FETCH_LIST({commit}, pageName) {
return fetchList(pageName)
.then(({data}) => {
commit('SET_LIST', data)
})
.catch(error => {
console.log(error)
});
},
}
오류수정
to: 이동할 url의 라우팅 정보
from: 현재 url의 라우팅 정보
next: 함수
📂router
import Vue from 'vue'
import VueRouter from 'vue-router';
import NewsView from '../views/NewsView.vue';
import AskView from '../views/AskView.vue';
import JobsView from '../views/JobsView.vue';
import ItemView from '../views/ItemView.vue';
import UserView from '../views/UserView.vue';
// import createListView from '../views/CreateListView.js';
import bus from '../utils/bus.js'
import { store } from '../store/index.js';
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
redirect: '/news',
},
{
// path: url 주소
path: '/news',
// component: url 주소로 갔을 때 표시될 컴포넌트
name: 'news',
component: NewsView,
// component: createListView('NewsView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
console.log('fetched');
bus.$emit('end:spinner');
next();
})
.catch((error) => {
console.log(error);
});
},
},
{
path: '/ask',
name: 'ask',
component: AskView,
// component: createListView('AskView'),
},
{
path: '/jobs',
name: 'jobs',
component: JobsView,
// component: createListView('JobsView'),
},
{
path: '/user/:id',
component: UserView,
},
{
path: '/item/:id',
component: ItemView,
}
]
});
인스턴스가 작성된 후 동기적으로 호출된다.
부모, 자식 관계의 컴퍼넌트가 랜더링 될 때 mounted보다 먼저 호출되며 부모, 자식순으로 실행한다. 데이터 초기화 선언을 created에서 많이 한다. 가상돔을 건드릴 수 없음
부모, 자식 관계의 컴퍼넌트가 랜더링 될때 created 다음으로 호출되며, 자식, 부모순으로 실행된다. el이 새로 생성된 vm.$el로 대체된 인스턴스가 마운트 된 직후 호출된다. 돔조작관련을 mounted 영역에 기술한다.
📂router
import Vue from 'vue'
import VueRouter from 'vue-router';
import NewsView from '../views/NewsView.vue';
import AskView from '../views/AskView.vue';
import JobsView from '../views/JobsView.vue';
import ItemView from '../views/ItemView.vue';
import UserView from '../views/UserView.vue';
// import createListView from '../views/CreateListView.js';
import bus from '../utils/bus.js'
import { store } from '../store/index.js';
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'history',
routes: [
{
path: '/',
redirect: '/news',
},
{
// path: url 주소
path: '/news',
// component: url 주소로 갔을 때 표시될 컴포넌트
name: 'news',
component: NewsView,
// component: createListView('NewsView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
console.log('fetched');
next();
})
.catch((error) => {
console.log(error);
});
},
},
{
path: '/ask',
name: 'ask',
component: AskView,
// component: createListView('AskView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
console.log('fetched');
next();
})
.catch((error) => {
console.log(error);
});
},
},
{
path: '/jobs',
name: 'jobs',
component: JobsView,
// component: createListView('JobsView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
console.log('fetched');
next();
})
.catch((error) => {
console.log(error);
});
},
},
{
path: '/user/:id',
component: UserView,
},
{
path: '/item/:id',
component: ItemView,
}
]
});
// mixin
import bus from '../utils/bus.js'
export default {
//재사용할 컴포넌트 옵션
mounted() {
bus.$emit('end:spinner');
},
}
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/ListItem.vue';
// import bus from '../utils/bus.js';
import ListMixin from '../mixins/ListMixin.js';
export default {
components: {
ListItem,
},
mixins: [ListMixin],
}
</script>
자바스크립트 비동기 처리 패턴의 최신 문법.
Promise, callback에서 주는 단점을 해결
자바스크립트의 비동기적 사고 방식에서 벗어나 동기적으로 코드 작성 가능
promise로 반환된 로직을 await로 호출한다.
async-await 프로젝트 생성
npm create async-await
npm cd async-await
npm install axios
npm run serve
<template>
<div>
<button @click="loginUser1">login</button>
<h1>List</h1>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
items: [],
}
},
methods: {
loginUser() {
axios.get('http://jsonplaceholder.typicode.com/users/1')
.then(response => {
if(response.data.id === 1) {
console.log('사용자가 인증되었습니다.');
axios.get('http://jsonplaceholder.typicode.com/todos')
.then(response => {
this.items = response.data;
})
.catch(error => console.log(error))
}
})
.catch(error => console.log(error));
},
async loginUser1() {
var response = await axios.get('http://jsonplaceholder.typicode.com/users/1'); // 첫번째 비동기
if (response.data.id === 1) {
console.log('사용자가 인증되었습니다.');
var list = await axios.get('http://jsonplaceholder.typicode.com/todos'); // 두번째 비동기
this.items = list.data;
}
}
},
}
</script>
<style>
</style>
promise를 사용할 때는 .then() / .catch()
async await 사용할 때는 try/catch
에러들에 처리를 빨리 낼 수 있다.
📂utils
export function handleException() {
...
}
<template>
<div>
<button @click="loginUser1">login</button>
<h1>List</h1>
<ul>
<li v-for="(item, index) in items" :key="index">{{ item }}</li>
</ul>
</div>
</template>
<script>
import axios from 'axios';
import { handleException } from './utils/handler.js';
export default {
data() {
return {
items: [],
}
},
methods: {
loginUser() {
axios.get('http://jsonplaceholder.typicode.com/users/1')
.then(response => {
if(response.data.id === 1) {
console.log('사용자가 인증되었습니다.');
axios.get('http://jsonplaceholder.typicode.com/todos')
.then(response => {
this.items = response.data;
})
.catch(error => console.log(error))
}
})
.catch(error => console.log(error));
},
async loginUser1() {
try {
var response = await axios.get('http://jsonplaceholder.typicode.com/users/1'); // 첫번째 비동기
if (response.data.id === 1) {
console.log('사용자가 인증되었습니다.');
var list = await axios.get('http://jsonplaceholder.typicode.com/todos'); // 두번째 비동기
this.items = list.data;
}
} catch (error) {
handleException(error);
console.log(error);
}
}
},
}
</script>
<style>
</style>
FETCH_NEWS에 적용하기
import { fetchAskList, fetchCommentInfo, fetchJobsList, fetchList, fetchNewsList, fetchUserInfo } from '../api/index.js';
export default {
// promise
// FETCH_NEWS(context) {
// return fetchNewsList()
// .then(response=> {
// console.log(response.data);
// context.commit('SET_NEWS', response.data);
// return response;
// })
// .catch(error=>{
// console.log(error);
// })
// },
// async
async FETCH_NEWS(context) {
var response = await fetchNewsList();
context.commit('SET_NEWS', response.data);
return response;
},
FETCH_ASK(context) {
return fetchAskList()
.then(response => {
context.commit('SET_ASK', response.data);
})
.catch(error=>{
console.log(error);
})
},
FETCH_JOBS({commit}) {
return fetchJobsList()
.then(({ data })=>{
commit('SET_JOBS', data);
})
.catch(error=> {
console.log(error);
})
},
FETCH_USER({commit}, name) {
return fetchUserInfo(name)
.then(({data}) => {
commit('SET_USER', data);
})
.catch(error => {
console.log(error);
});
},
FETCH_ITEM({commit}, id) {
return fetchCommentInfo(id)
.then(({data}) =>{
commit('SET_ITEM', data);
})
.catch(error=>{
console.log(error);
});
},
FETCH_LIST({commit}, pageName) {
return fetchList(pageName)
.then(({data}) => {
commit('SET_LIST', data)
})
.catch(error => {
console.log(error)
});
},
}
import { fetchAskList, fetchCommentInfo, fetchJobsList, fetchList, fetchNewsList, fetchUserInfo } from '../api/index.js';
export default {
// promise
// FETCH_NEWS(context) {
// return fetchNewsList()
// .then(response=> {
// console.log(response.data);
// context.commit('SET_NEWS', response.data);
// return response;
// })
// .catch(error=>{
// console.log(error);
// })
// },
// async
async FETCH_NEWS(context) {
var response = await fetchNewsList();
context.commit('SET_NEWS', response.data);
return response;
},
async FETCH_ASK({commit}) {
var response = await fetchAskList();
commit('SET_ASK', response.data);
return response;
},
async FETCH_JOBS({commit}) {
try {
const response = await fetchJobsList();
commit('SET_JOBS', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_USER({commit}, name) {
try {
const response = await fetchUserInfo(name);
commit('SET_USER', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_ITEM({commit}, id) {
try {
const response = await fetchCommentInfo(id);
commit('SET_ITEM', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_LIST({commit}, pageName) {
try {
const response = await fetchList(pageName);
commit('SET_LIST', response.data);
return response;
} catch (error) {
console.log(error);
}
},
}import { fetchAskList, fetchCommentInfo, fetchJobsList, fetchList, fetchNewsList, fetchUserInfo } from '../api/index.js';
export default {
// promise
// FETCH_NEWS(context) {
// return fetchNewsList()
// .then(response=> {
// console.log(response.data);
// context.commit('SET_NEWS', response.data);
// return response;
// })
// .catch(error=>{
// console.log(error);
// })
// },
// async
async FETCH_NEWS(context) {
var response = await fetchNewsList();
context.commit('SET_NEWS', response.data);
return response;
},
async FETCH_ASK({commit}) {
var response = await fetchAskList();
commit('SET_ASK', response.data);
return response;
},
async FETCH_JOBS({commit}) {
try {
const response = await fetchJobsList();
commit('SET_JOBS', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_USER({commit}, name) {
try {
const response = await fetchUserInfo(name);
commit('SET_USER', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_ITEM({commit}, id) {
try {
const response = await fetchCommentInfo(id);
commit('SET_ITEM', response.data);
return response;
} catch (error) {
console.log(error);
}
},
async FETCH_LIST({commit}, pageName) {
try {
const response = await fetchList(pageName);
commit('SET_LIST', response.data);
return response;
} catch (error) {
console.log(error);
}
},
}
actions에서 에러를 처리할 수도 있지만, api단에서 처리할 수도 있다
📂api
import axios from 'axios';
const config = {
baseUrl: 'https://api.hnpwa.com/v0/'
}
function fetchNewsList() {
// return axios.get(config.baseUrl + 'news/1.json');
return axios.get(`${config.baseUrl}news/1.json`);
}
async function fetchAskList() {
try {
const response = await axios.get(`${config.baseUrl}ask/1.json`);
return response;
} catch (error) {
console.log(error);
}
}
function fetchJobsList() {
return axios.get(`${config.baseUrl}jobs/1.json`);
}
function fetchUserInfo(username) {
return axios.get(`${config.baseUrl}user/${username}.json`);
}
function fetchCommentInfo(id) {
return axios.get(`${config.baseUrl}item/${id}.json`);
}
async function fetchList(pageName) {
try {
var response = await axios.get(`${config.baseUrl}${pageName}/1.json`);
return response;
} catch (error) {
console.log(error);
}
}
export {
fetchNewsList,
fetchAskList,
fetchJobsList,
fetchUserInfo,
fetchCommentInfo,
fetchList,
}