전체 적인 설정부분 node버전 16으로 변경하여 다시 진행해보도록하겠습니다. (시간이 날때 ㅠㅠ)

해당 글들에서 lint 내용 제외해서 보시는 것을 추천합니다.(린트 설정이 제대로 안된 듯 함)

 

> 할일 목록을 만들면서 Vue3 배워보자 - 01

   할일 목록을 만들면서 Vue3 배워보자 - 02

   할일 목록을 만들면서 Vue3 배워보자 - 03

 


 

Vue3와 Typescript를 사용해서 간단한 할일 목록을 만들어보겠습니다.

저도 학습하고나서 기억하기 위해 작성하는 포스트입니다.

그리고 해당 학습에서 중요하게 생각한 부분 외에는(lint, style 등) 거슬리지 않는한 신경쓰지 않고 진행하였고, 

UI는 bootstrap을 사용하였습니다.

 

기본 스펙 공유 (기존에 설치하고나서 업그레이드를 하지 않은 상태인데, 최신 안정 버전 사용해도 무방할 듯 합니다.)

- node 14.15.5 

- typescript 4.5.5

 

Node, Typescript 설치는 공식 사이트를 참고하세요.

https://nodejs.org/ko/

 

Node.js

Node.js® is a JavaScript runtime built on Chrome's V8 JavaScript engine.

nodejs.org

https://www.typescriptlang.org/download

 

How to set up TypeScript

Add TypeScript to your project, or install TypeScript globally

www.typescriptlang.org

 

 

vue-cli 설치

https://cli.vuejs.org/guide/installation.html

 

Installation | Vue CLI

Installation Warning regarding Previous Versions The package name changed from vue-cli to @vue/cli. If you have the previous vue-cli (1.x or 2.x) package installed globally, you need to uninstall it first with npm uninstall vue-cli -g or yarn global remove

cli.vuejs.org

저는 npm을 사용하므로 앞으로 명령어들은 npm으로 진행합니다.

npm install -g @vue/cli

 

 

프로젝트 생성

본인이 프로젝트를 설치할 경로 내에서 명령어를 통해 프로젝트를 생성해줍니다.

todo-list는 프로젝트 명입니다.

vue create todo-list

옵션은 아래와 같이 설정했습니다.

vue-cli 옵션 설정

간략하게 설명해보겠습니다.

- 수동으로 기능 선택

- 프로젝트에서 사용할 것들을 선택합니다. vue, babel, ts, router, css pre-processors, linter 

- vue 3.x 버전 선택

- class-style (vue-class-compoent 데코레이터를 사용하는 방식) 컴포넌트 사용 여부로, No

- TypeScript와 함께 Babel 사용

