import * as Vue from 'vue'
'vue'에서 상대경로를 입력하지 않는다면 node_modules의 패키지를 가져오는 것
번들러: App.vue를 브라우저에서 동작할 수 있도록 해주는 것
npm i -D parcel
개발용 parcel 설치
<script type="module" src="./main.js"></script>
index.html에 작성시 type="module "
넣어야 함
package.json
에서 "scripts": { "dev": "parcel ./src/index.html" }
을 통해 parcel이 index.html에 접근할 수 있도록 진입점 설정해줘야 함
배포할 때는"dev": "parcel ./src/index.html"
대신 "build": "parcel build ./src/index.html"
을 작성하고 build하면 dist폴더에 난독화(브라우저해석용) 되어있는 배포용파일이 생성된다. 이는 브라우저 기준으로 작성되었기 때문에 개발용보다 더욱 효율적이지만 사람이 읽을 수 없다.
vue파일 없이 dist폴더의 파일만으로 브라우저 실행이 가능하기 때문에 dist파일만 깃허브에 올려도 되지만, dist폴더 역시 npm run dev 혹은 npm run build하면 package-lock.json 덕분에 언제든지 재생성되므로 굳이 깃헙에 업로드할 필요는 없다.
npm i sass -D 설치 후 vue 파일 내 을 넣으면 scss문법 사용가능
npm init -y
이후 npm i -D webpack webpack-cli
,
package.json
에 "scripts": { "build":"webpack" }
webpack.config.js 파일 생성 후
const path = require('path') // nodeJS의 기본 내장기능 가져오기
const { VueLoaderPlugin } = require('vue-loader')
const HtmlPlugin = require('html-webpack-plugin')
module.exports = {
entry: './src/main.js', // 진입점. 임의로 파일명 작성가능
output: {
path: path.resolve(__dirname, 'dist') // __driname: 루트경로정보, 'dist' 폴더
filename: 'hello.js', // 생략하면 main.js로 생기므로 생략가능
clean: true // 불필요한 파일 삭제
},
module: {
rules: [
{
test: /\.vue$/, // 정규표현식으로 vue확장자를 모두 선택
use: 'vue-loader', // vue 해석을 도와주는 패키지 npm i -D vue-loader@next
}
]
},
plugins: [
new VueLoaderPlugin(),
new HtmlPlugin({
template: 'src/index.html'
})
]
}
webpack.config.js는 nodeJS환경에서 동작하기 때문에 module.exports를 작성해줘야 한다.
module.exports에서 entry를 제외한 옵션을 작성할 때에는 output의 위치를 보장할 수 없기 때문에 path.resolve()로 작성해야 하는데, path.resolve를 작성하지 않고 경로만 작성하는 경우는 path.resolve가 내장되어 있는 경우일 것이다.
vue-loader를 설치할 때 VueLoaderPlugin 설정과 compiler-sfc 설치를 해줘야 하는데, VueLoaderPlugin은 const { VueLoaderPlugin } = require('vue-loader')
선언과 webpack.config.js
의 module에서 plugins: [ new VueLoaderPlugin() ]
을 넣어줘야 한다.
컴파일을 도와주는 vue-loader
와 컴파일해주는 싱글파일컴파일러 compiler-sfc
npm i -D @vue/compiler-sfc
를 통해 설치할 때, package.json에 있는 @vue/compiler-sfc의 버전과 vue의 버전이 같아야 한다.
이후 npm run build 사용가능할 때 mode옵션을 넣어달라고 하는데, package.json의 "scirpts":
의 "build": "webpack"
을 "build": "webpack —mode production"
으로 하면 자동으로 mode옵션을 만들어준다.
html-webpack-plugin
: src의 index.html
가 업데이트 될 때 마다 dist의 index.html
에 반영하도록 만들어준다. 이때 module의 output옵션에 clean: true
을 넣으면 불필요한 파일을 자동으로 삭제해준다.
npm i -D html-webpack-plugin
설치 후 package.json파일에서 "scripts": {"dev": "webpack-dev-server --mode development"}
을 넣어주고 npm run dev하면 hot module replacement 기능이 담긴 새로운 포트(초기값8080, devServer: { port: xxx}
으로 지정가능)가 생성된다.
npm i -D css-loader vue-style-loader
을 통해 css-loader, vue-style-loader를 설치하고,
webpack.config.js의 module의 rulse에 아래와 같은 값을 넣는데, 먼저 읽어야 하는 것을 역순으로 정렬해야 한다. (먼저 읽어야 하는 것을 나중에)
{
test: /\.s?css$/, // scss와 css 둘다 읽도록
use: [
'vue-style-loader',
'css-loader',
'sass-loader'
]
}
css style은 전역으로 적용되는데,
scss를 사용하고 싶다면 npm i -D sass sass-loader
를 설치하고, webpack.config.js에 추가한 뒤<style lang="scss">
를 사용하면 된다.
module exports에 resolve: { extensions: ['.vue', '.js'] }
를 추가하면 vue파일에서 .vue, .js가 붙은 확장자를 생략할 수 있다. 다만 중복된 이름을 조심해야 한다.
module exports의 resolve에 alias: { '~': path.resolve(__dirname, 'src') }
를 추가하면 ~
를 이용해 루트경로의 src(절대경로)에서부터 파일을 찾을 수 있다. 이후 스크립트의import FILE from './place'
를 import FILE from '~/place'
으로 작성하면 FILE이 어느 위치로 이동해도 문제가 생기지 않는다.
npm i -D copy-webpack-plugin을 통해 CopyPlugin을 설치하고, webpack.config.js에 등록한다.
이후 플러그인 옵션으로 아래와 같이 작성한 후, static폴더(임의폴더명)를 생성 후 favicon.ico 파일을 넣는다. ico 확장자는 브라우저 아이콘을 뜻하며, index.html은 파비콘 설정이 없을 때 주변의 favicon.ico를 찾아 파비콘으로 설정한다.
new CopyPlugin({
patterns: [
{from:'static', to:'dist'} // to 위치생략시 output의 위치로 설정됨
]
})
Prettier는 팀원간의 코딩 컨벤션을 맞추기 위해 사용되고, eslint는 잘못 입력한 문법을 자동으로 수정하기 위해 사용된다.
확장플러그인 eslint을 설치하고, npm i -D eslint eslint-plugin-vue
으로 플러그인을 설치한 후, package.json에서 등록해준다.
이후 아래와 같이 .eslintrc.json
을 설정한다.
{
"env": {
"browser": true, // 브라우저에서 eslintrc 확인
"node": true
},
"extends": [
"eslint:recommended", // eslint는 추천규칙을 사용
"plugin:vue/vue3-recommended" // plugin에 해당하는 것은 vue3추천 사용
],
"rules": {
"vue/html-closing-bracket-newline": ["error", {
"singleline": "never",
"multiline": "never"
}]
}
}
import { createApp } from 'vue'
import App from '~/App.vue'
import Btn from '~/components/Btn'
const app = createApp(App)
app.component('Btn', Btn) // '컴포넌트이름', 옵션
app.mount('#app')
자주 사용하는 컴포넌트를 등록
`import Hello from '~/components/Hello'
export default {
components: {
Hello
},
}`
하위컴포넌트에서 양방향 반응을 하려면 받은 props데이터를 data() 로 가공해서 사용해야 한다.
// App.vue
<template>
<Hello
v-bind="post" />
</template>
<script>
// import Hello from './components/Hello' // 상대경로, 지역 컴포넌트 등록,
import Hello from '~/components/Hello' // 절대경로
export default {
components: {
Hello
},
data() {
return {
post: {
id: 1,
title: 'Hello Vue!',
}
// Hello.vue
<template>
<h1>{{ id }} / {{ title }} / {{ email }}</h1>
</template>
<script>
export default {
props: { // null, undefined는 모든 유효성검사 통과
id: {
type:Number, // 숫자만 허용
required: true // 필수
title: [String, Number] // 스트링, 문자 허용
email: {
type: String,
default: '기본값@abc.com' // 값이 없으면 기본값 반환
},
//객체나 배열은 참조형이므로 기본값이 항상 값이 유지되도록 함수로 반환(retrun)되어야 한다.
// default: function(){ return { message: 'hello' } }
}
}
</script>
prop은 대소문자를 구분하는데, 이때 html에서는 대쉬케이스를, 자바스크립트에서는 카멜케이스를 써야 한다.
//App.vue
<template>
<h1>
{{ msg }}
</h1>
<Hello
class="hello"
style="font-size: 100px;"
@click="msg += '!'" />
</template>
//Hello.vue
<template>
<h1 // h1, h2 최상위객체 2가지
:class="$attrs.class"
:style="$attrs.style">
Hello
</h1>
<h2 @click="$attrs.onClick"> // click이벤트는 onClick
Haha?!
</h2>
</template>
최상위객체(루트객체)가 여러개일땐 클래스가 적용이 안되는데, 이때 $attrs를 사용한다.
$는 Vue의 기본속성을 의미하며, $attrs(attribute 속성의 줄임말)는 컴포넌트가 갖고있는 속성을 갖는 객체이다.
따로따로 넣지 않고 하나에 몰빵하고 싶을 땐 v-bind="$attrs"
를 몰빵하고 싶은 태그에 넣으면 된다.
Props로 지정되지 않은 것들을 Non-Prop이라 하는데, $attrs는 Props가 아닌 객체로써, Props로 지정된다면 사용되지 않는다.
또한 Props가 없는 단일 최상위 루트도 Non-Prop인데, Vue에는 기본적으로 inheritAttrs: true이 적용된다. 때문에 자동으로 최상위루트에게 속성상속이 제공되는 것인데, inheritAttrs:false를 지정한다면 최상위루트가 하나뿐이라 하여도 속성이 상속되지 않는다.
// App.vue
<Hello :message="msg" @please="reverseMsg" />
// Hello.vue
<h1 @click="$emit('please')"> {{ message }} </h1>
<script>
export default {
props: {
message: {
type: String,
default: ''
}
},
emits: ['please'] // 명시적 등록
} // ⚠주의: 네이티브 이벤트명(ex'click')을 넣으면 커스텀이벤트로 네이티브 이벤트를 덮어쓰게 됨
</script>
'please'
라는 함수를 h1
클릭 시 $emit
을 통해 App.vue로 보낸다.@please
로 $emit
을 받아 reverseMsg
함수(methods)를 실행한다. 여기서 emits은 명시적 등록기능을 하며, 이전과 마찬가지로 검증기능도 가능하다. 다만 네이티브 이벤트명(ex'click')을 넣으면 커스텀이벤트로 네이티브 이벤트를 덮어쓰게 되므로 주의해야 한다.
// App.vue
<Hello v-model="msg" />
// Hello.vue
<input :value="modelValue"
@input="$emit('update:modelValue', $event.target.value)" />
props: {
modelValue: {}
}
modelValue
는 App.js의 v-model
디렉티브와 자동으로 연결되는 예약어이다.
Hello.vue의 value 속성을 modelValue
로 설정하고, $emit
의 커스텀이벤트에 :modelValue
를 추가하여 양방향 이벤트로 명시하면 App.vue에서 v-model
만으로도 양방향 이벤트를 설정할 수 있다.
modelValue 대신 다른 단어를 쓰거나 modelValue를 여러개로 만들고 싶다면 v-model:변경단어="msg"
로 바꾼 후, 기존 modelValue
대신 변경단어를 넣으면 된다.
fallback(기본값)
V-on:
➡ @
처럼 v-slot:
은 #
으로 사용가능
Named Slot 지정
슬롯은 범위를 가질 수 있는데(범위를 가지는 슬롯). 범위를 하나만 사용하는 슬롯은 defalut 슬롯 으로 아래 예시와 같이 사용될 수 있다.
<template #default="slotProps"> //
<h2>Hello {{ slotProps.hello }}</h2>
</template>
<slot :hello="123"></slot>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
keep-alive태그로 감싼 동적컴포넌트들은 처음 렌더링할 때 캐싱하여서 반복되는 create, mount 등을 막는다. 하지만 메모리를 차지하기 때문에 자주 토글되는 컴포넌트에 사용하는 것을 권장한다.
ref=""
는 참조(reference)한다는 의미로써 $refs
라는 내장객체의 값에 넣어주는 역할을 한다. const h1El= document.queryselector('h1')
하던 것을 해당 h1태그에 ref="hello"
만 넣어주면 const h1El = this.$refs.hello
으로 간단하게 사용할 수 있는 것이다.
다만 연결된 이후에 사용하는 것이므로 mounted()에서만 사용할 수 있으며, 네이티브요소(h1, div ...)가 아닌 컴포넌트에서 사용하려면 this.$refs.hello.$el
같이 $el을 붙여야 한다.
또한 참조하는 최상위요소가 여러개라면 this.$refs.hello.$refs.world
과 같이 .$refs
를 중첩해서 넣어 찾아야 한다.
바뀌는 요소에 ref를 사용할 때 주의할 점이 있는데, 요소가 바뀔 때 바로 렌더링된다고 보장할 수 없으므로 아래와 같은 methods를 추가하여 사용하여야 한다.
setTimeout()
setTimeout 웹API를 이용해 순서를 뒤로 늦추는 방법이다.
$nextTick
다음 DOM업데이트 주기 이후 실행된 콜백을 연기하는 함수로, 쉽게 말하자면 데이터를 수정했을 때 화면이 바뀌는 것을 확인한 뒤 콜백을 실행하는 함수이다.
this.$nextRick(() => {
<template>
<div v-if="!isEdit">
{{ msg }}
<button @click="onEdit">
Edit!
</button>
</div>
<div v-else>
<input
ref="editor"
v-model="msg"
type="text"
@keyup.enter="isEdit = false" />
</div>
</template>
<script>
export default {
data() {
return {
isEdit: false,
msg: 'Hello Vue!'
}
},
methods: {
onEdit() {
this.isEdit = true
// setTimeout(() =>{
this.$nextTick(() => {
this.$refs.editor.focus()
})
}
}
}
</script>