Vue.js 구조 내용 정리

lyju777·2023년 12월 3일
0
post-thumbnail

회사에서 스터디(?) 느낌으로 프론트 개발팀이 아닌 개발자분들에게 Vue에 대해 간략히 내용을 공유하는 시간을 가지게 되어 자료를 정리하게 되었다. 나 또한 아직 부족하기 때문에 하나씩 내용을 정리하며 다시 되새겨 보는 시간을 가지게 되어 좋은경험이 되었다고 생각한다.👍


Vue.js 소개

Vue는 사용자 인터페이스를 구축하기 위한 JavaScript 프레임워크로 표준 HTML, CSS 및 JavaScript를 기반으로 구축되며, 사용자 인터페이스를 효율적으로 개발할 수 있는 컴포넌트 기반 프로그래밍 모델을 제공합니다.

import Vue from 'vue'

<script>
	new Vue({  // 인스턴스 생성
	   el: "#app",
	   data: {
	     return {
	       count: 0  // data 선언
	    }
	  }
	})
</script>
<div id="app">
  <button @click="count++">  
	    숫자 세기: {{ count }}   // "이중 중괄호" 문법을 사용한 데이터 바인딩
  </button>
</div>

위의 예제를 통해 Vue의 핵심 기능 두가지를 확인할 수 있습니다.

  • 선언적 렌더링(Declarative Rendering)

    • 표준 HTML을 템플릿 문법으로 확장하여 JavaScript 상태(State)를 기반으로 화면에 출력될 HTML을 선언적으로 작성할 수 있습니다.
  • 반응성(Reactivity)

    • JavaScript 상태(State) 변경을 추적하고, 변경이 발생하면 DOM을 효율적으로 업데이트하는 것을 자동으로 수행합니다.

인스턴스 생성

모든 Vue 앱은 Vue 함수를 사용하여 새로운 인스턴스를 생성하는 것으로 시작합니다.

var vm = new Vue({
  el: '#app',
  router,
  store,
  render: h => h(App)
})
var vm = new Vue();
console.log(vm);

Vue 인스턴스를 생성하고 나면 아래와 같이 인스턴스 안에 어떤 속성이 있는지 콘솔 창에서 확인할 수 있습니다.


인스턴스에서 일반적으로 사용되는 속성은 다음과 같습니다.
new Vue({
  el: ,
  template: ,
  data: ,
  methods: ,
  created: ,
});

el : 인스턴스가 그려지는 화면의 시작점 정의(특정 HTML 태그로 일반적으로 id=”app” 사용)
template : 화면에 표시할 요소 작성(HTML, CSS 등)
data : 뷰의 데이터 속성 정의
methods : 화면의 동작과 이벤트 로직을 제어하는 메서드 정의
created : 뷰의 라이프 사이클과 관련된 속성


싱글 파일 컴포넌트

Vue 프로젝트에서는 싱글 파일 컴포넌트(Single-File Component: SFC)라는 파일 형식을 사용하여
JavaScript, HTML ,CSS을 하나의 파일에 캡슐화 하여 컴포넌트를 작성합니다.

-------- HTML --------
<template>
	<div>
	  <button @click="increment">숫자 세기: {{ count }}</button>
	</div>
</template>

-------- JAVASCRIPT --------
<script>
export default {
  // data()에서 반환된 속성들은 this로 접근하여 script 내에서 호출 가능
  data() {
    return {
      count: 0
    }
  },

  // data()속성값을 변경하기 위한 함수 정의
  // template 내에서 이벤트 리스너로서 사용 
  methods: {
    increment() {
      this.count++
    }
  },

  // 라이프사이클 훅(Lifecycle hooks)은 컴포넌트 생명주기 사이에 호출됨
	// mounted()는 Vue인스턴스가 DOM에 부착되는 시점을 의미
  mounted() {
    console.log(`숫자 세기의 초기값은 ${ this.count } 입니다.`)
  }
}
</script>

-------- CSS --------
<style scoped>
button {
  font-weight: bold;
}
</style>

인스턴스 라이프 사이클

인스턴스 라이프 사이클은 뷰의 인스턴스가 생성되어 소멸되기까지 거치는 과정을 의미합니다. 인스턴스가 생성되고 나면 내부적으로 다음과 같은 과정이 진행됩니다.

라이프 사이클의 과정은 해당 속성(라이프 사이클 훅)들로 구분할 수 있습니다.

