프론트엔드 데브코스 5기 TIL 38 - Vue의 조건부렌더링, 리스트 렌더링, 이벤트핸들, 폼 바인딩, 컴포넌트!

김영현·2023년 11월 22일
0

TIL

목록 보기
46/129

조건부 바인딩

v-if-else(조건문 사용)

v-if, v-else디렉티브를 이용하여 조건부 렌더링을 할 수 있다. v-else-if또한 가능하다.

 <div id="app">
    <h1 v-if="isShow">hello vue!</h1>
    <h1 v-else-if="0">good app!</h1>
    <h1 v-else>morning bro</h1>
  </div>
  <script>
    const { createApp } = Vue;
    const App = {
      data() {
        return {
          isShow: false,
        }
      },

    }; const vm = createApp(App).mount("#app"); </script>

결과:

아래와 같이 v-elsev-else-if가 바로 전,후 형제요소가 아니라면 작동하지 않는다

	<!--- 이렇게 하면 안된다 ---->
    <h1 v-if="isShow">hello vue!</h1>
	<div>
    	<h1 v-else-if="0">good app!</h1>
    </div>
    <h1 v-else>morning bro</h1>

	<!--- 이렇게 하세요! ---->
    <div v-else-if="true">
      <h1>good app!</h1>
      <span>우와앙</span>
    </div>

위와같이 래핑하고싶지 않을때도 있을 것이다. 그럴땐 template을 사용하면 그룹화할 수 있음.

    <template v-else-if>
      <h1>good app!</h1>
      <span>우와앙</span>
    </template>

참고로 v-for디렉티브를 사용한 컴포넌트에 절 대 v-if를 사용하면 안된다
v-if가 더 높은 우선순위를 가짐(v-for가무시됨)

	<!---- 나쁨 ---->
    <ul>
      <li v-for="" v-if="" :key="">{{data}}</li>
    </ul>

	<!---- 권장 ---->
    <ul>
      <template v-for="">
      	<li v-if="" :key="">{{data}}</li>
      </template>
    </ul>

v-show

v-if와 비슷하게 동작하지만, 요소 style태그에 display:none을 토글한다.
=> v-if는 DOM에서 제거되지만, v-show는 남아있다.

전환비용이 높다면 v-show를 사용하고 아니라면 v-if를 사용한다.

v-show를 사용할땐 v-cloak디렉티브를 같이 사용하면 좋다.
=> v-show로 인하여display:none이 부여되기 직전 잠깐동안 보간내용이 보일 수 있음. 따라서 v-cloak에 스타일로 display:none을 준다..

참고로 v-cloak은 연결된 컴포넌트의 인스턴스가 컴파일 완료할때 까지 엘리먼트에 남아있다.
=> 데이터가 바뀌어도 계속 display:none을 적용하고 있으니 계속 안보이게 할 수 있다.


리스트 렌더링

v-for을 이용하면 참조형데이터를 순회하며 꺼내올수 있다는걸 안다.

  <div id="app">
    <h1 v-for="(item, index) in items">{{index}} 	{{item}}</h1>
  </div>

이런식으로 인덱스도 꺼내올 수 있다.
만약 아이템이 객체라면 (value, key, index)까지 사용할 수 있다. index는 객체에 존재하지 않지만 임의로 부여해줌.
또한 in대신 of를 사용할 수도 있다. 별 차이는 없다.

key

v-for를 이용하여 렌더링된 엘리먼트 목록을 갱신할때 key를 부여해야 최적화가 됨
=> 리액트와 똑같은 내용. 키값을 지정해두면 뷰가 감지하지 못하는 부분도 감지해줄수 있다.

배열 교체

map, filter등으로 새로운 배열을 할당해도 전부 다 렌더링하지 않는다. 똑똑이 Vue가 최적화를 잘 해주어서 상관없음

중첩된 루프 내부

<ul v-for="numbers in sets">
  <li v-for="n in even(numbers)">{{ n }}</li>
</ul>
<script>
  ...
  data(){
  	return{
  	sets: [[1,2,3,4,5], [6,7,8,9,10]]
  	}
  },
  methods:{
  	even(numbers){
  	return numbers.filter(number => number % 2 === 0)
  }
  }
