반응형
<main.js>
import Vue from 'vue'
import App from './App.vue'
import { router } from './routes/index.js';
import { store } from './store/index.js'
Vue.config.productionTip = false
new Vue({
render: h => h(App),
router,
store
}).$mount('#app')
routes 폴더 <index.js>
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 bus from '../utils/bus.js'
import {store} from '../store/index.js'
// import createListView from '../views/CreateListView'
Vue.use(VueRouter);
export const router = new VueRouter({
mode: 'history', // url #값 제거
routes: [ //routes도 커졌을때 모듈화 가능
{
path: '/',// url에 대한 정보가 담기는곳, url주소
redirect: '/news',// url 주소로 갔을때 표시될 컴포넌트
},
{
path: '/news',
component: NewsView,
name: 'news',
// 하이 오더 컴포넌트
// 기존에 있던 컴포넌트 위에 컴포넌트가 하나 더 생김
// component: createListView('NewsView'),
// 특정 URL로 접근할때 인증정보가 있는지 없는지 확인할때 가장 흔하게 쓰임
beforeEnter:(to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
// #5.
next();
})
.catch((error) => {
console.log(error);
});
}
},
{
path: '/ask',
component: AskView,
name: 'ask',
// component: createListView('AskView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
// #5.
next();
})
.catch((error) => {
console.log(error);
});
}
},
{
path: '/jobs',
component: JobsView,
name: 'jobs',
// component: createListView('JobsView'),
beforeEnter: (to, from, next) => {
bus.$emit('start:spinner');
store.dispatch('FETCH_LIST', to.name)
.then(() => {
// #5.
next();
})
.catch((error) => {
console.log(error);
});
}
},
{
path: '/item/:id',
component: ItemView,
},
{
path: '/user/:id',
component: UserView,
},
]
});
api 폴더 <index.js>
import axios from 'axios';
// 1. HTTP Requset & Response 와 관련된 기본 설정
const config = {
baseUrl: 'https://api.hnpwa.com/v0/'
};
// 2. 공통 API 함수들 정리
function fetchNewsList(){
// return 바로 해준게 핵심
// return axios.get(config.baseUrl+'/news/1.json');
return axios.get(`${config.baseUrl}news/1.json`);
}
function fetchJobsList(){
return axios.get(`${config.baseUrl}jobs/1.json`)
}
function fetchAskList() {
return axios.get(`${config.baseUrl}ask/1.json`)
}
function fetchList(pageName) {
return axios.get(`${config.baseUrl}${pageName}.json`)
}
function fetchUserInfo(username){
// https://api.hnpwa.com/v0/user/32340433.json
return axios.get(`${config.baseUrl}user/${username}.json`)
}
function fetchCommentItem(id){
// https://api.hnpwa.com/v0/item/32340433.json
return axios.get(`${config.baseUrl}item/${id}.json`)
}
// 3. 마지막 내보내기
export { fetchNewsList, fetchJobsList, fetchAskList, fetchUserInfo, fetchCommentItem, fetchList }
store 폴더 <index.js>
import Vue from 'vue'
import Vuex from 'vuex'
import mutations from './mutation.js';
import actions from './action.js'
Vue.use(Vuex);
// vuex는 상태관리 도구
// 상태는 여러 컴포넌트 간에 공유되는 데이터 속성
export const store = new Vuex.Store({
state : { // 3. state에 저장한다.
// news : [],
// asks : [],
// jobs : [],
user : {},
items: {},
list: [], // list 데이터를 3개가 공유하다보니까 오동작남. 페이지 달라도 전 페이지 데이터 보여짐 잠깐
},
getters:{
fetchedNews(state) {
return state.news
},
fetchedAsk(state){
return state.asks
},
fetchedJobs(state){
return state.jobs
},
fetchedItem(state){
return state.items
}
},
mutations, // 2. mutations으로 데이터 받아
actions // 1. 백엔드 API를 actions으로 받고
})
<actions.js>
import { fetchNewsList, fetchAskList, fetchJobsList, fetchUserInfo, fetchCommentItem, fetchList } from '../api/index.js'
export default { // 1. 백엔드 API를 actions으로 받고
// FETCH_NEWS(context) {
// return fetchNewsList()
// .then(res => {
// context.commit('SET_NEWS', res.data);
// return res;
// })
// .catch(error => console.log(error));
// },
// FETCH_ASKS({ commit }) {
// return etchAskList()
// .then(({ data }) => {
// commit('SET_ASKS', data);
// })
// .catch(error => console.log(error))
// },
// FETCH_JOBS({ commit }) {
// return fetchJobsList()
// .then(({ data }) => {
// commit('SET_JBOS', 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 fetchCommentItem(id)
.then(({ data }) => {
commit('SET_ITEM', data);
})
.catch(error => console.log(error))
},
// #2.
FETCH_LIST({ commit }, pageName){
// #3.
return fetchList(pageName)
.then(res => {
// #4.
console.log(4);
commit('SET_LIST', res.data);
return res;
})
.catch(error => console.log(error))
}
}
<mutation.js>
export default{ // 2. mutations으로 데이터 받아서
// SET_NEWS(state, data) {
// state.news = data;
// },
// SET_ASKS(state, data) {
// state.asks = data;
// },
// SET_JBOS(state, data) {
// state.jobs = data;
// },
SET_USER(state, data){
state.user = data;
},
SET_ITEM(state, data){
state.items = data;
},
SET_LIST(state, data){
state.list = data;
}
}
<ListMixin.js>
import bus from '../utils/bus.js'
// mixins
export default {
// 재사용할 컴포넌트 옵션 & 로직
mounted() {
bus.$emit('end:spinner');
}
// created() {
// bus.$emit('start:spinner');
// // #1.
// this.$store.dispatch('FETCH_LIST', this.$route.name)
// .then(() => {
// // #5.
// console.log(5);
// console.log('fetched');
// bus.$emit('end:spinner');
// })
// .catch((error) => {
// console.log(error);
// });
// // setTimeout(() => {
// // }, 3000)
// }
}
<bus.js>
import Vue from 'vue'
// export 차이점
// 1. const로 했을때는
// 보내는곳에선 export const bus = new Vue();
// 받는곳에서 import {bus} from './bus.js' 이렇게 받고
// 2. default로 했을떄는
// 보내는곳에선 export default new Vue();
// 받는곳에선 import bus from './bus.js'
export default new Vue();
<ToolBar.vue>
<template>
<div class="header">
<router-link to="/news">News</router-link> |
<router-link to="/ask">Ask</router-link> |
<router-link to="/jobs">Jobs</router-link>
</div>
</template>
<script>
export default {
name: 'VueAdvancedToolbar',
data() {
return {
};
},
mounted() {
},
methods: {
},
};
</script>
<style scoped>
.header{
color: #fff;
background-color: #42b883;
display: flex;
padding: 8px;
}
.header .router-link-exact-active{
color: #354952;
}
.header a {
color: #fff;
}
</style>
<listItem.vue>
<template>
<div>
<ul class="news-list">
<li v-for="(item, i) in listItems" v-bind:key="i" 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_ago }}
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;
// const routename = this.$route.name;
// if (routename === 'news') {
// return this.$store.state.news;
// } else if (routename === 'ask') {
// return this.$store.state.asks;
// } else if (routename === 'jobs') {
// return this.$store.state.jobs;
// }
// return this.$store.state.news;
}
},
};
</script>
<style scoped>
.news-list {
margin: 0;
padding: 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>
<userProfile.vue>
<template>
<div>
<div class="user-container">
<div>
<i class="fa-solid fa-user"></i>
</div>
<div class="user-description">
<slot name="username">
<!-- 상위 컴포넌트에서 정의할 영역 -->
</slot>
<div class="time">
<slot name="time">
<!-- 상위 컴포넌트에서 정의할 영역 -->
</slot>
<slot name="karma">
<!-- 상위 컴포넌트에서 정의할 영역 -->
</slot>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
props:{
info: Object
},
};
</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>
<Spinner.vue>
<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 scoped>
.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>
<App.vue>
<template>
<div id="app">
<tool-bar></tool-bar>
<transition name="page">
<router-view/>
</transition>
<spinner :loading="lodingStatus"></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{
lodingStatus: false,
};
},
methods: {
startSpinner(){
this.lodingStatus = true;
},
endSpinner(){
this.lodingStatus = false;
}
},
created(){
bus.$on('start:spinner', this.startSpinner)
bus.$on('end:spinner', this.endSpinner)
},
beforeDestroy(){
// 이벤트버스는 이벤트객체가 계속 쌓이기전에 off 시켜야함
bus.$off('start:spinner', this.startSpinner)
bus.$off('end:spinner', this.endSpinner)
}
}
</script>
<style>
body{
padding: 0;
margin: 0;
}
a{
text-decoration: none;
color: #34495e;
}
a.router-link-exact-active{
text-decoration: underline;
}
a:hover{
cursor: pointer;
color: #42b884;
text-decoration: underline;
}
/* Router 트렌지션 */
.page-enter-active,
.page-leave-active {
transition: opacity .5s;
}
.page-enter,
.page-leave-to
/* .fade-leave-active below version 2.1.8 */
{
opacity: 0;
}
</style>
<NewsView.vue>
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/listItem.vue';
import ListMixin from '../mixins/ListMixin.js';
export default {
components:{
ListItem
},
mixins: [ListMixin]
};
</script>
<style scoped>
</style>
<AskView.vue>
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/listItem.vue';
import ListMixin from '../mixins/ListMixin.js';
export default {
components:{
ListItem
},
mixins: [ListMixin],
};
</script>
<style scoped>
</style>
<JobsView.vue>
<template>
<div>
<list-item></list-item>
</div>
</template>
<script>
import ListItem from '../components/listItem.vue';
import ListMixin from '../mixins/ListMixin.js';
export default {
components:{
ListItem
},
mixins: [ListMixin],
};
</script>
<style scoped>
</style>
<UserView.vue>
<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(){
const userName = this.$route.params.id;
this.$store.dispatch('FETCH_USER', userName);
},
};
</script>
<style lang="scss" scoped>
</style>
<ItemView.vue>
<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(){
const itemId = this.$route.params.id; // router-link로 넘긴 데이터 받을때
this.$store.dispatch('FETCH_ITEM', itemId); // actions에 보내는 dispatch
}
};
</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>
<CreateListView.js>
import ListView from './ListView.vue';
import bus from '../utils/bus.js'
export default function createListView(name){
return{
// 재사용할 인스턴스(컴포넌트) 옵션들이 들어갈 자리
name: name,
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);
});
},
render(createElement){
return createElement(ListView);
}
}
}
반응형
'vue.js > vue.js 2' 카테고리의 다른 글
68. Async & Await | 비동기 처리 패턴의 최신 문법 (0) | 2022.08.10 |
---|---|
67. 자바스크립트 비동기 처리 패턴의 발전 과정 (0) | 2022.08.10 |
65. UX를 고려한 데이터 호출 시점 (0) | 2022.08.07 |
64. <vue-news> 프로젝트 | Mixins (여러 컴포넌트 간에 공통으로 사용되고 있는 로직, 기능들을 재사용하는 방법) (0) | 2022.08.07 |
63. <vue-news> 프로젝트 | 컴포넌트의 코드마저 재사용하는 하이 오더 컴포넌트 (0) | 2022.08.07 |