created : 인스턴스가 생성된 후 호출되며 DOM에 접근이 불가하여 $el속성의 사용이 불가능
Mounted : 인스턴스가 마운트된 후 호출되며 DOM에 접근할 수 있음
updated : 데이터가 변경되어 DOM이 다시 렌더링되고 패치된 후에 호출
destroyed : 컴포넌트가 제거 된 후 호출되며 인스턴스와 디렉티브, 하위 컴포넌트도 모두 제거됨


라이프 사이클 훅

라이프 사이클 훅을 사용하면 인스턴스의 생성과 소멸 과정사이에 인스턴스의 특정 시점에 원하는 로직을 구현할 수가 있습니다.

예로 페이지 로드 시점에 데이터를 서버에서 받아오고 싶다면 created 또는 Mounted 라이프 사이클 훅을 사용하여 처리할 수 있습니다.

new Vue({
  methods: {
    init() {
      axios.get(url);
    }
  },
  created() {
    this.init();  // 인스턴스가 생성된 후 init() 호출
  }
})

템플릿 문법

템플릿 문법은 Vue로 화면을 조작하는 방법을 의미합니다. 템플릿 문법은 크게 데이터 바인딩과 디렉티브로 나눌 수 있습니다.


데이터 바인딩

데이터 바인딩은 뷰 인스턴스에서 정의한 데이터 속성들을 화면에 표시하는 방법입니다. 가장 기본적인 데이터 바인딩 방식은 “이중 중괄호” 입니다.

<div>{{ message }}</div>
new Vue({
  data: {
    message: 'Hello Vue.js'
  }
})

디렉티브

디렉티브는 Vue에서 화면의 요소를 더 쉽게 조작하기 위한 문법으로 화면 조작에서 자주 사용되는 방식들을 모아 디렉티브 형태로 사용할 수 있습니다.

예로 아래와 같이 v-if를 사용하면 특정 속성값에 따라 해당 태그를 화면에 노출 또는 미노출 시킬 수 있습니다.

<div>
 Hello <span v-if="show">Vue.js</span> // "show" 가 true값일 경우에만 노출
       <span v-else>React</span> // v-if 값이 false일 경우 노출
</div>
new Vue({
  data: {
    show: false
  }
})

이외에도 자주 사용되는 디렉티브는 다음과 같습니다.


v-for

v-for 은 배열을 기반으로 리스트를 순차적으로 렌더링 할 때 사용됩니다.

아래 예제에서 items 는 원본 데이터 배열을 나타내고  item 은 반복되는 배열 엘리먼트의 별칭으로 사용됩니다.

<ul><li v-for="item in items">{{ item }}</li></ul>
new Vue({
  data: {
    items: ['shirts', 'jeans', 'hats']
  }
})

v-bind

v-bind 는 Vue 인스턴스의 데이터 속성을 HTML 요소에 연결할 때 사용합니다.
v-bind 는 축약하여 : (콜론)으로 사용할 수도 있습니다.

<div id="app">
  <input type="text" v-bind:value="message">

  <!-- 축약 문법 -->
  <image :src="imageUrl">
</div>

v-on

v-on 은 HTML 요소의 이벤트를 뷰 인스턴스의 로직과 연결할 때 사용합니다.
v-on 은 축약하여 @(엣_)으로 사용할 수도 있습니다.

<div id="app">
	<button v-on:click="handleClick">click</button>

	<!-- 축약 문법 -->
	<input @change="handleChange"></input>
</div>

v-model

v-model 은 v-bind와 v-on의 기능의 조합으로 동작되는 속성입니다.

사용자의 입력을 받는 UI 요소들에 v-model 을 사용하면 입력 값이 자동으로 뷰 데이터 속성에 연결됩니다.

<!-- 하단 input 태그의 기능은 둘다 동일합니다. -->

<input v-bind:value="inputText" v-on:input="updateInput">

<input v-model="inputText">
new Vue({
  data: {
    inputText: ''
  },
  methods: {
    updateInput(event) {
      var updatedText = event.target.value;
      this.inputText = updatedText;
    }
  }
})

HTML 입력 요소의 종류에 따라 v-model 속성은 각각 다음과 같이 구성됩니다.

input : v-bind:value / v-on:input의 기능 조합으로 동작합니다.
checkbox : v-bind:checked / v-on:change 의 기능 조합으로 동작합니다.
select : v-bind:value / v-on:change 의 기능 조합으로 동작합니다.


컴포넌트

Vue는 싱글 파일 컴포넌트(SFC)로 이뤄진  .vue 확장자를 사용하는 전용 파일에 각 컴포넌트를 정의합니다.

