Skip to content

Clones Vue.js to implement a basic MVVM framework

License

Notifications You must be signed in to change notification settings

Heonys/vue-lite-js

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

Clones Vue.js to implement a basic MVVM framework

βœ… ν•œκ΅­μ–΄ | English

πŸš€ Introduction

ν˜„λŒ€μ μΈ ν”„λ ˆμž„μ›Œν¬λ“€μ΄ MVVM(Model-View-ViewModel) νŒ¨ν„΄μ„ μ‚¬μš©ν•˜μ—¬ 효율적인 데이터 바인딩과 μ‚¬μš©μž μΈν„°νŽ˜μ΄μŠ€ 관리λ₯Ό μ§€μ›ν•œλ‹€λŠ” μ μ—μ„œ μ˜κ°μ„ λ°›μ•„ μ΄λŸ¬ν•œ MVVM νŒ¨ν„΄μ„ 기반으둜 Vue.jsλ₯Ό ν΄λ‘ ν•˜μ—¬ μœ μ‚¬ν•œ κΈ°λŠ₯κ³Ό 문법을 μ œκ³΅ν•˜λŠ” 기초적인 MVVM ν”„λ ˆμž„μ›Œν¬μž…λ‹ˆλ‹€.

이 μ €μž₯μ†Œμ˜ 주된 λͺ©ν‘œλŠ” Vue.js의 핡심 λ™μž‘ 방식을 ν΄λ‘ ν•˜λ©΄μ„œ, MVVM νŒ¨ν„΄κ³Ό 핡심적인 μ˜΅μ €λ²„ νŒ¨ν„΄μ„ μ μš©ν•΄ λ³΄λŠ” κ²ƒμž…λ‹ˆλ‹€. ν”„λ‘œμ νŠΈμ˜ μ „λ°˜μ μΈ κ΅¬μ‘°λŠ” #Reference의 μ½”λ“œλ₯Ό 기반으둜 ν•˜μ˜€μœΌλ©° λ³΅μž‘ν•œ 문제λ₯Ό κ³ λ €ν•˜μ§€ μ•Šμ•˜μ§€λ§Œ, μ–‘λ°©ν–₯ 데이터 바인딩과 Vue.js의 핡심원리λ₯Ό μ΄ν•΄ν•˜λŠ”λ° 도움을 쀄 수 μžˆλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.

πŸŽ‰ Getting Started

  • Using npm

vueliteλ₯Ό npmμ—μ„œ μ„€μΉ˜ν•˜κ³  ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•˜λ €λ©΄, λ‹€μŒ λͺ…λ Ήμ–΄λ₯Ό μ‹€ν–‰ν•˜μ„Έμš”

npm install vue-lite-js@latest
  • Using cdn

λΈŒλΌμš°μ €μ—μ„œ 직접 μ‚¬μš©ν•˜λ €λ©΄, μ•„λž˜μ™€ 같이 cdn을 톡해 슀크립트λ₯Ό ν¬ν•¨ν•˜μ„Έμš”

<script src="https://unpkg.com/vue-lite-js@latest"></script>
  • Local Development

개발 ν™˜κ²½μ—μ„œ μ†ŒμŠ€ μ½”λ“œλ₯Ό μˆ˜μ •ν•˜κ³  직접 ν…ŒμŠ€νŠΈ ν•˜κ³ μ‹ΆμœΌλ©΄, λ‹€μŒ 단계λ₯Ό λ”°λΌμ£Όμ„Έμš”

μ €μž₯μ†Œ 클둠
git clone https://github.com/Heonys/vue-lite-js 
μ˜μ‘΄μ„± μ„€μΉ˜
npm install 
개발 μ„œλ²„ μ‹€ν–‰
npm run start 
ν…ŒμŠ€νŠΈλ₯Ό μœ„ν•œ λ§ˆν¬λ‹€μš΄ 및 슀크립트 μž‘μ„±
πŸ“¦ vuelite 
β”œβ”€β”€ πŸ“‚ dev 
β”‚    β”œβ”€β”€ πŸ“„ index.html βœ…
β”‚    └── πŸ“„ index.ts βœ…
β”œβ”€β”€ πŸ“‚ src βœ…
β”‚    β”œβ”€β”€ πŸ“‚ core
β”‚    β”œβ”€β”€ πŸ“‚ types
β”‚    ... 

src폴더 μ—μ„œ μ†ŒμŠ€μ½”λ“œλ₯Ό μˆ˜μ •ν•˜κ³  devν΄λ”μ—μ„œ λ§ˆν¬λ‹€μš΄κ³Ό 슀크립트 μž‘μ„±μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

πŸ’‘Basic usage

Description of GIF
CDN Demo: https://vuelite-demo.vercel.app

<div id="app">
  <input type="text" v-model="message" />
  <p v-style="textStyle">{{ message }}</p>
  <button v-on:click="handleClick">change vuelite</button>
  <div>
    <input type="checkbox" v-model="checked" />
    <span>{{ isChecked }}</span>
  </div>
</div>
import Vuelite from "vue-lite-js";

new Vuelite({
  el: "#app",
  data() {
    return {
      message : "",
      checked: true,
      textStyle: { color: "#FF0000" },
    }
  },
  methods: {
    handleClick() {
      this.message  = "vuelite";
    },
  },
  computed: {
    isChecked() {
      return this.checked ? "checked" : "unchecked";
    },
  }
})

