cyphen156

Vue3 핵심 문법-1 본문

웹/Vue

Vue3 핵심 문법-1

cyphen156 2023. 9. 5. 18:37

Single File Component

vue의 컴포넌트를 하나의 파일로 나타내는 것 독립된 하나의 틀을 만들어서 관리하는 것으로 컴포넌트 하나하나가 각각의 독립된 class파일(부품들)이라고 생각하면 편하다.

앞서 장에서 말했듯 컴포넌트 파일은 <temlpate>, <script>, <style>의 세 개의 섹터로 나뉘어 구성된다.

  • <templete> : 실제 렌더링 될 body에 해당하는 부분
  • <script> : 이벤트를 처리할 javescript/typescript가 들어가는 부분
  • <style> : 렌더링 될 body를 꾸며주는 css에 해당하는 부분

컴포지션 함수 setup 

스크립트 태그 내에서 template에서 사용할 변수들을 할당하고, 컨트롤하는 부분이다.

객체를 반환하여 여러개의 data를 컨트롤 할 수 있게 한다.

다음은 간단한 setup 함수의 구조다.

setup() {
	const data = 1
    return { data }
}

자세한 사용법은 나중에 추가적으로 후술한다고 한다.

컴포넌트 생명 주기

컴포넌트를 생성해서 DOM 노드 트리에 마운트하거나 불필요한 요소를 제거해 렌더링을 최적화 시켜주는것을 생명주기라고 한다.

각 생명주기를 컨트롤 하는것을 생명주기 훅이라고 하고, 지정된 옵션 함수를 통해 컴포넌트를 후킹할 수 있다.

  • beforeCreate : 컴포지션 api의  setup함수와 동일한 역할을 한다. 컴포넌트가 생성되기 전에 호출되기 때문에 watch와 같은 함수들이 동작하지 않는다. 아직 존재하지 않는데 어떻게 쓴다는 말인가?
  • created : beforeCreate와 같이 setup함수가 대체 할 수 있다. 하지만 생성될 때를 의미하기 때문에 data들을 초기화 할 때 사용한다.
  • beforeMount(onBeforeMount) : Vue의 가상 노드가 render함수를 호출하기 직전에 호출된다. onRenderTracted를 통해 관찰할 수 있다.
  • mounted(onMounted) : 실제로 렌더링되어 마운트 된 상태 이제부턴 data의 값들을 실제로 사용할 수 있다.
  • beforeUpdate(onBeforeUpdated) : 데이터가 변경되었지만 아직 재 랜더링 되지 않았을 때 사용한다. 
  • updated(onUpdated) : 데이터가 업데이트 된 이후 변경된 값을 사용할 때 사용한다. 주의할 점은 데이터가 변경되었지만 아직 모든 하위 컴포넌트들에게 전달하여 업데이트 된 것을 보장하지 않기 때문에 사용에 주의해야 한다. 이를 방지하려면 nextTick을 이용한다. updated() {this.$nextTick()}
  • beforeUnmount(onBeforeUnmount) : 컴포넌트를 마운트 하기 전에 처리해야될 것들을 할때 쓴다. ex) 에러 방지
  • unmounted(onUnmounted) : 마운트 된 컴포넌트를 없앤 뒤 사용한다. 모든 디렉티브와 이벤트가 사용 불가능해진다.
  • activated(onActivated) : keep-alive태그를 통해 컴포넌트가 다시 렌더링하는 것을 방지하고 상태(데이터)를 유지하기 위해 사용된다. 일반적으로 v-is 디렉티브와 함께 쓰인다.
  • deactivated(onDeactivated) : 상태를 유지하던 컴포넌트가 효력을 상실하면 호출된다. 보존되던 데이터의 소실을 의미한다.
  • renderTracked(onRenderTracked) : Virtual DOM이 변경될 때마다 관찰을 목적으로 호출된다. DebuggerEvent객체를 살펴보면 어떤 이유로 Virture DOM이 변경되는지 알 수 있고, 내부에 target속성을 통해 변경의 원인을 추적할 수 있다. renderTracted(e) {consloe.log(e.target)}
  • renderTriggered(onRenderTriggered) : Virture DOM이 실제 DOM으로 반영되어 재렌더링 되어야 할 때 호출된다. 
  • errorCaptured(onErrorCaptured) : 자식 컴포넌트에 에러가 발생하면 어떤 컴포넌트에서 에러가 발생했는지를 알려준다.

