데브코스

[Day 35] 렌더링, 이벤트 핸들링, 폼 바인딩, 컴포넌트

라다디 2022. 12. 6. 15:09

조건부 렌더링

v-if, v-else-if, v-else 디렉티브를 이용해서 조건부 렌더링이 가능하다. 

 

v-if

<h1 v-if="isShow">Hello Vue!</h1>
<template v-else-if="[]">
  <h2>Application..</h2>
  <p>1234</p>
  <span>987</span>
</template>
<h2 v-else>Good Morning</h2>
const App = {
  data() {
    return {
      isShow: null,
    };
  },
};
const vm = Vue.createApp(App).mount("#app");

특정한 요소에 wrapping 되어서 출력되는 것을 원하지 않을 경우 div 대신 template 태그를 사용할 수 있다.

template 태그를 사용하면 본인은 출력되지 않고 그 안에 있는 요소들만 출력된다.

 

v-show

v-show는 요소의 스타일 속성에 display: none을 추가하여 엘리먼트를 조건부로 표시합니다.

<h1 v-show="isShow">Hello Vue</h1>

다만 주의할 점은 v-show는 요소를 구조적으로 먼저 생성하고 데이터를 연결하기 때문에 이중 중괄호 구문을 사용하면 데이터로 대체되어 들어가기 전에 해당 중괄호 구문이 화면에 잠깐 출력되는 문제점이 있을 수 있다. 

 

따라서 v-cloak이라는 디렉티브와 같이 사용하는 것이 좋다.

v-cloak이중 중괄호 구문으로 출력해야 하는 데이터를 사용할 준비가 모두 되었으면 VueJS의 내부 동작을 통해 제거된다. 

 

v-cloack 속성 선택자를 전역으로 추가해서 사용하는 것이 좋다.

[v-clock] {
  display: none;
}
<button @click="toggle">Toggle</button>
<h1 v-show="isShow" v-cloak>{{ msg }}</h1>
const App = {
  data() {
    return {
      msg: "Hello Vue!",
      isShow: true,
    };
  },
  methods: {
    toggle() {
      this.isShow = !this.isShow;
    },
  },
};
const vm = Vue.createApp(App).mount("#app");

 

v-if vs v-show

v-if는 lazy하므로 조건이 true가 될 때까지 렌더링되지 않는다.

따라서 초기 렌더링 비용이 낮은 반면 전환 비용이 높다.

 

v-show는 요소의 css 기반으로 전환이 되므로 초기 조건과 관계없이 항상 렌더링 된다.

따라서 초기 렌더링 비용이 높은 반면 전환 비용이 낮다. 

 

리스트 렌더링

v-for

v-for 디렉티브를 사용하여 배열을 순회할 수 있다.

item on items 형태의 문법을 필요로 합니다.

<li v-for="item in items">
  {{ item.message }}
</li>

만일 index 값이 필요하다면 (item, index) on items 형태로 적을 수도 있다.

in 대신 of를 구분 기호로 사용하여 자바스크립트 반복문 문법처럼 사용할 수도 있다.

 

객체를 순회하는 것도 가능하다.

(value, key) in myObject의 형태의 문법을 필요로 하며 원한다면 (value, key, index) in myObject의 형태로 적을 수도 있다.

 

v-for는 정수를 사용하여 범위를 순회할 수 있다.

<p v-for="n in 10">{{ n }}</p> // 1 ~ 10 (제로 베이스 인덱스 x)

 

[ 참고 ]

v-if v-for를 함께 사용하는 것은 권장되지 않는다.

함께 사용해야 할 필요가 있다면 template 태그를 사용하여 해결할 수 있다.

<template v-for="todo in todos">
  <li v-if="!todo.isComplete">
    {{ todo.name }}
  </li>
</template>

 

상태 유지

v-for로 렌더링된 리스트를 업데이트할 때, 리스트 아이템의 순서가 변경된 경우 아이템의 순서와 일치하도록 DOM 엘리먼트를 이동하는 대신, 변경이 필요한 인덱스의 엘리먼트들을 제자리에서 패치해 아이템을 렌더링한다.

 

이 과정에서 문제가 생기는 것을 방지하기 위해 고유한 key 속성을 제공하는 것이 좋다.

<div v-for="item in items" :key="item.id">
  <!-- 내용 -->
</div>

가능한 한 언제나 v-for key 속성과 함께 사용하는 것이 권장된다.

 

e.g. TodoList

<form @submit.prevent="addNewTodo">
  <label for="new-todo">Add a todo</label>
  <input
    v-model="newTodoText"
    id="new-todo"
    type="text"
    placeholder="E.g. Feed the cat"
  />
  <button>Add</button>
