05 컴포넌트 Basic

vencott·2022년 2월 17일
0

5.1 컴포넌트란?

View, Data, Code의 세트

컴포넌트는 재사용이 가능 → 다른 컴포넌트에 import 해서 사용

컴포넌트의 분리

src/components

  • 다른 vue 파일에서 호출해 공통으로 사용할 수 있는 vue 컴포넌트 파일

src/views

  • 흔히 페이지라고 부르는, 화면 하나하나에 해당하는 vue 컴포넌트 파일

두가지 모두 vue 입장에서는 컴포넌트이고 내부적으로 동일한 구조를 가지나, 프로젝트 관리 차원에서 물리적으로 분리한다

5.2 컴포넌트 구조 이해하기

vue 코드의 기본 구조를 만들고, snippet로 등록한다

5.2.1 컴포넌트 기본 구조

vue 컴포넌트에는 name, components, data, computed 같은 기본 프로퍼티 외에도 라이프사이클 훅에 해당하는 메소드 등이 있다

이 중 자주 사용하게 되는 기본 코드 구조를 snippet에 등록한다

<template>
  <div></div>
</template>

<script>
export default {
  name: "",
  components: {},
  data() {
    return {
      sampleData: "",
    };
  },
  setup() {},
  created() {},
  mounted() {},
  unmounted() {},
  methods() {},
};
</script>
  • <template> : View에 해당하는 html 코드 작성
  • name : 컴포넌트 이름
  • components : 외부 컴포넌트를 사용하게 되면 해당 컴포넌트를 import 한 후, 이곳에 배열로 등록
  • data :
    • html 코드와 JS 코드에서 전역 변수로 사용하기 위해 선언하는 데이터
    • 데이터 바인딩을 통해 html과 JS 코드 간 양방향 통신 지원
    • 이곳에 정의된 변수는 this를 통해 접근
  • setup : 컴포지션 API 구현
  • created : 컴포넌트 생성 시 실행
  • mounted : 템플릿에 작성한 HTML 코드가 랜더링 된 후 실행
  • unmounted : 컴포넌트를 빠져나갈 때 실행
  • methods : 컴포넌트 내에서 사용할 메소드, this를 통해서 접근

5.2.2 Snippet 설정

vue.json : Code → Preference → User Snippets → vue(Vue) 선택

{
  // Place your snippets for vue here. Each snippet is defined under a snippet name and has a prefix, body and
  // description. The prefix is what is used to trigger the snippet and the body will be expanded and inserted. Possible variables are:
  // $1, $2 for tab stops, $0 for the final cursor position, and ${1:label}, ${2:another} for placeholders. Placeholders with the
  // same ids are connected.
  // Example:
  // "Print to console": {
  // 	"prefix": "log",
  // 	"body": [
  // 		"console.log('$1');",
  // 		"$2"
  // 	],
  // 	"description": "Log output to console"
  // }
  "Generate Basic Vue Code": {
    "prefix": "vue-start",
    "body": [
      "<template>\n<div></div>\n</template>\n\n<script>\nexport default{ \n\tname:'',\n\tcomponents:{},\n\tdata(){\n\t\treturn{\n\t\t\tsampleData:''\n\t\t};\n\t},\n\tsetup(){},\n\tcreated(){},\n\tmounted(){},\n\tunmounted(){},\n\tmethods:{}\n}\n</script>"
    ],
    "description": "Generate Basic Vue Code"
  }
}

5.2.3 Snippet 사용

에디터에 vue-start를 쳐서 템플릿이 생성되는지 확인한다

5.2.4 Lifecycle Hooks

모든 컴포넌트는 생성될 때 초기화 단계를 거친다

실무 TIP!

라이프사이클 훅에 따라 프로그램을 적절히 배치하면 화면 로딩시간을 개선하고 사용자 체감 속도를 높일 수 있다

사용자가 특정 화면 접속 시, 가장 먼저 보여줘야하는 데이터 영역은 created()에, 화면 로딩 이후에 삽입되어도 되는 데이터는 mounted() 훅에 정의해 타이밍을 적절히 분배한다

