Untitled

πŸ˜‰ ν—·κ°ˆλ Έλ˜ & λͺ°λžλ˜ λΆ€λΆ„λ“€λ§Œ μ •λ¦¬ν•˜λŠ” λ‚˜λ§Œμ˜ TIL
😯 λͺ¨λ“  κ°•μ˜ λ‚΄μš©μ€ 적지 μ•Šμ•„μš”!

였늘의 μ†Œκ°μ€?
토이 ν”„λ‘œμ νŠΈ λ•Œ 처음 λ§Œλ‚¬λ˜ Vue..
데브 μ½”μŠ€κ°€ λ°”λΉ  잊고 μ‚΄μ•˜λŠ”λ° λ‹€μ‹œ λ§Œλ‚˜κ²Œ 될 쀄은 λͺ°λžμ–΄μš”.

잊고 μ‚° 만큼..λ‹€ κΉŒλ¨Ήμ—ˆλ”λΌκ³ μš”...
우리 λ‹€μ‹œ μ‹œμž‘ν•˜μž




[1] VueJS

Vue.jsλŠ” μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€κΈ° μœ„ν•œ ν”„λ‘œκ·Έλ ˆμ‹œλΈŒ ν”„λž˜μž„μ›Œν¬μž…λ‹ˆλ‹€.




[2] 선언적 λ Œλ”λ§(Declarative Rendering)

선언적 λ Œλ”λ§μ€ Vue의 μ€‘μš”ν•œ νŠΉμ§•μž…λ‹ˆλ‹€.

κ°„λ‹¨ν•œ ν…œν”Œλ¦Ώ ꡬ문을 μ‚¬μš©ν•˜μ—¬ DOMμ—μ„œ 데이터λ₯Ό μ„ μ–Έμ μœΌλ‘œ λ Œλ”λ§ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<div id="counter">Counter: {{ counter }}</div>

const Counter = {
  data() {
    return {
      counter: 0,
    }; // 이쀑 μ€‘κ΄„ν˜Έ ꡬ문
  },
};

Vue.createApp(Counter).mount("#counter");

counter 데이터와 html DOM이 μ—°κ²°λ˜μ—ˆμœΌλ©°, 이λ₯Ό λ°˜μ‘ν˜•(reactive)이 λ˜μ—ˆλ‹€κ³  말할 수 μžˆμŠ΅λ‹ˆλ‹€.


λ§Œμ•½ counter 데이터가 맀 μ΄ˆλ§ˆλ‹€ μ¦κ°€ν•˜κ³  html DOM이 변경될 λ•Œ μ–΄λ–»κ²Œ λ Œλ”λ§λ κΉŒμš”?

const Counter = {
  data() {
    return {
      counter: 0,
    }; // 이쀑 μ€‘κ΄„ν˜Έ ꡬ문
  },
  mounted() {
    setInterval(() => {
      this.counter++;
    }, 1000);
  },
};

Vue.createApp(Counter).mount("#counter");

이미 ν•΄λ‹Ή λ°μ΄ν„°λŠ” html DOMκ³Ό μ—°κ²°λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ 데이터가 변경될 μ‹œ, html 이쀑 μ€‘κ΄„ν˜Έ ꡬ문 λ‚΄μ˜ λ³€μˆ˜μ˜ 값도 λ³€κ²½λ˜μ–΄ λ Œλ”λ§λ˜λŠ” 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.


Untitled 1 κ³„μ†ν•΄μ„œ μ¦κ°€ν•©λ‹ˆλ‹€.


  • setInterval Func

μ΄λ•Œ, setInterval ν•¨μˆ˜λŠ” ν™”μ‚΄ν‘œ ν•¨μˆ˜λ‘œ μž‘μ„±λ˜μ–΄μ•Ό ν•©λ‹ˆλ‹€.

일반 ν•¨μˆ˜μ—μ„œμ˜ thisλŠ” ν•¨μˆ˜κ°€ μ‹€ν–‰(호좜)λ˜λŠ” μœ„μΉ˜μ—μ„œ μ •μ˜κ°€ λ©λ‹ˆλ‹€.

setInterval(function () {
  this.counter += 1;
  // μ—¬κΈ°μ„œ μ‚¬μš©ν•˜λŠ” thisλŠ” function λ‚΄λΆ€μ—μ„œ μ •μ˜λ˜λ―€λ‘œ, μ™ΈλΆ€μ˜ 값을 μ°Έμ‘°ν•  수 μ—†μŠ΅λ‹ˆλ‹€.
}, 1000);


