본문 바로가기
Dev/Vue.js

[Vue.js] Hacker News - 컴포넌트 공통화

by dev_jsk 2020. 9. 23.
728x90
반응형

컴포넌트 공통화 리팩토링

페이지에서 공통적으로 사용되는 부분들을 컴포넌트로 만드는 작업. 하나의 피드를 컴포넌트로 만들어 컴포넌트를 반복적으로 뿌리는 것이 더 효율적이다.

리스트 아이템 컴포넌트 공통화

아이템 리스트를 뿌려줄 컴포넌트를 공통화를 하여 NewsView, AskView, JobsView에 공통적으로 작성된 코드를 정리할 수 있다.

 

구현하기

1. src/components/ListItem.vue 생성

<template>
  <div>
    <ul class="news-list">
      <li v-for="item in listItems" class="post">
        <!-- 포인트 영역 -->
        <div class="points">{{ item.points || 0 }}</div>
        <!-- 기타 정보 영역 -->
        <div>
          <!-- 타이틀 영역 -->
          <p class="news-title">
            <!-- v-if v-else 사용 -->
            <!-- item.domain이 있으면 NewsView, JobsView -->
            <template v-if="item.domain">
              <a v-bind:href="item.url">{{ item.title }}</a>
            </template>
            <!-- item.domain이 없으면 AskView -->
            <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
            <!-- item.user가 있으면 유저 상세페이지 이동 -->
            <router-link
              v-if="item.user"
              v-bind:to="`/user/${item.user}`"
              class="link-text"
            >{{ item.user }}</router-link>
            <!-- item.user가 없으면 item.url로 이동 -->
            <a v-bind:href="item.url" v-else>{{ item.domain }}</a>
          </small>
        </div>
      </li>
    </ul>
  </div>
</template>

<script>
export default {
  created() {
    // router name에 따라 가져올 데이터 분기처리
    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");
    }
  },
  computed: {
    listItems() {
      // router name에 따라 state 속성 분기처리
      const name = this.$route.name;
      if (name === "news") {
        return this.$store.state.news;
      } else if (name === "ask") {
        return this.$store.state.ask;
      } else if (name === "jobs") {
        return this.$store.state.jobs;
      }
    },
  },
};
</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>

※ router name은 src/routes/index.js에 VueRouter의 routes 속성 구현 시 name 값 선언하여 사용

 

2. NewsView.vue, AskView.vue, JobsView.vue 코드 정리

<template>
  <div>
    <list-item></list-item>
  </div>
</template>

<script>
import ListItem from '../components/ListItem.vue';

export default {
  components: {
    ListItem,
  }
};
</script>

※ NewsView, AskView, JobsView 코드 동일. ListItem으로 공통 컴포넌트화를 하였고 각 페이지에 필요한 데이터에 대한 분기처리도 되어있기 때문

사용자 프로필 컴포넌트 공통화

NewsView, AskView, ItemView에서 접근 가능한 UserView에 표시할 컴포넌트를 공통화

 

구현하기

1. src/components/UserProfile.vue 생성

<template>
  <div>
    <section>
      <div class="user-container">
        <div>
          <i class="fas fa-user"></i>
        </div>
        <div class="user-description">
          <div>User Id</div>
          <div class="time">User Created</div>
        </div>
      </div>
    </section>
  </div>
</template>

<script>
export default {};
</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>

2. src/views/UserView.vue 수정

<template>
  <div>
    <user-profile></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>

※ 데이터 처리 흐름 차이

1. UserProfile에서 computed로 접근

// UserProfile.vue
<template>
  <div class="user-description">
    <div>{{ userInfo.id }}</div>
    <div class="time">{{ userInfo.created }}</div>
  </div>
</template>

<script>
export default {
  computed: {
    userInfo() {
      return this.$store.state.user;
    },
  },
};
</script>

// UserView.vue
<template>
  <div>
    <user-profile></user-profile>
  </div>
</template>

<script>
import UserProfile from '../components/UserProfile.vue';

export default {
  components: {
    UserProfile,
  },
  created() {
    const userName = this.$route.params.id;
    this.$store.dispatch("FETCH_USER", userName);
  },
};
</script>

2. UserView에서 props로 전달

// UserProfile.vue
<template>
  <div class="user-description">
    <div>{{ info.id }}</div>
    <div class="time">{{ info.created }}</div>
  </div>
</template>

<script>
export default {
  props: {
    info: Object,
  },
};
</script>

// UserView.vue
<template>
  <div>
    <!-- info라는 이름으로 props속성 이용 데이터 전달 -->
    <user-profile :info="userInfo"></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>

3. 동작 방식

slot을 이용한 사용자 프로필 컴포넌트 구현

컴포넌트의 재사용성을 높이기 위해 slot을 이용하여 UserProfile 컴포넌트를 구현

 

구현하기

1. src/components/UserProfile.vue 수정

<template>
  <div>
    <section>
      <div class="user-container">
        <div>
          <i class="fas 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>
    </section>
  </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>

2. ItemView, UserView 수정

// ItemView.vue
<template>
  <div>
    <!-- 사용자 상세 정보 -->
    <section>
      <user-profile :info="fetchedItem">
        <!-- slot 사용 -->
        <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>

// UserView.vue
<template>
  <div>
    <user-profile :info="userInfo">
      <!-- slot 사용 -->
      <div slot="username">{{ userInfo.id }}</div>
      <span slot="time">{{ 'Joined ' + userInfo.created }}, </span>
      <span slot="karma">{{ userInfo.karma }}</span>
    </user-profile>
  </div>
</template>
728x90
반응형

댓글