</script>

이런식으로 중첩된 내부 노드에 메서드를 할당하여 사용할수도 있음.

컴포넌트의 v-for

Vue에서 컴포넌트를 작성할 수 있다고 했다.
그 컴포넌트 역시 v-for디렉티브를 사용하여 반복할 수있다.

<div id="app">
    <form @submit="addNewTodo">
      <label for="new-todo"></label>
      <input v-model="newTodoText" id="new-todo" placeholder="플레이스 홀더" />
      <button>Add</button>
    </form>
    <ul>
      <todo-item v-for="todo in todos" :key="todo.id" :todo="todo" @remove="removeTodo">
      </todo-item>
    </ul>
  </div>
  <script>
    const { createApp } = Vue;
    const TodoItem = {
      template: `
      <li> 
        {{ todo.title }} 
        <button @click="$emit('remove', todo.id)">Remove</button>
      </li>`,
      props: ['todo']
    }
    const generateId = () => `${Date.now()}${Math.random()}`;
    const App = {
      components: {
        TodoItem
      },
      data() {
        return {
          newTodoText: '',
          todos: []
        }
      },
      methods: {
        addNewTodo(e) {
          e.preventDefault();
          this.todos.push({
            id: generateId(),
            title: this.newTodoText
          });
          this.newTodoText = ""
        },
        removeTodo(todoId) {
          this.todos = this.todos.filter(({ id }) => id !== todoId);
        }
      }
    }; const vm = createApp(App).mount("#app"); </script>

$emit키워드를 사용하면 (eventName, args)를 넘겨줄수있다. 이때 매개변수는 컴포넌트 내부에서 밖으로 넘겨주는 행위임.


이벤트 핸들링

v-on디렉티브를 사용하면 이벤트 핸들러 등록이 가능하다.
축약표현(@)을 많이씀

아무런 매개변수를 전달하지 않으면 이벤트를 쉽게 액세스 할수있음

<button @click="handler"></button>
<script>
...
  handler(e){
  	e.target...
  }
</script>

매개변수를 전달할때도 이벤트에 액세스하려면 $event키워드를 인자로 전달해주면 된다.

<button @click="handler(args, $event)"></button>
<script>
...
  handler(args, event){
  	e.target...
  }
</script>

이벤트 수식어

preventDefault()같은 메서드를 작성하지 않아도 되게 만들어줌.

<a @click.prevent="onSubmit">...</a>

다양한 수식어가 존재. 수식어는 체이닝 가능.

  • stop (이벤트 버블링 중지)
  • prevent
  • capture
  • self (e.target이 엘리먼트 자체인 경우에만 처리. 아주 유용해보인다!)
  • once(딱 한번만 이벤트 핸들러 발생)
  • passive => 아래에서 설명하겠따

passive 수식어

보통 스크롤, 휠이벤트를 사용할때 같이 씀.
화면 렌더링과 로직 처리를 분리해준다. 참고로 바닐라JS에서도 사용 가능함.

자세한 내용
나중에 참고해보겠다. 브라우저 렌더링 과정중 합성단계의 일이다.
이벤트 핸들러에 부착된 메서드는 compositer쓰레드에서 처리.
이때 수신한 이벤트를 메인쓰레드에 넘겨준뒤 렌더트리를 받을때 까지 대기하는데, passive를 사용하면 메인쓰레드를 기다리지 않고 페인트작업을 시작함.

키 수식어

<input @keyup.page-dowon="onPageDown"/>

키 이벤트를 케밥-케이스로 수식어를 붙이면, 해당 키가 눌렸을때만 이벤트핸들러 동작함. 와우!
하지만 딱 그 키만 눌렸을때 동작하는 게 아니라 다른 키와 조합해도 동작함.
=> .exact수식어 사용하면 놀랍게도 딱 그 키만 눌렀을때 동작...!

마우스 수식어

  • left
  • right
  • middle(휠버튼)

쉽구먼?


폼 입력 바인딩

위 컴포넌트의 렌더링 부분 보면 v-model디렉티브를 사용해서 양방향 바인딩을 했다.
v-model에 대해 자세히 알아보자.

v-model

사실상 문법적 설탕임

