Brandi Internship - Part 2

Brandi Internship - Part 2

인턴쉽 2주차 시작

처음 브랜디 인턴쉽 시작할 때, 브랜디 측에서 일정에 대해서 혼동한 부분이 있었는데, 본래 15일까지 진행되야 했던 인턴쉽이 8일까지라고 잘못 전달되었었다. 그렇다면 웹팩을 설정한 후 남은 기간은 3주밖에 되지 않았다.(고 생각했다. 나중에 다시 15일까지라는 것을 전달받아서 사실을 4주였다.) 짧은 시간동안에 많은 페이지를 그리려면 한꺼번에 그리는 수밖에 없었다. 그래서 페이지를 관찰해서 작성하는 코드의 숫자를 최소화하면서 효율적으로 코딩을 할 수 있는 부분이 있을 지 찾아보았다.

10개의 페이지를 1개의 컴포넌트로 그리면 어떨까?

관찰 시작

페이지 관찰을 시작했다. 어드민 페이지에서 큰 카테고리는 주문관리, 취소/환불 관리, 상품 관리, 고객응대관리, 회원 관리가 있었고, 카테고리 하위에 페이지들이 있었다. 다행히도 어드민 페이지는 페이지 간에 유사성이 높았다. 그렇다면 컴포넌트를 만들어서 재사용되게끔 만들면 되었다. 일단 페이지를 관찰한 결과 아래와 같았다.



주문관리, 취소/환불 관리, 상품 관리, 고객응대관리에 속해있는 페이지들은 크게 4개의 요소로 구성되어 있었다. 페이지 제목, 검색 조건을 위한 필터링 박스, breadcrumb, 테이블로 구성되어 있는데, 페이지마다 각 요소의 세부내용이 달랐다. 결과적으로 10개의 페이지를 1개의 컴포넌트에서 그리도록 만들기로 했는데, 그렇게 하기 위해서는 10개의 페이지를 그리는 컴포넌트가 10개의 페이지의 세부 사항들을 다 포괄할 수 있어야 했다. 여기서 vue의 장점이 드러났는데, vue는 코드를 짤 때, 코드를 짜는 사람의 사고를 vue의 구조에 맞추도록 강제한다. vue 파일은 렌더링을 담당하는 template태그, 로직을 담당하는 script태그, 스타일을 담당하는 style 태그로 나누어 진다. 여기서 vue의 사고 방식대로 코드를 짠다면 우리는 데이터 로직과 렌더링 로직을 분리해서 사용할 수 있다. 물론 자유도가 높은 react에서도 가능한 부분이지만 분리가 강제되지는 않기 때문에 쉽게 데이터로직과 렌더링 로직이 분리되지 않은 상태로 짜게 된다. 그러나 vue는 직접적인 데이터 부분을 script에 분리해서 작성하도록 생각을 도와준다.(물론 분리가 안되게 데이터를 전부 template 안에 넣어서 짤 수도 있다. 그런데 이렇게 할거면 vue를 왜 ㅆ..)

다시 돌아가서 각 요소의 세부내용이 다른 부분은 별도의 textMap이라는 파일로 분리했고, textMap에는 화면을 그리는 데 필요한 텍스트 값들(필터링 박스 값들, 테이블의 헤더들 등)을 저장했다. vue에서는 script 부분에서 textMap을 가져와 상태로 저장했고, template 부분에서는 저장한 textMap을 그리는 방식으로 구성했다. 이렇게 구성할 경우 필터링 구성이 변경될 때, 데이터에 해당하는 textMap만 수정하면 되고 렌더링 로직은 수정하지 않아도 된다는 장점이 있었다. 이렇게 구성한 결과 컴포넌트에서는 페이지 uri에 맞는 textMap 값을 읽어서 렌더링 로직에 보내기만 하면 렌더링 로직에서는 받은 값을 그려주기만 하면 되었다. 실제로 프로젝트 후반에 백엔드를 담당한 용민님이 추가적으로 vue를 사용한 페이지 구현에 참여하고 싶다고 하셨을 때, textMap에 정보를 추가하는 정도로도 쉽게 페이지 구현이 될 수 있었다.

컴포넌트 분리하기