선언적 렌더링

Vue에는 선언적 렌더링이라는 것이 있다.

공식 문서의 설명으로는

  • 선언적 렌더링(Declarative Rendering): Vue는 표준 HTML을 템플릿 문법으로 확장하여 JavaScript 상태(State)를 기반으로 화면에 출력될 HTML을 선언적(declaratively)으로 작성할 수 있습니다.

이렇게 되어있고, 이것을 GPT4에게 물어보면 이렇게 설명해준다.

Vue.js 3에서의 선언적 렌더링(Declarative Rendering)은 UI를 구성하는 방법 중 하나로, 원하는 화면의 최종 상태를 선언적으로 작성하는 것을 의미합니다. 
이 방식은 명령적 렌더링(Imperative Rendering)과 대조되는데, 명령적 렌더링에서는 DOM을 직접 조작하여 원하는 화면을 만들어나갑니다.

Vue.js에서는 HTML 템플릿을 사용해 선언적 렌더링을 할 수 있습니다. 
예를 들어, 데이터와 템플릿이 다음과 같다고 가정해 봅시다.

javascript
Copy code
const app = Vue.createApp({
  data() {
    return {
      message: '안녕하세요, Vue.js!'
    }
  }
})
HTML 템플릿에서는 이렇게 작성할 수 있습니다.

html
Copy code
<div id="app">
  {{ message }}
</div>
이렇게 하면, Vue.js는 message 데이터의 현재 값에 따라 자동으로 해당 부분을 업데이트합니다. 
message 값이 변경되면, 렌더링된 내용도 자동으로 변경됩니다.

이처럼 선언적 렌더링을 사용하면 상태에 따라 UI가 어떻게 변경될지를 선언적으로 기술할 수 있어 코드가 더 간결하고 이해하기 쉬워집니다.
또한, Vue.js가 DOM 업데이트를 자동으로 처리해 주기 때문에 개발자는 상태 변화에 따른 DOM 업데이트를 직접 관리할 필요가 없습니다.

즉, 다시말하면 DOM에서 데이터가 변경된 것을 가져와서 재 랜더링 할 때 변수의 위치를 지정해놓고, 어떻게 변할지를 "선언(declare)" 했다고 보면 된다.

맨 처음에는 반응형 웹과 헷갈렸었다.

렌더링 될 타입과 데이터를 선언하고, 타입에 선언된 데이터의 값에 따라 A타입인지, B타입인지를 결정하는 반응형과 관련있는것인줄 알았는데 사실 react-native에서의 prop와 같이 상태의 저장에 관련된 것이었다. 

가장 간단하게 확인하는 방법은 버튼을 만들고 여기에 counter 변수를 통해 렌더링 될 값을 변경시켜 보는 것이다.

버튼을 누를 때마다 보라색 부분이 변경되면서 버튼 안의 숫자가 바뀌어 렌더링 되는 것을 확인할 수 있다.

선언적 렌더링은 스크립트 태그내에서 data 섹션에 선언하거나, 컴포지션 API와 함께 setup함수를 통해 사용된다.

스크립트에 선언된 변수를 템플릿에서 사용하기 위해서는 변수의 값을 두 개의 중괄호{{}}로 감싸면 되는데 이것을 수염표기법(Mustache syntax)이라고 한다.

선언형 변수는 반드시 객체 형식으로 반환되어야 템플릿에서 사용할 수 있다.