ν™”μ‚΄ν‘œ ν•¨μˆ˜μ˜ 경우 ν•¨μˆ˜κ°€ μ„ μ–Έλ˜λŠ” μœ„μΉ˜μ—μ„œ μ •μ˜κ°€ 되기 λ•Œλ¬Έμ—, ν•¨μˆ˜ λ‚΄λΆ€κ°€ μ•„λ‹Œ μ„ μ–Έλ˜λŠ” μœ„μΉ˜μ— μžˆλŠ” 값을 μ°Έμ‘°ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

setInterval(() => {
  this.counter++;
}, 1000);


  • component

componentλŠ” λ°μ΄ν„°μ˜ μΊ‘μŠν™”λ₯Ό μ§€μ›ν•©λ‹ˆλ‹€. (ν•˜λ‹¨μ˜ νŒŒνŠΈμ—μ„œ 더 닀루도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€. )

λ˜ν•œ v-for을 μ‚¬μš©ν•˜μ—¬ 반볡 좜λ ₯을 ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<!-- 3. 카멜 μΌ€μ΄μŠ€ -> μΌ€λ°₯ μΌ€μ΄μŠ€λ‘œ λ°”κΎΈμ–΄ μž‘μ„±  -->
<!-- 4. μ‚¬μš©ν•  μ»΄ν¬λ„ŒνŠΈ λ„€μ΄λ°μœΌλ‘œ νƒœκ·Έ 생성  -->
<!-- 5. μ‚¬μš©ν•  μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ˜ 데이터 ν”„λ‘­μŠ€μ— λ“€μ–΄κ°ˆ κ°’ μ •μ˜  -->
<fruit-item fruit-name="Orange"></fruit-item>

// 1. component둜 μ‚¬μš©ν•  객체 νƒ€μž… μ •μ˜
const FruitItem = {
  template: "<li>{{ fruitName }}</li>",
  props: ["fruitName"],
};
// 2. components μ•ˆμ—μ„œ ν•΄λ‹Ή 데이터 μ‚¬μš©ν•  것 λͺ…μ‹œ
const App = {
  components: {
    FruitItem,
  },
  data() {
    return {
      fruits: ["Apple", "Banana", "Cherry"],
    };
  },
};

Untitled 2

fruit-name ν”„λ‘­μŠ€ κ°’μœΌλ‘œ μ „λ‹¬λœ orange λ¬Έμžμ—΄μ΄ ν•΄λ‹Ή μ»΄ν¬λ„ŒνŠΈμ˜ template에 μ μš©λ˜μ–΄ λ Œλ”λ§ 된 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.


κ·Έλ ‡λ‹€λ©΄ v-for을 μ‚¬μš©ν•˜μ—¬ fruits 데이터λ₯Ό 넣어보도둝 ν•˜κ² μŠ΅λ‹ˆλ‹€.

<fruit-item v-for="f in fruits" v-bind:fruit-name="f"></fruit-item>

μ΄λ•Œ v-for둜 순회된 데이터 fλ₯Ό μ‚¬μš©ν•  땐 v-bind λ””λ ‰ν‹°λΈŒλ₯Ό λΆ™μ—¬μ€˜μ•Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Untitled 3


λ”°λΌμ„œ λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•¨μœΌλ‘œμ¨, JS ꡬ문과 DOM μš”μ†Œλ“€μ˜ μƒν˜Έμž‘μš©μ„ λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.


[2-2] Component

Componentλž€ 미리 μ •μ˜λœ μ˜΅μ…˜μ„ 가진 Vue μΈμŠ€ν„΄μŠ€λ₯Ό λ§ν•©λ‹ˆλ‹€.


μ»΄ν¬λ„ŒνŠΈ μ‹œμŠ€ν…œμ€ Vue의 μ€‘μš”ν•œ κ°œλ…μž…λ‹ˆλ‹€.

λ°μ΄ν„°μ˜ μΊ‘μŠν™”λ₯Ό μ§€μ›ν•˜λ©°, μž¬μ‚¬μš©ν•  수 μžˆλŠ” μž‘μ€ λ‹¨μœ„λ‘œ λ§Œλ“€ 수 있기 λ•Œλ¬Έμ—

λŒ€κ·œλͺ¨ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ„ ꡬ좕할 수 있게 ν•΄μ£ΌλŠ” 좔상적 κ°œλ…μž…λ‹ˆλ‹€.

Untitled 4

μ „μ—­, 지역 μ„ μ–Έ 방법은 μ»΄ν¬λ„ŒνŠΈ μ„ μ–Έ 방법 νŒŒνŠΈμ—μ„œ λ‹€λ£Ήλ‹ˆλ‹€.