✨ Details

  • 기본적으둜 Vue.js의 Option API 방식을 ν΄λ‘ ν•˜κ³  있으며, Vue.js의 핡심 κΈ°λŠ₯을 μ§€μ›ν•˜μ§€λ§Œ λͺ¨λ“  κΈ°λŠ₯을 μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

  • μ˜΅μ…˜μ—μ„œ template 속성은 μ§€μ›ν•˜μ§€λ§Œ, Vue.js의 .vue ν™•μž₯μžμ™€ 같은 λ‘œλ”λ₯Ό μ§€μ›ν•˜μ§€ μ•ŠκΈ° λ•Œλ¬Έμ— HTML νŒŒμΌμ—μ„œ λ”°λ‘œ λ§ˆν¬μ—…μ„ μž‘μ„±ν•΄μ•Ό ν•˜λŠ” λΆˆνŽΈν•¨μ΄ μžˆμŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ ν…œν”Œλ¦Ώμ„ λΆ„λ¦¬ν•΄μ„œ μ‚¬μš©ν•˜λŠ” 방식은 전톡적인 Vue.jsλ³΄λ‹€λŠ” Angular와 μœ μ‚¬ν•œ 면이 μžˆμŠ΅λ‹ˆλ‹€.

  • μ‹±κΈ€ 파일 μ»΄ν¬λ„ŒνŠΈ 포맷을 μ§€μ›ν•˜μ§€ μ•ŠλŠ” μ΄λŸ¬ν•œ νŠΉμ„± λ•Œλ¬Έμ— <style> νƒœκ·Έ ν˜•νƒœλ₯Ό μ§€μ›ν•˜κΈ° μœ„ν•΄μ„œ styles 속성을 μ§€μ›ν•©λ‹ˆλ‹€.

new Vuelite({
  // ... 
  styles: {    
    "#wrapper": {
      // only camelCase key
      width: "50%",
      background: "#ffa",
      cursor: "pointer",
    },
  },
})
  • v-if/else와 v-showλ₯Ό ν†΅ν•œ 쑰건뢀 λ Œλ”λ§μ„ μ§€μ›ν•˜μ§€λ§Œ, <template>μ—μ„œμ˜ v-ifλ₯Ό μ§€μ›ν•˜μ§€ μ•Šμ•„μ„œ 단일 μ—˜λ¦¬λ¨ΌνŠΈμ—μ„œλ§Œ μ‚¬μš©κ°€λŠ₯ν•˜λ©° v-else-if λ””λ ‰ν‹°λΈŒλ₯Ό μ§€μ›ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

🧩 Overview

Description of diagram

⭐ Workflow

1. viewmodel 생성

class Vuelite {
  constructor(options: Options) {
    this.el = document.querySelector(options.el);
    this.options = options;
    injectReactive(this);
    injectStyleSheet(this);
    const scanner = new VueScanner(new NodeVisitor());
    scanner.scan(this);
  }
}

MVVM νŒ¨ν„΄μ—μ„œ viewmodel 역할을 μˆ˜ν–‰ν•˜λŠ” vue μΈμŠ€ν„΄μŠ€μ˜ 생성 λ‹¨κ³„λ‘œ option 객체λ₯Ό λ°›μ•„μ„œ DOMκ³Ό 데이터 바인딩을 μ œκ³΅ν•  수 μžˆλ„λ‘ ν•˜λŠ” μ§„μž…μ  역할을 ν•©λ‹ˆλ‹€.

Viewmodel을 κ΅¬ν˜„ν•˜λŠ” 핡심 아이디어

  1. μ˜΅μ…˜κ°μ²΄λ‘œ λ°›μ•„μ˜¨ 데이터에 λ°˜μ‘μ„±μ„ λΆˆμ–΄λ„£μ–΄ λ°μ΄ν„°μ˜ λ³€ν™”λ₯Ό 감지
  2. DOM을 μˆœνšŒν•˜λ©° λ””λ ‰ν‹°λΈŒλ₯Ό νŒŒμ‹±ν•˜κ³  μ˜΅μ €λ²„λ₯Ό 생성
  3. λ°˜μ‘ν˜• 데이터와 μ˜΅μ €λ²„μ˜ μƒν˜Έμž‘μš©μ— λ”°λ₯Έ μ–‘λ°©ν–₯ 바인딩을 달성

2. λ°˜μ‘μ„± μ£Όμž…

Description of diagram

// target: λž˜ν•‘ν•˜λ €λŠ” 원본 객체
// handler: λ™μž‘μ„ κ°€λ‘œμ±„λŠ” λ©”μ„œλ“œμΈ '트랩(trap)'이 λ‹΄κΈ΄ 객체
new Proxy(target, handler);

Proxy κ°μ²΄λŠ” 원본 객체λ₯Ό κ°μ‹ΈλŠ” 객체둜 νƒ€κ²Ÿμ΄ λ˜λŠ” 원본 객체에 λŒ€ν•œ 접근을 μ œμ–΄ν•˜κ±°λ‚˜, νŠΉμ • λ™μž‘μ„ κ°€λ‘œμ±„μ„œ μƒˆλ‘œμš΄ κΈ°λŠ₯을 μΆ”κ°€ν•  수 있게 ν•˜λŠ” λž˜ν•‘ κ°μ²΄μž…λ‹ˆλ‹€.

