본문 바로가기
개발 지식 A+/FE

Vue.js 시작하기

by ddubbu 2021. 2. 20.
728x90
반응형

Nuxt.js 시작하려는데 React와 다르게 *.vue 파일에 HTML, CSS, JS 한꺼번에 정의하고 사소한 문법이 달라서 그 기반이 되는 Vue.js 부터 천천히 살펴보자 - 참고 문서


템플릿 구문

데이터 바인딩의 기본은 Mustache 구문 이다 : {{ JS 단일 표현식 }}

하지만 React JSX 와 달리 HTML 속성에서는 사용하지 못한다. 대신 v-bind 디렉티브를 이용하면 된다.

<a href="{{ link }}">링크</a> // (x)

<a v-bind:href="link">링크</a> // (o)
<a v-bind:href=[link]>링크</a> // (o) 2.6.0+ 추가

// 혹은 겹따옴포 안에 홑따옴표로 정의
<a v-bind:href="'https://www.naver.com'">네이버</a> // (o)

<script>
  // Vue instance
  data: {
    link: "https://www.naver.com"
  }
</script>

또한 하위 컴포넌트props 전달시 falsy 한 boolean 값을 전달하면 해당 element에 포함되지 않는다.

 

DOM selector

const data = {
  message: 'hello world'
}
cont app = new Vue({
  el: 'selector',
  data: data  // 여기에 변수 선언
})

// 참고로 속성 변경을 막고자 한다면
Object.freeze(data);

 

디렉티브

v-*  접두사가 있는 특수 속성

 

(사용방법) v-*:동적_전달인자 = "바인딩되는 객체_JS_단일표현식"

동적 전달인자 (콜론 뒤에 오는 속성 이름)는 v-*:href 처럼 직접 지정할 수도 있고 v-*:[변수] 처럼 Vue instance 속성으로 정의된 변수를 사용할 수 있다. 이때  없거나 null 인 경우 해당 코드는 작동하지 않는다. 이 외에는 string 변환이 예상되고 작동한다. 그리고 바인딩되는 객체 또한 v-for을 제외하고 단일 JS 표현식을 정의할 수 있다. 예시로, " 'text-' + variable " 처럼  겹따옴표 내부에 text와 변수를 혼용할 수 있다.

 

시작에 앞서 만약 디렉티브를 사용하는 tag 내부에 여러 엘리먼트 블럭을 렌더링한다면 <template> tag를 사용해야한다.

 

1. v-bind : 하위 컴포넌트에 전달될 porps 혹은 및 내부 content로 출력될 데이터 제어

<tag v-bind:prop1 ="message" />

// 자주 사용되는 v-bind 약어 제공됨.
<tag :prop1 ="message" />

 

 

Vvue instance를 이용해 동적으로 제어 가능하다.

또한 나중에 분리된 하위 component에 prop 전달 시 사용되겠다. 

<div id="app-7">
  <ol>
    <todo-item
      v-for="item in groceryList"
      v-bind:todo="item" // -> todo-item 컴포넌트에 props 전달을 위해
      v-bind:key="item.id"
    ></todo-item>
  </ol>
</div>


<script>
  Vue.component('todo-item', {
    props: ['todo'],
    template: '<li>{{ todo.text }}</li>'
  })

  var app7 = new Vue({
    el: '#app-7',
    data: {
      groceryList: [
        { id: 0, text: 'Vegetables' },
        { id: 1, text: 'Cheese' },
        { id: 2, text: 'Whatever else humans are supposed to eat' }
      ]
    }
  })
</script>

 

2. v-if  : 조건문 제어

조건문 제어가 간편하고만 JSX 내부에서 3항 연산자 안써도 되고

<tag v-if="booleanValue" > seen-toggle 데이터 </tag>
<tag v-else-if="booleanValue"> 조건에 따라 </tag> // -> 2.1.0+
<tag v-else> 다 falsy 하면 </tag>

 

이때, 처음부터 렌더링하지 않으면 내부 데이터가 새로이 리셋되지 않을 수 있다. (만약 toggle 했을 때 이전 입력값이 여전히 남아있을 수 있음) 이를 원치 않는다면 key 속성을 추가해준다.

<template v-if="loginType === 'username'">
  <label>사용자 이름</label>
  <input placeholder="사용자 이름을 입력하세요" key="username-input">