예시(SampleButton.vue)

<template>
  <div id="app">
    <button class="purple" @click="incrementCounter">
      {{ counter }}
    </button>
  </div>
</template>

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

<style>
.purple {
  background-color: purple;
  color: white;
}
</style>

ES6 단축 속성을 통한 key-value처리

선언적 렌더링에서 setup함수를 통해 반환받을 때 객체의 키 값과 변수 명이 동일하면 ES6 단축 속성을 통해 변수명을 작성하지 않고 데이터를 가져올 수 있다.

<template>
  <p>{{ date }}<p>
</template>

// 원래의 데이터 리턴방식
setup() {
  const date = new Date();
  return { date: date };
}

// ES6 단축 속성을 통한 데이터 리턴
setup() {
  const date = new Date();
  return { date };
}

 

수염표기법이 아닌 디렉티브 사용하기

데이터를 {{ data }}와 같은 방법이 아닌 다른 방식으로 표기할때는 디렉티브(Directive)표기법을 사용해 직접 데이터를 가져올 수 있다.

컴파일 되는 디렉티브 표기법

  • v-text : <p v-text="msg"></p> == <p>{{ data }}</p>

컴파일 되지 않는 디렉티브 표기법

  • v-html : v-text와는 다르게 innerHTML 값에 변수로 전달하기 때문에 문자열이 마크업 언어로 표기된다.
                 <div v-html="<i>HTML TEXT</i>"></div>
  • v-pre : 해당 엘리먼트와 하위 엘리먼트들을 모두 컴파일하지 않고 그대로 출력한다. 즉, 태그를 그대로 화면에 렌더링한다.
                 <div v-pre>{{ data }}</div> ==> 화면에 렌더링 되는 내용 : {{ data }}

데이터 결합을 통한 사용자 입력 처리(데이터 바인딩)

템플릿 내부의 컴포넌트 변수와 html태그의 속성을 하나로 묶어(매핑하여) 데이터를 렌더링하는 방법이다.

이런걸 어디다가 쓰냐!싶을텐데 지금 딱 떠오르는건 데이터 크롤링(자바스크립트를 통한 데이터 동적 할당)할때 쓰면 엄청 유용할 것 같다. 아니면 실시간으로 올라오는 채팅창 같은걸 구현할 때 유용할 것 같다.

  1. 단방향 결합을 지원하는 바인딩
    • v-bind : v-bind:HTML속성="변수명" or :HTML속성="변수명" ---> <div HTML속성="변수명"></div>
  2. 양방향 결합을 지원하는 바인딩
    • v-model : v-model="변수명" <---> <input value="변수값"> (v-bind와 v-on의 조합을 통해서 구현이 가능하다.)
    • v-model의 변수 값을 변경할 수 있는 수식어(내장함수)
      • .lazy : 즉시 동기화가 아닌 changed 이벤트(submitted)와 동기화되어 데이터가 변경된다.(사용자가 입력을 완료하고 다른 곳을 클릭했을 때)
      • .number : 문자열을 integer로 타입캐스팅한다.
      • .trim : 좌우 공백 제거
      • 사용자 정의 수식어 : 수식어를 사용자가 만들어서 쓴다
        // 나중에 이해되면 설명하겠습니다. 지금은 이해못햇어요 TT 대충 변수 입력값을 직접 제어하는 함수를 사용할 때 쓴다고 생각하면 될것 같습니다.

둘은 뭐가다를까? 기본적인 내용은 단방향이라 함은 웹 앱이 vue를 통해 컴파일 될 때 즉, 화면이 로딩될 때 한번만 데이터를 매핑한다는 것이고, 양방향 결합을 지원하게 되면 데이터가 변할 때 마다 재렌더링 하면서 웹 앱과 브라우저가 상호작용한다는 것이다. 그래서 양방향 결합을 지원하는 v-model은 실시간에 중점을 둔 어플리케이션을 만들 때 자주 사용된다.