viewmodelμ—μ„œ λ°μ΄ν„°μ˜ λ³€ν™”λ₯Ό κ°μ§€ν•˜μ—¬ μ‹€μ œ λ·°(DOM)μ™€μ˜ μ–‘λ°©ν–₯ λ°”μΈλ”©ν•˜λŠ” 것이 우리의 λͺ©μ μ΄κΈ° λ•Œλ¬Έμ— λ°μ΄ν„°μ˜ 변화에 μ–΄λ–»κ²Œ 감지할 수 μžˆμ„μ§€ κ³ λ―Όν•΄ 봐야 ν•˜λŠ”λ° 이λ₯Ό μœ„ν•΄ μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” μ–Έμ–΄ μ°¨μ›μ—μ„œ 객체의 속성에 λ™μ μœΌλ‘œ getter와 setterλ₯Ό 등둝할 수 있게 ν•΄μ£ΌλŠ” Object.defineProperty와 λ”λΆˆμ–΄ Proxyλ₯Ό μ‚¬μš©ν•˜μ—¬ 이λ₯Ό κ΅¬ν˜„ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‹€μ œλ‘œ Vue2 μ—μ„œλŠ” defineProperty둜 λ°˜μ‘ν˜• 데이터λ₯Ό κ΅¬ν˜„ν•˜κ³ , Vue3 μ—μ„œλŠ” Proxyλ₯Ό μ‚¬μš©ν•˜μ—¬ λ°˜μ‘ν˜• 데이터λ₯Ό κ΅¬ν˜„ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ Proxy객체둜 viewmodel의 λͺ¨λ“  data 속성을 λž˜ν•‘ν•˜μ—¬ get 트랩, set νŠΈλž©μ„ μΆ”κ°€ν•˜κ³  λͺ¨λ“  μ†μ„±λ“€μ˜ λ³€ν™”λ₯Ό κ°μ§€ν•˜λ„λ‘ κ΅¬ν˜„ν•  κ²ƒμž…λ‹ˆλ‹€.

const handler = {
  get(target: Target, key: string, receiver: Target) {
    // 1. get 트랩 (getter)
  },
  set(target: Target, key: string, value: any, receiver: Target) {
    // 2. set 트랩 (setter)
  },
};
new Proxy(data, handler);

ν•˜μ§€λ§Œ Proxyλ₯Ό μƒμ„±ν•˜λŠ” ν˜„μž¬ 단계에선 getter, setterλ₯Ό μ„€λͺ…ν•˜κΈ° λ‚œν•΄ν•œ 뢀뢄이 μ‘΄μž¬ν•˜λŠ”λ° ν—·κ°ˆλ¦¬μ§€ 말아야 ν•˜λŠ”κ±΄ 이 뢀뢄은 ν•΄λ‹Ή 속성에 μ ‘κ·Όν•˜κ±°λ‚˜ ν•΄λ‹Ή μ†μ„±μ˜ 값을 μˆ˜μ •ν•  λ•Œ μž‘λ™ν•˜λŠ” 트랩으둜 μ–΄μ°¨ν”Ό λ‚˜μ€‘μ— μ‹€ν–‰λ˜λŠ” λΆ€λΆ„μœΌλ‘œ 핡심적인 둜직이긴 ν•˜μ§€λ§Œ, μ§€κΈˆ 단계에선 κ·Έλƒ₯ getter, setterλ₯Ό λ“±λ‘ν•¨μœΌλ‘œμ¨ Reactivtyλ₯Ό μ£Όμž…ν–ˆκ΅¬λ‚˜ ν•˜κ³  μƒκ°ν•˜λ©΄ 될 것 κ°™μŠ΅λ‹ˆλ‹€.

  1. get 트랩
    Dep 객체λ₯Ό μƒμ„±ν•˜κ³  ν˜„μž¬ ν™œμ„±ν™”λœ Observerμ™€μ˜ μ˜μ‘΄μ„±μ„ μ—°κ²°ν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€. Scannerμ—μ„œ λ””λ ‰ν‹°λΈŒλ₯Ό νŒŒμ‹±ν•˜κ³  Observerλ₯Ό 생성할 λ•Œ, ν•΄λ‹Ή λ””λ ‰ν‹°λΈŒμ— ν•΄λ‹Ήν•˜λŠ” expresion을 vmμ—μ„œ μ°ΎλŠ” κ³Όμ •μ—μ„œ getterλ₯Ό λ°œμƒμ‹œν‚€κ³  λ”°λΌμ„œ ν•΄λ‹Ή expresion에 λ§€ν•‘λ˜λŠ” Dep이 μƒμ„±λ˜μ–΄ μƒμ„±λœ Obserberμ™€μ˜ 연결이 λ§Ίμ–΄μ§‘λ‹ˆλ‹€.

  2. set 트랩
    get νŠΈλž©μ€ μ˜΅μ €λ²„κ°€ 생성될 λ•Œ 이미 ν•œλ²ˆμ€ μ‹€ν–‰λ˜μ—ˆκΈ° λ•Œλ¬Έμ— 이후에 set νŠΈλž©μ—μ„œλŠ” 항상 ν•΄λ‹Ήν•˜λŠ” key에 λŒ€μ‘ν•˜λŠ” Depκ³Ό λ§€ν•‘λ˜μ–΄ μžˆμŠ΅λ‹ˆλ‹€. setterκ°€ λ°œμƒν•œ μ‹œμ μ€ ν•΄λ‹Ή 속성 κ°’μ˜ λ³€ν™”κ°€ μΌμ–΄λ‚¬λ‹€λŠ” 뜻으둜 notifyλ₯Ό ν˜ΈμΆœν•¨μœΌλ‘œμ¨ ν•΄λ‹Ή Dep을 κ΅¬λ…ν•˜κ³  μžˆλŠ” λͺ¨λ“  Observerλ“€μ—κ²Œ λ„ˆκ°€ μ˜μ‘΄ν•˜κ³  μžˆλŠ” 속성에 λ³€ν™”κ°€ μΌμ–΄λ‚¬μœΌλ‹ˆ updateλ₯Ό ν•˜λΌκ³  μ•Œλ¦Όμ„ λ³΄λ‚΄λŠ” 역할을 ν•©λ‹ˆλ‹€.

