Vue : Basic Syntax 2

김재훈·2024년 4월 29일

Vue

목록 보기
3/9
post-thumbnail

Computed Properties

  • 계산된 속성

computed()

  • "계산된 속성"을 정의하는 함수

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

computed 기본 예시

  • 할 일이 남았는지 여부에 따라 다른 메시지를 출력하기

js

const todos = ref([
  { text: 'Vue 실습' },
  { text: '자격증 공부' },
  { text: 'TIL 작성' },
])

html

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

템플릿이 복잡해지며 todos에 따라 계산을 수행하게 됨

만약 이 계산을 템플릿에 여러 번 사용하는 경우에는 반복이 발생

  • computed 적용
  • 반응형 데이터를 포함하는 복잡한 로직의 경우 computed를 활용하여 미리 값을 계산하여 계산된 값을 사용

js

const { createApp, ref, computed } = Vue
const restOfTodos = computed(() => {
  return todos.value.length > 0 ? '아직 남았다' : '퇴근!'
})

html

<h2>남은 할 일</h2>
<p>{{ restOfTodos }}</p>

computed 특징

  • 반환되는 값은 computed ref이며 일반 refs와 유사하게 계산된 결과를 .value로 참조할 수 있음 (템플릿에서는 .value 생략 가능)
  • computed 속성은 의존된 반응형 데이터를 자동으로 추적
  • 의존하는 데이터가 변경될 때만 재평가
    • restOfTodos의 계산은 todos에 의존하고 있음
    • 따라서 todos가 변경될 때만 restOfTodos가 업데이트

js

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

Computed vs. Methods

  • computed 속성 대신 method로도 동일한 기능을 정의할 수 있음

js

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

html

<p>{{ getRestOfTodos() }}</p>
  • 차이
    • computed 속성은 의존된 반응형 데이터를 기반으로 캐시(cached)된다
    • 의존하는 데이터가 변경된 경우에만 재평가됨
    • 즉, 의존된 반응형 데이터가 변경되지 않는 한 이미 계산된 결과에 대한 여러 참조는 다시 평가할 필요 없이 이전에 계쇤된 결과를 즉시 반환

반면, method 호출은 다시 렌더링이 발생할 때마다 항상 함수를 실행

computed와 method의 적절한 사용처

computedmethod
의존된 데이터가 변경되면 자동으로 업데이트호출해야만 실행됨
  • computed

    • 의존하는 데이터에 따라 결과가 바뀌는 계산된 속성을 만들 때 유용
    • 동일한 의존성을 가진 여러 곳에서 사용할 때 계산 결과를 캐싱하여 중복 계산 방지
  • method

    • 단순히 특정 동작을 수행하는 함수를 정의할 때 사용
    • 데이터에 의존하는지 여부와 관계없이 항상 동일한 결과를 반환하는 함수

Cache (캐시)

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

Cache 예시

  • "웹 페이지의 캐시 데이터"
    • 과거 방문한 적이 있는 페이지에 다시 접속할 경우
    • 페이지 일부 데이터를 브라우저 캐시에 저장 후 같은 페이지에 다시 요청시 모든 데이터를 다시 응답받는 것이 아닌 일부 캐시된 데이터를 사용하여 더 빠르게 웹 페이지를 렌더링

Conditional Rendering

v-if

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

v-if 예시

  • 'v-else' directive를 사용하여 v-if에 대한 else 블록을 나타낼 수 있음

js

const isSeen = ref(true)

html

<p v-if="isSeen">true일 때 보여요</p>
<p v-else>false일때 보여요</p>
<button @click="isSeen = !isSeen">토글</button>
  • 'v-else-if' directive를 사용하여 v-if에 대한 else if 블록을 나타낼 수 있음

js

const name = ref('Cathy')

html

<div v-if="name === 'Alice'">Alice입니다</div>
<div v-else-if="name === 'Bella'">Bella입니다</div>
<div v-else-if="name === 'Cathy'">Cathy입니다</div>
<div v-else>아무도 아닙니다.</div>

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

  • HTML template 요소에 v-if를 사용하여 하나 이상의 요소에 대해 적용할 수 있음(v-else, v-else-if 모두 적용 가능)

html

<template v-if="name === 'Cathy'">
  <div>Cathy입니다</div>
  <div>나이는 30살입니다</div>
</template>

HTML <template> element

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

"보이지 않는 wrapper 역할"

v-show

  • 표현식 값의 true/false를 기반으로 요소의 가시성(visibility)을 전환