</template>
<template v-else>
  <label>이메일</label>
  <input placeholder="이메일 주소를 입력하세요" key="email-input">
</template>

참고로 v-show 디렉티브도 조건부 렌더링 방법인데 이는 DOM에 남아있으며 display CSS 속성을 변경할 뿐이다.

 

 

3. v-for

2.2.0 이상부터는 v-for 사용 시 key 가 필수적이다.

 

3-1. v-for 배열 

<li v-for="(item, idx) in items"> {{ item.message }} </li>
<!-- of 문법도 가능
  <li v-for="(item, idx) of items"> {{ item.message }} </li>
-->

<script>
  var example1 = new Vue({
    el: '#example-1',
    data: {
      items: [
        { message: 'Foo' },
        { message: 'Bar' }
      ]
    }
  })
</script>

 

 

배열 변경 감지

Vue가 감시 가능한 배열 변이 메소드는 push, pop, shift, splice, sort, reverse 등 이며 배열에 직접 할당함으로써 바꿀 수 있다. 만약 인덱스로 배열에 있는 항목 변경한다면 감지하지 못한다. 대신  Vue.set 함수 사용을 권장한다.

var vm = new Vue({
  data: {
    items: ['a', 'b', 'c']
  }
})

vm.items[1] = 'newValue' // (x)
Vue.set(vm.items, 1, 'newValue') // o)

 

 

3-2. v-for 객체 

<ul id="v-for-object" class="demo">
  <li v-for="(value, key, index) in object">
    {{ value }}
  </li>
</ul>

<!--객체에서 index는 일관적이지 않아서.. 그닥 비추 -->

<script>
  new Vue({
    el: '#v-for-object',
    data: {
      object: {
        title: 'How to do lists in Vue',
        author: 'Jane Doe',
        publishedAt: '2016-04-10'
      }
    }
  })
</script>

 

 

객체 변경 감지

속성 값 변경은 감지하지만, 속성 추가 및 삭제를 감지하지 못한다. 이또한 Vue.set 함수 혹은 Object.assign() 으로 새로이 할당할  것을 권장한다.

var vm = new Vue({
  data: {
    userProfile: {
      name: 'Anika'
    }
  }
})

Vue.set(vm.userProfile, 'newKey', 'newValue');

vm.userProfile = Object.assign({}, vm.userProfile, {
  'newKey' : 'newValue'
})

 

3-3. v-for Ragne

숫자 반복도 가능하다.

<div>
  <span v-for="n in 10">{{ n }} </span>
</div>

 

4. v-on  이벤트리스너 연결

<button v-on:"clickHandler"> click </button>

// 자주 사용되는 v-on 약어 제공됨.
<tag @click ="clickHandler" />

// 혹은 함수 인자 전달도 가능함.
// DOM event 객체는 $event 로 전달한다.
<button v-on:"clickHandler(전달인자들, $event)"> click </button>

<script>
	...,
    method: {
      clickHandler: function(전달인자들,event){
      	//이게 연결됨!
      }
    }
    
</script>

 

대박, 데이터 뿐만 아니라 함수도 동적 할당 됨. 아래 예시처럼 기존 함수를 버튼을 누를 때마다 click이 출력되는 함수로 바꿈.

그럼 여기서 궁금한게, Vue instance 에서는 data, methods 등으로 분리되지만 instance 의 속성으로 바로 접근 가능한데 그럼 전체 data 속성은 어떻게 들어가는거지 싶어서 보니

Observer 라는 객체로 정의되고 instace._data (혹은 instance.$data) 속성으로 존재함.

 

이벤트 수식어

이벤트 핸들러 내부에 event.preventDefault()와 같은 자주 사용하는 메소드를 접미사로 제공한다. 접미사는 여러개를 사용하며 순서가 반영된다. (이벤트 관련 메소드를 잘 안써봐서 캡처링, 버블링 때 유용할 듯)

<!-- 클릭 이벤트 전파가 중단됩니다 -->
<a v-on:click.stop="doThis"></a>

<!-- 제출 이벤트가 페이지를 다시 로드 하지 않습니다 -->
<form v-on:submit.prevent="onSubmit"></form>

 

키보드 이벤트 수식어

이거 대박이다. ✍ v-on:keyup.enter 처럼 특징 키코드 없이 사용할 수 있다. 개꿀

 

