SFC 프로젝트 구성 w. Parcel
SFC(싱글 파일 컴포넌트)
- 기본적으로 3개의 구조를 가지고 시작함.
- template - html 구조
- script - vue js 옵션 활용(JS)
- style - css 작성
- 하나의 파일 안에 내용이 있고 모듈 형태로 내보내고, 가져올 수 있도록 프로젝트를 만듦.
번들러를 통한 Vue js 프로젝트 구성
- import 시 경로를 명시하지 않으면 node_modules에 있는 패키지를 가져온다는 의미임.
- import와 export는 commonjs 방식이 아닌 ESM 방식임 → vue/index.js에서 동작하는 방식은 node Js에서 동작하는 내용임. CDN이 아닌 npm으로 설치했기 때문임.
(x)
import Vue from 'vue';
, (o)import * as Vue from 'vue';
- 설치 패키지를 사용할 때 내용 구성이 어떻게 되어있는지 node_modules 열어 확인하기!
- .vue 파일의 코드 하이라이팅 확장 프로그램 : vuter
- .vue 파일 초기 구성 생성 및 자동완성 확장 프로그램 : vue 3 snippet
- 브라우저에서 동작하려면 Parcel 이란 번들러의 도움이 필요함.
- 번들러는 묶어서 내보내는 역할임.
Pracel 시작하기
npm i -D pracel
- package.json에 script 명령 추가.
"scripts": { "dev": "parcel ./src/index.html" //진입점을 입력해야 함. },
npm run dev
- parcel은 node js의 14버전 이상에서 동작함.
- 빌드 실패했을 떄
ctrl + c
로 탈출 가능. - 빌드 성공 시 devDependencies에 parcel/transformer 코드가 작성됨.

- 개발 서버를 오픈해서 브라우저에서 바로 확인 가능.
- .parcel-cache/dist/index.html에 웹에서 동작하는 html이 명시되어 있음.
- dist 폴더는 gitignore 시켜야 함.
- 보통 src 폴더는 개발용, dist 폴더는 배포용으로 만듦.
- 빌드 용으로 난독화된 배포코드를 만들고 싶을 땐 script 파일에 다음과 같이 작성.
이때 main 옵션으로 인해 충돌이 날 수 있으니 삭제해야 함.
"scripts": { "dev": "parcel ./src/index.html", "build": "parcel build ./src/index.html" },

- .vue 파일에서 style 태그의 lang 속성을 'scss'로 지정하면 SCSS 사용할 수 있음.
- 단 sass 패키지가 설치되어 있지 않다면
npm i sass -D
를 통해 설치 진행하기. SCSS를 사용하더라도 sass로 설치해주는 거 잊지 말기.SFC 프로젝트 구성 w. Webpack
Webpack시작하기
npm init -y
로 package.json 파일 생성.
npm i -D webpack webpack-cli
설치. 웹팩 본연의 기능과 터미널에서 웹팩을 동작시키게 하는 cli를 설치함. cli는 command line interface의 약어로 터미널을 의미함.

- scripts에 웹팩 동작 명령 추가.
"scripts": { "build": "webpack" },
webpack.config.js
파일 생성.- node js 환경에서 실행되기 때문에 export, import 사용 불가.
- entry에 빌드할 진입 파일을 적고, ouput으로 결과물 배출할 경로를 지정함.
__dirname
: webpack.config.js에 경로 정보를 갖고 있는 node js의 전역 변수.dist
: 원하는 결과물이 들어갈 폴더 이름을 설정. 이 외에도 build, public으로 쓰기도 함.- entry는 상대 경로가 되지만 output은 절대경로로 작성해주어야 함.
const path = require('path') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), //생략시 기본 이름으로 설정됨. //filenmae:'hello.js' } }
npm run build
Webpack으로 vue 템플릿 만들기
- entry은 JS 파일만 읽을 수 있음 → 내부의 vue 파일이 있다면 다른 확장자를 해석할 수 있도록
loader
의 도움을 받아야 함.
const path = require('path') const { VueLoaderPlugin } = require('vue-loader') const HtmlPlugin = require('html-webpack-plugin') module.exports = { entry: './src/main.js', output: { path: path.resolve(__dirname, 'dist'), clean: true //빈 폴더 제거 //생략시 기본 이름으로 설정됨. //filenmae:'hello.js' }, module: { rules: [ { test: /\.vue$/, use: 'vue-loader' } ] }, plugins: [ new VueLoaderPlugin(), new HtmlPlugin({ template: 'src/index.html' }) ] }
- vue-loader 패키지는 vue 파일을 로드해서 해석할 수 있게 도움을 주는 패키지.
- 터미널에
npm i vue-loader@next
npm i -D @vue/compiler-sfc
설치 (vue-loader를 HTML, CSS, JS로 변환하는 패키지)- vue와 @vue/compiler-sfc 버전 앞자리가 3으로 일치해야 함.
VueLoaderPlugin
을 생성자로 만들어 반환한 인스턴스를 등록해야 함.
- pacakge.json에 scripts에 build라는 이름의 webpack이 있는지 확인.
npm run build
후 dist 폴더에 index.html 파일을 만들지 않음.- commit 시 무시하는 dist 폴더이기 때문에 직접 만들어주지 않음.
npm i -D html-webpack-plugin
설치.- webpack.config.js에 인스턴스 생성.

"build": "webpack --mode production"
기재.개발 용도로 로컬 환경에 서버 구성하기
npm i -D webpack-dev-server
- scripts에 개발용 스크립트 추가
"dev": "webpack-dev-server --mode development"
- 콘솔 창에
[webpack-dev-server] Hot Module Replacement enabled.
라는 뜻은 코드를 수정할 때 바로 브라우저에 내용이 반영된다는 의미임.
- 포트 번호를 변경하려면 webpack.config.json에 다음과 같이 코드 추가 후
npm run dev
실행.
devSever: { port: 1234 }
스타일 패키지 설치
css를 해석할 수 있는 loader를 추가적으로 설치해야 함.
npm i -D css-loader vue-style-loader
: css와 vue 스타일을 해석할 수 있는 loader.
- webpack.config.js에 rules 추가.
module: { rules: [ { test: /\.vue$/, use: 'vue-loader' }, { test: /\.css$/, use: [ 'vue-style-loader', 'css-loader' //하단에 적어야함. 나중에 적힌 것이 더 먼저 해석됨. ] } ] },
- 빌드 시 화면에 잘 그려짐
- 단 자식 컴포넌트 스타일이 부모에게도 영향을 미치기 때문에 자식 컴포넌트 스타일에
scoped
라는 키워드를 작성해야 함. style hash를 부여하여 원하는 스타일을 지정한 곳에 배치. 스타일 태그에도 선택자에 hash로 구성된 속성 선택자에 스타일이 지정되어 있음.
SCSS 패키지 설치
npm i -D sass sass-loader
- webpack.config.js에
css-loader
하단에sass-loader
추가.
- test를
test: /\.s?css$/,
로 변경.
webpack 경로
resolve: { extensions: ['.vue', '.js'], alias: { '~': path.resolve(__dirname, 'src') } },
- webpack.config.js에 extensions에 작성된 확장자는 적어주지 않아도 자동으로 입력됨.
- alias에 경로에 대한 별칭을 사용하면 절대 경로로 파일 위치를 찾음. 빌드 시 상대 경로로 작성된 코드는 오류를 일으킬 수 있기 때문.
JS와 이름이 같다면 충돌이 날 수 있으니 주의하자.
favicon
dist 폴더에 있는 index.html에 이미지 등을 넣어야 할 때 사용하는 플러그인이 있음.
npm i -D copy-webpack-plugin
- webpack.config.js에 다음과 같이 작성.
const CopyPlugin = require('copy-webpack-plugin') plugins: [ new VueLoaderPlugin(), new HtmlPlugin({ template: 'src/index.html' }), new CopyPlugin({ patterns: [ { from: 'static' } //to 옵션은 output의 path로 기본값을 가져 생략 가능. ] }) ]
- static이란 이름의 폴더를 만들고 그 아래
favicon.ico
라는 이름으로 이미지 이름을 저장함.
- 빌드.
왜 edge에서 파비콘이 안보일까?
ESLint
npm i -D eslint eslint-plugin-vue
- root에 .eslintrc.json 파일 생성해서 다음과 같이 작성.
{ "env": { "browser": false, "node": false }, "extends": ["eslint:recommended", "plugin:vue/vue3-recommended"] }
- ESLint 확장 프로그램 설치.
ctrl + shift + p
를 눌러 json setting에 들어가 다음과 같이 추가 작성. 이 외 파일 깃허브
"editor.codeActionsOnSave": { "source.fixAll.eslint": true },
prettier vs ESLint
- ESLint : 포매팅(일관된 코딩 컨벤션 유지) + 코드 품질(잠재적인 오류를 찾음)
- prettier : 더 일관된 포매팅. 주로 협력 시 코드 컨벤션을 맞추기위해 사용됨.
컴포넌트 등록
- 전역 등록 시 컴포넌트 파일과 상관없이 따로 지역등록을 하지 않아도 자유롭게 쓸 수 있음. 다만 전역 등록은 자주 사용하는 컴포넌트를 등록하는 개념이지 사용하는 모든 컴포넌트를 등록하면 관리가 어려움.
- 지역 등록은 App.vue의 script 태그 안에
import Hello form '~/comonents/Hello
와 같이 import를 이용하여 등록해주는 것임. 추가로 template 태그와 script 내 컴포넌트 등록을 해줘야 함.
<template> <div> <h1>hello vue</h1> <Btn /> <Hello /> </div> </template> <script> import Hello from "~/components/Hello"; export default { components: { Hello, }, }; </script>
컴포넌트 Props

- data에 있는 msg를 hello 컴포넌트에 전달 → Hello.vue의 props에서 msg를 받아 template 이중 중괄호 구문에 전달함.
- App.vue(상위 컴포넌트)에서 Hello.vue(하위컴포넌트)로 데이터 전달은 가능하지만 그 반대는 안됨.
- template 내에선 케밥 케이스, script 내에선 카멜 케이스를 적용함.
- props로 받은 데이터를 다음과 같이 작성하면 새롭게 갱신할 수 있음.
export default { props: ["message"], data() { return { newMsg: this.message } }, methods: { updateMsg() { this.newMsg = 'Good' } } };
props: { message: String, name: [String, Number] //2가지 종류를 받을 때. },
props: { id: { type: Number, default: NaN }, title: { type: String, required: true }, },
// 기본값이 있는 오브젝트 propE: { type: Object, // 오브젝트나 배열은 꼭 기본값을 반환하는 // 팩토리 함수의 형태로 사용되어야 합니다. default: function () { return { message: 'hello' } } }, // 커스텀 유효성 검사 함수 propF: { validator: function (value) { // 값이 항상 아래 세 개의 문자열 중 하나여야 합니다. return ['success', 'warning', 'danger'].indexOf(value) !== -1 } }
컴포넌트 Non-Prop 속성
- 최상위 요소가 하나인 경우에 컴포넌트 자체에 부여하는 속성, 이벤트가 적용되지만 여러 개인 경우 적용되지 않음.
- 최상위 요소가 여러 개일 때 적용하는 방법 :
$attrs
라는 vue 내장 객체를 사용. - 컴포넌트에 부여된 속성, 이벤트를 중복 등록해주는 것도 가능.
- non-prop 속성은 컴포넌트에 전달되지만, props나 emits에 정의되지 않은 속성, 이벤트를 의미함. 이러한 속성은
$attrs
프로퍼티를 통해 접근할 수 있음. - 자식 컴포넌트의 script태그 내
inheritAttrs: false
로 지정하면 props에 정의되지 않더라도 자동으로 속성이 상속되지 않음.
<template> <div> <h1>{{ msg }}</h1> <Hello class="hello" style="font-size: 100px" @click="msg += '!'" /> </div> </template>
<template> <h1 :class="$attrs.class" :style="$attrs.style">hello</h1> <h2 @click="$attrs.onClick">hahah</h2> </template>
컴포넌트 커스텀 이벤트
- props가 부모 → 자식으로 데이터를 전달하는 거라면, emit은 자식 → 부모로 이벤트를 전달함. props와 emit은 부모, 자식관계에서 유효함!
- $emit을 사용할 때 script 태그에 명시해야 함. ex)
emits: ["please"]
- native event와 이름이 겹치지 않도록 주의.
<template> <div> <h1>{{ msg }}</h1> <Hello :message="msg" @please="reverseMsg" /> </div> </template>
<template> <h1 @click="$emit('please')">{{ message }}</h1> </template>
양방향 데이터 전달하기
v-model
,modelValue
로 양방향 데이터 바인딩을 사용.
- modelValue는 예약어지만 다른 단어로 대체하고 싶다면 다른 단어로 교체 후 App.vue에서 콜론 기호로 v-model 다음 교체한 단어를 적어주면 됨(하단 코드 예시).
<template> <div> <h1> {{ msg }} </h1> <Hello v-model:message="msg" /> </div> </template>
<template> <div> <label> <input :value="message" @input="$emit('update:message', $event.target.value)" /> </label> </div> </template> <script> export default { props: { //modelValue message: { type: String, default: "", }, }, }; </script>

컴포넌트 Slots
- App.vue에 있는 컴포넌트 태그에 콘텐츠를
slot
태그에 넣어주면 해당 콘텐츠로 대체됨.
- slot 내부 콘텐츠는 default 값을 나타냄.
<template> <div> <h1> {{ msg }} </h1> <Hello>Hello Vue?</Hello> <Btn>Click me</Btn> <Btn /> </div> </template>
<template> <button><slot>Default</slot></button> </template>
import { createApp } from "vue"; import App from "~/App"; import Btn from "~/components/Btn"; const app = createApp(App); app.component("Btn", Btn); app.mount("#app");
<template> <slot></slot> </template>
slot 지칭하기
v-slot
디렉티브를 통해 이름을 만들 수 있음.
- v-slot은
#
기호로 대체할 수 있음.
- v-slot 이름은 중복되면 안되고, 만약 같은 이름을 사용해야 한다면 하나의 태그로 모아주어야 함.
<template> <div> <template #abc> <h1>ABC</h1> </template> <Hello>Hello Vue?</Hello> <template #xyz> <h1>xyz</h1> </template> </div> </template>
<template> <slot name="abc"></slot> <slot name="xyz"></slot> </template>
slot 동적 제어

- msg를 클릭하면 slotName을 다른 데이터로 변경할 수 있음.
- slot 구조 분해 할당 가능.
동적 컴포넌트
- App.vue의 template 태그로 컴포넌트를 명시해주는 것이 아닌, 동적으로 컴포넌트를 만드는 것.
<template> <div> <h1> {{ msg }} </h1> <component :is="currentComp"></component> </div> </template> <script> import Hello from "~/components/Hello"; export default { components: { Hello, }, data() { return { msg: "Hello Webpack!?", currentComp: "Hello", }; }, }; </script>
- component 태그를 사용하여 is로 컴포넌트 이름을 지정함.
- data에 해당 컴포넌트 이름으로 연결.
keep-alive
라는 캐시를 저장하는 태그도 있음. 자주 토글되는 요소에 유용하나 데이터를 차지하기 때문에 신중하게 사용해야 함.
Refs
document.querySelector
등의 선택자는 루트부터 요소를 찾기 때문에 비효율적임.
<template> <h1 ref="hello">hellllo</h1> </template> <script> export default { mounted() { const h1El = this.$refs.hello } }; </script>
- this를 이용해 $ref.hello로 바로 요소를 검색할 수 있음.
- html과 연결한 직후인
mounted
에서만 동작함.
- 다른 컴포넌트에서 ref 속성으로 찾을 경우
$el
를 추가해야 함. ex)this.$refs.hello.$el
- 다만 만약 최상위 요소가 여러 개일 경우
$el
이 아닌 $ref를 다시 사용해서this.$refs.hello.$refts.world
로 찾아야 함.
- FE 개발에서는 querySelector 등 HTML 요소를 전부 검색하는 걸 잘 사용하지 않음.
간단한 에디터 만들기
<template> <div> <div v-if="!isEdit"> {{ msg }} <button @click="isEdit = true">Edit</button> </div> <div v-else> <input ref="edtior" v-model="msg" @keyup.enter="isEdit = false" type="text" /> </div> </div> </template> <script> export default { data() { return { isEdit: false, msg: "Hello Webpack!?", }; }, methods: { onEdit() { this.isEdit = true; this.$nextTick(() => { this.$refs.editor.focus(); }); }, }, }; </script>
- 데이터가 바뀐다고 해서 렌더링을 보장할 수 없음 →
$nextTick
은 데이터를 수정해서 화면이 바뀌는 것을 보장해서 내부에 있는 콜백을 실행함.