</form>
<ul>
  <todo-item
    v-for="todo in todos"
    :key="todo.id"
    :todo="todo"
    @remove="removeTodo"
  />
</ul>
function generateId() {
  return `${Date.now()}${Math.random()}`;
}
const TodoItem = {
  template: `
  <li>
    {{ todo.title}}
    <button @click="$emit('remove', todo.id)">Remove</button>
  </li>`,
  props: ["todo"],
};
const App = {
  components: {
    TodoItem,
  },
  data() {
    return {
      newTodoText: "",
      todos: [],
    };
  },
  methods: {
    addNewTodo() {
      this.todos.push({
        id: generateId(),
        title: this.newTodoText,
      });
      this.newTodoText = "";
    },
    removeTodo(todoId) {
      this.todos = this.todos.filter((todo) => {
        return todo.id !== todoId;
      });
    },
  },
};
const vm = Vue.createApp(App).mount("#app");

 

이벤트 핸들링

v-on 디렉티브는 DOM 이벤트를 수신하고 트리거될 때 정의한 자바스크립트 코드를 실행할 수 있다.

 

인라인 핸들러

이벤트 핸들러의 논리가 간단할 경우 다음과 같이 사용한다. 

<button @click="count++">1 추가</button>

인라인 핸들러에서 메소드를 호출할 수도 있다. 

사용자 지정 인자와 함께 이벤트 객체를 보내고 싶을 경우 $event를 사용하여 전달할 수 있다.

<button @click="say('안녕')">안녕이라고 말하기</button>
methods: {
  say(message, event) {
    // 내용
  }
}

 

메소드 핸들러

이벤트 핸들러의 논리가 복잡할 경우 메소드 이름을 직접 바인딩할 수 있다.

메소드 핸들러는 이벤트 객체를 자동으로 수신한다. 

<h1 @click="say">{{ msg }}</h1>
methods: {
  say(event) { // 이벤트 객체
    console.log(event.target.textContent);
  }
}

 

복합 이벤트 핸들러

여러개의 함수를 실행시키고 싶을 때 인라인 메소드 방식을 사용한다.

하나의 메소드를 실행하고 나서 , 혹은 ;로 마무리한다.

<h1 @click="a(); b(); c()">Hello Vue</h1>
<h1 @click="a(), b(), c()">Hello Vue</h1>

 

이벤트 수식어

v-on은 점(.)으로 시작하는 지시적 접미사인 이벤트 수식어를 제공한다.

수식어는 순서가 중요하며 체이닝이 가능하다. 

<!-- 이벤트 버블링 방지 -->
<a @click.stop="doThis"></a>

<!-- 기본 이벤트 동작 방지 후 작성한 메소드 실행 -->
<form @submit.prevent="onSubmit"></form>

<!-- 이벤트 캡쳐링 -->
<!-- 내부 엘리먼트에서 이벤트 핸들러가 실행되기 전에, 여기에서 먼저 핸들러가 실행 -->
<div @click.capture="doThis">...</div>

<!-- event.target이 엘리먼트 자체인 경우 핸들러 실행-->
<!-- currentTarget과 target이 같은 요소일 경우 -->
<div @click.self="doThat">...</div>

<!-- 이벤트는 단 한 번만 실행 -->
<a @click.once="doThis"></a>

<!-- 로직의 처리와 화면의 동작 독립시킴 -->
<div @scroll.passive="onScroll">...</div>

화면의 구현과 이에 해당하는 로직을 브라우저가 동시에 처리하기 때문에 로직이 복잡하고 양이 많다면 그만큼 부하가 발생한다.

.passive화면의 동작과 로직 처리를 독립시키기 때문에 부하를 줄일 수 있다.

화면은 부드러워보이지만 로직은 매우 열심히 돌아가고 있는 구조가 되는 것이다.

 

.passive 수식어는 모바일 환경에서 성능 향상에 도움이 된다.

 

.passive는 브라우저에게 이벤트의 기본 동작을 방지하지 않겠다는 의도를 전달한 것이다.

따라서 .passive와 .prevent함께 사용해서는 안된다.

 

입력키 수식어

<!-- key가 Enter일 때만 submit 호출 -->
<input @keyup.enter="submit" />

.exact 수식어를 사용하면 이벤트를 트리거하는 데 필요한 시스템 수식어의 정확한 조합을 제어할 수 있다.