v-show 예시

  • v-show 요소는 항상 DOM에 렌더링되어 있음
  • CSS display 속성만 전환하기 때문

js

const isShow = ref(false)

html

<div v-show="isShow">v-show</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

  • 소스 데이터를 기반으로 요소 또는 템플릿 블록을 여러 번 렌더링
    • 소스 데이터 : Array, Object, Number, String, Iterable

v-for 구조

  • v-for는 alias in expression 형식의 특수 구문을 사용

html

<div v-for="item in items">
  {{ item.text }}
</div>
  • 인덱스(객체에서는 key)에 대한 별칭을 지정할 수 있음

js

<div v-for="(item, index) in arr"></div>

<div v-for="value in object"></div>
<div v-for="(value, key) in object"></div>
<div v-for="(value, key, index) in object"></div>
  • 배열 반복

js

const myArr = ref([
  { name: 'Alice', age: 20 },
  { name: 'Bella', age: 21 },
])

html

<div v-for="(item, index) in myArr">
  {{ index }} / {{ item }}
</div>
  • 객체 반복

js

const myInfo = ref([
  { name: 'Alice', age: 20, friends: ['Bella', 'Cathy', 'Dan'] },
  { name: 'Bella', age: 21, friends: ['Alice', 'Cathy'] },
])

html

<div v-for="(value, key, index) in myObj">
  {{ index }} / {{ key }} / {{ value }}
</div>
  • 여러 요소에 대한 v-for 적용
    • HTML template 요소에 v-for를 사용하여 하나 이상의 요소에 대해 반복 렌더링할 수 있음

html

<ul>
  <template v-for="item in myArr">
    <li>{{ item.name }}</li>
    <li>{{ item.age }}</li>
    <hr />
  </template>
</ul>

중첩된 v-for

  • 각 v-for 범위는 상위 범위에 접근할 수 있음

js

const myInfo = ref([
  { name: 'Alice', age: 20, friends: ['Bella', 'Cathy', 'Dan'] },
  { name: 'Bella', age: 21, friends: ['Alice', 'Cathy'] },
])

html

<ul v-for="info in myInfo">
  <li v-for="friend in info.friends">
    {{ info.name }}의 친구들 - {{ friend }}
  </li>
</ul>

v-for with key

  • 반드시 v-for와 key를 함께 사용한다
    • 내부 컴포넌트의 상태를 일관되게 하여 데이터의 예측 가능한 행동을 유지하기 위함
    • key는 반드시 각 요소에 대한 고유한 값을 나타낼 수 있는 식별자여야 함

js

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

html

<div v-for="item in items" :key="item.id">
  <p>{{ item }}</p>
</div>

내장 특수 속성 key

Vue 내부 동작 관련 부분이기에 최대한 작성하려고 노력할 것

v-for with v-if

동일 요소에 v-for와 v-if를 함께 사용하지 않는다

  • 동일한 요소에서 v-if가 v-for보다 우선순위가 더 높기 때문

v-if에서의 조건은 v-for 범위의 변수에 접근할 수 없음

  • todo 데이터 중 이미 처리한(isComplete === true) todo만 출력하기
const todos = ref([
  { id: id++, name: '복습', isComplete: true },
  { id: id++, name: '예습', isComplete: false },
  { id: id++, name: '저녁식사', isComplete: true },
  { id: id++, name: '노래방', isComplete: false },
])
  • v-if가 더 높은 우선순위를 가지므로
    v-for 범위의 todo 데이터를 v-if에서 사용할 수 없음
<ul>
  <li v-for="todo in todos" v-if="todo.isComplete === false" :key="todo.id">{{ todo.name }}</li>
</ul>

v-for와 v-if 해결법 2가지

  1. computed 활용
    • computed를 활용해 필터링 된 목록을 반환하여 반복하도록 설정

js

const completeTodos = computed(() => {
  return todos.value.filter((todo) => 
    !todo.isComplete)
})

html

<ul>
  <li v-for="todo in completeTodos" :key="todo.id">
    {{ todo.name }}
  </li>
</ul>
  1. v-for와 <template> 요소 활용
    • v-for와 template 요소를 사용해서 v-if 위치를 이동

html

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

Watchers

watch()

  • 하나 이상의 반응형 데이터를 감시하고, 감시하는 데이터가 변경되면 콜백 함수를 호출

watch 구조