Dep 객체 생성

Depκ°μ²΄λŠ” Dependency의 μ•½μžλ‘œ λ°μ΄ν„°μ˜ λ³€ν™”λ₯Ό κ°μ§€ν•˜κ³ , κ΅¬λ…μžμΈ Observerλ“€μ—κ²Œ μ•Œλ¦Όμ„ ν•˜λŠ” 역할을 ν•©λ‹ˆλ‹€. Proxyλ₯Ό 생성할 λ•Œ λ°μ΄ν„°μ˜ λͺ¨λ“  μ†μ„±λ§ˆλ‹€ Dep객체가 μƒμ„±λ˜λŠ” κ²ƒμœΌλ‘œλ„ μ•Œ 수 μžˆμ§€λ§Œ λͺ¨λ“  λ°˜μ‘ν˜• 데이터듀은 λ§€ν•‘λ˜λŠ” Dep을 가지고 μžˆμŠ΅λ‹ˆλ‹€. μ—¬κΈ°μ„œ Dep μΈμŠ€ν„΄μŠ€ μžμ²΄λŠ” λ§€ν•‘λœ λ°˜μ‘ν˜• 데이터에 λŒ€ν•œ μƒνƒœλ₯Ό 가지고 μžˆμ§€ μ•ŠμœΌλ©°, μ΄λŠ” Reactivty의 defineλ©”μ†Œλ“œμ—μ„œ λ‚΄λΆ€μ μœΌλ‘œ depsλΌλŠ” μ΄λ¦„μœΌλ‘œ key와 Depλ₯Ό λ§€ν•‘ν•˜μ—¬ κ΄€λ¦¬ν•˜κ³  있기 λ•Œλ¬Έμ— λ‚˜μ€‘μ— setterκ°€ λ™μž‘ν•  λ•Œ ν΄λ‘œμ € 곡간에 μžˆλŠ” deps에 μ ‘κ·Όν•˜μ—¬ λ§€ν•‘λ˜λŠ” keyκ°€ 뭔지 μ•Œ 수 있기 λ•Œλ¬Έμ— Dep μžμ²΄λŠ” μžκΈ°κ°€ λ§€ν•‘λœ 킀에 λŒ€ν•œ μƒνƒœλ₯Ό κ°–κ³  μžˆμ§€ μ•Šκ³  notifyλ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

class Dep {
  static activated: Observer
  //...
}

activated속성은 ν˜„μž¬ ν™œμ„±ν™”λœ μ˜΅μ €λ²„κ°€ 무엇인지 μƒνƒœλ₯Ό κ°–κ³  Depκ³Ό Obserber의 μ˜μ‘΄κ΄€κ³„λ₯Ό λ§ΊκΈ° μœ„ν•œ staticλ³€μˆ˜λ‘œ μΌμ’…μ˜ μ „μ—­λ³€μˆ˜ 같은 λŠλ‚ŒμœΌλ‘œ μ‚¬μš©λ©λ‹ˆλ‹€.

computed와 methods μ£Όμž…

injectMethod(vm);
injectComputed(vm);

DOMκ³Ό 바인딩이 λ˜μ–΄μ•Ό ν•˜λŠ” dataλ“€κ³ΌλŠ” λ‹€λ₯΄κ²Œ computed와 method듀은 λ°˜μ‘μ„±μ„ μ£Όμž…ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€. λ”°λΌμ„œ viewmodelμ—μ„œ μ ‘κ·Όν•  수 μžˆλ„λ‘ viewmodel의 μ†μ„±μœΌλ‘œ 등둝해 μ£Όλ©΄ λ˜λŠ”λ° 핡심은 this 바인딩을 톡해 computed λ˜λŠ” methodλ‚΄λΆ€μ—μ„œ thisλ₯Ό μ‚¬μš©ν•  λ•Œ thisκ°€ vm을 가리킀도둝 λͺ…μ‹œμ μœΌλ‘œ 지정해 μ€λ‹ˆλ‹€.

3. λ””λ ‰ν‹°λΈŒ 및 ν…œν”Œλ¦Ώ νŒŒμ‹±

const scanner = new VueScanner(new NodeVisitor());
scanner.scan(this);

μ˜΅μ…˜μ—μ„œ 전달받은 el μ†μ„±μœΌλ‘œλΆ€ν„° ν•˜μœ„μ˜ λͺ¨λ“  λ…Έλ“œλ₯Ό μˆœνšŒν•˜λ©΄μ„œ v-접두사가 뢙은 λ””λ ‰ν‹°λΈŒ 속성 λ˜λŠ” ν…œν”Œλ¦Ώ 문법 {{ }} 을 μ‚¬μš©ν•˜κ³  μžˆλŠ” λͺ¨λ“  ν…μŠ€νŠΈλ₯Ό κ²€μ‚¬ν•©λ‹ˆλ‹€. μ—¬κΈ°μ„œ DOM을 μˆœνšŒν•˜λŠ”κ²Œ μ•„λ‹Œ Node λ‹¨μœ„λ‘œ μˆœνšŒν•˜λŠ” μ΄μœ λŠ” ν…œν”Œλ¦Ώμ„ νŒŒμ‹±ν•˜κΈ° μœ„ν•΄ ν…μŠ€νŠΈ λ…Έλ“œκΉŒμ§€ κ²€μ‚¬ν•΄μ•Όν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