예시 코드

App.vue

<template>
  <VueBind></VueBind>
</template>

<script>
import VueBind from './components/VueBind.vue';

export default {
  name: 'App',
  components: {
    VueBind,
}
}
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

VueBind.vue

<template>
    <p>줄임말과 원래말을 입력하세요.</p>
    <input type="text" v-bind:value="abbr" />
    <input type="text" v-model="normal" />
    <hr />
    <abbr v-bind:title="normal">{{ abbr }}</abbr>
</template>

<script>
import { ref } from 'vue';

export default {
    setup () {
        const abbr = ref('DOPT');
        const normal = ref('Dong Project Team');
        return {
            abbr,
            normal,
        }
    }
}
</script>

bind와 model의 차이 로직

※ 반응형 객체(Proxy Pattern Object) 생성을 위한 ref()함수

원래 변수를 const로 선언하면 상수 변수가 되어 할당된 값의 변경을 허용하지 않는다. 하지만 ref()함수를 통해 데이터에 간접적으로 접근하여 원본 데이터를 중간에서 참조를 통해 변경할 수 있도록 허용한다. 즉, C언어에서의 Pointer와 유사한 기능을 수행한다고 보면 적절하다.

vue라이브러리 안에 내장되어있는 ref()함수는 디자인 패턴중 Proxy패턴을 이용해 만들었기 때문에 생성되는 객체가 Proxy 객체인 것이다.

※ setup과는 살짝 다른 script setup

앞서 setup함수를 통해 변수를 바인딩 하게 되면 return 타입을 반드시 객체 형식으로 반환해야 한다고 했다. 

// 일반적인 setup함수 작성법
<script>
import { ref } from 'vue';

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

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

    function sayHello() {
      alert(`Hello, ${name.value}`);
    }

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

그런데 script 태그 내부에 setup함수를 속성으로 선언하면 좀더 간단하게 변수를 DOM객체와 바인딩할 수 있다.

// script태그 내부에 setup함수를 속성으로 적용
<script setup>
import { ref } from 'vue';

const count = ref(0);
const name = ref('John');

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

function sayHello() {
  alert(`Hello, ${name.value}`);
}
</script>

script태그 내부에 setup함수를 속성으로 주면 setup함수를 만들 필요가 없으니 리턴할 객체도 필요하지 않고, components옵션을 사용하지 않기 때문에 value속성을 이용하지 않아도 데이터에 접근이 가능해진다.

 

이벤트 리스너

말그대로 이벤트를 기다리고 있는 리스너를 DOM객체에 등록하는 것이다. 사용자의 입력에 따라 동작을 달리하는 함수를 만들어 웹 앱을 제어할 수 있다.

v-on : v-on:click="스크립트 코드, 또는 함수를 호출" or @click="스크립트 코드, 또는 함수를 호출"

예시(EventListener.vue)

<template>
    <div>
        <p>counter + counter2 : {{ counter + counter2 }}</p>
        <button @click="counter++">클릭하면 counter의 숫자가 올라갑니다.</button>
        <button @click="counter2++">클릭하면 counter2의 숫자가 올라갑니다.</button>
    </div>
    <div>
        <p>counter3 + counter4 : {{ counter3 + counter4 }}</p>
        <button @click="onClick3">클릭하면 counter3의 숫자가 올라갑니다.</button>
        <button @click="onClick4">클릭하면 counter4의 숫자가 올라갑니다.</button>    
    </div>
</template>

<script>
import { ref } from 'vue';

export default {
    data() {
        return {
            counter2: 0,
        };
    },
    setup() {
        let counter = ref(0);
        let counter3 = ref(0);
        let counter4 = ref(0);

        const onClick3 = () => {
            counter3.value++;
        };

        const onClick4 = () => {
            counter4.value++;
        };

        return {
            counter,
            counter3,
            counter4,
            onClick3,
            onClick4,
        };
    },
}
</script>

