[멋쟁이사자처럼] 부트챌린지: 11시간만에 트위터만들기 - Vue.js 기본

부트챌린지/멋쟁이사자처럼:트위터 클론코딩
2026.07.09

 

드디어 Vue.js를 시작했다. 좀 쓸 것이 많아진 날.

 

오늘 배운 것

먼저 Vue.js의 devtools를 까는 법을 배웠다. Chrome 확장 프로그램을 깔아야한다.

 

https://chromewebstore.google.com/detail/nhdogjmejiglipccpnnnanhbledajbpd?utm_source=item-share-cb

 

Vue.js devtools - Chrome 웹 스토어

DevTools browser extension for Vue.js

chromewebstore.google.com

 

다만 Vite 기반으로 할 경우 자체적으로 devtools가 깔려있다. 하단에 있는 모듈을 통해서 이용이 가능하다.

 

 

 

 

다음으로는 컴포넌트 구성 요소에 대해서 알려주셨는데, template, script,  style 이렇게 세 가지였다. 먼저 템플릿의 경우는 UI 영역이라고 보면 편하고(굳이 따지면 DOM에 가깝다고 보면 된다), 스크립트는 동적인 부분, 즉 JS와 같이 이벤트나 로직을 담당하는 부분, 마지막으로 스타일은 컴포넌트 스타일을 정의하는 부분이다. 간단히 말하면 그냥 HTML, JS, CSS 같다고 보면 된다.

 

Vue.js에서 데이터를 표시하는 방식은 텍스트 보간Text Interpolation이라고 하는데, Mustache 문법을 사용하여 데이터를 바인딩한다. 표기법은 {{ property }} 처럼 되어 사용된다. React에서 사용하는 { property }와 동일한 방식이라고 생각하면 편하다. 다만 React처럼 속성에도 바인딩 처리를 할 수 없고 : 처리를 해야한다. 즉, 다음처럼 처리하는 것

<!-- React 방식, attribute에 사용할 경우에도 마찬가지로 {}를 사용할 수 있다. -->
<img src={imageSrc} />

<!-- Vue.js 방식, attribute에 사용할 경우에는 콜론으로 처리해야 property를 받는다 -->
<img :src="imageSrc" />

 

 

이후로 설명한 것은 v-model로, 폼 입력과 데이터를 양방향으로 바인딩Two-way Data Binding한다. 입력값이 변경되면 자동으로 데이터가 업데이트 된다. 이에 따라 데이터가 변경되면 화면도 자동으로 변경된다. 사용하는 방법은 다음과 같다.

 

<!-- input에 넣은 값이 바인딩되어 실시간으로 property에 적용된다 -->
<p>안녕하세요, {{name}}입니다.</p>
<input type="text" v-model="name">

 

v-model은 텍스트 입력, 체크박스, 라디오 버튼, 선택 목록처럼 입력 형태의 요소들과 매칭이 잘 된다. 이러한 양방향 데이터 바인딩은 단방향 데이터 바인딩인 React에 비해서 장점을 가진다. 데이터와 UI 간의 동기화 과정을 프레임워크가 알아서 처리해 주므로 불필요한 보일러플레이트 코드가 줄어들어,React의 State가 가지는 수동적인 부분을 보완할 수 있다.

 

다음은 v-on으로, HTML에 다양한 이벤트를 연결하는 지시어다. @로 간단히 줄여 나타낼 수 있다. v-on은 JS의 on- 이벤트와 비슷하다고 생각하면 좀 더 이해가 편해진다. 사용하는 방식은 다음과 같다. JS와 크게 차이나지 않으므로 좀 더 이해에 편할 것이다.

 

<script setup>
import { ref } from 'vue'

const currentStatus = ref('대기 중')

// 인자를 받아 상태를 변경하는 함수
const updateStatus = (newStatus) => {
  currentStatus.value = newStatus
}
</script>

<template>
  <div>
    <p>현재 상태: {{ currentStatus }}</p>

    <!-- React 방식: 인자 전달 시 화살표 함수 필수 -> onClick={() => updateStatus('완료')} -->
    <!-- Vue 방식: 화살표 함수 선언 없이 템플릿 내부에서 직접 인자 전달 가능 -->
    <button @click="updateStatus('완료')">상태 변경</button>
  </div>
</template>

 