λ…Έλ“œ 순회λ₯Ό μœ„ν•΄ μˆœνšŒν•˜λŠ” μ—­ν•  μžμ²΄λŠ” Visitorμ—κ²Œ μœ„μž„ν•˜κ³  λ…Έλ“œλ§ˆλ‹€ μ²˜λ¦¬ν•  ꡬ체적인 μ•‘μ…˜μ€ Scannerμ—μ„œ μ²˜λ¦¬ν•˜λ„λ‘ Visitor와 Scannerλ₯Ό λΆ„λ¦¬ν•©λ‹ˆλ‹€.

const action = (node: Node) => {
  isReactiveNode(node) && new Observable(vm, node);
};

λͺ¨λ“  λ…Έλ“œλ₯Ό μˆœνšŒν•˜λ©΄μ„œ ν•΄λ‹Ή λ…Έλ“œκ°€ λ””λ ‰ν‹°λΈŒλ₯Ό κ°–κ±°λ‚˜ ν…μŠ€νŠΈμ— ν…œν”Œλ¦Ώ 문법을 κ°€μ‘ŒλŠ”μ§€λ₯Ό ν™•μΈν•˜κ³  Observable μƒμ„±ν•©λ‹ˆλ‹€.

μ—¬κΈ°μ„œ Observable은 λ‹¨μˆœνžˆ v-접두사λ₯Ό κ°–λŠ” λ””λ ‰ν‹°λΈŒμΈμ§€ ν…œν”Œλ¦ΏμΈμ§€μ˜ μ—¬λΆ€λ§Œ ν™•μΈν•˜μ—¬ Directiveλ₯Ό μƒμ„±ν•˜κ³ , ν…œν”Œλ¦Ώ 바인딩은 v-text λ””λ ‰ν‹°λΈŒλ‘œ λ³€κ²½λ©λ‹ˆλ‹€. μ΄λ•Œ, 이벀트λ₯Ό λ“±λ‘ν•˜λŠ” v-on을 μ œμ™Έν•˜κ³  λͺ¨λ“  λ””λ ‰ν‹°λΈŒλŠ” λ””λ ‰ν‹°λΈŒ μ’…λ₯˜μ— λ”°λΌμ„œ updaterλ₯Ό 인자둜 λ°›μ•„μ„œ v-bind μ—μ„œ μΌκ΄„μ μœΌλ‘œ Observerλ₯Ό μƒμ„±ν•©λ‹ˆλ‹€.

4. v-model 바인딩

Vue.js의 v-model λ””λ ‰ν‹°λΈŒλŠ” μ–‘λ°©ν–₯ 데이터 바인딩을 μ•„μ£Ό κ°„λ‹¨ν•˜κ²Œ κ΅¬ν˜„ν•  수 μžˆκ²Œν•˜λŠ” λ””λ ‰ν‹°λΈŒλ‘œ μ‚¬μš©μž μž…λ ₯을 vue μΈμŠ€ν„΄μŠ€μ˜ 데이터와 μžλ™μœΌλ‘œ λ™κΈ°ν™”ν•©λ‹ˆλ‹€. λ”°λΌμ„œ μ‚¬μš©μžμ˜ μž…λ ₯을 λ°›λŠ” UI μš”μ†Œλ“€μΈ input, textarea, select μš”μ†Œμ—μ„œ μ‚¬μš©λ©λ‹ˆλ‹€.

<!-- v-model을 μ‚¬μš©ν•œ μ–‘λ°©ν–₯ 바인딩 -->
<div>
  <input type="text" v-model="title">
  <div>{{ title }}</div>