이벤트 내장 함수(수식어)

  • .stop : event.stopPropagation()을 호출합니다. 즉, 이벤트가 상위 DOM 요소로 전파되는 것을 막습니다.
  • .prevent : event.preventDefault()를 호출합니다. 기본 이벤트를 막습니다 (예: 클릭했을 때 페이지 이동을 막음).
  • .capture
  • .self : 이벤트가 해당 요소에서 발생한 경우에만 핸들러를 실행합니다.
  • .once : 한 번만 실행되는 이벤트 리스너를 추가합니다.
  • .passive : 스크롤 이벤트의 성능을 향상시키기 위해 { passive: true } 옵션으로 이벤트 리스너를 추가합니다.
  • .exact : 정확히 해당 이벤트만을 감지합니다.
  • 마우스 클릭과 관련된 수식어
    • .left
    • .right
    • .middle
  • 키 수식어 : 버튼이 활성화 되어 있을때 키를 누르면 버튼을 누른것(submit)과 같은 동작과 동일한 작동을 하게 만든다.
    ex) <input @keyup.enter />

템플릿 내부 조건문반복문 (v-if, v-else-if, v-else) / (v-for)

변수의 값을 통해 다른 UI를 그릴 수 있도록 허용하는 템플릿 조건문, 일반적인 조건문과 사용방법이 같다.

반응형 웹과 같이 조건부 렌더링을 정의한다고 보면 편하다.

조건문 예시

<template>
    <p> {{ counter }}</p>
    <p v-if="counter <= 5">5보다 작습니다.</p>
    <p v-else-if="counter > 5 && counter <= 10">5초과 10이하입니다.</p>
    <p v-else>10을 초과했습니다.</p>
    <button @click="counter++"></button>
</template>

<script setup>
import { ref } from 'vue';

const counter = ref(0);

</script>

v-show

v-if는 조건이 변경되면 DOM엘리먼트를 처음부터 다시 렌더링하지만 v-show는 일단 모든 조건의 DOM을 그려놓은 후, 비가시모드(hide)시킨다. 메모리 관점에서 보면 메모리를 조금 더 사용하여 미리 그려놓았다가 조건을 만족하면 화면을 재렌더링하여 보여주므로, 조금 더 화면의 변경이 빨라진다는 장점이 있다. 초기 페이지 렌더링보다 이벤트에 따른 빠른 렌더링 변경을 원한다면 v-if보다는 v-show가 더 적절한 선택이 될 수 있다.

v-show예시 코드

<template>
    <p> {{ counter }}</p>
    <p v-show="counter <= 5">5보다 작습니다.</p>
    <p v-show="counter > 5 && counter <= 10">5초과 10이하입니다.</p>
    <p v-show="counter > 10">10을 초과했습니다.</p>
    <button @click="counter++"></button>
</template>

<script setup>
import { ref } from 'vue';

const counter = ref(0);

</script>

반복문 예시

:key="키 값"은 원래 생략해도 되지만 DOM요소들에 대한 고유 식별을 위해서 ES6 권장 사용조건이니 사용하는게 좋습니다.

  • 배열에의 사용
    • v-for="값 in 배열" :key="키 값"
    • v-for="(값, index) in 배열"
  • 객체로의 사용
    • v-for="값 in 객체" :key="키 값"
    • v-for="(값, key) in 객체"
    • v-for="(값, key, index) in 객체"

예시(IfFor.vue)

<template>
    <div>
        <p> {{ counter }}</p>
        <p v-if="counter <= 5">5보다 작습니다.</p>
        <p v-else-if="counter > 5 && counter <= 10">5초과 10이하입니다.</p>
        <p v-else>10을 초과했습니다.</p>
        <button @click="counter++"></button>
    </div>
    <div style="width: 200px">
        <ol>
            <li v-for="item in items" :key="item">{{ item }}</li>
        </ol>
    </div>
</template>