컴포넌트 등록

하위 컴포넌트를 사용하기 위해서는 상위 컴포넌트에서 import 하여 컴포넌트를 선언 한 후 사용할 수 있습니다.

<template>
  <h1>아래에 하위 컴포넌트가 있습니다.</h1>
	  <ButtonCounter /> 
</template>

<script>
import ButtonCounter from './ButtonCounter.vue' // 하위 컴포넌트 가져오기

export default {
  components: {
	    ButtonCounter  // 컴포넌트 사용선언
  }
}
</script>
<h1>이곳에 많은 하위 컴포넌트가 있습니다.</h1>
<ButtonCounter />
<ButtonCounter />
<ButtonCounter />

또한 불러온 컴포넌트는 여러번의 재사용이 가능합니다.

컴포넌트는 재사용할 때마다 해당 컴포넌트의 새 인스턴스가 생성되기 때문에 각 컴포넌트의 데이터는 독립적으로 유지됩니다.


컴포넌트 간 데이터 통신

상위 컴포넌트와 하위 컴포넌트간에 데이터 통신을 하기 위해서는 다음과 같은 속성을 사용할 수 있습니다.

props

props 는 상위 컴포넌트에서 하위 컴포넌트로 내려보내는 데이터 통신 방식입니다.
props 속성을 사용하기 위해서는 상위 컴포넌트에서 전달할 데이터를 하위컴포넌트 태그를 통해
v-bind속성을 사용하여 별칭을 지정하고 데이터값을 전달합니다.

// 상위 컴포넌트(ParentComponent)

<template>
 <div> 
   <ChildComponent v-bind:childVaule="parentVaule"/> // v-bind로 데이터 연결
 </div>
</template>

<script> 
import ChildComponent from "./components/childComponent.vue";

export default {
 name: "ParentComponent", 
 components: {
   ChildComponent
 }, 
 data() {
   return {
     parentVaule: 0,
   };
 },
}; 
</script>

이후 상위 컴포넌트에서 전달받은 데이터는 props로 선언하여 컴포넌트 내에서 사용됩니다.
props로 전달받은 데이터는 하위 컴포넌트에서 직접 수정이 불가능합니다.

// 하위 컴포넌트(ChildComponent)

<template>
  <div>
  값은 {{ childValue }} 입니다.   // 2.전달받은 props 사용
  </div>
</template>

<script>

export default {
name: "ChildComponent",
props: {
  childValue: {
    type: Number  // 1.전달받은 props선언 및 type지정
		default: 0
		}
  },
};
</script>

emit

emit 은 하위 컴포넌트에서 상위 컴포넌트로 통신하는 방식입니다.
emit 속성을 사용하기 위해서는 하위 컴포넌트의 메서드를 통해 emit(“이벤트명”) 이벤트를 선언하고 해당 메서드를 특정 이벤트와 연결합니다.

// 하위 컴포넌트(ChildComponent)

<template>
  <button v-on:click="updateParentValue">버튼</button> // 2.v-on 이벤트와 연결
  <div>
  값은 {{ childValue }} 입니다.
  </div>
</template>

<script> 
export default {
  name: "ChildComponent",
	props: {
	  childValue: {
	    type: Number  
			default: 0
	  }
	},
  methods: {
    updateParentValue() {
	      this.$emit("childEvent");  // 1.emit(“이벤트명”) 속성을 선언
    },
  },
};
</script>

해당 메서드가 실행되면 이벤트가 발생되고, 이를 상위 컴포넌트에서 사용중인 하위 컴포넌트에 v-on 디렉티브로 이벤트를 받아 연결된 메서드를 실행합니다.

// 상위 컴포넌트(ParentComponent)

<template>
  <div>
    <ChildComponent
		  v-bind:childVaule="parentVaule"  
		  v-on:childEvent="updateParentValue"/>  // 1.emit의 "이벤트명" 으로 v-on 연결 
  </div>
</template>

<script> 
import ChildComponent from "./components/childComponent.vue";

export default {
  name: "ParentComponent",
  components: {
    ChildComponent
  },
  data() {
    return {
      parentVaule: 0,
    }
  },
  methods: {
    updateParentValue() {  // 2.emit 이벤트 연결로 버튼 클릭 시 해당 메서드 실행 
      this.parentVaule++;
    },
  },
};
</script>

Vue 라우터

Vue 라우터는 싱글 페이지 애플리케이션(SPA)을 구현할 때 사용하는 Vue 라이브러리입니다.

NPM 방식으로 설치