5.3 데이터 바인딩

단방향 데이터만 지원하는 React와 다르게, Vue는 Angular와 같이 양방향 데이터 바인딩(Two-way data binding)을 지원한다

실무에서 서버로부터 받아온 데이터를 바인딩하는 경우는 다음과 같다

  • 데이터가 html tag 안에 텍스트로 바인딩
  • 데이터가 html tag의 속성으로 바인딩
  • 데이터가 html의 Form element의 value에 바인딩
  • 다중 데이터가 html의 다중 element 생성을 위해 바인딩

Vue 컴포넌트에서 데이터가 바인딩 되는 유형에 따라 적용 방식에 차이가 있다. 지금부터 하나하나 알아보자!

src/views/DataBinding.vue

<template>
  <h1>Hello, {{ title }}</h1>
</template>

<script>
export default {
  data() {
    return {
      title: "World",
    };
  },
};
</script>
  • {{ title }} : Vue 컴포넌트에서 data에 정의되는 데이터를 이중 중괄호를 이용해 html에 바인딩

src/router/index.js

{
    path: "/databinding",
    name: "DataBinding",
    component: DataBinding,
  },

routes에 위 루트를 추가한다

src/App.vue

<router-link to="/">Home</router-link> |
<router-link to="/databinding">DataBinding</router-link>

5.3.1 문자열 데이터 바인딩

문자열은 앞서 실행한 것처럼 이중 중과라호를 이용해 데이터를 바인딩한다

<h1>Hello, {{ title }}</h1>

5.3.2 raw(원시) HTML 데이터 바인딩

HTML 태그를 바인딩 할때 이중 중괄호를 쓰면 텍스트로 취급하므로 v-html 디렉티브를 이용한다

src/views/DataBindingHtml.vue

<template>
  <div>
    <div>{{ htmlString }}</div>
    <div v-html="htmlString"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      htmlString: "<p style='color:red;'>This is a red string</p>",
    };
  },
};
</script>

5.3.3 Form 입력 데이터 바인딩

웹 페이지에서 사용자로부터 데이터를 입력받을 수 있는 필드를 Form Element라고 한다

v-model 디렉티브를 사용하여 양방향 데이터 바인딩을 생성할 수 있다

💡 `v-model`은 내부적으로 서로 다른 속성을 사용하고 서로 다른 입력 요소에 대해 서로 다른 이벤트를 전송한다는 점을 꼭 주의하자

5.3.3.1 Input type=text

사용자로부터 텍스트를 입력받는 input type=text의 경우, 입력받은 텍스트는 value에 저장이 된다

v-model은 내부적으로 value 속성을 사용한다

data()에 정의된 데이터 키 명을 v-model에 넣어주면 data(Model)input의 value 속성(View) 사이에 양방향 데이터 바인딩이 설정된다

src/views/DataBindingInputText.vue

<template>
  <div>
    <input type="text" v-model="valueModel" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      valueModel: "South Korea",
    };
  },
};
</script>
  • 모델인 data에서 뷰인 input 객체로 단방향 데이터 바인딩이 된 것처럼 보이지만, 실제로는 input의 데이터가 변경되면 data의 valueModel의 값도 변경된다

5.3.3.2 Input type=number

사용자의 입력 값이 문자가 아닌 숫자로 바로 처리되도록 v-model.number 디렉티브를 사용할 수 있다

src/views/DataBindingInputNumber.vue

<template>
  <div>
    <input type="number" v-model.number="numberModel" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      numberModel: 1,
    };
  },
};
</script>

5.3.3.3 Textarea

<textarea> v-model=""</textarea>

src/views/DataBindingTextarea.vue

<template>
  <div>
    <textarea v-model="message"></textarea>
  </div>
</template>

<script>
export default {
  data() {
    return {
      message: "여러 줄을 입력할 수 있는 textarea 입니다",
    };
  },
};
</script>

5.3.3.4 Select

input type=text와 마찬가기로 v-model은 내부적으로 value 속성을 사용하여 양방향 데이터 바인딩을 한다