[3] 라이프 사이클

각 μ»΄ν¬λ„ŒνŠΈλŠ” 생성될 λ•Œ 일련의 μ΄ˆκΈ°ν™” 과정을 κ±°μΉ©λ‹ˆλ‹€.

(데이터 κ΄€μ°°, ν…œν”Œλ¦Ώ 컴파일, μΈμŠ€ν„΄μŠ€ DOM 마운트(μ—°κ²°), 데이터 λ³€κ²½ μ‹œ DOM μ—…λ°μ΄νŠΈ)

이 κ³Όμ •μ—μ„œ 라이프사이클 ν›… ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


[3-1] 라이프사이클 λ‹€μ΄μ–΄κ·Έλž¨

Untitled 5

beforeCreate() {
  // λ§Œλ“€μ–΄μ§€κΈ° μ „
  console.log("before Create!", this.msg);
  console.log(document.querySelector("h1"));
},
created() {
  // λ§Œλ“€μ–΄μ§„ 직후
  console.log("created!", this.msg);
  console.log(document.querySelector("h1"));
},
beforeMount() {
  // DOM element μ—°κ²°λ˜κΈ° 직전
  console.log("before Mount!", this.msg);
  console.log(document.querySelector("h1"));
},
mounted() {
  // DOM element μ—°κ²°λœ 직후
  console.log("mounted!", this.msg);
  console.log(document.querySelector("h1"));
},
beforeUpdate() {
  // 데이터가 λ³€κ²½λœ ν›„ λ Œλ”λ§λ„ μ—…λ°μ΄νŠΈ 되기 μ „
  console.log("before update!", this.msg);
  console.log(document.querySelector("h1").textContent);
},
updated() {
  // 데이터와 λ Œλ”λ§ λͺ¨λ‘ 변경사항이 반영된 직후
  console.log("updated!", this.msg);
  console.log(document.querySelector("h1").textContent);
},
beforeUnmount() {
  // DOM element와 연결이 끊기기 직전
  console.log("before unmount");
},
unmounted() {
  // DOM element와 연결이 λŠμ–΄μ§„ ν›„
  console.log("unmounted!");
},




[4] ν…œν”Œλ¦Ώ 문법

[4-1] 보간법 (Interpolation)

이쀑 μ€‘κ΄„ν˜Έ ꡬ문(Mustache) 기법을 μ‚¬μš©ν•œ λ¬Έμžμ—΄ 보간법을 μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<p>메세지: {{ message }}</p>

ν•΄λ‹Ή 이쀑 μ€‘κ΄„ν˜Έ ꡬ문은 μ»΄ν¬λ„ŒνŠΈ μΈμŠ€ν„΄μŠ€μ˜ message 속성 κ°’μœΌλ‘œ λŒ€μ²΄λ˜λ©°, 속성 값이 λ³€κ²½λ λ•Œλ§ˆλ‹€ κ°±μ‹ λ©λ‹ˆλ‹€.


ν•΄λ‹Ή 이쀑 μ€‘κ΄„ν˜Έ ꡬ문 λ‚΄μ—μ„œλŠ” κ°„λ‹¨ν•œ JavaScript ν‘œν˜„μ‹μ„ μ§€μ›ν•©λ‹ˆλ‹€.

단, 단일 ν‘œν˜„μ‹λ§Œ μ§€μ›ν•©λ‹ˆλ‹€.


<h1 v-bind:class="name">{{ name + 'μž…λ‹ˆλ‹€' }}</h1>
<h1>{{ count + 100 }}</h1>


  • v-once

μ΄λ•Œ v-once λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•˜μ—¬ 데이터가 λ³€κ²½λ˜μ–΄λ„ κ°±μ‹ ν•˜μ§€ μ•ŠλŠ” μΌνšŒμ„± 보간을 μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<p v-once>{{ message }}</p>


  • v-html

λ§Œμ•½ 이쀑 μ€‘κ΄„ν˜Έ ꡬ문 μ•ˆμ— html을 좜λ ₯ν•˜κ³  μ‹Άλ‹€λ©΄ v-html λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•˜μ—¬ 좜λ ₯ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<div id="app">
  <h1 v-html="msg">{{ msg }}</h1>
</div>

const App = {
  data() {
    return {
      msg: "<p style='color:red'>hello Vue!</p>",
    };
  },
};

Vue.createApp(App).mount("#app");

Untitled 6