npm install vue-router

Vue 라우터를 설치하고 나면 아래 코드와 같이 라우터 인스턴스를 생성하고 Vue 인스턴스에 등록하여 사용할 수 있습니다.

// router.js

import VueRouter from 'vue-router'

// 라우터 인스턴스 생성
var router = new VueRouter({
  // 라우터 옵션
})
// main.js

import App from "./App.vue";
import router from './router'

new Vue({
  el: "#app",
  router: router, // Vue 인스턴스의 router속성에 연결
  render: h => h(App)
}

위와 같이 라우터를 Vue 인스턴스에 등록하면 라우터에 옵션을 정의할 수 있습니다. 라우터를 설정할 시에는 아래의 두 옵션을 필수로 지정하게 됩니다.

routes : 라우팅 할 URL과 컴포넌트 값 지정
mode : URL의 해쉬 값 제거 속성

var router = new Router({
  mode: 'history',  // url 해쉬값(#) 제거 
	const routes = [
  {
    path: '/userM',
    component: User,
    children: [
      {
        path: 'user/list',
        component: () => import('@/views/user/userList'),
        meta: {
	        title: 'UserList',
        }
      },
      {
        path: 'user/detail/:login_id',
        component: () => import('@/views/user/userDetail'),
				props: true,
      },
    ],
  },
]
})

필요에 따라서는 다음과 같은 옵션을 지정할 수도 있습니다.

children : 상위 라우트 path를 기준으로 하위 라우트를 설정합니다.
meta : 라우트로 이동시 전달할 meta 데이터를 설정합니다.
props : 라우트 컴포넌트로 라우트의 동적 파라미터를 전달하도록 설정합니다.

설정한 라우터를 템플릿 내에서 사용하는 방법은 다음과 같습니다.


router-view

<router-view> 는 브라우저의 URL이 변경되면 정의한 라우터 속성에 따라 해당 컴포넌트와 연결되어 화면을 보여주는 속성입니다.

<div id="app">
  <router-view></router-view> // 변경된 라우터 컴포넌트를 화면에 노출
</div>

<router-link> 는 클릭 시 지정한 라우터로 이동할 수 있도록 설정하는 속성입니다.
화면상에서는 <a> 태그와 같이 변형되어 노출됩니다.

<div>
	<router-link to="/user/list"></router-link>  // 이동할 라우터 path 지정
</div>

화면이동 시 파라미터가 전달되어야 하는 경우에는 v-bind 속성을 사용하여 이동할 컴포넌트의 name값을 지정하고 파라미터를 전달하여 페이지 이동을 할 수 있습니다.

<div>
	<router-link :to="{ name:'UserDetail', params:{ login_id: this.login_id }}"></router-link> 
</div>

vuex

Vuex는 컴포넌트 간의 통신과 데이터 전달을 유기적으로 관리하기 위해 사용하는 Vue의 상태 관리 라이브러리입니다.

NPM 방식으로 설치

npm install vuex

Vuex를 설치하고 나면 아래 코드와 같이 Vuex 인스턴스를 생성하고 Vue 인스턴스에 등록하여 사용할 수 있습니다.

// store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  // ...
});
// main.js 

import App from "./App.vue";
import { store } from "./store";

new Vue({
  el: "#app",
  store: store, // Vue 인스턴스의 store 속성에 연결
  render: h => h(App)
});

상태관리 속성

<template>
	<div>{{ count }}</div> // 뷰(view)
</template>   

<script>
export default {
  data() {     // 상태(state)
    return {
      count: 0
    }
  },
  methods: {   // 기능(actions)
    increment() {
      this.count++
    }
  }
}
</script>

Vue의 상태 관리 구성요소는 크게 3가지로 나눌 수 있습니다.

view : 데이터가 표현될 템플릿
state : 컴포넌트 간에 공유할 데이터
actions : 사용자의 입력에 따라 반응할 메서드


단순 상위컴포넌트와 하위컴포넌트 간의 데이터 통신은 `props` 또는 `emit`을 사용하여 처리할 수 있지만 컴포넌트의 깊이가 깊어질수록 이는 비효율적이고 유지관리 측면에서 어려움이 생기게 됩니다.

이를 대비하여 Vuex 를 사용하게 될 경우 전역적으로 상태관리가 가능해지기 때문에 컴포넌트의 유지관리 효율성을 높일 수 있습니다.

Vuex 로 상태관리를 하기 위해 사용되는 속성은 다음과 같습니다.


state

