VUE

[VUE] 2. Vue 핵심문법 알아보기 / typescript 모듈 또는 해당 형식 선언을 찾을 수 없습니다. 오류 해결

예글 2024. 4. 9. 16:38

1️⃣ UI를 컨트롤하는 디렉티브 - UserInterface

1. 선언적 렌더링

  • Vue.js 데이터바인딩의 가장 기본
  • 데이터 바인딩의 가장 기본적인 형태: 이중 중괄호 {{ }} 문법을 사용한 텍스트 보간법
  • 이중 중괄호는 데이터를 HTML이 아닌 일반 텍스트로 해석. 실제 HTML을 출력하려면 v-html 디렉티브를 사용

 

2. Class와 Style 바인딩

1) HTML Class 바인딩

  • HTML 클래스 바인딩을 하기 위해선 v-bind: class (축약형 - :class)를 사용
  • 객체로 바인딩 되며 클래스를 동적으로 토글하기 위해 객체를 :class에 전달할 수 있음
<div :class="{ active: isActive }"></div>
  • isActive: state 영역 즉, data 영역에 선언한 임의의 변수이며 이 변수는 true와 flase 값을 가짐

 

2) HTML Style 바인딩

  • v-bind 디렉티브를 활용하여 클래스 바인딩과 동일하고 객체로 바인딩함
  • :style은 HTML Element의 style 속성에 해당하는 자바스크립트 객체에 대해 바인딩을 지원
<div :style="{ color: activeColor, fontSize: fontSize + 'px' }"></div>
  • 해당 프로퍼티 속성은 카멜케이스로 작성

 

<template>
  <div>{{ rawHtml }}</div>
  <div>{{ rawHtml2 }}</div>
  <!-- html처럼 사용하기 위해서 -->
  <h1 v-html="rawHtml2"></h1>

  <h2 v-bind:class="{ active: isActive }">클래스 바인딩 테스트입니다.</h2>
  <!-- 축약형 -->
  <h2 :class="{ active: isActive }">클래스 바인딩 테스트입니다.</h2>
  <button @click="change">버튼</button>

  <h3 style="color: red; font-size: 24px">스타일 바인딩 테스트입니다.</h3>
  <h3 :style="{ color: fontColor, fontSize: fontSize + 'px' }">
    스타일 바인딩 테스트입니다.
  </h3>
</template>

<script>
export default {
  data() {
    return {
      rawHtml: "이것은 텍스트입니다.",
      rawHtml2: '<span style="color: red">이것은 빨간색이어야 합니다.</span>',
      isActive: false,
      fontColor: "#888888",
      fontSize: 48,
    };
  },
  methods: {
    change() {
      this.isActive = !this.isActive;
    },
  },
};
</script>

<style scoped>
h2.active {
  color: green;
}
</style>

 

3. 조건부 렌더링

 

1) v-if / v-else-if / v-else

  • 조건부로 블록을 렌더링하는 데 사용
  • 블록은 디렉티브 표현식이 truthy값을 반환하는 경우에만 렌더링 됨
  • 전환 비용이 더 높음 -> 실행 중에 조건이 변경되지 않을 경우에 사용

 

2) v-show

  • v-if와 사용법이 크게 다르지 않음
  • 다만, v-show가 있는 엘리먼트는 항상 렌더링 되어 DOM에 남아있다는 것이 차이점
  • v-show는 엘리먼트의 display CSS 속성만 전환이 됨
  • 초기 렌더링 비용이 더 높음 -> 매우 자주 전환해야 하는 경우에 사용
<template>
  <div>
    <div v-if="isVisible" class="red"></div>
    <div v-if="isVisible" class="blue"></div>
    <div v-else class="black"></div>

    <div v-if="count > 1" class="red"></div>
    <div v-else class="blue"></div>
    <button @click="count++">증가</button>
    <button @click="count--">감소</button>

    // v-show는 false여도 다 렌더링 됨
    <div v-show="isVisible" class="red"></div>
    <div v-show="!isVisible" class="blue"></div>
    
    // v-if는 ture인 것만 렌더링 됨
    <div v-if="isVisible" class="black"></div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isVisible: false,
      count: 0,
    };
  },
};
</script>

 