src/views/DataBindingSelect.vue

<template>
  <div>
    <select v-model="city">
      <option value="02">서울</option>
      <option value="21">부산</option>
      <option value="064">제주</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      city: "02",
    };
  },
};
</script>

5.3.3.5 체크박스 (input type=checkbox)

input type=text, select와는 다르게 v-modelchecked 속성 사용

value 속성에 바인딩 하려면 v-bind:value를 사용

src/views/DataBindingCheckbox.vue

<template>
  <div>
    <select v-model="city">
      <option value="02">서울</option>
      <option value="21">부산</option>
      <option value="064">제주</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      city: "02",
    };
  },
};
</script>

여러개의 체크박스를 사용할 경우 배열을 사용해 데이터를 바인딩하여 한번에 처리할 수 있다

src/views/DataBindingCheckbox2.vue

<template>
  <div>
    <label><input type="checkbox" value="서울" v-model="checked" />서울 </label>
    <label><input type="checkbox" value="부산" v-model="checked" />부산 </label>
    <label><input type="checkbox" value="제주" v-model="checked" />제주 </label>
    <br />
    <span>체크한 지역 : {{ checked }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      checked: [],
    };
  },
};
</script>

5.3.3.6 라디오 (input type=radio)

라디오 역시 체크박스와 마찬가지로 v-model은 내부적으로 checked 속성과 바인딩된다

value 속성에 바인딩 하려면 v-bind:value를 사용

라디오에서 체크를 하게 되면 체크된 v-bind:value에 연결된다

src/views/DataBindingRadio.vue

<template>
  <div>
    <label
      ><input type="radio" v-bind:value="radioValue1" v-model="picked" />서울
    </label>
    <label
      ><input type="radio" v-bind:value="radioValue2" v-model="picked" />부산
    </label>
    <label
      ><input type="radio" v-bind:value="radioValue3" v-model="picked" />제주
    </label>
    <br />
    <span>선택한 지역 : {{ picked }}</span>
  </div>
</template>

<script>
export default {
  data() {
    return {
      picked: "",
      radioValue1: "서울",
      radioValue2: "부산",
      radioValue3: "제주",
    };
  },
};
</script>

5.3.4 속성(Attribute)

value를 제외한 HTML 객체의 속성에 데이터를 바인딩하기 위해선 v-bind: 디렉티브를 사용한다 (v-bind 를 생략하고 그냥 :로 사용 가능)

5.3.4.1 img 객체의 src

이미지 주소를 img 객체의 src 속성에 바인딩한다

src/views/DataBindingAttribute.vue

<template>
  <div>
    <img v-bind:src="imgSrc" />
  </div>
</template>

<script>
export default {
  data() {
    return {
      imgSrc: "https://kr.vuejs.org/images/logo.png",
    };
  },
};
</script>

5.3.4.2 button 객체의 disabled

버튼에서 disabled 속성에 따라 활성화/비활성화 여부가 결정된다

화면에서 조회 조건 중 필수 입력 조건이 모두 입력이 되었을 때 등 disabled 속성을 이용하면 더 좋은 UX를 제공할 수 있다

src/views/DataBindingButton.vue

<template>
  <div>
    <input type="text" v-model="textValue" />
    <button type="button" v-bind:disabled="textValue == ''">Click</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      textValue: "",
    };
  },
};
</script>
  • v-bind:disabled="textValue == ''" : textValue가 비어있을 경우엔 비활성화

5.3.5 클래스 바인딩

반드시 적용해야하는 클래스는 기존 html에서 사용하던 방식대로 class 속성에 클래스명을 입력하면 된다

조건에 따라 바인딩할 클래스는 v-bind:class를 이용해서 추가적으로 정의해서 사용한다

즉, 다른 속성의 경우 하나의 속성만을 이용해서 바인딩 해야하지만 클래스의 경우는 기본 클래스바인딩 처리를 하는 클래스를 공존해서 사용할 수 있다

