const app = Vue.createApp({
data() {
return {
courseGoal: '123',
};
},
});
app.mount('#user-goal');
....
<section id="user-goal">
<h2>My Course Goal</h2>
<p>{{ courseGoal }}</p>
</section>
위처럼 Vue 앱을 만들고, 객체의 형태로 데이터를 선언하고 app.mount
로 이어주면 데이터 바인딩이 되는것을 확인할 수 있다. HTML에서는 {{ 변수명 }}
의 형태로 원하는 변수를 표시한다.
위가 user-goal이라는 id에 바인딩되듯, 하나의 요소에만 바인딩될 수 있다.
Vue의 위같은 방식을, 선언적 접근/선언적 렌더링 (Declarative Approach)이라고 한다. 개발자는 변수와 함수등 목적을 선언만 하고, 과정은 신경쓰지 않는다. 그러면 Vue가 DOM과 알아서 상호작용하며 렌더링된다.
<a href='{{ vueLink }}'/> // Wrong
<a v-bind:href='vueLink'/> // Good
HTML Attribute를 동적으로 설정하려면 첫줄처럼 표준 문법을 사용하면 실패한다. 이런 경우 v-bind
를 활용하면 HTML Attribute 값을 설정할 수 있다.
const app = Vue.createApp({
data() {
return {
blabla: "123",
};
},
methods: {
randomNum() {
const randomNum = Math.random();
// console.log(randomNum);
if (randomNum < 0.5) {
return "Hi";
} else {
return "Nope";
}
},
},
});
...
<p>{{ outputGoal() }}</p> // Shows Hi or Nope
데이터 뿐만 아니라 methods에도 함수를 선언하고, HTML에서 함수 콜 하듯 그냥 부르면 똑같이 함수가 호출된다.
const app = Vue.createApp({
data() {
return {
optionOne: "Hi",
optionTwo: "Byeeee",
vueLink: "https://vuejs.org",
};
},
methods: {
outputGoal() {
const randomNum = Math.random();
// console.log(randomNum);
if (randomNum < 0.5) {
return this.optionOne;
} else {
return this.optionTwo;
}
},
},
});
...
<p>{{ outputGoal() }}</p>
위처럼 this.변수명
을 함수 내에서 사용하면 마법같이 위에 선언한 데이터를 사용할 수 있다. 일반적인 상식으로 볼때 methods와 data는 같은 this로 묶일 수가 없는데, Vue가 여기서 마법을 부려서 이 둘을 'Global Vue instance object'로 합쳐서 가능하다고 한다.
const app = Vue.createApp({
data() {
return {
optionOne: "<h2>Yooooo</h2>",
optionTwo: "<h2>Byeeee</h2>",
vueLink: "https://vuejs.org",
};
},
methods: {
outputGoal() {
const randomNum = Math.random();
console.log(randomNum);
if (randomNum < 0.5) {
return this.optionOne;
} else {
return this.optionTwo;
}
},
},
});
위처럼 옵션들이 html이라 하면, 함수를 호출하면 그냥 html 마크업 자체가 텍스트로 출력된다. 하지만 상황에 따라 마크업이 전달되길 원한다면...
<p v-html="outputGoal()"></p>
위처럼 v-html을 추가하면 내부의 내용이 HTML로 해석되어야 한다고 Vue에게 알려줄 수 있다.
React에서 state, setState만 하다가 해보니 좀 색다른 부분이다.
const app = Vue.createApp({
data() {
return {
counter: 0,
};
},
});
...
<button v-on:click="counter++">Add</button>
<button v-on:click="counter--">Remove</button>
<p>Result: {{ counter }}</p>
정말 리액트랑은 비교도 안되게 간결하게 된다. 눈물이 날정도다.
v-on을 사용하면 클릭, 마우스 진입 등 HTML 기본 액션들에 대한 자바스크립트 함수를 실행 할 수 있다. 따로 메서드를 지정할 필요도 없이, 간단한 구문들의 경우 그냥 바로 입력하는것으로 끝난다. 하지만 가독성때문에 당연히 이런짓은 지양하는게 좋아보인다.
<button @click="counter--">Remove</button>
위처럼 줄이는 것도 된다.
const app = Vue.createApp({
data() {
return {
counter: 0,
};
},
methods: {
add() {
this.counter++;
},
reduce() {
this.counter--;
},
}
});
...
<button v-on:click="add">Add</button>
<button v-on:click="reduce">Remove</button>
가능하면 당연히 위처럼 하는게 좋다.
리액트에서 할때, 인풋을 받아올때 박스 안의 텍스트를 setState로 연관시키던 느낌이다.
const app = Vue.createApp({
data() {
return {
name: '',
};
},
methods: {
setName(event) {
this.name = event.target.value;
}
}
});
...
<input type="text" v-on:input="setName"/>
<p>Name: {{ name }}</p>
굉장히 간단하고, 리액트랑도 비슷하다. 그도 그럴것이 자바스크립트에 기본적으로 있는 event를 인자로 받아 event.target.value
를 받아오는것 자체는 리액트랑 똑같기 때문이다. 다만 리액트처럼 상태 관리를 훅으로 하지 않고 바로 함수로 하는데 실시간으로 업데이트되는것은 매우 신기하다. 바뀌고 있는 텍스트 상자쪽만 DOM 조작을 하기에 성능 저하도 없다.
만약 이벤트 말고 다른 인자도 함수에 넣어야 한다면 어떻게 해야 할까?
const app = Vue.createApp({
data() {
return {
name: '',
};
},
methods: {
setName(event, lastName) {
this.name = event.target.value + ` ${lastName}`;
}
}
});
...
<input type="text" v-on:input="setName($event, 'Jang')"/>
<p>Name: {{ name }}</p>
이렇게만 하면 이벤트도 받아오고, 원하는 인자도 추가할 수 있다.
리액트때와 똑같다. 버튼을 누르면 기본값으로 페이지를 새로고침 해버리는 버튼들을 처리하던 것과 같은 개념.
const app = Vue.createApp({
data() {
...
},
methods: {
submitForm() {
alert('Submitted!');
}
}
});
...
<form v-on:submit="submitForm">
<input type="text"/>
<button>Sign up</button>
</form>
위처럼 해도, 새로고침은 일어나버린다. 버튼의 기본 행동이 페이지를 새로고침 해버리기 때문. 고치기 위해선 두가지 방법이 있는데, 첫번째는:
submitForm(event) {
event.preventDefault();
alert('Submitted!');
}
안될건 없는 자바스크립트 기본 속성을 이용한 방법이긴 한데, Vue 내장 기능으로:
const app = Vue.createApp({
data() {
...
},
methods: {
submitForm() {
alert('Submitted!');
}
}
});
...
<form v-on:submit.prevent="submitForm">
<input type="text"/>
<button>Sign up</button>
</form>
위처럼 하면 된다! Vue의 event modifier로 .prevent
를 추가하면 쉽게 기본 행동이 억제된다.
<input type="text" v-bind:value="name" v-on:input="setName($event, 'Jang')"/>
위처럼 해두면 입력값과 데이터값이 계속 동일함을 보장할 수는 있다. 다만 더 좋은 방법이 있는데...
<input type="text" v-model="name"/>
이게 끝이다! 이러면 양방향으로 데이터가 바인딩됨을 보장할 수 있다.
<input type="text" :value="name" v-on:input="setName($event, 'Jang')"/>
위처럼 줄여도 된다.
재렌더링이 일어난다면, HTML 내부의 이벤트와 바인딩 되어있지 않은 모든 함수들은 변경이 있던 없던 재호출된다. 리액트와 다르게 데이터 관리가 쉬운 면의 양면성이라고 본다.
여기서 Computed 속성을 쓰면 된다. 리액트 훅의 dependency array마냥 Computed 속성이 변경되면 Vue는 해당 속성과 연관있는 부분만 재렌더링한다.
const app = Vue.createApp({
data() {
return {
counter: 0,
name: '',
};
},
computed: {
fullName() {
if(!this.name) return '';
return this.name + ` Jang`;
}
},
...
<p>Name: {{ fullName }}</p>
위의 경우 다른게 바뀌더라도 fullName
의 경우는 name
이 바뀌지 않는 한 재호출 되지 않는다. 성능을 위해 이는 중요하다.
const app = Vue.createApp({
data() {
return {
counter: 0,
name: "",
fullName: "",
};
},
watch: {
name(value) {
this.fullName = value + ' Jang';
}
},
...
<p>Name: {{ fullName }}</p>
Computed 대신 쓸 수 있는 방법이다. 위에서 name값이 바뀌는지 실시간으로 감지하고, 안에 메서드처럼 로직을 구현해 최신 값을 기준으로 위처럼 작업을 할 수 있다.
<div
class="demo"
:style="{borderColor: boxASelected ? 'red' : '#ccc'}"
@click="boxSelected('A')"
></div>
간단하게 :style
로 선택적 스타일링을 리액트에서 했듯 할 수 있다. 하지만 위의 인라인 방법은 길게 말 안해도 단점이 많다.
<div
class="demo"
:class="{active: boxASelected}"
@click="boxSelected('A')"
></div>
위의 경우 인라인이 아니라, 이미 정의된 스타일을 특정 변수에 바인딩해서 조건부 적용할 수 있다.
const app = Vue.createApp({
data() {
return { goals: [] };
},
});
...
<p v-if="goals.length === 0">No goals have been added yet - please start adding some!</p>
<ul v-else>
<li>Goal</li>
</ul>
v-if와 v-else를 사용하면 위처럼 쉽게 선택적 렌더링을 할 수 있다. v-else의 경우 v-if가 있는 요소와 붙어있어야 한다.
저것들 대신 v-show를 써도 되는데, 이경우는 그냥 display: show
속성을 바꾸는 것이고, 전자의 경우 통째로 원소가 사라진다. 그러다 보니 전자를 쓰는게 낫다.
리액트에서도 자주 하던 목록 렌더링이다.
<li v-for="goal in goals">{{goal}}</li>
<li v-for="(goal, index) in goals">{{goal}}</li>
자바스크립트 루프 문법과 비슷하고, 리액트보단 간결해진 느낌이다. goal과 index는 태그 자기 자신과 내부에 모두 사용 가능하다.
리액트는 이런짓을 하면 강제로 key를 쓰게 만든다. 비슷하게 문제가 생기기 때문에 여기도 똑같다.
<li v-for="(goal, index) in goals" @click="removeGoal(index)" :key="goal">
{{index + 1}}. {{goal}}
</li>
데이터베이스에 연결하면 해당 데이터의 id로 키를 설정하면 되겠다.
리액트랑 비슷하게, 태그에 ref를 붙여서 구별이 가능하다.
<input type="text" ref="userText"/>
...
setText() {
this.message = this.$refs.userText;
}
위같은 식으로 태그의 값을 가져올 수 있다.