혹여 별칭 key가 아니라 특수한 키코드라면 전역 객체로 지정할 수 있다.

// `v-on:keyup.f1`을 사용할 수 있습니다.
Vue.config.keyCodes.f1 = 112

조합키 누름 여부도 확인할 수 있음  : .exact

 

 

 

5. v-model 입력 상태를 양방향으로 바인딩

대박 이건 React에서 불편했던 input tag value, onChange , state 정의해야하는 번거로움을 줄여주겠다!

 

그런데 이상한 주의사항이있다.

  • 한국어는 한글자씩 느리게 업데이트됨. - input 이벤트 대신 사용
  • HTML에서 정의한 value, checked, selected 속성을 무시한다. - Vue 인스턴스 내부 데이터 옵션에 값을 부여하자.
  • text, textarea 태그 - value 속성과 input 이벤트를 사용
  • 체크 박스, 라디오 버튼들은 checked 속성과 change 이벤트 사용
  • select 태그는 value 속성과 change를 이벤트로 사용

뭐지.. 왜 이렇게 예와사항이 많아?  다 된밥에 재 뿌린 거 같은 이 기분은, Vue 가 너무 좋다고 생각했다가 기분이 잡...

 

 사용자 정의 입력 컴포넌트 - 한글을 위해서 시도해볼만함.
<input
  v-bind:value="something"
  v-on:input="something = $event.target.value">
  
// 혹은

<custom-input
  :value="something"
  @input="value => { something = value }">
</custom-input>

@input 이벤트를 좀더 공부할 필요가 있어보임 (event.target.value 를 바로 받는 듯 하네)

 

 <input type="checkbox" /> 
  • 배열로 다루기
    필수로 value 속성을 선언하고 v-model 에 바인딩된 변수 타입이 배열이면 해당 값들이 요소로 들어간다. (혹은 삭제)
    여러 checkbox 타입의 input 태그가 같은 v-model을 바인딩하고 있으면 배열에 추가됨 (혹은 삭제)
  • 일반적으로
    value를 지정하지 않고 v-model에 배열을 바인딩하면 null 이 들어간다.
    value를 지정하지 않고 v-model에 빈문자열을 바인딩하면 boolean 값을 가진다.
    (만약 여러개가 문자열로 초기화된 같은 v-model을 공유하고 있다면 함께 controll 됨) 
 <input type="radio" /> 

하위 option 들끼리 같은 v-model 을 공유하기. 이때, 각 value 부여하기

<select />

select tag에서 v-model을 바인딩하고 하위 option 심플하게 정의 (value 옵션을 넣지않으면 textContent가 value 됨)

이때, 초기값에 어떤 옵션에도 없으면 iOS에서는 변경 이벤트가 발생하지 않아 빈 옵션을 설정함.

<select v-model="selected">
  <option disabled value="">Please select one</option>
  <option>A</option>
  <option>B</option>
  <option>C</option>
</select>

<script>
 // Vue instance data 옵션에
    selected: ''
</script>

 

select tag에 multiple 속성 넣고 [shift] 눌러서 다중 선택하면 data 옵션에서 배열로 초기화하지 않아도 여러개가 들어감.

 

v-for 로 동적 옵션 렌더링 필수 숙지!
  <option v-for="option in options" v-bind:value="option.value">
    {{ option.text }}
  </option>
  
  <script>
  new Vue({
  el: '...',
    data: {
      selected: 'A',
      options: [
        { text: 'One', value: 'A' },
        { text: 'Two', value: 'B' },
        { text: 'Three', value: 'C' }
      ]
    }
  })
</script>

 

v-model 수식어
  • .trim 좌우 띄어쓰기 삭제
  • .number 숫자로 형변환
  • .lazy change 이벤트 이후에 동기화  무슨말?

 

추가 내용은 읽다만 곳부터 - 여기

 


컴포넌트 등록

 

주의사항이 있다.

  • template : 항상 문자열 템플릿을 사용하는 것이 좋다.
  • data
    - 생성된 컴포넌트가 같은 data context를 갖지 않도록 새로운 객체를 리턴할 수 있도록 반드시 함수여야한다.
    - React의 state 같은 개념인듯.
  • props
    - 부모로부터 물려받으며 데이터와 마찬가지로 템플릿 내부에서 사용가능.
    - props 리스트 이름만 등록하거나, 객체로 정의해서 요구사항을 정의할 수 있다. ( 자세한 내용은 접은 글 )