바인딩할 클래스를 Key, 바인딩 여부를 true/false의 Value로 오브젝트 형태를 사용한다

src/views/DataBindingClass.vue

<template>
  <div
    class="container"
    v-bind:class="{ active: isActive, 'text-red': hasError }"
  >
    Class Binding
  </div>
</template>

<script>
export default {
  data() {
    return {
      isActive: true,
      hasError: false,
    };
  },
};
</script>

<style scoped>
container {
  width: 100%;
  height: 200px;
}
.active {
  background-color: yellow;
  font-weight: bold;
}
.text-red {
  color: red;
}
</style>

배열을 사용해 클래스를 바인딩할 경우 특정 조건에 따른 바인딩 처리를 true/false로 할 수는 없다

src/views/DataBindingClass2.vue

<template>
  <div class="container" v-bind:class="[activeClass, errorClass]">
    Class Binding
  </div>
</template>

<script>
export default {
  data() {
    return {
      activeClass: "active",
      errorClass: "text-red",
    };
  },
};
</script>

<style scoped>
container {
  width: 100%;
  height: 200px;
}
.active {
  background-color: yellow;
  font-weight: bold;
}
.text-red {
  color: red;
}
</style>

5.3.6 인라인 스타일 바인딩

인라인 스타일은 데이터를 오브젝트로 선언해서 바인딩한다

src/views/DataBindingStyle.vue

<template>
  <div v-bind:style="styleObject">인라인 스타일 바인딩</div>
</template>

<script>
export default {
  data() {
    return {
      styleObject: {
        color: "red",
        fontSize: "13px",
      },
    };
  },
};
</script>

클래스 바인딩과 마찬가지로 배열을 이용해서 바인딩 할 수 있다

src/views/DataBindingStyle2.vue

<template>
  <div v-bind:style="[baseStyle, addStyle]">인라인 스타일 바인딩</div>
</template>

<script>
export default {
  data() {
    return {
      baseStyle: "background=color:yellow;width:100%;height:200px",
      addStyle: "color:red;font-weight:bold;",
    };
  },
};
</script>

5.4 리스트 렌더링 (v-for)

서비스 개발 시 다중 데이터를 처리해야 할 일이 자주 발생한다

배열 데이터는 v-for 디렉티브를 이용해서 바인딩한다

반복적으로 랜더링 할 html 태그에 v-for 디렉티브를 사용하면 배열에 있는 데이터 수 만큼 html 태그를 반복적으로 랜더링 한다

v-for="(item, index) in items"

src/views/DataBindingList.vue

<template>
  <div>
    <table>
      <thead>
        <tr>
          <th>제품명</th>
          <th>가격</th>
          <th>카테고리</th>
          <th>배송료</th>
        </tr>
      </thead>
      <tbody>
        <tr v-bind:key="i" v-for="(product, i) in productList">
          <td>{{ product.product_name }}</td>
          <td>{{ product.price }}</td>
          <td>{{ product.category }}</td>
          <td>{{ product.delivery_price }}</td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script>
export default {
  data() {
    return {
      productList: [
        {
          product_name: "기계식키보드",
          price: 25000,
          category: "노트북/태블릿",
          delivery_price: 5000,
        },
        {
          product_name: "무선마우스",
          price: 12000,
          category: "노트북/태블릿",
          delivery_price: 5000,
        },
        {
          product_name: "아이패드",
          price: 725000,
          category: "노트북/태블릿",
          delivery_price: 5000,
        },
        {
          product_name: "태블릿거치대",
          price: 32000,
          category: "노트북/태블릿",
          delivery_price: 5000,
        },
        {
          product_name: "무선충전기",
          price: 42000,
          category: "노트북/태블릿",
          delivery_price: 5000,
        },
      ],
    };
  },
};
</script>
<style scoped>
table {
  font-family: arial, sans-serif;
  border-collapse: collapse;
  width: 100%;
}
td,
th {
  border: 1px solid #dddddd;
  text-align: left;
  padding: 8px;
}
</style>

5.5 랜더링 문법 (v-if, v-show)