- history 모드 사용 (url에 #이 붙지 않도록)

- css pre-processor로 sass/scss 선택 (node-sass)

- eslint + prettier 선택

- 추가 린트 기능 선택: 린트 저장 시

- 각각 파일에서 설정할 수 있도록 선택

- 향후 프로젝트를 위해서 설정 저장 No

 

이렇게 설정하고 나면 아래와 같이 프로젝트 설치가 완료됩니다.

기본 구조

이제 todo-list 프로젝트로 이동하여 로컬 서버를 띄워서 정상적인지를 확인하고 넘어갑니다.

npm run serve

http:localhost:8080으로 접근하면 아래와 같은 화면을 확인할 수 있습니다.

기본화면

 

 

VSCode 인텔리센스 플러그인 설정

인텔리센스는 에디터에서 구문 강조 표시, Typescript 지원, 템플릿 표현식 및 구성 요소 소품에 대하여 도움을 주어 코딩을 편리하게 할 수 있는 기능의 집합을 말합니다.

지금 제가 개발하려는 vue3의 script setup 방식은 Vetur에서 동작이 되지 않기 때문에

vue3 공식 사이트에서 추천하는 Volar을 설치하도록 하겠습니다.(Volar은 많은기능과 함께 Vue SFC 내에서 TypeScript 지원을 제공하는 공식 VSCode 확장 플러그인입니다.)

 

자, 먼저 Vetur 플러그인과 충돌이 나므로, Vetur 플러그인을 비활성화 해줍니다.

다른 프로젝트에서 사용하고 있을 수 있으므로 해당 워크스페이스에서만 비활성화 해줍니다.

vetur 플러그인 비활성화

 

그 후에 Volar을 검색하여 아래 두 플러그인을 설치하고 해당 워크스페이스에서 활성화를 시켜줍니다.

- Vue Language Features (Volar)
- TypeScript Vue Plugin (Volar)

volar 플러그인 활성화

 

 

Bootstrap 설치

https://getbootstrap.com/

 

Bootstrap

The most popular HTML, CSS, and JS library in the world.

getbootstrap.com

모듈을 설치해줍니다. bootstrap docs에서 컴포넌트들을 확인 할 수 있습니다.

앞으로 UI에서 사용하게 됩니다.

npm i bootstrap bootstrap-vue-3

설치가 끝났다면 main.ts 파일에 global로 등록해줍니다.

필요한 파일들을 import해오고, bootstrap-vue-3 모듈은 use를 통해 등록해줍니다.

import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";

import BootstrapVue3 from "bootstrap-vue-3";
import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-vue-3/dist/bootstrap-vue-3.css";

createApp(App).use(router).use(BootstrapVue3).mount("#app");

 

 

Lint & VSCode Settings.json 설정

 

App.vue

기존에 작성되어있던 파일 내용을 다 지우고 난 후, 아래와 같이 수정합니다.

container, row 클래스에 대해 궁금하시다면 bootstrap의 grid를 확인하세요.

프로젝트 설정 시에 sass를 선택했기 때문에 다른 설정을 하지 않아도 scss를 사용할 수 있습니다.

<template>
  <div class="container">
    <div class="row">
      Header
    </div>
    <div class="row">
      input
    </div>
    <div class="row">
      <router-view />
    </div>
  </div>
</template>

<style lang="scss">
#app {
  padding: 100px;
  
  .row + .row {
    margin-top: 15px;
  }
}
</style>

위와 같이 작성하고 저장하고나면 에러가 나고 있는 것을 확인 할 수 있습니다.

@popperjs/core 설치하라

해당 안내에 따라 모듈을 추가적으로 설치해줍니다.

npm i @popperjs/core

에러를 해결하고 나니, 보기 싫은 노란색의 경고 줄이 보입니다.

린트 설정은 하지 않겠다고 하였으나 너무 보기 싫어서 eslint를 수정해줍니다.

lint 경고

 

 

 

.eslintrc.js

eslint 설정 파일에 들어가서 아래와 같이 속성을 추가합니다. 

module.exports = {
  root: true,
  env: {
    node: true,
  },
  extends: [
    "plugin:vue/vue3-essential",
    "eslint:recommended",
    "@vue/typescript/recommended",
    "@vue/prettier",
    "@vue/prettier/@typescript-eslint",
  ],
  parserOptions: {
    ecmaVersion: 2020,
  },
  rules: {
    "no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
    "no-unused-vars": "off",
    "prettier/prettier": [
      "error",
      {
        endOfLine: "auto",
      },
    ],
  },
};

eslint 설정 추가

 

그리고나서 저장할 때마다 eslint가 수정될 수 있도록 VSCode 설정도 진행합니다.

ctrl + , 단축키를 눌러 settings에 접근한 후에 workspace를 선택하여 해당 워크스페이스의 설정만 수정하도록 합니다.

그 후에 setting를 검색하여 Edit in settings.json을 클릭하여 셋팅 파일을 엽니다.

setting 검색

Edit in settings.json을 누르는 순간 프로젝트 폴더를 보면 .vscode라는 폴더가 생기고 그 하위에 settings.json 파일이 생성된 것을 확인할 수 있습니다. (비어있습니다.)

.vscode/settings.json

 

.vscode/settings.json

아래의 설정을 추가합니다.

- editor.formatOnSave: 자동 save 사용안함

- editor.codeActionsOnsave > source.fixAll.eslint: save시 eslint 적용

- editor.tabSize & editor.detectIndentation: tab 시 스페이스 2칸, detectIndentation를 비활성화

