[Vue] Basic Syntax (2)

young-gue Park·2023년 11월 1일
0

Vue.js

목록 보기
4/10
post-thumbnail

⚡ Basic Syntax (2)


📌 Computed Properties

🔷 computed()

  • 계산된 속성을 정의하는 함수
  • 미리 계산된 속성을 사용하여 템플릿에서 표현식을 단순하게 하고 불필요한 반복 연산을 줄임

🔷 computed 기본 예시

const todos = ref([
  {text: 'Vue 공부'},
  {text: 'Vue 실습'},
  {text: 'Spring 복습'},
]);

이런 데이터가 있을 때

<p>{{todos.length > 0 ? '아직 남았다' : '퇴근!'}}</p>

콧수염 구문으로 템플릿에 쓸 수도 있지만

const restOfTodos = computed(() => {
  return todos.value.length > 0 ? '아직 많이 남았다' : '퇴근';
});

이걸 템플릿에 리턴으로 받으면 템플릿이 더 단순해진다.

🔷 computed 특징

const restOfTodos = computed(() => {
  return todos.value.length > 0 ? '아직 남았다' : '퇴근!'
})
  • 반환되는 값은 computed ref 이며 일반 refs와 유사하게 계산된 결과를 .value 로 참조 할 수 있음 (템플릿에서는 .value 생략가능)
  • computed 속성은 의존된 반응형 데이터를 자동으로 추적
  • 의존하는 데이터가 변경될 때만 재평가
    • restOfTodos의 계산은 todos에 의존하고 있음
    • 따라서 todos가 변경될 때만 restOfTodos가 업데이트 됨

🔷 computed 속성 대신 method로도 동일한 기능을 정의할 수 있다.

const getRestOfTodos = function () {
	return todos.value.length > 0 ? '아직 남았다' : '퇴근!'
}

💡 두 가지 접근 방식은 실제로 완전히 동일하다.

🔷 computed와 method 차이

  • computed 속성은 의존된 반응형 데이터를 기반으로 캐시(cached)된다
  • 의존하는 데이터가 변경된 경우에만 재평가됨
  • 즉, 의존된 반응형 데이터가 변경되지 않는 한 이미 계산된 결과에 대한 여러 참조는 다시 평가할 필요 없이 이전에 계산된 결과를 즉시 반환
  • 반면, method 호출은 다시 렌더링이 발생할 때마다 항상 함수를 실행

🔷 Cache (캐시)

  • 데이터나 결과를 일시적으로 저장해두는 임시 저장소
  • 이후에 같은 데이터나 결과를 다시 계산하지 않고 빠르게 접근할 수 있도록 함

💡 웹 페이지의 캐시 데이터
페이지 일부 데이터를 브라우저 캐시에 저장 후 같은 페이지에 다시 요청 시 모든 데이터를 다시 응답 받는 것이 아니라 캐시 된 데이터를 사용하여 더 빠르게 웹 페이지를 렌더링한다.

🔷 computed와 method의 적절한 사용처

  • computed
    • 의존하는 데이터에 따라 결과가 바뀌는 계산된 속성을 만들 때 유용
    • 동일한 의존성을 가진 여러 곳에서 사용할 때 계산 결과를 캐싱하여 중복 계산 방지
  • method
    • 단순히 특정 동작을 수행하는 함수를 정의할 때 사용
    • 데이터에 의존하는지 여부와 관계없이 항상 동일한 결과를 반환하는 함수

