Vue2 + TypeScript

Pasted image 20231222163839.png

TypeScript 에서 Class 문법을 권장하지 않는 이유

기존에는 Vue가 Anlgure 처럼 Class 문법을 지향하며, 가길 원했지만 이를 결국은 폐기 시켰습니다.
클래스 문법에 대한 개발자의 생각

Vue.js 에서 TypeScript 적용하는 방법

  1. 서비스를 처음 구축할 때 부터 TypeScript를 적용한다.
  2. 기존에 이미 구현된 서비스에 TypeScript를 점진적으로 적용한다.

프로젝트 구성

! 400

shims-vue.d.ts

// Vue 파일 안에 들어가잇는 것들은 모두 Vue로 해석해
declare module "*.vue" {
  import Vue from "vue";
  export default Vue;
}

실습 프로젝트 내용

할일 관리 앱

  • 할 일 CRUD

단축키

  • App.vue 파일에 ts 를 입력해준 후에, vbase-ts를 선택 해줍니다.
  • Pasted image 20231226094832.png
  • 위의 Extension을 설치 해야만 사용 가능합니다.

TS ESLint Error

  • Vue 프로젝트를 진행할 때 아래의 에러가 발생되면,
    Pasted image 20231226100523.png
  • VSCode 하단에 있는 CLRF를 클릭 후 아래의 사진처럼 바꾼다면 에러는 없어집니다.
    Pasted image 20231226100538.png
    Pasted image 20231226100500.png

기존에 JS에서 보지 못했던 오류 입니다.
Pasted image 20231226101844.png

LocalStorage Input

<template>
  <div>
    <h1>Vue Todo with Typescript</h1>
    <todo-input :item="todoText" @input="updateTodoText" @add="addTodoItem" />
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import TodoInput from "./components/TodoInput.vue";

export default Vue.extend({
  components: { TodoInput },
  data() {
    return {
      todoText: "hello world",
    };
  },
  methods: {
    updateTodoText(value: any) {
      this.todoText = value;
    },
    addTodoItem() {
      const value = this.todoText;
      localStorage.setItem(value, value);
      this.initTodoText();
    },
    initTodoText() {
      this.todoText = "";
    },
  },
});

</script>

  

<style scoped></style>

App.vue

<template>
  <div>
    <label for="todo-input"></label>
    <input type="text" id="todo-input" :value="item" @input="handleInput" />
    <button @click="addTodo">add</button>
  </div>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  props: ["item"],
  methods: {
    handleInput(e: any) {
      e.target.value;
      this.$emit("input", e.target.value);
    },
    addTodo() {
      this.$emit("add");
    },
  },
});
</script>

<style scoped></style>

TodoInput.vue

결과

Pasted image 20231226103713.png
Pasted image 20231226103722.png

사실 해당 모델은 v-model을 사용하면 금방 해결 되는 문제이다.

v-model을 풀어서 사용하는 이유?

Props의 Validation

...
props: {
    item: {
      type: String,
      required: true,
    },
  },
 ...

Pasted image 20231226104539.png

// 아래의 방식을 사용해도 여전히 에러가 발생합니다.
if(!event.target){
	return ;
}
this.$emit("input", e.target.value);

// 아래의 방식을 사용하면 에러가 발생 되지 않습니다.
const eventTarget = e.target as HTMLInputElement;
this.$emit("input", eventTarget.value);

할일 앱 조회

<script>
const STORAGE_KEY = "vue-todo-ts-v1";
const storage = {
  fetch() {
    const todoItems = localStorage.getItem(STORAGE_KEY) || "[]"; // <---
    const result = JSON.parse(todoItems);
  },

};
</script>
<script>
const storage = {

  save(todoItems: any[]) {
    localStorage.setItem(STORAGE_KEY, todoItems);
  },
  fetch() {
    const todoItems = localStorage.getItem(STORAGE_KEY) || "[]";
    const result = JSON.parse(todoItems);
  },
};
</script>

Pasted image 20231227094342.png

<template>
  <div>
    <header>
      <h1>Vue Todo with Typescript</h1>
    </header>
    <main>
      <div>
        <todo-input
          :item="todoText"
          @input="updateTodoText"
          @add="addTodoItem"
        />
      </div>
      <ul>
        <TodoListItem
          v-for="(todoItem, index) in todoItems"
          :key="index"
          :todoItem="todoItem"
          :index="index"
          @remove="removeTodoItem"
          @toggle="toggleTodoItemComplete"
        ></TodoListItem>
  
        <!-- <li>아이템 1</li>
        <li>아이템 2</li>
        <li>아이템 3</li> -->
      </ul>
    </main>
  </div>
</template>
  
<script lang="ts">
import Vue from "vue";
import TodoInput from "./components/TodoInput.vue";
import TodoListItem from "./components/TodoListItem.vue";
  
const STORAGE_KEY = "vue-todo-ts-v1";
const storage = {
  save(todoItems: Todo[]) {
    const parsed  JSON.stringify(todoItems);
    localStorage.setItem(STORAGE_KEY, parsed);
  },
  fetch(): Todo[] {
    const todoItems = localStorage.getItem(STORAGE_KEY) || "[]";
    const result = JSON.parse(todoItems);
    return result;
  },
};
  
export interface Todo {
  title: string;
  done: boolean;
}
  
export default Vue.extend({
  components: { TodoInput, TodoListItem },
  data() {
    return {
      todoText: "hello world",
      todoItems: [] as Todo[],
    };
  },
  methods: {
    updateTodoText(value: any) {
      this.todoText = value;
    },
    addTodoItem() {
      const value = this.todoText;
      const todo: Todo = {
        title: value,
        done: false,
      };
  
      this.todoItems.push(todo);
      storage.save(this.todoItems);
      // localStorage.setItem(value, value);
      this.initTodoText();
    },
    initTodoText() {
      this.todoText = "";
    },
    fetchTodoItems() {
      this.todoItems = storage.fetch().sort((a: Todo, b: Todo) => {
        if (a.title < b.title) {
          return -1;
        }
        if (a.title > b.title) {
          return 1;
        }
        return 0;
      });
    },
    removeTodoItem(index: number) {
      console.log("remove", index);
      this.todoItems.splice(index, 1);
      storage.save(this.todoItems);
    },
    toggleTodoItemComplete(todoItem: Todo, index: number) {
      this.todoItems.splice(index, 1, {
        ...todoItem,
        done: !todoItem.done,
      });
      storage.save(this.todoItems);
    },
  },
});
</script>
  
<style scoped></style>

App.vue

이미 구현된 서비스에 TS 점진적으로 적용하는 방법

# vue3 버전을 설치했다면, 아래의 명령어로 TS를 설치 할 수 있습니다.
vue add typescript

#Vue #Vue2 #FrontEnd #JavaScript