- eslint.workingDirectories: 옵션을 사용하여 current working directory를 지정 ([{ "mode": "auto" }] : package.json, .eslintignore 그리고 .eslintrc* 파일이 위치한 폴더를 working directory로 지정한다. )

- eslint.validate: validate할 파일 (자신이 사용하는 언어가 있으면 추가하면 됨)

{
  "editor.formatOnSave": false,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": true,
  },
  "editor.tabSize": 2,
  "editor.detectIndentation": false,
  "editor.formatOnType": true,
  "eslint.workingDirectories": [{ "mode": "auto" }],
  "eslint.validate": [
    "vue",
    "javascript",
    "typescript",
    "html"
  ],
}

 

수정을 마쳤다면 App.vue에서 저장을 해봅니다. 

eslint가 정상적으로 동작한다면 정리가 되면서 노란 경고 줄도 사라집니다.

 

App.vue

lint 적용 후 경고줄이 사라진 모습

 

 

UI 만들기

간단한 할일 목록을 만드는 일이기에 UI는 크게 신경쓰지 않았습니다.

bootstrap > docs에서 원하는 것을 검색하여 가져다 씁니다.

아까 위에서 말했던 container, row는 grid를 검색하여 확인 할 수 있습니다.

grid 시스템

 

 

사용하지 않는 컴포넌트 제거

App.vue를 제외하고 components 하위, views 하위 파일들을 죄다 지워줍니다.

그 후 router/index.ts에서 import 된 컴포넌트들도 제거해줍니다.

지우고 난 후는 아래와 같아야합니다.

// router/index.ts
import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";

const routes: Array<RouteRecordRaw> = [];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

 

 

Header 컴포넌트 만들기 (UI)

components 폴더에에 header.vue 파일을 생성하고, bootstrap 사이트에서 breadcrumb을 검색하여 마크업을 복사해옵니다.

bootstrap breadcrumb

 

components/header.vue

<template>
  <header>
    <h1>TODO LIST</h1>
    <nav aria-label="breadcrumb">
      <ol class="breadcrumb">
        <li class="breadcrumb-item"><a href="#">Home</a></li>
        <li class="breadcrumb-item"><a href="#">Library</a></li>
        <li class="breadcrumb-item active" aria-current="page">Data</li>
      </ol>
    </nav>
  </header>
</template>
<script></script>
<style lang="scss"></style>

 

 

App.vue

방금 생성한 header 컴포넌트를 App.vue 파일에서 등록해줍니다.

script setup 방식은 import한 후 component에 등록하지 않아도 template에서 바로 사용가능합니다.

<template>
  <div class="container">
    <div class="row">
      <base-header />
    </div>
    <div class="row">input</div>
    <div class="row">
      <router-view />
    </div>
  </div>
</template>
<script lang="ts" setup>
import BaseHeader from "@/components/header.vue";
</script>
<style lang="scss">
#app {
  padding: 50px 0;
  
  .row + .row {
    margin-top: 15px;
  }
}
</style>

lang을 ts로 적용해줘야 typescript가 인식할 수 있습니다.

setup은 script setup 방식을 의미합니다.

header 컴포넌트 등록

 

 

components/header.vue

a 태그로 되어있던 부분을 router-link로 바꿔줍니다. 나중에 이 router-link를 통해 모든 할일 목록(All), 해야하는 목록(Active), 진행 한 목록(Clear)을 분류하여 정렬할 것입니다.그 외 마음에 안드는 스타일 부분도 조금 손 봐줍니다.

<template>
  <header class="header">
    <h1 class="header__title">TODO LIST</h1>
    <nav aria-label="breadcrumb">
      <ol class="breadcrumb">
        <li class="breadcrumb-item">
          <router-link to="/">All</router-link>
        </li>
        <li class="breadcrumb-item">
          <router-link to="/active">Active</router-link>
        </li>
        <li class="breadcrumb-item">
          <router-link to="/clear">Clear</router-link>
        </li>
      </ol>
    </nav>
  </header>