💡 computed는 의존된 데이터가 변경되면 자동으로 업데이트 되고 method는 호출해야만 실행됨을 기억하자.

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <h2>남은 할일</h2>
      <p>{{todos.length > 0 ? '아직 남았다' : '퇴근!'}}</p>
      <hr>
      <h2>계산된 속성</h2>
      <p>{{restOfTodos}}</p>
      <hr>
      <h2>메서드 호출</h2>
      <p>{{getRestOfTodos()}}</p>
    </div>
    <script>
      const { createApp, ref, computed } = Vue;

      const app = createApp({
        setup() {
          const todos = ref([
            {text: 'Vue 공부'},
            {text: 'Vue 실습'},
            {text: 'Spring 복습'},
          ]);

          const restOfTodos = computed(() => {
            console.log("computed")
            return todos.value.length > 0 ? '아직 많이 남았다' : '퇴근해 이제...';
          });

          const getRestOfTodos = () => {
            console.log("method")
            return todos.value.length > 0 ? '넌 퇴근 못한다니까' : '퇴근하라고.......';
          };

          return { 
            todos,
            restOfTodos,
            getRestOfTodos,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

배열 내 모든 todo가 삭제되면?

computed의 반환 값은 변경하지 말 것
▪ computed의 반환 값은 의존하는 데이터의 파생된 값
▪ 일종의 snapshot 이며 의존하는 데이터가 변경될 때 마다 새 snapshot이 생성된다.
▪ snapshot 을 변경하는 것은 의미가 없으므로 계산된 반환 값은 읽기 전용으로 취급되어야 하며 변경되어서는 안됨...
▪ 대신 새 값을 얻기 위해서는 의존하는 데이터를 업데이트 해야 한다.

computed 사용 시 원본 배열 변경하지 말 것
▪ computed 에서 reverse() 및 sort() 사용 시 원본 배열을 변경하기 때문에 복사본을 만들어서 진행 해야 함


📌 Conditional Rendering

🔷 v-if

  • 표현식 값의 T/F 를 기반으로 요소를 조건부로 렌더링

🔷 예시

<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <p v-if="isSeen">true일 때 보임</p>
      <p v-else>false일 때 보임</p>
      <button @click="isSeen = !isSeen">변환</button>
      <hr>
      <div v-if="name === 'Alice'">Alice 입니다.</div>
      <div v-else-if="name === 'Bella'">Bella 입니다.</div>
      <div v-else-if="name === 'Bob'">Bob 입니다.</div>
      <div v-else-if="name === 'Bzeromo'">Bzeromo 입니다.</div>
      <div v-else>아무도 아닙니다.</div>
    </div>
    <script>
      const { createApp, ref } = Vue;

      const app = createApp({
        setup() {
          const isSeen = ref(true);
          const name = ref('Bzeromo');
          return {
            isSeen,
            name,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

변환에 따라 true/false로 바뀐다. 정확히 말하면 두 div가 번갈아가며 렌더링된다.
그리고 조건에 따라 렌더링될 div가 결정된다.

🔷 여러 요소에 대한 v-if 적용

  • v-if 는 directive 이기 때문에 단일 요소에만 연결 가능
  • 이 경우 template 요소에 v-if를 사용하여 하나 이상의 요소에 대해 적용할 수 있음
    (v-else, v-else-if 모두 적용가능)
<template v-if="name === 'Cathy'">
  <div>Cathy입니다</div>
  <div>나이는 30살입니다</div>
</template>

🔷 HTML <template> element

  • 페이지가 로드 될 때 렌더링 되지 않지만 JavaScript를 사용하여 나중에 문서에서
    사용할 수 있도록 하는 HTML 을 보유하기 위한 메커니즘
  • “보이지 않는 wrapper 역할”

💡 실제로 template 태그는 렌더링 후 확인해보면 보이지 않는다.

🔷 v-show

  • 표현식 값의 T/F 를 기반으로 요소의 가시성을 전환
  • CSS display 속성만 전환하기 때문에 v-show 요소는 항상 렌더링 되어 DOM에 남아 있다.
<div v-show="isSeen">이게 보여?</div>

🔷 v-if vs v-show

  • v-if (Cheap initial load, expensive toggle)
    • 초기 조건이 false 인 경우 아무 작업도 수행하지 않음
    • 토글 비용이 높음
  • v-show (Expensive initial load, cheap toggle)
    • 초기 조건에 관계 없이 항상 렌더링
    • 초기 렌더링 비용이 더 높음

💡 무언가를 매우 자주 전환해야 하는 경우에는 v-show를,
실행 중에 조건이 변경되지 않는 경우에는 v-if를 권장한다.


📌 List Rendering

🔷 v-for

  • 소스 데이터를 기반으로 요소 또는 템플릿 블록을 여러 번 렌더링
  • v-for 는 alias in expression 형식의 특수 구문을 사용하여 반복되는 현재 요소에 대한 별칭(alias)을 제공
  • 인덱스(객체에서는 키)에 대한 별칭을 지정할 수 있음
  • 배열 및 객체 반복 렌더링이 가능하고 template을 이용해 하나 이상의 요소에 대해 반복 렌더링 하거나 중첩된 v-for를 사용할 수 있다.
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <div v-for="p in myArr">
        {{p.name}} / {{p.age}}
      </div>
      <hr>
      <!-- p, index 순서가 매우 중요! -->
      <div v-for="(p, index) in myArr">
        {{index}} : {{p.name}} / {{p.age}}
      </div>
      <hr>
      <div v-for="value in myObj">
        {{value}}
      </div>
      <hr>
      <div v-for="(value, key) in myObj">
        {{key}} : {{value}}
      </div>
      <hr>
      <!-- 객체에서 값, 키, 인덱스 순 -->
      <div v-for="(value, key, index) in myObj">
        {{index}} - {{key}} : {{value}}
      </div>
      <hr>
      <!-- 템플릿으로 여러 요소 for 적용 -->
      <ul>
        <template v-for="item in myArr">
          <li>{{item.name}}</li>
          <li>{{item.age}}</li>
        </template>
      </ul>
      <hr>
      <!-- 중첩된 for문 -->
      <ul v-for="item in myInfo">
        <li v-for="friend in item.friends">
          {{item.name}}는 {{friend}}와 친구라고 생각합니다.
        </li>
      </ul>
      <hr>
      
    </div>
    <script>
      const { createApp, ref } = Vue;

      const app = createApp({
        setup() {
          const myArr = ref([
            { name: "Alice", age: 20 },
            { name: "Bella", age: 21 },
          ]);
          const myObj = ref({
            name: "Cathy",
            age: 30,
          });

          // nested v-for
          const myInfo = ref([
            { name: "Alice", age: 20, friends: ["Bella", "Cathy", "Dan"] },
            { name: "Bella", age: 21, friends: ["Alice", "Cathy"] },
          ]);

          return {
            myArr,
            myObj,
            myInfo,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

🔷 v-for with key

  • 🌟 반드시 v-for와 key를 함께 사용한다.
    • 내부 컴포넌트의 상태를 일관되게 유지
    • 데이터의 예측 가능한 행동을 유지 (Vue 내부 동작 관련)
  • key는 반드시 각 요소에 대한 고유한 값을 나타낼 수 있는 식별자여야 한다.
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <!-- 순서를 일관되게 유지(객체에 유니크함을 부여)하기 위해 id를 key에 바인딩하여 사용 (정석) -->
      <!-- 인덱스는 데이터를 넣고 뺌에 따라 변할 수 있기 때문에 키로 사용할 수 없다! -->
      <div v-for="item in items" :key="item.id">
        {{item.name}}
      </div>
    </div>
    <script>
      const { createApp, ref } = Vue;

      const app = createApp({
        setup() {
          let id = 0;

          const items = ref([
            { id: id++, name: "Alice" },
            { id: id++, name: "Bella" },
          ]);

          return {
            items,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

🔷 v-for with v-if

  • 🌟 동일 요소에 v-for와 v-if를 함께 사용하지 않는다
    • 동일한 요소에서 v-if가 v-for보다 우선순위가 더 높기 때문
    • v-if 조건은 v-for 범위의 변수에 접근할 수 없음
    • 이미 처리한 데이터 출력을 위해 computed와 필터를 이용할 수 있다.
    • 또는 v-if가 v-for보다 더 높은 우선순위를 가지고 있는 점을 대비하기 위해 template 요소를 사용하여 v-if를 이동한다.
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <h3>완료된 일</h3>
      <ul>
        <li v-for="todo in completeTodos" :key="todo.id">
          {{todo.name}}
        </li>
      </ul>
      <hr>
      <h3>미완료된 일</h3>
      <ul>
        <!-- 2. template을 통해 for와 if의 순서를 바꿔 우선순위 영향 안받게 하기 -->
        <template v-for="todo in todos" :key="todo.id">
          <li v-if="!todo.isComplete">
            {{todo.name}}
          </li>
        </template>
      </ul>
    </div>
    <script>
      const { createApp, ref, computed } = Vue;

      const app = createApp({
        setup() {
          let id = 0;

          const todos = ref([
            { id: id++, name: "복습", isComplete: true },
            { id: id++, name: "예습", isComplete: false },
            { id: id++, name: "저녁식사", isComplete: true },
            { id: id++, name: "노래방", isComplete: false },
          ]);

          //1. computed를 통해 미리 필터링 후 보내기
          const completeTodos = computed(() => {
            return todos.value.filter((todo) => {
              return todo.isComplete;
            })
          })

          return {
            todos,
            completeTodos,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

Vue Style Guide에 따라 필수(Essential)적이다.


📌 Watchers

🔷 watch()

  • 반응형 데이터를 감시하고, 감시하는 데이터가 변경되면 콜백 함수를 호출
    variable : 감시하는 변수
    newValue : 감시하는 변수가 변화된 값, 콜백 함수의 첫번째 인자
    oldValue : 콜백 함수의 두번째 인자
<!DOCTYPE html>
<html lang="ko">
  <head>
    <meta charset="UTF-8" />
    <title>Vue</title>
    <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
  </head>

  <body>
    <div id="app">
      <!--1. 감시하는 변수에 변화가 생겼을 때 기본 동작 확인하기-->
      <p>{{count}}</p>
      <button @click="count++">증가</button>
      <hr>
      <!--2. 감시하는 변수에 변화가 생겼을 때 연관 데이터 업데이트 하기
-->
      <input type="text" v-model="message"/>
      <p>{{messageLength}}</p>
    </div>
    <script>
      const { createApp, ref, watch } = Vue;

      const app = createApp({
        setup() {
          const count = ref(0);
          const message = ref('');
          const messageLength = ref(0);
          //첫번째 인자: 감시할 대상, 두번째 인자: 콜백함수(변화한 값, 변화 전 값)
          const countWatch = watch(count, (newValue, oldValue) => {
            console.log(`new : ${newValue} / old: ${oldValue}`);
          });

          const messageWatch = watch(message, (newValue, oldValue) => {
            messageLength.value = newValue.length;
          })

          //watch는 반환하지 않아도 괜찮다
          return {
            count,
            message,
            messageLength,
          };
        },
      });
      app.mount("#app");
    </script>
  </body>
</html>

🔷 computed와 watchers는 데이터의 변화를 감지하고 처리한다는 공통점이 있지만 다음과 같은 차이를 보인다.

ComputedWatchers
동작의존하는 데이터 속성의 계산된 값을 반환특정 데이터 속성의 변화를 감시하고 작업을 수행
사용 목적템플릿 내에서 사용되는 데이터 연산용데이터 변경에 따른 특정 작업 처리용
사용 예시연산 된 길이, 필터링 된 목록 계산 등비동기 API 요청, 연관 데이터 업데이트 등

❗ computed와 watch 모두 의존(감시)하는 원본 데이터를 직접 변경하지 않는다!


📌 Lifecycle Hooks

🔷 Vue 인스턴스의 생애주기 동안 특정 시점에 실행되는 함수

  • 개발자가 특정 단계에서 의도하는 로직이 실행될 수 있도록 함
<!DOCTYPE html>
<html lang="ko">

<head>
  <meta charset="UTF-8" />
  <title>Vue</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>

<body>
  <div id="app">
    <button @click="count++">증가</button>
    <p>{{count}}</p>
    <p>{{message}}</p>
  </div>
  <script>
    const { createApp, ref, onMounted, onUpdated } = Vue;

    const app = createApp({
      setup() {
        const count = ref(0);
        const message = ref(null);
        //이미지 API 요청 메서드 생성 후

        //1. Vue 컴포넌트 인스턴스가 초기 렌더링 및 DOM 요소 생성이 완료된 후 특정 로직을 수행하기
        onMounted(() => {
          //이미지 호출 API 메서드 호출
          console.log("연결 완료")
        });

        //▪ 2. 반응형 데이터의 변경으로 인해 컴포넌트의 DOM이 업데이트된 후 특정 로직 수행하기
        onUpdated(() => {
          message.value = "update";
          console.log("update");
        });

        return {
          count,
          message,
        };
      },
    });
    app.mount("#app");
  </script>
</body>

</html>

  • Vue는 Lifecycle Hooks에 등록된 콜백 함수들을 인스턴스와 자동으로 연결함

    • 이렇게 동작하려면 Hooks 함수들은 반드시 동기적으로 작성되어야 한다.
  • 인스턴스 생애 주기의 여러 단계에서 호출되는 다른 Hooks도 있으며, 가장 일반적으로 사용되는 것은 onMounted, onUpdated, onUnmounted

💡 생명주기 훅에 대한 더 자세한 내용


어후... 외울게 많다...

profile
Hodie mihi, Cras tibi

0개의 댓글

관련 채용 정보