</div>`;

<!-- 단방ν–₯ 바인딩 + 이벀트 ν•Έλ“€λŸ¬ -->
<div>
  <input 
    type="text" 
    v-bind:value="title" 
    v-on:input="handleInput"
  >
  <div>{{ title }}</div>
</div>

μ‹€μ œλ‘œ v-model은 μœ„μ˜ μ½”λ“œμ²˜λŸΌ v-bind와 v-on:event의 μ‘°ν•©μœΌλ‘œ λ™μΌν•˜κ²Œ λ™μž‘ν•˜λ©° vuelite μ—μ„œλ„ μ΄λŸ¬ν•œ 두가지 방식을 λͺ¨λ‘ μ§€μ›ν•©λ‹ˆλ‹€.

<input type="checkbox" v-model="isChecked">

<input type="radio" name="gender" value="male" v-model="selectedOption">
<input type="radio" name="gender" value="female" v-model="selectedOption">

<select v-model="selectedRadio">
  <option value="javascript">javascript</option>
  <option value="python">python</option>
</select>

v-model을 κ΅¬ν˜„ν•  λ•Œ λ¬Έμ œλŠ” 각각의 μš”μ†Œλ§ˆλ‹€ λ°”μΈλ”©λ˜λŠ” 값이 value, checked λ“±μœΌλ‘œ λ‹€λ₯Ό λΏλ”λŸ¬ 같은 checked 속성에 바인딩 ν•˜λ”λΌλ„ checkbox와 radio λ²„νŠΌμ€ λ™μž‘ 방식이 λ‹€λ₯΄κ³ , μ΄λ²€νŠΈλ„ change, input 처럼 달라지기 λ•Œλ¬Έμ— μš”μ†Œμ˜ κ°’μ΄λ‚˜ μƒνƒœλ₯Ό ν†΅μΌλœ λ°©μ‹μœΌλ‘œ μ ‘κ·Όν•  수 μžˆκ²Œν•΄μ„œ μΌκ΄€λ˜κ²Œ λ°”μΈλ”©ν•˜κ²Œ 해쀄 ν•„μš”κ°€ μžˆμŠ΅λ‹ˆλ‹€.

λ”°λΌμ„œ Directive ν΄λž˜μŠ€μ—μ„œ v-model을 μ²˜λ¦¬ν• λ•ŒλŠ” μ΄λŸ¬ν•œ μš”μ†Œλ“€ λ˜λŠ” νƒ€μž…μ— λ”°λΌμ„œ μΌκ΄€λ˜κ²Œ μ‚¬μš©ν•  수 있게 λΆ„κΈ°μ²˜λ¦¬ν•˜μ—¬ updater와 μ΄λ²€νŠΈλ¦¬μŠ€λ„ˆλ₯Ό λ“±λ‘ν•©λ‹ˆλ‹€.

5. Observer 생성

bind(updater?: Updater) {
  // ... 
  const value = evaluateValue(this.vm, this.exp);
  updater && updater(this.node, value);
  new Observer(this.vm, this.exp, (value) => {
    updater && updater(this.node, value);
  });

λ””λ ‰ν‹°λΈŒ μ’…λ₯˜μ— λ”°λΌμ„œ updaterκ°€ 정해지고 결과적으둜 Obserberκ°€ μƒμ„±λ©λ‹ˆλ‹€. μ—¬κΈ°μ„œ updaterλž€ Reactiveκ°€ μ£Όμž…λœ μ†μ„±μ—μ„œ λ³€ν™”κ°€ μΌμ–΄λ‚˜ set νŠΈλž©μ—μ„œ notifyκ°€ λ°œμƒν–ˆμ„ λ•Œ ν•΄λ‹Ή dep을 κ΅¬λ…ν•˜κ³  μžˆλŠ” λͺ¨λ“  Observerλ“€μ—κ²Œ λ³€ν™”κ°€ μΌμ–΄λ‚¬μŒμ„ μ•Œλ¦¬κ³  μ—…λ°μ΄νŠΈλ₯Ό μš”μ²­ν•˜λŠ” ꡬ체적인 μ—…λ°μ΄νŠΈ ν•¨μˆ˜λ₯Ό μ˜λ―Έν•©λ‹ˆλ‹€. 즉, ObserverλŠ” 변화에 λŒ€μ‘ν•˜μ—¬ DOM을 μ—…λ°μ΄νŠΈν•˜κ³  λ”°λΌμ„œ viewmodel의 data λ³€ν™”κ°€ μ΅œμ’…μ μœΌλ‘œ 화면에 λ°˜μ˜λ©λ‹ˆλ‹€.

μœ„μ˜ μ½”λ“œμ—μ„œ Observerλ₯Ό μƒμ„±ν•˜κΈ° 전에 updaterλ₯Ό 미리 ν•œλ²ˆ μ‹€ν–‰ν•˜λŠ”λ° 이건 첫 λ Œλ”λ§μ— viewmodel의 속성을 DOM에 λ°˜μ˜ν•˜κΈ° μœ„ν•¨μž…λ‹ˆλ‹€.

Observer와 Dep의 관계

μ„œλ‘œκ°€ μ„œλ‘œλ₯Ό μ»¬λ ‰μ…˜μœΌλ‘œ κ΄€λ¦¬ν•˜λŠ” λ‹€λŒ€λ‹€μ˜ 관계λ₯Ό κ°–μŠ΅λ‹ˆλ‹€.

Dep의 μž…μž₯μ—μ„œλŠ” μ—¬λŸ¬ 개의 λ””λ ‰ν‹°λΈŒμ—μ„œ 같은 속성을 μ‚¬μš©ν•  수 있기 λ•Œλ¬Έμ— μ—¬λŸ¬ Observer듀을 κ΄€λ¦¬ν•˜λŠ” 것이고, λ°˜λŒ€λ‘œ ν•˜λ‚˜μ˜ λ””λ ‰ν‹°λΈŒμ—μ„œ μ—¬λŸ¬ 개의 λ°˜μ‘ν˜• 데이터에 μ˜μ‘΄ν•  수 있기 λ•Œλ¬Έμ— ObserverλŠ” μ—¬λŸ¬ Dep을 κ°€μ§ˆ 수 μžˆμŠ΅λ‹ˆλ‹€.

μ˜΅μ…˜μ—μ„œ μ „λ‹¬ν•œ data 듀은 λͺ¨λ‘ 1:1둜 λ§€ν•‘λ˜λŠ” Depκ°€ μƒμ„±λ˜κ³ , λ°˜λŒ€λ‘œ λͺ¨λ“  λ””λ ‰ν‹°λΈŒλŠ” 1:1둜 λ§€ν•‘λ˜λŠ” Observerκ°€ μƒμ„±λ˜μ–΄ κ·Έ λ‘˜μ΄ μƒν˜Έμž‘μš© ν•œλ‹€κ³  μƒκ°ν•˜λ©΄ λ©λ‹ˆλ‹€.

getterTrigger

// Observer
getterTrigger() {
  Dep.activated = this;
  const value = evaluateValue(this.vm, this.exp);
  Dep.activated = null;
  return value;
}
// Dep 
depend() {
    Dep.activated?.addDep(this);
}

Observer ν΄λž˜μŠ€μ—λŠ” getterTrigger λ©”μ†Œλ“œκ°€ μ‘΄μž¬ν•˜λŠ”λ° 이 λ©”μ†Œλ“œμ˜ 역할은 λ‹¨μˆœνžˆ vmμ—μ„œ ν•΄λ‹Ή 속성을 κ°€μ Έμ˜€λŠ” 일을 ν•˜κ³  μžˆμ–΄ λ³΄μ΄μ§€λ§Œ, 이 ν•¨μˆ˜λŠ” κ·Έ μ΄μƒμœΌλ‘œ μ€‘μš”ν•œ 역할을 ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

  1. μ²˜μŒμ— Reactivty ν΄λž˜μŠ€μ—μ„œ λͺ¨λ“  data 속성에 λž˜ν•‘ν•œ ν”„λ‘μ‹œ 객체의 get νŠΈλž©μ„ μ˜λ„μ μœΌλ‘œ λ°œμƒμ‹œν‚€ μœ„ν•΄ μ‚¬μš©λ©λ‹ˆλ‹€.
  2. get 트랩이 λ°œμƒλ˜κΈ° 이전에 Dep.activatedλ₯Ό ν˜„μž¬μ˜ this 즉, ν˜„μž¬μ˜ Observer둜 섀정을 해놓고 get 트랩이 λ°œμƒν•˜λ©΄ dep.depend()λ₯Ό ν˜ΈμΆœν•˜μ—¬ ν˜„μž¬ ν™œμ„±ν™”λœ Observer와 Dep의 관계λ₯Ό κ΅¬μΆ•ν•©λ‹ˆλ‹€.

결과적으둜 getterTriggerλŠ” λ°˜μ‘ν˜• λ°μ΄ν„°μ˜ get νŠΈλž©μ„ λ°œμƒμ‹œμΌœμ„œ Dep 객체λ₯Ό μƒμ„±ν•˜λ©°, 값을 κ°€μ Έμ˜΄κ³Ό λ™μ‹œμ— μ΄λ ‡κ²Œ λ§Œλ“€μ–΄μ§„ Dep객체가 Observerμ™€μ˜ 관계λ₯Ό λ§Ίμ–΄μ£ΌλŠ” μ€‘μš”ν•œ 역할을 ν•©λ‹ˆλ‹€. 즉, ObserberλŠ” Dep을 κ΅¬λ…ν•˜μ—¬ 기닀리고 Dep은 Obserberμ—κ²Œ κ°μ‹œλ‹Ήν•˜λ‹€κ°€ Dep이 μžμ‹ μ˜ λ³€ν™”κ°€ λ°œμƒν–ˆμ„ λ•Œ κ΅¬λ…μž(Observer)λ“€μ—κ²Œ λ³€ν™”λ₯Ό ν†΅μ§€ν•˜λŠ” 관계λ₯Ό ν˜•μ„±ν•©λ‹ˆλ‹€.

6. 쑰건뢀 λ Œλ”λ§ <1.4.3>

Description of GIF

<div>
  <input type="checkbox" v-model="visible" />
  <span>{{ visible ? "πŸ”“" : "πŸ”’" }}</span>

  <h1 v-if="visible">Vue is awesome!</h1>
  <h1 v-else>Oh no 😒</h1>
</div>
new Vuelite({
  el: "#app",
  data() {
    return {
      visible: true,
    }
  },
})

핡심 아이디어

  1. v-show와 v-if의 차이
    v-show λ””λ ‰ν‹°λΈŒλŠ” μš”μ†Œμ˜ κ°€μ‹œμ„±μ„ μ œμ–΄ν•˜κΈ° λ•Œλ¬Έμ— λ‹¨μˆœνžˆ 값에 λ”°λΌμ„œ display: none의 μ—¬λΆ€λ₯Ό κ²°μ •ν•΄ μ£Όλ©΄ λ˜μ§€λ§Œ, v-ifλŠ” λ§€ν•‘λœ 데이터가 false일 λ•Œ DOMμ—μ„œ 사라지고 trueκ°€ 될 λ•Œ λ‹€μ‹œ DOM에 μ‚½μž…λ©λ‹ˆλ‹€. 즉, v-showλŠ” 항상 λ Œλ”λ§λ˜κ³  값에 λ”°λΌμ„œ μŠ€νƒ€μΌλ§Œ λ³€κ²½ν•΄ μ£Όλ©΄ 되기 λ•Œλ¬Έμ— 기쑴의 λ””λ ‰ν‹°λΈŒ 처럼 μ²˜λ¦¬ν•  수 있고 μ•„μ£Ό λ‹¨μˆœν•œ 반면, v-ifλŠ” μ‘°κ±΄λΆ€λ‘œ μš”μ†Œκ°€ μ‚­μ œλ˜κ±°λ‚˜ μ‚½μž…λ˜μ•Όν•˜λŠ”λ° μ—¬κΈ°μ„œ μ‚­μ œν•˜λŠ” 건 어렡지 μ•Šμ§€λ§Œ, λ‹€μ‹œ μ‚½μž…λ  λ•Œ μ–΄λ–»κ²Œ μ΄μ „μ˜ μœ„μΉ˜λ₯Ό κΈ°μ–΅ν•˜κ³  μ›λž˜ μžλ¦¬μ— μ‚½μž…ν• μ§€ κ³ λ―Όν•΄ λ΄μ•Όν•©λ‹ˆλ‹€.

  2. v-if의 λ™μž‘ 흐름

  1. v-if λ””λ ‰ν‹°λΈŒκ°€ 적용된 μš”μ†Œ λΆ€λͺ¨ μš”μ†Œμ˜ children 속성을 톡해 ν˜„μž¬ μžμ‹μ΄ λͺ‡ 번째 인덱슀 인지λ₯Ό κΈ°μ–΅
  2. 쑰건이 falseμΌλ•Œ ν˜„μž¬ λ Œλ”λ§λ˜λŠ” v-if 속성을 가진 μš”μ†Œλ₯Ό fragment둜 μ΄λ™ν•˜μ—¬ μž„μ‹œλ³΄κ΄€
  3. 쑰건이 λ‹€μ‹œ true되면 λΆ€λͺ¨λ‘œ λΆ€ν„° μ•„κΉŒ κ΅¬ν•œ 인덱슀 μœ„μΉ˜ λ°”λ‘œ 이전에 fragment둜 λΆ€ν„° λ‹€μ‹œ 값을 κ°€μ Έμ™€μ„œ μ‚½μž…
  4. v-else λ””λ ‰ν‹°λΈŒκ°€ μ‘΄μž¬ν•˜λ©΄ v-if와 μŒμ„ 이루고 이와 λ°˜λŒ€λ‘œ λ™μž‘ν•¨
  1. v-if의 λ Œλ”λ§ μ „λž΅
    v-if의 κ²½μš°λŠ” DOM에 직접적인 영ν–₯을 미치기 λ•Œλ¬Έμ— 쀑간에 DOM의 ꡬ쑰λ₯Ό λ³€κ²½ν•˜κ³  이둜 인해 λ‹€λ₯Έ λ””λ ‰ν‹°λΈŒκ°€ μ „λΆ€ νŒŒμ‹±λ˜κΈ° 전에 λ‹€λ₯Έ λ””λ ‰ν‹°λΈŒμ— 영ν–₯을 λ―ΈμΉ  수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€. κ·Έλž˜μ„œ v-if λ””λ ‰ν‹°λΈŒ 처리λ₯Ό 미뀄놓고 λͺ¨λ“  λ””λ ‰ν‹°λΈŒλ₯Ό μ²˜λ¦¬ν•œ 후에 lazyν•˜κ²Œ μ²˜λ¦¬ν•˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€. λ˜ν•œ v-ifλŠ” 기쑴의 Directive, Observer의 관계가 μ•„λ‹Œ Conditionμ΄λΌλŠ” μΈμŠ€ν„΄μŠ€λ₯Ό μƒˆλ‘œ μƒμ„±ν•©λ‹ˆλ‹€. μ™œλƒν•˜λ©΄ λ§€ν•‘λœ 값이 falsyμΌλ•Œ DOM을 λ³΄κ΄€ν•΄μ•Όν•˜λ©° v-else ꡬ문에 λŒ€ν•œ μŒμ„ μ΄λ€„μ•Όν•˜κΈ° λ•Œλ¬Έμ— λ‹€λ₯Έ λ””λ ‰ν‹°λΈŒμ™€ λ‹€λ₯΄κ²Œ 좔가적인 μƒνƒœλ₯Ό ν•„μš”λ‘œ ν•΄μ„œ λΆ„λ¦¬ν–ˆμŠ΅λ‹ˆλ‹€.

  2. μ œν•œμ‚¬ν•­
    v-else-if와 <template v-if>λŠ” μ§€μ›ν•˜μ§€ μ•ŠμœΌλ©°, v-elseλŠ” λ°˜λ“œμ‹œ v-if λ°”λ‘œ 뒀에 μ™€μ„œ μŒμ„ 이룰 λ•Œλ§Œ 정상 λ™μž‘ν•©λ‹ˆλ‹€. κ·Έ μ™Έμ˜ κ²½μš°μ—” v-else λ””λ ‰ν‹°λΈŒκ°€ λ¬΄μ‹œλ©λ‹ˆλ‹€.

7. 리슀트 λ Œλ”λ§ <1.5.1>

<ul>
  <li v-for="item in items" :key="item.id">
    {{ item.message }}
  </li>
</ul>
data() {
  return {
    items: [
      { id: 1, message: "Item 1" },
      { id: 2, message: "Item 2" },
      { id: 3, message: "Item 3" },
      { id: 4, message: "Item 4" },
    ],
  }
},

핡심 아이디어

  1. μ»¨ν…μŠ€νŠΈμ˜ 생성

  2. μ œν•œμ‚¬ν•­
    :key 제곡 x

πŸ“ Todos

  • methods, computed λ‚΄λΆ€μ—μ„œ this의 νƒ€μž…μΆ”λ‘  및 μžλ™μ™„μ„± κ°œμ„  <1.1.0>
  • λ””λ ‰ν‹°λΈŒ μΆ•μ•½ ν˜•νƒœ μ§€μ›ν•˜κΈ° <1.2.1>
  • ν…œν”Œλ¦Ώ λ¬Έλ²•μ—μ„œ ν‘œν˜„μ‹ μ§€μ›ν•˜κΈ° <1.3.0>
  • 쑰건뢀 λ Œλ”λ§ μΆ”κ°€ (v-if/else, v-show λ””λ ‰ν‹°λΈŒ) <1.4.3>
  • 리슀트 λ Œλ”λ§ μΆ”κ°€ (v-for λ””λ ‰ν‹°λΈŒ) <1.5.1>
  • created, mounted, updated λ“±μ˜ 라이프사이클 ν›… μ§€μ›ν•˜κΈ°
  • watch μ§€μ›ν•˜κΈ°
  • λΆ€λΆ„μ μœΌλ‘œ Composition API μ§€μ›ν•˜κΈ°

πŸ“– Reference

About

Clones Vue.js to implement a basic MVVM framework

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published