html μ½”λ“œκ°€ 잘 적용된 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ μž„μ˜μ˜ HTML을 λ™μ μœΌλ‘œ λ Œλ”λ§ ν•  경우, XSS의 μœ„ν—˜μ΄ μžˆμœΌλ―€λ‘œ μ£Όμ˜ν•΄μ„œ μ‚¬μš©ν•΄μ•Όν•©λ‹ˆλ‹€.


[4-2] λ””λ ‰ν‹°λΈŒ

λ””λ ‰ν‹°λΈŒλŠ” Vueμ—μ„œ μ œκ³΅ν•˜λŠ” 특수 μ†μ„±λ“€μž…λ‹ˆλ‹€.

이 속성듀은 v-접두어가 λΆ™μ–΄μžˆμœΌλ©°, λ Œλ”λ§ 된 DOM이 λ°˜μ‘ν˜• λ™μž‘μ„ ν•˜λ„λ‘ λ„μ™€μ€λ‹ˆλ‹€.

β€Ό κ°„λ‹¨ν•œ λ””λ ‰ν‹°λΈŒλ“€λ§Œ 닀룬 ν›„, 각 μžμ„Έν•œ λ‚΄μš©μ€ 후에 λ‹€λ£Ήλ‹ˆλ‹€.


[1] v-bind

JavaScript의 객체λ₯Ό μ†μ„±μ˜ κ°’μœΌλ‘œ λ„£κ³  싢을 λ•Œ, v-bind λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<div v-bind:class="{ orange: active }">{{ counter }}</div>

ν•΄λ‹Ή λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•˜λ©΄ 속성 κ°’μœΌλ‘œ 넣은 JS 데이터 ν˜•νƒœκ°€ ν•˜λ‚˜μ˜ 속성 κ°’μœΌλ‘œ 읽힐 수 μžˆμŠ΅λ‹ˆλ‹€.

λ˜ν•œ λ‚΄λΆ€μ—μ„œ data()에 μ •μ˜ν•΄λ†“μ€ κ°’λ“€κ³Ό μ—°κ²°ν•΄μ„œ μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


ν•΄λ‹Ή λ””λ ‰ν‹°λΈŒλŠ” μ•½μ–΄λ‘œ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<a v-bind:href="url"> ... </a>
<a :href="url"> ... </a>

<a : [key]="url"> ... </a>
<!-- 동적 μ „λ‹¬μΈμžμ™€ ν•¨κ»˜ μ“΄ μ•½μ–΄ -->


[2] v-on

v-on λ””λ ‰ν‹°λΈŒλ₯Ό μ‚¬μš©ν•˜μ—¬ htmlμ—μ„œ 이벀트 ν—¨λ“€λŸ¬λ₯Ό μΆ”κ°€ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<button v-on:click="increase">Click me!</button>
const App = {
  data() {
    return {
      counter: 0,
      active: false,
    };
  },
  methods: {
    increase() {
      this.counter += 1;
    },
  },
};

ν•΄λ‹Ή λ””λ ‰ν‹°λΈŒλŠ” μ•½μ–΄λ‘œ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<a v-on:click="doSomething"> ... </a>
<a @click="doSomething"> ... </a>

<a @[event]="doSomething"> ... </a>
<!-- 동적 μ „λ‹¬μΈμžμ™€ ν•¨κ»˜ μ“΄ μ•½μ–΄ -->


[3] v-if

v-if λ””λ ‰ν‹°λΈŒλ₯Ό ν†΅ν•˜μ—¬ 마치 속성을 μž‘μ„±ν•˜λ“―μ΄ λ Œλ”λ§ μ—¬λΆ€λ₯Ό κ²°μ •ν•΄μ£ΌλŠ” 데이터λ₯Ό μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<div v-if="active">Hello Vue!</div>
const App = {
  data() {
    return {
      active: false,
    };
  },
  methods: {
    toggle() {
      this.active = !this.active;
    },
  },
};

toggle μ΄λ²€νŠΈκ°€ λ°œμƒν•˜μ—¬ active 값이 변함에 따라 μœ„μ˜ div λ Œλ”λ§ μ—¬λΆ€κ°€ κ²°μ •λ©λ‹ˆλ‹€.


[4] v-for

v-for λ””λ ‰ν‹°λΈŒλ₯Ό ν†΅ν•˜μ—¬ JS의 μ΄ν„°λŸ¬λΈ” 데이터λ₯Ό μˆœνšŒν•  수 μžˆμŠ΅λ‹ˆλ‹€.