현재까지는 10개의 페이지를 한개의 컴포넌트에서 그리도록 결정했고, 각 페이지들을 구성하는 text 값들을 textMap으로 분리하는 것으로 결정했다. 이제 컴포넌트들을 어떻게 구성할지를 결정해야 했다. 페이지 제목은 별도의 컴포넌트로 분리할 필요없는 작은 부분이었지만, 필터링 박스, breadcrumb, 테이블은 별도의 컴포넌트로 분리할 필요가 있었다. 그런데 4개의 요소들 중에서 특별히 필터링 박스는 내부를 구성하는 form 요소들이 조금씩 달랐다. 필터링 박스의 경우 내부에서 좀 더 작은 컴포넌트들로 쪼개질 필요가 있었다. 10개의 페이지를 관찰한 결과 필터링 박스 내부의 form 요소들을 8개의 종류로 나눠 볼 수 있었다. 결과적으로는 textMap에서 필터링 박스 내부 form 요소들의 타입 값 1~8을 가지도록 했고, 필터링 박스 내부에서는 textMap의 필터링 타입 값에 따라서 그려지거나 그려지지 않도록 했다. 이해를 돕기 위해 작성한 코드를 보면서 설명하겠다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
  <div>
    <PageLoading v-if="!done" />
    <h3 class="page-title">
      
      <small></small>
    </h3>
    <FilterBox :filters="filters" :orderStatus="orderStatus" />
    <BreadCrumb :orderStatus="orderStatus" />
    <Table
      :table="table"
      :tableMap="tableMap"
      :tableId="tableId"
      :orderStatus="orderStatus"
    />
  </div>
</template>
 
<script>
import path from '@/assets/textMap';
 
...
 