4. 리스트 렌더링

  • 배열 데이터를 기반으로 동일한 구조의 UI를 반복호출하는 기능
  • v-for 디렉티브 사용
  • 배열을 리스트로 렌더링 할 수 있음
  • item in items 형식의 특별한 문법이 필요 (item: 배열 내 반복되는 엘리먼트의 별칭 / items: 선언한 배열 데이터)
  • 객체의 속성을 반복하는 데 사용 가능
  • 순회순서: 해당 객체를 Object.keys()를 호출한 결과에 기반
<template>
  <div>
    <li v-for="item in sampleArray" :key="item">{{ item }}</li>
    <li v-for="user in otherArray" :key="user.id">
      {{ user.id }} / {{ user.name }}
    </li>
  </div>
</template>

<script>
export default {
  data() {
    return {
      sampleArray: ["a", "b", "c", "d"],

      otherArray: [
        { id: 0, name: "John" },
        { id: 1, name: "Kim" },
        { id: 2, name: "Lee" },
        { id: 3, name: "Park" },
      ],
    };
  },
};
</script>

<style lang="scss" scoped></style>

 

2️⃣ Data를 컨트롤하는 디렉티브

1. 이벤트 핸들링

1) 인라인 핸들러

  • 이벤트가 트리거 될 때, 실행되는 인라인 JavaScript 기능
  • 어떤 기능을 동작하는 코드가 HTML Element 내에 직접 할당되는 것을 의미

2) 메서드 핸들러

  • 컴포넌트 script 부분에 정의된 메서드(함수)를 이벤트 핸들러에 할당해주는 방식
<template>
  <div>
    <button v-on:click="count++">인라인 핸들러</button>
    <h1>{{ count }}</h1>

    <button v-on:click="changeName">메서드 핸들러</button>
    <h1>{{ name }}</h1>
  </div>
</template>

<script>
export default {
  data() {
    return {
      count: 0,
      name: "Vue.js",
    };
  },

  methods: {
    changeName() {
      this.name = "변경된 텍스트 데이터입니다.";
    },
  },
};
</script>

<style lang="scss" scoped></style>

 

2. Computed

  • 정의: 함수처럼 코드를 작성하지만, return 시키는 데이터를 사용하기 때문에 데이터 취급을 하는 공통적으로 사용되는 로직 혹은 복잡한 로직을 미리 처리하여 반복된 로직처리를 방지하는 계산된 형태의 데이터를 만드는 속성
  • 사용 이유: 너무 많은 연산을 스크립트 혹은 템플릿 HTML 안에서 처리하면 코드가 비대해지고, 유지보수가 어렵다는 치명적인 단점이 있음
  • 연산이 복잡한 형태라면 계산된 데이터 형태로 만드는 computed 속성을 사용해서 해결할 수 있음
<template>
  <h1>{{ text }}</h1>
  <h1>changeText 함수 호출 값: {{ changeText() }}</h1>
  <h1>changeText 함수 호출 값: {{ changeText() }}</h1>
  <h1>changeText 함수 호출 값: {{ changeText() }}</h1>

  <h2>{{ computedText }}</h2>
  <h2>{{ computedText }}</h2>
  <h2>{{ computedText }}</h2>
</template>

<script>
export default {
  data() {
    return {
      text: "Computed 테스트 데이터 문구입니다.",
    };
  },
  // methods 부분에 선언된 함수와 동일한 로직일 때,
  // 캐싱 기능이 없는 methods는 호출될 때마다 console 값이 출력 됨
  // 반면에, computed는 캐싱 기능이 있기 때문에 methods와 어떤 차이점이 있는지 주의 깊게 살펴보는 것이 포인트
  computed: {
    computedText() {
      console.log("Computed 기능을 생성하였습니다.");
      return this.text.split("").reverse().join("");
    },
  },
  methods: {
    changeText() {
      console.log("함수 호출");
      console.log(this.text);

      return this.text.split("").reverse().join("");
    },
  },
};
</script>

<style lang="scss" scoped></style>

  • 똑같이 3번 호출하였음에도 methods는 3번 다 호출 computed는 한 번만 호출

 