Vue 컴포넌트에서 조건에 따라 랜더링을 하는 방법은 v-if, v-show 디렉티브를 사용하는 것이다

5.5.1 v-if

v-if, v-else를 사용해 조건에 따라 html 블록을 생성한다

<div v-if="isRender">isRender가 true이면 html 블록이 생성됩니다</div>
<div v-else>isRender가 false이면 html 블록이 생성됩니다</div>

src/views/RenderingVIf.vue

<template>
  <div>
    <h1 v-if="type == 'A'">A</h1>
    <h1 v-else-if="type == 'B'">B</h1>
    <h1 v-else>C</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      type: "A",
    };
  },
};
</script>

5.5.2 v-show

v-show를 사용해 해당 html 블록의 보임/숨김 처리를 할 수 있다

<div v-show="isShow">isShow가 true이면 현재 블록이 화면에 보입니다.</div>

5.5.3 v-if와 v-show의 차이점

v-ifv-show는 비슷해 보이지만, 내부적으로 랜더링 되는 방식에는 큰 차이가 있다

  • v-if : 조건을 만족하면 그 순간에 html 블록이 생성되고, 조건에 만족하지 않으면 html 블록은 삭제된다
  • v-show : 조건 만족 여부에 상관없이 무조건 html 블록이 생성되고 조건을 만족하면 css의 display를 이용해서 보임/숨김 처리만 된다

html 블록에 토글이 자주 일어난다면 v-show가 더 적합하나, 조건에 관계없이 처음에 무조건 html 블록이 생성된다는 단점이 있다

5.6 이벤트 처리 (v-on)

Vue 컴포넌트에서 이벤트를 처리할 때는 v-on을 사용하며 심볼 @로 사용도 가능하다

5.6.1 클릭 이벤트 (v-on:click)

src/views/EventClick.vue

<template>
  <div>
    <button type="button" @click="increaseCounter">Add</button>
    <p>The counter is : {{ counter }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0,
    };
  },
  methods: {
    increaseCounter: function () {
      this.counter += 1;
    },
  },
};
</script>

클릭 이벤트를 통해 지정된 함수로 파라미터를 전달할 수 있다

<template>
  <div>
    <button type="button" @click="increaseCounter(3)">Add</button>
    <p>The counter is : {{ counter }}</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      counter: 0,
    };
  },
  methods: {
    increaseCounter: function (counter) {
      this.counter = counter;
    },
  },
};
</script>

클릭 이벤트 발생 시 여러 함수를 호출할 수 있다

<template>
  <div>
    <button type="button" @click="one(), two()">Alert</button>
  </div>
</template>

<script>
export default {
  methods: {
    one: function () {
      alert("one");
    },
    two: function () {
      alert("two");
    },
  },
};
</script>

5.6.2 Change 이벤트

select 태그에서 사용자가 옵션을 바꿀 때 change 이벤트가 발생한다

src/views/EventChange.vue

<template>
  <div>
    <select v-model="selectedValue" @change="changeSelect">
      <option value="서울">서울</option>
      <option value="부산">부산</option>
      <option value="제주">제주</option>
    </select>
  </div>
</template>

<script>
export default {
  data() {
    return {
      selectedValue: "",
    };
  },
  methods: {
    changeSelect: function () {
      alert(this.selectedValue);
    },
  },
};
</script>

5.6.3 Key 이벤트

사용자의 키보드 입력 이벤트를 다룰땐 v-on:keyup.xxx=""를 사용한다

검색창에 검색어를 입력한 후 엔터를 쳤을때 submit을 하고 싶다면 아래와 같이 사용한다

<input @keyup.enter="submit" />

enter와 같이 Vue에서는 자주 사용되는 Kye 이벤트를 제공한다

  • .enter
  • .tab
  • .delete
  • .esc
  • .spcae
  • .up
  • .down
  • .left
  • .right

control, shift, alt와 같이 다른 키와 함께 사용되는 특수 키에 대해선 다음과 같이 처리할 수 있다

<!-- Alt + Enter -->
<input @keyup.alt.enter="clear" />
      