props는 부모 컴포넌트에서 자식 컴포넌트로 데이터를 전달하는데 사용된다. 이 부분은 React와 거의 일치한다. 차이점은 표기법이나 문법 정도로 기본적인 내용은 비슷하다.

 

 

<!-- React, 보다시피 props.를 사용해서 자식에서 받는다 -->
// 부모 컴포넌트
<ChildComponent userTitle="반갑습니다" userAge={25} userName={userName} />

// 자식 컴포넌트
function ChildComponent(props) {
  return <p>{props.userTitle} / {props.userName} / {props.userAge}</p>;
}


<!-- Vue.js, 대조적으로 동적 데이터는 콜론(:)을 붙여 부모에서 넘긴다 -->

<!-- 부모 컴포넌트 -->
<child-component user-title="반갑습니다" :user-age="25" :user-name="userName" />

<!-- 자식 컴포넌트, 3(Composition API)에서는 defineProps를 사용하여 부모의 것을 받는다 -->
<script setup>
defineProps({ userTitle: String, userName: String, userAge: Number })
</script>
<template>
  <p>{{ userTitle }} / {{ userName }} / {{ userAge }}</p>
</template>


<!-- 수업에서 배운 방식인 Options API, props 객체 옵션을 사용하여 부모의 것을 받는다 -->
<template>
  <p>{{ toChild }}</p>
</template>
<script>
export default {
  props: {
    toChild: String
  }
}
</script>

 

 

props 개념은 아래 포스트에서 정리해놨었다.

 

[Java 풀스택 개발자] React의 언어체계

 

[Java 풀스택 개발자] React의 언어체계

0. 들어가기에 앞서본래는 React 실전으로 포스트를 작성하려고 했지만, React의 언어 체계에 대한 이론적 정리가 필요할 거 같아 계획을 변경했다. 대체 이 라이브러리는 무엇이길래 이렇게 복잡

bbbbabbbababababa.tistory.com

 

그리고 React와 Vue.js의 결정적인 차이는 바로 emit이다. 이건 자식 컴포넌트에서 부모 컴포넌트로 이벤트를 전달하는 것으로, emit을 통하여 자식에서 부모에게 쏘아올릴 수 있다(!) 단방향에 가까운 React와 달리 Vue.js는 느슨한 결합을 지향하고 있어 생긴 것으로, 단방향의 단점을 그대로 보완했다고 할 수 있다. React의 경우 일단 부모에서 어떻게든 자식에게 콜백함수를 전달해야한다는 불편함이 있다(즉 자식은 어쨌든 수동적일 수밖에 없고, 부모의 역할이 많아진다). 이로 인해 자식은 부모가 주는 함수에 의존하는 수동적인 존재가 될 수밖에 없고, 부모 컴포넌트는 자식들의 통제실 역할을 하느라 전달해야 할 함수와 props가 많아져 비대해진다.

 

반면 Vue는 emit을 통해 자식이 주도적으로 신호를 발생시킨다. 부모는 그 신호를 감지할지 말지만 선택하면 되므로, 컴포넌트 간의 역할 분담이 훨씬 깔끔해지고 자식 컴포넌트의 독립성이 보장된다. 

 

<!-- React, 부모가 자식에게 콜백 함수를 Props로 내려주고 자식이 이를 실행한다 -->
// 부모 컴포넌트
<ChildComponent onUpdate={handleUpdate} />

// 자식 컴포넌트
function ChildComponent(props) {
  return <button onClick={() => props.onUpdate('새 데이터')}>보내기</button>;
}


<!-- Vue.js, 자식이 부모에게 커스텀 이벤트를 발송(Emit)하고 부모는 @로 감지한다 -->

<!-- 부모 컴포넌트 -->
<child-component @update-data="handleUpdate" />

<!-- 자식 컴포넌트, 3(Composition API)에서는 defineEmits를 사용하여 신호를 보낸다 -->
<script setup>
const emit = defineEmits(['update-data'])

const sendData = () => {
  emit('update-data', '새 데이터') // 이벤트명, 전달할 데이터 순서
}
</script>
<template>
  <button @click="sendData">보내기</button>
</template>


<!-- 수업에서 배우는 Options API 방식, this.$emit을 사용하여 신호를 보낸다 -->
<template>
  <button @click="sendData">보내기</button>
</template>
<script>
export default {
  methods: {
    sendData() {
      this.$emit('update-data', '새 데이터')
    }
  }
}
</script>

 