<script setup>
import { ref, reactive } from 'vue';

const counter = ref(0);
const items = reactive(['item1', 'item2', 'item3', 'item4', 'item5'])
</script>

조건부 반응형 객체 렌더링을 위한 reactive()

ref()가 단일 객체에 대한 반응형 함수였다면 reactive()는 다중 데이터를 위한 반응형 함수다.

ref()보다 깊은 반응성(deep reactivity)를 제공해 객체 내부의 모든 중첩된 프로퍼티까지 반응성을 갖는다.

computed 속성

직역하면 "계산된"이라는 뜻을 가지고 있다. 뭐가 계산되어있다는 것일까?

ref와 reactive, watcher가 실시간 데이터의 변경을 감시하는 것을 기본으로 한다면, computed 속성은 "다른 데이터에 의존하여 "계산된 값"을 제공해주는 기능을 한다. v-if와 v-for만으로도 반응형 객체를 통해 웹 앱을 컨트롤 할 수 있지만 조금더 복잡하고, 캐시에 저장되어있는 데이터를 통해 DOM자체를 업데이트 하지 않고 재렌더링 한다는, v-show와 유사한 기능을 하지만 데이터에 따른 렌더링 조을 컨트롤 할 수 있다는 점에서 조금 더 유용하다.

뭐가 다른가를 생각해보면 위의 예시에서 사용한 counter는 반응형 객체로 "그 값이 변경될 때마다" [DOM객체(브라우저)]를 다시 렌더링한다.  처음부터 모든 것을 다시 계산한다는 소리다. 

그런데 이렇게 하면 바뀌지 않는 부분, items 배열에 해당하는 부분도 다시 연산해서 렌더링해야 하기 때문에 시간이 오래 걸린다.

이것을 방지하기 위해 items를 computed속성으로 선언하면 브라우저의 캐시에 items배열의 렌더링 정보를 가져다 놓고, items배열 안의 요소가 변경되는 경우를 제외하면 다시 연산하는것을 방지한다는 것이다.

Computed 사용 예시(ComputedFor.vue)

<template>
    <div>
      <p> {{ counter }}</p>
      <p v-if="counter <= 5">5보다 작습니다.</p>
      <p v-else-if="counter > 5 && counter <= 10">5초과 10이하입니다.</p>
      <p v-else-if="counter < 20">10을 초과했습니다.</p>
      <p v-else>counter : {{ counter }} 이므로 20 이상입니다. items를 한 번 더 렌더링합니다.</p>
      <button @click="counter++">Increase</button>
    </div>
    <div style="width: 200px">
      <ol>
        <li v-for="item in computedItems" :key="item">{{ item }}</li>
      </ol>
    </div>
  </template>
  
  <script setup>
  import { ref, reactive, computed } from 'vue';
  
  const counter = ref(0);
  const items = reactive(['item1', 'item2', 'item3', 'item4', 'item5']);
  const computedItems = computed(() => {
    if (counter.value >= 20) {
      return [...items, ...items];
    }
    return items;
  });
  </script>

20미만일 때
20 이상일 때 다시 한번 렌더링된 items

쓰다보니 너무 길어져서 다음글로 넘기겠다.

거의 책 예제 코드랑 같은것이 없는거 같은데 실습이니까 그냥 넘어가자.

 

모든 예제 코드의 소스파일은 

GitHub - dongprojectteam/vue3_examples

 

GitHub - dongprojectteam/vue3_examples

Contribute to dongprojectteam/vue3_examples development by creating an account on GitHub.

github.com

또는 제 깃허브 레포지토리에 존재합니다.

Workspace/Vue/project at main · cyphen156/Workspace · GitHub

 

 

 

 

 

' > Vue' 카테고리의 다른 글

Vue3 핵심 문법-2  (0) 2023.09.06
Vue3 프로젝트 뜯어보기  (0) 2023.09.01
Vue.js 입문하기  (0) 2023.09.01