<input :input="onInput" value="inputValue"/>
<input v-model="inputValue">

위의 두개는 같은 의미다.
단, v-model은 초기 value, checked, selected속성을 무시함.

IME언어는 언어 합성중 v-model이 업데이트되지 않음
=> 자음모음 합쳐질때 말하는 것. 이때 업데이트가 필요하다면 input을 사용하라.

checked는 여러 값을 사용할때 값을 배열로 만들어주면 된다.

v-model의 수식어들

  • lazy : input말고 change이벤트 이후에 동기화
    => change는 이벤트 요소 변경이 아예 끝나야 동작.
  • trim : trim()과 똑같음
  • number : 숫자로 자동 형변환

컴포넌트 기초

컴포넌트를 지역, 전역으로 활용할 수 있다.
보통 지역으로 많이 사용하는데, 전역으로 사용하는 방법도 알아놓기만 하자.

전역 컴포넌트

<div id="app">
    <global-component name="apple"></global-component>
  </div>
  <script>
    const { createApp } = Vue;
    const App = {
      data() {
        return {
        }
      },
    };
    const app = createApp(App);
    app.component('global-component', {
      template: `<div>{{ name }}</div>`,
      props: ['name']
    });
    const vm = app.mount("#app");
  </script>

전역 컴포넌트는 케밥-케이스로 작성해야 한다.

props 수정하기

props로 받아온 데이터는 readonly
따라서 상위에서 처리해주어야함.

<div id="app">
    <global-component @to-upper="toUpper(fruit, $event)" v-for="fruit in fruits" :key="fruit.id"
      :name="fruit.name"></global-component>
  </div>
  <script>
    const { createApp } = Vue;
    const App = {
      data() {
        return {
          fruits: [
            { id: '1', name: 'apple' },
            { id: '2', name: 'banana' },
            { id: '3', name: 'cherry' },
          ]
        }
      },
      methods: {
        toUpper(fruit, upperName) {
          this.fruits.find(({ id }) => id === fruit.id).name = upperName
        }
      }
    };
    const app = createApp(App);
    app.component('global-component', {
      template: `<div @click="capitalize">{{ name }}</div>`,
      props: ['name'],
      methods: {
        capitalize() {
          this.$emit('to-upper', this.name.toUpperCase());
        }
      }
    });
    const vm = app.mount("#app");
  </script>

글자를 누르면 어떤 단계로 진행되는지 알아보자.

  1. 클릭이벤트 발생. 이때 할당된 capitalize가 실행된다.
  2. capitalize는 컴포넌트 내부에 작성된 메서드 중 하나다.
    this.$emit을 이용하여 사용자 정의 이벤트를 송신한다.
  3. @to-upper에 할당된 toUpper()메서드 인자로 컴포넌트에 할당된 fruit값과 $event객체를 통해 capitalize메서드에서 넘겨준 this.name.toUpperCase()값을 받아옴.

이렇게 나온다.
$emitdispatchEvent와 비슷해보이지만 다르다고한다.

결론: $emit을 이용하여 상위컴포넌트로 "이거 좀 처리해주소!"하고 넘겨준다.


느낀점

수식어가 굉장히 편리해보인다. $emit은 좀 어렵게 느껴졌는데 api함수를 만들때 사용했던 dispatchEvent를 떠올리니 쉽게 연상이 됐다.
뷰가 확실히 리액트보단 더 쉬워보인다. 더 최신 기능이라 그렇겠지?
낼도 힘내보자!

profile
모르는 것을 모른다고 하기

1개의 댓글

comment-user-thumbnail
2023년 11월 22일

안녕하세요 :) 국비지원 부트캠프 엘리스트랙입니다! 오늘도 개발 공부 열심히 하고 계시군요! 멋지십니다 :)
혹시 개발 공부하면서 기술면접에 대한 대비가 막막하시다면, 이번 기술면접 특강도 관심 가져보시면 좋을 것 같아 댓글로 행사 안내드려요~

프론트/ 백엔드 모두 실력있고, 실제 면접관으로 활동하고 계신 개발자 코치님께서 진행하시니 참여해 보세요> https://festa.io/events/4389

그럼 오늘도 화이팅입니다!🙇🏻‍♀️💪🏻

답글 달기