3. Watch

  • 어떤 데이터를 감시하고 있다가 그 데이터의 값이 변했을 때, 그것을 감지하여 그와 관련된 함수, 로직, 연산 등 다양한 기능을 추가적으로 활용할 수 있도록 도와주는 속성
  • 데이터 변경에 대한 응답으로 비동기식 또는 시간이 많이 소요되는 조작을 수행하려는 경우에 가장 유용
  • 예) 게시판 페이지를 변경하거나, 선택한 페이지에 대한 리스트만 불러올 경우, 변경된 페이지번호만 감지하여 해당 데이터만 호출할 때 사용
<template>
  <button @click="changeMessage">{{ message }}</button>
</template>

<script>
export default {
  data() {
    return {
      message: "안녕하세요. Vue.js Watch 기능 테스트 오리지널 문구",
    };
  },

  // message라는 변수가 바뀌는 것을 지켜보고 있다가 changeMessage()를 통해 바뀌게 되면 alert창을 띄워줌
  // 데이터 뿐만 아니라 computed로 계산된 형태의 데이터도 watch로 감지할 수 있음
  // 보통 게시판에서 한 컬럼을 선택하였을 때, 고유한 id 값이 바뀜을 감지하고
  // 이때, 그 id 값에 따른 상세 데이터를 호출할 때 주로 사용함
  watch: {
    message() {
      window.alert("message 변수에 담긴 데이터가 변경되었습니다.");
    },
    id() {
      // 해당 상세데이터를 조회하는 api 호출
    },
  },

  methods: {
    changeMessage() {
      console.log("함수 호출");
      this.message = "변경된 message 데이터입니다.";
    },
  },
};
</script>

<style lang="scss" scoped></style>

 

4. Props와 Emits

Props Emits
상위 컴포넌트 -> 하위 컴포넌트 하위 컴포넌트 -> 상위 컴포넌트
OptionsAPI: props:{} 사용
CompositionAPI: defineProps 사용
OptionsAPI: this.$emits 사용
CompositionAPI: defineEmits 사용
Prop 받은 데이터의 타입 설정 첫 번째 인자: 이벤트 이름
두 번째 인자: 보낼 데이터

 

1) Options API - Props

<template>
  <div>
    <ChildComponent
      v-bind:sendProps1="title"
      v-bind:sendProps2="createdAt"
      v-bind:sendProps3="obj"
    />
  </div>
</template>

<script>
import ChildComponent from "./components/ChildComponent.vue";

export default {
  components: {
    ChildComponent,
  },
  data() {
    return {
      title: "부모 컴포넌트에서 선언된 데이터입니다.",
      createdAt: 2024,
      obj: {
        id: 2024,
        name: "John",
      },
    };
  },
};
</script>

<style lang="scss" scoped></style>

 

- ChildComponent.vue

<template>
  <div>{{ sendProps1 }}</div>
  <div>{{ sendProps2 }}</div>
  <div>{{ sendProps3.id }}</div>
  <div>{{ sendProps3.name }}</div>
</template>

<script>
export default {
  props: {
    sendProps1: String,
    sendProps2: Number,
    sendProps3: Object,
  },
  data() {
    return {};
  },
};
</script>

<style lang="scss" scoped></style>

 

2) CompositionAPI - Props

<template>
  <div>
    <ChildComponent
      :sendProps1="title"
      :sendProps2="createdAt"
      :sendProps3="obj"
    />
  </div>
</template>

<script setup lang="ts">
import { reactive, ref } from "vue";
import ChildComponent from "./components/ChildComponent.vue";

interface Obj {
  id: number;
  name: string;
}

const title = ref<string>("부모컴포넌트에서 선언된 데이터입니다.");
const createdAt = ref<number>(2024);
const obj = reactive<Obj>({
  id: 2024,
  name: "John",
});
</script>

<style lang="scss" scoped></style>
  • compositionAPI 에서는 setup을 꼭 추가해줘야 한다.
  • './components/ChildComponent.vue' 모듈 또는 해당 형식 선언을 찾을 수 없습니다.
  • 갑자기 이런 오류가 나타나서 구글링 해봤더니 개발도구인 VSCode에서 개발에 제공되는 내부 타입스크립트 플러그인과 뷰와 관련된 타입스크립트의 중첩으로 발생하는 문제였다. 

https://lts0606.tistory.com/663

 

[Vuejs] 타입스크립트(Typescript) 적용, 모듈 또는 해당 형식 선언을 찾을 수 없습니다.

