데브코스 37일차 TIL : Vue [ parcel, webpack 개발환경 구축 및 컴포넌트, prop, ref ]

te-ing·2021년 9월 30일
0
post-thumbnail

import * as Vue from 'vue' 'vue'에서 상대경로를 입력하지 않는다면 node_modules의 패키지를 가져오는 것

번들러: App.vue를 브라우저에서 동작할 수 있도록 해주는 것

parcel

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 덕분에 언제든지 재생성되므로 굳이 깃헙에 업로드할 필요는 없다.

parcel을 통해 Vue에서 scss 사용하기

npm i sass -D 설치 후 vue 파일 내 을 넣으면 scss문법 사용가능

webpack

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와 compiler-sfc, VueLoaderPlugin

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}으로 지정가능)가 생성된다.

css처리를 위한 webpack loader

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이 어느 위치로 이동해도 문제가 생기지 않는다.

favicon

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의 위치로 설정됨
      ]
    })

eslint

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"
    }]
  }
}

컴포넌트

컴포넌트 W3C 규칙

  1. 영어 소문자만 사용
  2. 하이픈으로 단어 연결

컴포넌트 전역등록

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() 로 가공해서 사용해야 한다.

props 유효성 검사

// 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:dash-case, jsx: camelCase

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"를 몰빵하고 싶은 태그에 넣으면 된다.

Non-Prop속성

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>
  1. Hello.vue에서 만든 'please'라는 함수를 h1 클릭 시 $emit을 통해 App.vue로 보낸다.
  2. App.vue는 @please$emit을 받아 reverseMsg 함수(methods)를 실행한다.
  3. App.vue의 Props가 갱신되어 App.vue의 msg도 바뀐다

여기서 emits은 명시적 등록기능을 하며, 이전과 마찬가지로 검증기능도 가능하다. 다만 네이티브 이벤트명(ex'click')을 넣으면 커스텀이벤트로 네이티브 이벤트를 덮어쓰게 되므로 주의해야 한다.

props: modelValue

// 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 대신 변경단어를 넣으면 된다.

컴포넌트 Slots

fallback(기본값)

V-on:@처럼 v-slot:# 으로 사용가능

Named Slot 지정

슬롯은 범위를 가질 수 있는데(범위를 가지는 슬롯). 범위를 하나만 사용하는 슬롯은 defalut 슬롯 으로 아래 예시와 같이 사용될 수 있다.

<template #default="slotProps"> //
      <h2>Hello {{ slotProps.hello }}</h2> 
    </template>

<slot :hello="123"></slot>

keep alive 컴포넌트 캐싱

<keep-alive>
    <component :is="currentComponent" />
  </keep-alive>

keep-alive태그로 감싼 동적컴포넌트들은 처음 렌더링할 때 캐싱하여서 반복되는 create, mount 등을 막는다. 하지만 메모리를 차지하기 때문에 자주 토글되는 컴포넌트에 사용하는 것을 권장한다.

ref

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>
profile
프론트엔드 개발자

0개의 댓글