<!-- Ctrl과 함께 Alt 또는 Shift를 누른 상태에서도 클릭하면 실행 -->
<button @click.ctrl="onClick">A</button>

<!-- 오직 Ctrl만 누른 상태에서 클릭해야 실행 -->
<button @click.ctrl.exact="onCtrlClick">A</button>

 

폼 입력 바인딩

v-model디렉티브를 사용하면 양방향 데이터 바인딩이 가능하다.

v-model은 :value@input을 단축시킨 것과 동일하다.

// :value="message"
// @input="message = $event.target.value"

<p>메세지: {{ message }}</p>
<input v-model="message" placeholder="메세지 입력하기" />

 

유의해야 할 점은 v-model한글을 작성할 때 영어와 달리 데이터가 즉시 바인딩이 되지 않고 글자가 완성이 되어야만 바인딩이 된다.

따라서 한글을 처리할 때는 v-model을 사용하지 않고 다음과 같이 작성하여 문제점을 해결할 수 있다.

<input :value="msg" @input="msg = $event.target.value" />

 

체크박스

배열에 체크박스 값을 바인딩 할 수 있다.

<input type="checkbox" v-model="checked" value="1번" />
<input type="checkbox" v-model="checked" value="2번" />
<input type="checkbox" v-model="checked" value="3번" />
data() {
  return {
    checked: [], // {0: '1번', 1: '2번', 2: '3번'}
  };
},

배열 데이터를 연결할 경우, 체크 여부에 따라 value의 값이 객체 형태로 담기게 된다.

 

셀렉트

v-model을 사용하여 선택한 옵션의 value를 데이터에 담을 수 있다.

<select v-model="selected">
 <option value="">과일을 선택해주세요.</option>
  <option v-for="fruit in fruits"
          :value="fruit.value"
          :key="fruit.value">{{ fruit.text }}</option>
</select>
data() {
  return {
    selected: "",
    fruits: [
      { value: "사과", text: "apple" },
      { value: "오렌지", text: "orange" },
      { value: "바나나", text: "banana" },
    ],
  };
},

 

수식어

.lazy: input 이벤트로 동 작하는 양방향 데이터 바인딩을 change로 변환

<input v-model.lazy="msg" />

.number: 사용자 입력을 자동으로 숫자로 형변환

<input v-model.number="msg" />

.trim: 사용자가 입력한 내용에서 자동으로 앞뒤 공백 제거

<input v-model.trim="msg" />

 

컴포넌트 기초

컴포넌트를 사용하면 UI를 독립적이고 재사용 가능한 일부분으로 분할하고 각 부분을 개별적으로 다룰 수 있습니다.

전역 등록

컴포넌트의 등록 방법은 전역 등록지역 등록으로 나뉜다. 

app.component() 메서드를 사용하여 현재 뷰 어플리케이션에서 컴포넌트를 전역으로 사용할 수 있도록 할 수 있다.

const app = createApp({})

app.component(
  'MyComponent',
  // 구현체
  {
    /* ... */
  }
)

 

하위 컴포넌트의 이벤트 수신

props는 외부에서 들어오는 데이터이기 때문에 readonly이다.
그래서 데이터를 수정하려면 데이터가 들어오는 외부에서 변경을 해주어야 한다.

<upper-name
  v-for="fruit in fruits"
  :name="fruit.name"
  :key="fruit.id"
  @to-upper="toUpper(fruit, $event)"
></upper-name>
<!-- 커스텀 이벤트에서 $event 객체는 $emit에서 두 번째 인자로 올려준 데이터 -->
 const App = {
  data() {
    return {
      fruits: [
        { id: 1, name: "apple" },
        { id: 2, name: "orange" },
        { id: 3, name: "banana" },
      ],
    };
  },
  methods: {
    toUpper(fruit, upperName) {
      fruit.name = upperName;
    },
  },
 };

const app = Vue.createApp(App);
app.component("upper-name", {
  props: ["name"],
  template: `<div @click="capitalize">{{ name }}</div>`,
  methods: {
    capitalize() {
      this.$emit("to-upper", this.name.toUpperCase());
      // 데이터를 컴포넌트 바깥으로 올림
    },
  },
});
const vm = app.mount("#app");

하위 컴포넌트에서는 상위에서 props로 내려준 데이터를 수정할 수 없다.

따라서 하위 컴포넌트 내부에서 emit 커스텀 이벤트를 통해 상위에 전달하고 상위에서 이벤트를 받으면 작성해놓은 이벤트를 실행시켜서 데이터를 변경한다.

 


출처: 프로그래머스 프론트엔드 데브코스 

[Day 35] Vue (3)