* 개발도구 : vs코드(VSCode) * 운영체제 : 윈도우 뷰에서 타입스크립트(typescript)를 적용해 보았는데 아무 행위도 안했는데 만났던 오류 입니다. 난 그저 단지 설치하고 소스코드를 눌러서 보려고 했

lts0606.tistory.com

이 블로그에서 하신 대로 했더니 바로 해결!

 

- ChildComponent.vue

<template>
  <div>{{ props.sendProps1 }}</div>
  <div>{{ props.sendProps2 }}</div>
  <div>{{ props.sendProps3.id }}</div>
  <div>{{ props.sendProps3.name }}</div>
</template>

<script setup lang="ts">
interface Obj {
  id: number;
  name: string;
}
interface Props {
  sendProps1: String;
  sendProps2: Number;
  sendProps3: Obj;
}

// import 할 필요 없음
const props = defineProps<Props>();
</script>

<style lang="scss" scoped></style>

<CompositionAPI에서 타입스크립트를 활용하는 방법>

① Prop 받은 데이터 형식에 맞게 타입을 지정해준다.

② Prop 받은 데이터 이름 자체에 미리 선언해둔 타입을 설정해준다.

interface Obj {
  id: number;
  name: string;
}

interface Props {
  sendProps1: String;
  sendProps2: Number;
  sendProps3: Obj;
}

 

defineProps, toRefs 내장함수를 통해 Prop 받은 데이터를 활용하여 템플릿 부분에 호출하여 사용한다.

  • defineProps는 import 할 필요 X
  • toRefs는 import 해야함
const props = defineProps<Props>();
const { sendProps1, sendProps2, sendProps3 } = toRef(props);

 

3) CompositionAPI - Emits

 

- ChildComponent.vue

<template>
  <button @click="sendEvent">자식컴포넌트에서 만든 버튼</button>
</template>

<script setup lang="ts">
import { ref } from "vue";

const data = ref<string>("자식 컴포넌트에서 선언된 데이터입니다.");
const emit = defineEmits(["send-event"]);
const sendEvent = () => {
  emit("send-event", data.value);
};
</script>

<style lang="scss" scoped></style>

 

- App.vue

<template>
  <div>
    부모컴포넌트 레이아웃
    <ChildComponent @send-event="parentEvent" />
  </div>
</template>

<script setup lang="ts">
import ChildComponent from "./components/ChildComponent.vue";

const parentEvent = (event: string) => {
  console.log(event);
};
</script>

<style lang="scss" scoped></style>

 

4) OptionsAPI - Emits

 

- ChildComponent.vue

<template>
  <button @click="sendEvent">자식컴포넌트에서 만든 버튼</button>
</template>

<script>
export default {
  data() {
    return {
      text: "자식 컴포넌트에서 선언된 데이터입니다.",
    };
  },
  methods: {
    sendEvent() {
      this.$emit("send-event", this.text);
    },
  },
};
</script>

<style lang="scss" scoped></style>

 

- App.vue

<template>
  <div>
    부모컴포넌트 레이아웃
    <ChildComponent @send-event="parentEvent" />
  </div>
</template>

<script>
import ChildComponent from "./components/ChildComponent.vue";

export default {
  components: {
    ChildComponent,
  },
  methods: {
    parentEvent(event) {
      console.log(event);
    },
  },
};
</script>

<style lang="scss" scoped></style>

5. v-model

1) 양방향 데이터 바인딩

  • 'Props와 Emits의 기능이 동시에 진행된다.'

2) input의 기능에 따라

  • 보통 v-model은 input 태그의 inputValue 값과 연관지어 많이 사용함
<template>
  <div>
    <!-- 영어로 입력받을 때 -->
    <input type="text" v-model="inputValue1" />

    <!-- 한글을 입력받을 땐 이 방법이 권장됨 -->
    <input
      type="text"
      :value="inputValue2"
      @input="inputValue2 = $event.target.value"
    />
  </div>
  <div>{{ inputValue1 }}</div>
  <div>{{ inputValue2 }}</div>
</template>

<script>
export default {
  data() {
    return {
      inputValue1: "",
      inputValue2: "",
    };
  },
};
</script>

<style lang="scss" scoped></style>