export default {
...  
  data() {
    const {
      main,
      sub,
      filters,
      sort,
      table,
      tableMap,
      tableId,
      orderStatus
    } = path[this.$route.params.subMenu];
    return {
      main,
      sub,
      filters,
      sort,
      table,
      tableMap,
      tableId,
      orderStatus: orderStatus ? orderStatus : 0,
      namespace: this.$route.params.subMenu,
      done: true
    };
  },
 
...
cs

우선 10개의 페이지를 그리는 컴포넌트 코드이다. 스크립트 태그 안에서는 textMap의 값을 읽어서 저장하고, template태그 안에서는 저장한 textMap 값을 필터링 박스, breadcrumb, 테이블 컴포넌트에 전달한다. 그래서 이 컴포넌트 자체로는 textMap을 전달하는 역할 외에 다른 큰 역할을 부여받지 않았다.(물론 페이지 전환시 loading을 진행하는 역할을 맡긴 했다.) 이제 필터링 박스를 담당하고 있는 컴포넌트 내부를 보면

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
<template>
  <div class="filter-box">
    <ul class="filter-block">
      <li class="filter-list" :key="filter.title" v-for="filter of filters">
        <SelectInput v-if="filter.type === 1" :options="filter.values" />
        <ButtonDate
          v-if="filter.type === 2"
          :title="filter.title"
          :buttons="filter.values"
          :default="filter.default"
        />
        <SingleButtons
          v-if="filter.type === 3"
          :title="filter.title"
          :buttons="filter.values"
          :filterKey="filter.key"
          :default="filter.default"
        />
        <MultiButtons
          v-if="filter.type === 4"
          :title="filter.title"
          :buttons="filter.values"
          :default="filter.default"
        />
        <Date v-if="filter.type === 5" :title="filter.title" />
        <Input
          v-if="filter.type === 6"
          :title="filter.title"
          :filterKey="filter.key"
          :placeholder="filter.values"
        />
        <SelectDate
          v-if="filter.type === 7"
          :options="filter.select"
          :buttons="filter.values"
          :default="filter.default"
        />
        <Select
          v-if="filter.type === 8"
          :title="filter.title"
          :filterKey="filter.key"
          :options="filter.values"
        />
      </li>
    </ul>
    <div class="filter-action">
      <div class="button search" @click="searchResult">검색</div>
      <div class="button reset" @click="reset">초기화</div>
    </div>
  </div>
</template>
cs

필터링 박스 컴포넌트에서는 전달 받은 textMap의 type값을 확인해서 해당 type 값에 해당하는 form 요소만 그려준다. 따라서 페이지가 추가되었거나 특정 페이지의 필터 요소가 변경될 경우에는 textMap값만 변경하면 렌더링 로직을 변경하지 않고도 변경된 사항이 그려진다.

api 통신 고려하기

그런데 textMap을 구성할 때, 한가지 더 고려할 점이 있었다. 필터링 박스에 해당하는 textMap의 경우 단순히 그림을 그리는 데이터만 있어서는 안되었다. 왜냐하면 선택된 필터 값들은 실제 백엔드에 요청하는 값이 되어야 했기 때문이다. 따라서 textMap은 화면에 표시될 text 값과 백엔드에 보낼 필터 값 2가지 모두를 가지고 있어야 했다. 따라서 textMap은 객체로 만들어졌고, key는 백엔드로 보내는 필터 값, value는 화면에 표시될 text 값으로 구성했다. 아래의 코드는 textMap의 일부이다. 상품에 대한 검색을 할 때 필요한 text 값인데, 백엔드에 보낼 값이 PRODUCT_NAME이라면 해당 값은 화면에 상품명으로 그려진다.

1
2
3
4
5
6
7
8
9
...
 
export const PRODUCT_SELECT = {
  PRODUCT_NAME: '상품명',
  PRODUCT_NO: '상품번호',
  PRODUCT_CD: '상품코드'
};
 
...
cs

그렇다면 유저가 선택한 특정 값, 필터링된 값들은 어디에 저장을 해야 할까? 여기서 2가지의 선택지가 있는데, 하나는 부모 컴포넌트 필터링 박스에서 값을 가지고 있고 자식 form 요소들에 이벤트가 발생했을 때 부모 컴포넌트의 값을 가지도록 하는 방법이다. 다른 방법은 vuex를 사용해서 모든 필터링 값들을 vuex에서 관리하도록 하는 방법이다. 2가지 방법 모두가 장단점이 있었다. 우선 생각을 해보면 중요한 건 데이터와 데이터를 처리하는 로직이고 이 부분의 역할을 컴포넌트가 가질지 vuex가 가질지를 정해야 한다. 컴포넌트가 이 역할을 가진다면 컴포넌트 자체가 독립적이며 의존성이 적은, 좀 더 자유롭게 다른 컴포넌트와 조합이 가능한 컴포넌트를 만들 수 있다. 여기서 또 새롭게 고려할 지점이 생겼는데, 사실 자식이 부모의 상태 값을 바꾸는 것은 vue에서 편리하게 만들 수 있기 때문에 부모가 값을 전부 가지고 있고 상태 값을 처리하는 메소드를 자식 컴포넌트에 넘겨주게 할 수 있다. 하지만 이 경우 부모 컴포넌트의 데이터 로직이 너무 길어진다. 그렇다고 거꾸로 자식이 값을 가지고 있다면 검색이나 초기화 같은 기능을 사용할 때 부모에서 이벤트가 발생하면 자식의 상태 값을 바꾸도록 이벤트 버스를 관리해야 하는데 관리해야 할 상태값과 컴포넌트의 수가 적지 않아서 불편해진다. 그래서 결론적으로 vuex를 사용하기로 결정했다. vuex를 사용한다면 의존성이 생겨서 자유롭게 다른 컴포넌트와 조합이 가능하지는 않지만, 컴포넌트 간 구성이 사실상 정해진 상태이고 vuex만 잘 관리한다면 컴포넌트 구성을 고정한 상태로는 비교적 변화에 유연하게 코드를 구성할 수 있다. 그래서 결과적으로 아래와 같은 구성이 되었다.


정적인 데이터 textMap은 lookup 컴포넌트에서 읽어서 자식 컴포넌트에 전달을 하고, 동적인 상태값들은 각 컴포넌트에서 vuex를 사용하도록 했다. vuex에서는 동적인 상태값들을 관리하고, 저장된 상태값을 사용하여 api 통신도 담당한다. 마지막으로 다음 글에서는 이번 프로젝트에서 vuex를 어떻게 사용했는지를 정리하겠다.

댓글