state는 컴포넌트 간에 공유할 데이터 속성을 의미합니다.

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
  state: {
    counter: 0   // counter라는 state 속성을 추가
  }
});

state에 등록한 데이터는 컴포넌트 내에서  $store.state 로 접근할 수 있습니다.

// 상위 컴포넌트(ParentComponent)

<div id="app">
  Parent counter : {{ $store.state.counter }} <br />
  <button @click="addCounter">+</button>

  <Child/>
</div>
// 하위 컴포넌트(ChildComponent)

export default {
  components: {
    child: Child
  },

  methods: {         
    addCounter() {
	      this.$store.state.counter++; // $store.state으로 접근하여 데이터 변경
    },
  }
};

getters

getters 는 여러 컴포넌트에서 Vuex 의 데이터를 접근할 때 중복된 코드를 반복호출 하게되는 문제점을 보안하기위해 사용합니다.

// store.js

export const store = new Vuex.Store({
  getters: {
    getCounter: function (state) {
      return state.counter;
    }
  }
});

getters 는 컴포넌트 내에서  $store.getters 로 접근할 수 있습니다.

단순 state값 호출이 아닌 filter(), find() 등의 여러 처리속성이 포함되어 있을 경우 getters 는 상태관리 측면에서 활용도 높게 사용할 수 있습니다.

// App.vue
computed: {
  parentCounter() {
    return this.$store.getters.getCounter;
  }
},

// Child.vue
computed: {
  childCounter() {
    return this.$store.getters.getCounter;
  }
},

mutations

mutationsstate 값을 변경하는 로직들을 동기적으로 처리합니다.

만일 여러 컴포넌트에서 같은 state 값을 동시에 제어하게 되면, state 값이 어떤 컴포넌트에서 호출되어 변경됐는지에 대해 추적이 어렵기 때문입니다.

// store.js

import Vue from "vue";
import Vuex from "vuex";

Vue.use(Vuex);

export const store = new Vuex.Store({
    mutations: {
	    addCounter: function (state, payload) {   // state값 변경 메서드 정의
	      return state.counter++;
    }
  }
});

mutations 에 등록한 메서드는 컴포넌트 내에서 $store.commit('메서드명') 으로 접근할 수 있습니다.

<div id="app">
  Parent counter : {{ parentCounter }} <br>
  <button @click="addCounter">+</button>
</div>
methods: {
  addCounter: function () {
    this.$store.commit('addCounter');  // mutations의 "메서드명" 호출
  }
},

각 컴포넌트에서 Vuex 의 state 를 조작하는데 필요한 특정 값들을 전달하기 위해서는  commit('') 에 두 번째 인자를 추가하여 전달 할 수 있습니다.

this.$store.commit('addCounter', 10);
this.$store.commit('addCounter', {
  value: 10,
  arr: ["a", "b", "c"]
});

이를 Vuex 에서는 다음과 같이 전달받을 수 있습니다.

mutations: {
  addCounter: function (state, payload) {
    state.counter = payload.value;  // 전달받은 인자를 state에 할당
  }
}

actions

actions는 비동기적인 로직의 처리를 담당하며 데이터의 상태변화를 추적하기 위해 mutations 의 메서드를 호출하여 처리합니다.

// store.js
export const store = new Vuex.Store({
  // ...
  mutations: {
    addCounter: function (state, payload) {
      return state.counter++;
    }
  },
  actions: {
    addCounter: function ({ commit }) {
      return commit('addCounter');
    }
  }
});

아래와 같이 Axios를 통한 API 요청이나 setTimeout 과 같은 비동기 처리 로직들은 actions 에 선언하여 처리합니다.

// store.js

export const store = new Vuex.Store({
  actions: {
    getServerData: function (context) {
      return axios.get("sample.json").then(function() {
        // ...
      });
    },
    delayFewMinutes: function (context) {
      return setTimeout(function () {
        commit('addCounter');
      }, 1000);
    }
  }
});

actions 는 컴포넌트 내에서 $store.dispatch('메서드명') 으로 접근할 수 있습니다.

methods: {
  addCounter() {
    this.$store.dispatch('addCounter');
  }
},

또한 actions 를 호출할 시 특정 값을 전달하고 싶다면 mutations 와 같이 dispatch('') 에 두번째 인자를 추가하여 전달할 수 있습니다.

export const store = new Vuex.Store({
  actions: {
    asyncIncrement: function ({ commit }, payload) {
      return commit('increment', payload); // 인자값을 mutations로 전달 
    }
  }
})
profile

0개의 댓글