watch(source, (newValue, oldValue) => {
  // do something
})
  • 첫 번째 인자 (source)
    • watch가 감시하는 대상 (반응형 변수, 값을 변환하는 함수 등)
  • 두 번째 인자 (callback function)
    • source가 변경될 때 호출되는 콜백 함수
    1. newValue
      • 감시하는 대상이 변화된 값
    2. oldValue (optional)
      • 김사하는 대상의 기존 값

watch 기본 동작

js

const count = ref(0)

watch(count, (newValue, oldValue) => {
  console.log(`변화된 값: ${newValue} / 변하기 전 값: ${oldValue}`)
})

html

<button @click="count++">Add 1</button>
<p>Count: {{ count }}</p>
  • 감시하는 변수에 변화가 생겼을 때 연관 데이터 업데이트하기

js

const message = ref('')
const messageLength = ref(0)

watch(message, (newValue) => {
  messageLength.value = newValue.length
})

html

<input v-model="message" />
<p>Message length: {{ messageLength }}</p>

computed vs. watch

Computedwatchers
공통점데이터의 변화를 감지하고 처리데이터의 변화를 감지하고 처리
동작의존하는 데이터 속성의 계산된 값을 반환특정 데이터 속성의 변화를 감시하고 작업을 수행 (side-effects)
사용 목적계산한 값을 캐싱하여 재사용 중복 계산 방지데이터 변화에 따른 특정 작업을 수행
사용 예시연산된 길이, 필터링된 목록 계상 등DOM 변경,
다른 비동기 작업 수행, 외부 API와 연동 등

Lifecycle Hooks

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

Lifecycle Hooks Diagram

Lifecycle Hooks 예시

  1. Vue 컴포넌트 인스턴스가 초기 렌더링 및 DOM 요소 생성이 완료된 후 특정 로직을 수행하기

  2. 반응형 데이터의 변경으로 인해 컴포넌트의 DOM이 업데이트된 후 특정 로직을 수행하기

Lifecycle Hooks 특징

  • Vue는 Lifecycle Hooks에 등록된 콜백 함수들을 인스턴스와 자동으로 연결함
  • 이렇게 동작하려면 hooks 함수들은 반드시 동기적으로 작성되어야 함
  • 인스턴스 생애 주기의 여러 단계에서 호출되는 다른 hooks도 있으며,
    가장 일반적으로 사용되는 것은 onMounted, onUpdated, onUnmounted
  • https://vuejs.org/api/composition-api-lifecycle.html

Vue3 컴포넌트 생명주기 훅 (Lifecycle hooks)

  • 컴포넌트의 생성부터 파괴에 이르는 다양한 단계에서 실행되어 특정 작업을 수행할 수 있게 해줍니다.
  • 각 훅은 특정 시점에 자동으로 호출되며, 컴포넌트의 상태를 효과적으로 관리할 수 있도록 도와줍니다
  • 사용 이유 : 초기화 및 설정 (onBeforeMount 및 onMounted와 같은 훅 사용), 반응적 데이터 관리(onBeforeUpdate와 onUpdated를 사용하여 컴포넌트의 데이터가 변경될 때 수행해야 할 특정 작업을 관리) 등
  • 공식 문서 : https://v3-docs.vuejs-korea.org/api/composition-api-lifecycle.html

주요 생명주기 훅 설명

  1. onBeforeMount: 컴포넌트가 마운트되기 전에 호출됩니다. DOM이 생성되기 전에 실행되어, 필요한 설정이나 초기화 작업을 수행할 수 있습니다.
  2. onMounted: 컴포넌트가 마운트된 후, 즉 DOM에 인스턴스가 부착된 직후에 호출됩니다. 이 시점에서 DOM 요소에 접근할 수 있으며, 외부 라이브러리를 연동하거나 추가적인 DOM 조작을 수행할 수 있습니다.
  3. onBeforeUpdate: 컴포넌트가 업데이트되기 전에 호출됩니다. 반응형 데이터가 변경되어 DOM이 업데이트되기 전에 필요한 로직을 수행할 수 있습니다.
  4. onUpdated: 컴포넌트의 데이터가 변경되어 DOM이 업데이트된 후 호출됩니다. 업데이트된 DOM에 대한 추가적인 처리를 수행할 수 있습니다.
  5. onBeforeUnmount: 컴포넌트가 언마운트되기 전에 호출됩니다. 이 훅에서는 타이머 제거, 이벤트 리스너 해제 등과 같은 정리 작업을 수행할 수 있습니다.
  6. onUnmounted: 컴포넌트가 완전히 언마운트된 후 호출됩니다. 모든 정리 작업이 완료되고, 컴포넌트가 DOM에서 제거된 후에 실행됩니다.