<div id="app">
  <ul>
    <li v-for="f in fruits">{{f}}</li>
    <!-- fλŠ” μΌμ’…μ˜ λ§€κ°œλ³€μˆ˜μž…λ‹ˆλ‹€. -->
  </ul>
</div>

const App = {
  data() {
    return {
      fruits: ["Apple", "Banana", "Cherry"],
    };
  },
};

Vue.createApp(App).mount("#app");

Untitled 7

fruits dataκ°€ μˆœνšŒλ˜μ–΄ li에 μž…λ ₯된 것을 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.


[5] 동적 데이터

λ””λ ‰ν‹°λΈŒμ˜ 속성 이름도 λ™μ μœΌλ‘œ μž‘μ„±ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

<h1 v-bind:[attr]="'active'">Hello vue!</h1>
const App = {
  data() {
    return {
      attr: "class",
    };
  },
};
const vm = Vue.createApp(App).mount("#app");

Untitled 8




[5] Data & Methods

[5-1] Data

Vueμ—μ„œ 동적 데이터에 μ ‘κ·Όν• λ•ŒλŠ” $dataλΌλŠ” λ‚΄μž₯ 객체에 μ ‘κ·Όν•΄μ„œ 확인할 수 μžˆμŠ΅λ‹ˆλ‹€.

vm.$data.count; // 7
vm.count; // 7

일반적으둜 $dataλ₯Ό μ΄μš©ν•΄μ„œ μ ‘κ·Όν•˜λŠ” 것이 ꢌμž₯λ©λ‹ˆλ‹€.


[5-2] Proxy

Proxy κ°μ²΄λŠ” 기본적인 λ™μž‘(속성 μ ‘κ·Ό, ν• λ‹Ή, 순회, μ—΄κ±°, ν•¨μˆ˜ 호좜 λ“±)의 μƒˆλ‘œμš΄ 행동을 μ •μ˜ν•  λ•Œ μ‚¬μš©ν•©λ‹ˆλ‹€.

new Proxy(target, handler);
// target : κ°μ‹œν•  데이터
// handler : getter, setter을 가지며, 데이터λ₯Ό κ°€μ Έμ˜€κ³  μ‘°μž‘ν•œ ν›„ λ°˜ν™˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

ProxyλŠ” λ°μ΄ν„°μ˜ 변경을 κ°μ§€ν•΄μ„œ 화면에 좜λ ₯ν•˜λŠ” λ‘œμ§κΉŒμ§€ λ‹΄λ‹Ήν•©λ‹ˆλ‹€.

λ”°λΌμ„œ μƒˆλ‘œμš΄ Proxyλ₯Ό μ„ μ–Έν•œ ν›„ μ›ν•˜λŠ” λ‘œμ§μ„ μž…λ ₯ν•˜μ—¬ λ Œλ”λ§κΉŒμ§€μ˜ 과정에 κ°œμž…ν•  수 μžˆμŠ΅λ‹ˆλ‹€.


[5-3] Methods

μ»΄ν¬λ„ŒνŠΈ μΈμŠ€ν„΄μŠ€μ— ν•¨μˆ˜λ₯Ό μΆ”κ°€ν•˜κΈ° μœ„ν•΄ methodsλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

const App = {
  data() {
    return {
      count: 0,
    };
  },
  methods: {
    increase() {
      this.count += 1; // this값은 μžλ™μœΌλ‘œ λ°”μΈλ”©λ©λ‹ˆλ‹€.
    },
  },
};

methodsλ₯Ό μ„ μ–Έν•  λ•Œ, ν™”μ‚΄ν‘œ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜λ©΄ Vueκ°€ μ μ ˆν•œ this값을 λ°”μΈλ”©ν•˜μ§€ λͺ»ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ ν™”μ‚΄ν‘œ ν•¨μˆ˜λ₯Ό μ‚¬μš©ν•˜μ§€ μ•Šλ„λ‘ ν•©λ‹ˆλ‹€.


methodsλŠ” 일반적으둜 ν…œν”Œλ¦Ώ λ‚΄λΆ€μ—μ„œ 이벀트 λ¦¬μŠ€λ„ˆλ‘œ ν™œμš©λ©λ‹ˆλ‹€.

<button @click="increase">count ++</button>
<!-- v-on의 μ•½μ–΄λŠ” @ μž…λ‹ˆλ‹€. -->





좜처

ν”„λ‘œκ·Έλž˜λ¨ΈμŠ€

Vue.js3 Document

Proxy MDN Document

μΉ΄ν…Œκ³ λ¦¬: ,

μ—…λ°μ΄νŠΈ:

λŒ“κΈ€λ‚¨κΈ°κΈ°