<!-- Click + Ctrl-->
<input @click.ctrl="doSomeThing" />

5.7 computed와 watch

computedwatch는 둘 다 Vue 인스턴스 내에 정의된 데이터 값에 변경이 일어나는지를 감시하고, 변경될 때마다 정의된 함수를 실행한다

5.7.1 computed

DB에서 사용자의 이름을 first name과 last name으로 구분해서 저장하고 있고, 화면에서 이를 합쳐서 보여줘야 한다면 다음 두가지 방법을 사용할 수 있다

1) 문자열 합치기

<template>
  <div>
    <h1>{{ firstName + " " + lastName }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: "Jungmin",
      lastName: "Kim",
    };
  },
};
</script>

2) 사용자 이름을 합치는 함수를 만들고, 이를 호출하기

<template>
  <div>
    <h1>{{ getFullName() }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: "Jungmin",
      lastName: "Kim",
    };
  },
  methods: {
    getFullName: function () {
      return this.firstName + " " + this.lastName;
    },
  },
};
</script>

만약 사용자 이름을 여러 곳에서 보여줘야 한다면 이런 연산은 매번 일어날 것이다

computed는 Vue 인스턴스 내에 정의된 데이터 값과 연관된 또 하나의 데이터를 정의해서 사용할 수 있도록 해준다

src/views/Computed.vue

<template>
  <div>
    <h1>Full Name: {{ fullName }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: "Jungmin",
      lastName: "Kim",
    };
  },
  computed: {
    fullName() {
      return this.firstName + "" + this.lastName;
    },
  },
};
</script>
  • computed 내에 fullName이 정의되어 있다
  • 함수명인 fullName은 함수이자 Vue 인스턴스 데이터 키 값으로, firstName, lastName 등과 같이 데이터로 선언된다

computed는 데이터 값에 변경이 일어나는지 감시하고, firstName 또는 lastName 값 중 하나라도 변경이 일어나면 fullName 함수를 자동으로 실행하고, fullName 값이 갱신된다

computed에 정의된 fullName은 함수이자 동시에 Vue 인스턴스의 데이터이다

화면 내 여러곳에서 fullName을 사용해도 이에 대한 연산은 한 번밖에 일어나지 않는다

5.7.2 watch

watch 역시 computed처럼 Vue 인스턴스에 정의된 데이터 값의 변경을 감시하고, 변경이 일어나면 지정된 함수를 실행하지만 아래와 같은 차이점이 있다

  • computed : 기존에 정의된 데이터 값을 기반으로 새로운 데이터 값을 활용하기 위해서 사용
  • watch : watch에 정의된 데이터 값 하나만을 감시하기 위한 용도이다

또한 watch의 경우 computed와 다르게 실제 데이터 변경이 일어나기 전까지는 실행되지 않는다

즉, 초기에 지정된 값인 firstName, lastName에 값이 있음에도 불구하고 아직 한번의 변경도 일어나지 않았기 때문에 fullName에는 아무런 값이 할당되지 않는다

src/views/Watch.vue

<template>
  <div>
    <h1>Full Name: {{ fullName }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      firstName: "Jungmin",
      lastName: "Kim",
      fullName: "",
    };
  },
  watch: {
    firstName() {
      this.fullName = this.firstName + " " + this.lastName;
    },
    lastName() {
      this.fullName = this.firstName + " " + this.lastName;
    },
  },
};
</script>

정리하자면,

computed

  • 정의된 데이터 값을 바탕으로 새로운 데이터 값을 생성
  • 새로운 데이터 값에서 참조하고 있는 기존 데이터 값의 변경을 감지
  • 참조하고 있는 데이터 값의 변경 여부와 상관없이 최초에 computed에 정의된 데이터 함수를 실행

watch

  • 초기에 할당된 값에서 변경이 일어나야 watch에 정의한 함수를 실행

출처: 고승원 저, 『Vue.js 프로젝트 투입 일주일 전』, 비제이퍼블릭(2021)


profile
Backend Developer

0개의 댓글