예시 코드

<template>
  <div>
    <h1>{{ count }}</h1>
    <button @click="increment">Increment</button>
  </div>
</template>

<script>
import { ref, onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue';

export default {
  setup() {
    const count = ref(0);

    // onBeforeMount
    onBeforeMount(() => {
      console.log('Component is about to be mounted');
    });

    // onMounted
    onMounted(() => {
      console.log('Component has been mounted');
    });

    // onBeforeUpdate
    onBeforeUpdate(() => {
      console.log('Component will update');
    });

    // onUpdated
    onUpdated(() => {
      console.log('Component has updated');
    });

    // onBeforeUnmount
    onBeforeUnmount(() => {
      console.log('Component will unmount');
    });

    // onUnmounted
    onUnmounted(() => {
      console.log('Component has been unmounted');
    });

    function increment() {
      count.value++;
    }

    return { count, increment };
  }
};
</script>

<style>
/* CSS here */
</style>

Vue Style Guides

  • Vue의 스타일 가이드 규칙은 우선순위에 따라 4가지 범주로 나눔
  • 규칙 범주
    • 우선순위 A: 필수 (Essential)
      • 오류를 방지하는데 도움이 되므로 어떤 경우에도 규칙을 학습하고 준수
      1. v-for에 key 작성하기
      2. 동일 요소에 v-if와 v-for 함께 사용하지 않기
    • 우선순위 B: 적극 권장 (Strongly Recommended)
      • 가독성 및/또는 개발자 경험을 향상시킴
      • 규칙을 어겨도 코드는 여전히 실행되겠지만, 정당한 사유가 있어야 규칙을 위반할 수 있음
    • 우선순위 C: 권장 (Recommended)
      • 일관성을 보장하도록 임의의 선택을 할 수 있음
    • 우선순위 D: 주의 필요 (Use with Caution)
      • 잠재적 위험 특성을 고려함
  • https://vuejs.org/style-guide/

참고

computed 주의사항

  • computed의 반환 값은 변경하지 말것

    • computed의 반환값은 의존하는 데이터의 파생된 값
      • 이미 의존하는 데이터에 의해 계산이 완료된 값
    • 일종의 snapshot이며 의존하는 데이터가 변경될 때만 새 snapshot이 생성됨
    • 계산된 값은 읽기 전용으로 취급되어야하며 변경되어서는 안됨
    • 대신 새 값을 얻기 위해서는 의존하는 데이터를 업데이트해야 함
  • computed 사용시 원본 배열 변경하지 말것

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

배열과 v-for 관련

  • v-for와 배열을 함께 사용시 배열의 메서드를 주의해서 사용해야 함
  1. 변화 메서드
    • 호출하는 원본 배열을 변경
    • push(), pop(), shift(), unshift(), splice(), sort(), reverse()
  2. 배열 교체
    • 원본 배열을 수정하지 않고 항상 새 배열을 반환
    • filter(), concat(), slice()

v-for와 배열을 활용해 "필터링/정렬" 활용하기

  • 원본 데이터를 수정하거나 교체하지 않고 필터링하거나 정렬된 새로운 데이터를 표시하는 방법
  1. computed 활용
    • 원본 기반으로 필터링 된 새로운 결과를 생성

js

const numbers = ref([1, 2, 3, 4, 5])

const evenNumbers = computed(() => {
  return numbers.value.filter((number) => number % 2 === 0)
})

html

<li v-for="num in evenNumbers">
  {{ num }}
</li>
  1. method 활용 (computed가 불가능한 중첩된 v-for의 경우 사용)
    • computed가 불가능한 중첩된 v-for의 경우

js

const numberSets = ref([
  [1, 2, 3, 4, 5],
  [6, 7, 8, 9, 10]
])

const evenNumbers = function (numbers) {
  return numbers.filter((number) => number % 2 === 0)
}

html

<ul v-for="numbers in numberSets">
  <li v-for="num in evenNumbers(number)">
    {{ num }}
  </li>
</ul>

배열의 인덱스를 v-for의 key로 사용하지 말 것

  • 인덱스는 식별자가 아닌 배열의 항목 위치만 나타내기 때문
  • 만약 새 요소가 배열의 긑이 아닌 위치에 삽입되면 이미 반복된 구성 요소 데이터가 함께 업데잍되지 않기 때문

직접 고유한 겂을 만들어내는 메서드를 만들거나 외부 라이브러리 등을 활용하는 등 식별자 역할을 할 수 있는 값을 만들어 사용

0개의 댓글