더보기
Vue.component('example', {
  props: {
    // 기본 타입 확인 (`null` 은 어떤 타입이든 가능하다는 뜻입니다)
    propA: Number,
    // 여러개의 가능한 타입
    propB: [String, Number],
    // 문자열이며 꼭 필요합니다
    propC: {
      type: String,
      required: true
    },
    // 숫자이며 기본 값을 가집니다
    propD: {
      type: Number,
      default: 100
    },
    // 객체/배열의 기본값은 팩토리 함수에서 반환 되어야 합니다.
    propE: {
      type: Object,
      default: function () {
        return { message: 'hello' }
      }
    },
    // 사용자 정의 유효성 검사 가능
    propF: {
      validator: function (value) {
        return value > 10
      }
    }
  }
})

 

1. 전역 등록

<script>
  // 전역으로 등록
  Vue.component( tag-이름, {
    template: '<div>{{ myMessage }}</div>', // camelCase로 사용함
    props: [ myMessage, ... ] 
  } )


  // 루트 인스턴스 생성
  new Vue({
    el: '#example'
  })
</script>

<!-- 최종 렌더된 DOM -->
<!-- 부모 컴포넌트로부터 propr으로 데이터 물려받음 (kebab-case 로 정의됨) -->
<div id="example" my-message="사용자 정의 컴포넌트 입니다!">
  <div>사용자 정의 컴포넌트 입니다!</div>
</div>

2. local 등록

<script>
  // local 등록
  var Child = {
    template: '<div>사용자 정의 컴포넌트 입니다!</div>'
  }

  // 루트 인스턴스 생성
  new Vue({
    // ...
    components: {
      'my-component': Child
    }
  })
</script>

<!-- 최종 렌더된 DOM -->
<div id="example">
  <div>사용자 정의 컴포넌트 입니다!</div>
</div>

 

3. 동적 컴포넌트

뭔가 routing 과 비슷한데...

<script>
  var vm = new Vue({
    el: '#example',
    data: {
      currentView: 'home'
    },
    components: {
      home: { /* ... */ },
      posts: { /* ... */ },
      archive: { /* ... */ }
    }
  })
</script>

<component v-bind:is="currentView">
  <!-- vm.currentView가 변경되면 컴포넌트가 변경됩니다! -->
</component>

 

특정 엘리먼트 사용자 지정 컴포넌트 사용 불가

<ul>,<ol>,<table>과<select> 은 하위 엘리먼트가 제한되어있다. is 와 같은 특수 속성으로  사용자 지정 컴포넌트를 연결.

<!-- 잘못됨

<table>
  <my-row>...</my-row>
</table>

-->

<!-- 올바른 예시 -->

<table>
  <tr is="my-row"></tr>
</table>

 

부모-자식 관계

React와 비슷하지만 Lifting state 개념을 Emit Events 라고 일컫는다.

(참고로 자식 컴포넌트 참조시 ref 에 등록하고 parent.$refs.xx 로 접근 가능, React Hook 같다.)

숫자 props 전달

만약 일반적인 prop으로 전달하면 문자열로 인식된다.

<comp v-bind:some-prop="1"></comp>

 

 

 


Routing 

왜 간단한거지..
참고

 

 

Vue instance

위 개념들을 훑어보면서 자연스럽게 Vue instance 를 사용하였다. 하지만 좀 더 구체적으로 살펴보자.

const vm = new Vue({
  /* 
    ※ 주의사항 ※
    this context 는 Vue instance 이다.
    허나, 함수 선언 시 화살표 함수를 사용하면 this 를 갖지 않기 때문에 사용을 지양한다.
  */
  el: 'selector',
  data: data, // data 밑에 정의된 속성들은 반응형으로 변경 시 re-rendering된다.
  methods: {
    fn1
  },
  
  /* Lifecycle HOOK */
  created: fn() {}, // 인스턴스 생성된 후 호출됨
  mounted: fn() {}, // first render
  updated: fn() {}, // re-render
  destroyed: fn() {} // DOM에서 지워짐
})

 

Vue Lifecyle Diagram