여기까지 마치고 실습(과제)에 들어갔는데, 위에서 React와 Vue.js의 비교를 하여 Vue.js의 장점만 다루었지만, 사실 Vue.js가 무조건 장점만 있는 게 아니다. 먼저 React는 라이브러리고, Vue.js는 프레임워크이므로 둘의 특성 상 비교를 하는 건 React에게는 억울한 일이다. 만약에 비교를 할 거라면 React보다는 Next.js와 비교를 해야겠지만, Next.js를 내가 해본적이 없으므로 패스.

 

실습(과제)에 대한 설명은 하지 않으려 하지만 위에서 설명하지 않았으나 실습하면서 알게 된 몇 가지를 얘기한다. 먼저 modelValue가 있다. v-model로 부모에서 데이터를 넘겨주면 자식에서 이를 modelValue로 받는다. 그리고 emit에서  update를 사용하는데, 자식 컴포넌트가 바뀐 값을 부모에게 보낼 때 사용하는 것이다.

 

이렇게만 두고보면 너무 감이 안 잡힐 거 같아 코드를 가져왔다. 아래는 자식 컴포넌트다.

 

<template>
  <div>
    <!-- 표준 input 이벤트를 감지하여 updateValue 메서드로 연결 -->
    <input 
      :type="type" 
      :value="modelValue" 
      @input="updateValue"
    />
  </div>
</template>

<script>
export default {
  name: "CustomInput",
  props: {
    type: String,
    modelValue: String
  },
  // 부모에게 보낼 커스텀 이벤트 이름을 명시적으로 선언
  emits: ['update:modelValue'],
  methods: {
    updateValue(event) {
      // 입력된 데이터의 공백을 제거한 뒤 부모의 v-model 주파수로 발송
      const value = event.target.value.trim();
      this.$emit('update:modelValue', value);
    }
  }
}
</script>

 

 

부모 컴포넌트

<template>
  <div>
    <!-- v-model 속성을 통해 자식 컴포넌트와 데이터를 양방향 바인딩 -->
    <CustomInput type="text" v-model="text" />
    <p>입력된 값: {{ text }}</p>
  </div>
</template>

<script>
import CustomInput from './CustomInput.vue';

export default {
  components: { CustomInput },
  data() {
    return {
      text: ''
    };
  }
}
</script>

 

아래는 위를 실행했을 경우다(물론 어느정도 커스텀이 들어갔다)

 

자식 컴포넌트를 생성하여 v-model text를 바인딩, 자식에게 넘긴다. 자식은 일단  value modelValue 를 물어 초기값을 그대로 반영한다. 또한 @input을 통하여 input에 입력될 때마다 실시간으로  updateValue()를 처리한다. 그리고 이 updateValue()에서 update를 통해 modelValue update하겠다는  emits를 하는데,  emits를 먼저 예약해두지 않으면 작동되지 않는다. 하나의 예약어에 가까운 것. 이렇게 올라간 modelValue 는 text로 적용, 그대로 화면에 뜨게 되는 것이다.

 

아래는 3버전으로 다시 편집하였다.

<!-- 자식 컴포넌트 영역 -->
<template>
  <div>
    <input 
      :type="type" 
      :value="modelValue" 
      @input="updateValue"
    />
  </div>
</template>

<script setup>
// 외부 import 없이 내장 매크로로 props와 emits를 직관적으로 선언
defineProps({
  type: String,
  modelValue: String
})

const emit = defineEmits(['update:modelValue'])

const updateValue = (event) => {
  const value = event.target.value.trim();
  emit('update:modelValue', value); // 인스턴스(this) 접근 없이 직접 호출
}
</script>

<!-- 부모 컴포넌트 영역 -->
<template>
  <div>
    <CustomInput type="text" v-model="text" />
    <p>입력된 값: {{ text }}</p>
  </div>
</template>

<script setup>
import { ref } from 'vue'
import CustomInput from './CustomInput.vue' // 컴포넌트 import 시 자동 등록

const text = ref('') // 반응형 상태 정의
</script>

 

3에서는 복잡한 객체 설정 구조가 빠지고, defineProps  defineEmits가 이를 대신하게 된다. 그리고 this 거치지 않고 변수에 할당된 함수를 통해 곧바로 직접 호출이 가능하게 되었다.

 

이렇게 Vue.js의 간단한 기본을 알아보았다. 생각보다 간단했기 때문에 빠르게 익힐 수 있을 거 같다.

«   2026/07   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31