</template>
<script lang="ts">
export default {
  name: "BaseHeader",
};
</script>
<style lang="scss">
.header {
  padding: 0 !important;

  .header__title {
    text-align: center;
    margin-bottom: 20px;
    font-weight: 700;
  }

  .breadcrumb {
    margin: 0;
    padding: 6px 12px;
    background: #e9ecef;
    border-radius: 5px;
    box-sizing: border-box;
  }
}
</style>

코드를 보면 script가 두번 들어가 있는것이 확인됩니다.

일반 script는 normal script라고 불리우는데, 이것은 script setup과 함께 사용할 수 있습니다.

normal script에서는 name이나 inheritAttrs 옵션등, 사용자 지정 옵션이 필요할 때 사용됩니다.

 

 

할일 목록 입력을 받을 input 컴포넌트를 만들기 위해 bootstrap에서 form text를 검색하여 UI의 마크업을 가져옵니다.

form text

 

components/todo-input.vue

<template>
  <input type="text" class="form-control" placeholder="할일을 입력해주세요." />
</template>
<script lang="ts">
export default {
  name: "TodoInput",
};
</script>
<script lang="ts" setup></script>
<style lang="scss">
.todo-input {
  font-size: 14px;

  &::placeholder {
    font-size: 14px;
  }
}
</style>

 

App.vue

방금 만든 todo-input 컴포넌트도 App.vue에 등록해줍니다.

<template>
  <div class="container">
    <div class="row">
      <base-header />
    </div>
    <div class="row">
      <todo-input />
    </div>
    <div class="row">
      <router-view />
    </div>
  </div>
</template>
<script lang="ts" setup>
import BaseHeader from "@/components/header.vue";
import TodoInput from "@/components/todo-input.vue";
</script>
<style lang="scss">
#app {
  padding: 50px 0;
  
  .row + .row {
    margin-top: 15px;
  }
}
</style>

아래와 같이 정상적으로 출력되는 것을 확인합니다.

todo-input 등록

 

 

등록된 할일 아이템 컴포넌트도 만들겠습니다.

이번에도 bootstrap에서 form을 검색하여 button과 input등의 UI 마크업을 가져옵니다.

 

components/todo-item.vue

<template>
  <div class="input-group">
    <div class="input-group-text">
      <input class="form-check-input mt-0" type="checkbox" />
    </div>
    <input type="text" class="form-control" value="밥 잘먹기" disabled />
    <button class="btn btn-outline-secondary" type="button">X</button>
  </div>
</template>
<script lang="ts">
export default {
  name: "TodoItem",
};
</script>
<script lang="ts" setup></script>
<style lang="scss">
.input-group {
  padding: 0 !important;

  input {
    font-size: 14px;

    &::placeholder {
      font-size: 14px;
    }
  }

  button {
    font-size: 14px;
  }
}
</style>

 

 

views/item-list.vue

이번에는 App.vue에 등록하지 않고 views 폴더 하위에 할일 목록 리스트 컴포넌트를 만들어서 이곳에 등록하겠습니다.

 

할일 목록 리스트 컴포넌트에서 나중에 header에서 말했던 all, active, clear에 대한 분류작업도 진행할 것입니다.

<template>
  <todo-item />
</template>
<script lang="ts">
export default {
  name: "ItemList",
};
</script>
<script lang="ts" setup>
import TodoItem from "@/components/todo-item.vue";
</script>
<style lang="scss"></style>

 

자 대중 UI를 등록하는 작업이 끝났다면, 라우터를 등록해주겠습니다.

페이지를 여러개 생성하지 않고 다우나믹 라우터를 통해 status를 받아서, 분류를 진행할 것입니다.

 

router/index.ts

path에서 /:status가 다우나믹 라우터의 핵심이며 물음표는 옵셔널을 뜻합니다.

옵셔널을 해줄 경우 '/'로 접근한 경우 item-list 컴포넌트가 라우팅됩니다. 

import { createRouter, createWebHistory, RouteRecordRaw } from "vue-router";
import itemList from "@/views/item-list.vue";

const routes: Array<RouteRecordRaw> = [
  {
    path: "/:status?",
    name: "item-list",
    component: itemList,
  },
];

const router = createRouter({
  history: createWebHistory(process.env.BASE_URL),
  routes,
});

export default router;

UI 결과

 

 

다음편 보기

 

+ Recent posts