React와 같은 듯 다르다. 특히 Vue instance가 create 될 때의 시점이 존재한다.

 

computed and watch option

 

computed 내부 속성에 복잡한 로직 함수를 정의할 수 있고 이는 기존 data 사용 시 자동으로 의존 관계가 형성되어 변경사항이 반영된다. 물론 method 속성에 정의해서 함수( ) 로 실행시켜 사용할 수 있다. 그럼 둘의 차이점은 무엇일까? 전자는 연결된 데이터가 변경될 때만 계산되어 캐싱 (저장) 된다는 것이고 바뀌지 않으면 기존 값을 유지한다. 즉 계산 시간을 절약할 수 있다. 후자는 렌더링 할 때마다 매번 계산된다. 예시로 Date.now( ) 를 return 하면 computed 속성의 경우 절대로 업데이트되지 않는다. 둘의 차이점을 이해하고 적절히 사용하면될 듯.

// 방법 1 computed 사용
<p>뒤집힌 메시지: "{{ reversedMessage }}"</p>

<script>
  //  Vue instance 선언시 
  computed: {
    // 계산된 getter
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }
</script>

// 방법 2 method 사용
<p>뒤집힌 메시지: "{{ reversedMessage() }}"</p>

<script>
  //  Vue instance 선언시 
  methods: {
    reversedMessage: function () {
      return this.message.split('').reverse().join('')
    }
  }
</script>

 

watch 속성 (명령형 프로그래밍)은 특정 data 의 변화를 감지하는데 변경해야할 다른 데이터가 있다면 자동으로 해주는 computed 속성 (선언형 프로그래밍)가 더 나은 듯. 만약 특정 행동(예를 들어 비동기 연산)을 취해야한다면 watch가 낫고! 직접 경험해봐야 할 듯. 아, 참고로 input 값 data에 반영은 v-model 디렉티브 사용하면 됨.

 

꽤 괜찮은 예시 -  watch 속성을 이용해 입력 전, 중, 후 에 따라 메세지를 상이하게 출력

 

 

Binding Class and Style

 

일반적으로 v-bind 를 사용해 class, style이 처리된다. 하지만 값으로 입력되는 최종 문자열 연산에 대한 오류를 줄이기 위해 향상된 기능을 제공한다.

 

대박 isActive 값에 따라 active 클래스 존재 여부를 토글할 수 있다. modal 제어에 편하겠다잉. 그리고 일반 class 속성과 공존할 수 있다. 토글 여부 필요하지 않으면 그냥 class 로 바로 정의하면 되겠다.

<div
  class="static"
  v-bind:class="{ active: isActive, 'text-danger': hasError }"
></div>

<!--
  // 2번째 방법 객체 반환
  <div v-bind:class="classObject"></div>
  
  // 3번째 방법 배열 전달
  <div v-bind:class="[activeClass, errorClass]"></div>
-->

<script>
  data: {
    isActive: true,
    hasError: false,
    
    // 2번째 방법
    classObject: {
      active: true,
      'text-danger': false
    },
    
    // 3번째 방법
    activeClass: 'active',
    errorClass: 'text-danger'
  }
</script>


<!-- 최종 렌더링된 DOM 구조 -->
<div class="static active"></div>

 

그리고 사용자 정의 component 가 정의될 때, 사용될 때 모두 class 값을 부여할 수 있는데 덮어씌우지 않고 추가됨.

 

스타일 적용은 템플릿이 간단하도록 분리하는게 더 낫다는데.....

<div v-bind:style="styleObject"></div>

<script>
  data: {
    styleObject: {
      color: 'red',
      fontSize: '13px'
    }
  }
</scipt>

 

아니, CSS 로 분리하는 게 더 낫지 않나??

 

 


질문

 

1. is, for props 는 뭐지??

여기 에서 출발한 의문

 

is는 component를 연결하는 것 같지만,  for 은 뭐지....  무튼 예시 코드 매우 간단해서 좋다. remove도 간편하고

 

반응형

'개발 지식 A+ > FE' 카테고리의 다른 글

Web 3D rendering  (0) 2023.09.28
Nuxt.js 시작하기  (0) 2021.02.21
Redux  (0) 2021.01.26
Hooks 공식문서로 이해하기  (0) 2021.01.26
this 키워드  (0) 2021.01.26