아래처럼 중복되는 표현식이 존재한다고 치자.
<div id="app">
<h1>{{ count }}</h1>
<h2>{{ count * 2 }}</h2>
<h2>{{ count * 2 }}</h2>
<h2>{{ count * 2 }}</h2>
<h2>{{ count * 2 }}</h2>
</div>
반복되는 표현식은 함수로 바꿔줄수 있다.
<div id="app">
<h1>{{ count }}</h1>
<h2>{{ double() }}</h2>
<h2>{{ double() }}</h2>
<h2>{{ double() }}</h2>
<h2>{{ double() }}</h2>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
count: 3
}
},
methods: {
double() {
return this.count * 2
}
}
};
const vm = createApp(App).mount("#app");
</script>
하지만 똑같이 4번의 표현식을 실행하는건 똑같다.
4개면 다행이지, 단위가 커진다면 리소스가 꽤 낭비된다.
이를 보완하는 기능이 Computed다.
<div id="app">
<h1>{{ count }}</h1>
<h2>{{ double }}</h2>
<h2>{{ double }}</h2>
<h2>{{ double }}</h2>
<h2>{{ double }}</h2>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
count: 3
}
},
computed: {
double() {
return this.count * 2
}
}
};
const vm = createApp(App).mount("#app");
</script>
언뜻보면 표현식을 4번 실행하듯 보이지만, 결과는 이미 computed에 저장되어있다.
=> 그냥 표현식의 결과를 변수에 할당하는 것 아닌가?
조금 더 복잡한 예를 보자.
참고로 api fetching까지 사용할건데, 주소는
https://jsonplaceholder.typicode.com/
를 사용한다.
=> 무료 가상 api서버를 제공해주는 곳이다!
나중에 투두리스트 만들때 연습용으로 사용해도 되겠다!
<div id="app">
<ul>
<li v-for="todo in todos">{{ todo.title }} </li>
</ul>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
todos: []
}
},
created() {
fetch("https://jsonplaceholder.typicode.com/todos")
.then(res => res.json())
.then(res => {
console.log(res);
this.todos = res;
})
},
};
const vm = createApp(App).mount("#app");
</script>
이렇게 가져온 투두리스트들의 제목을 모두 대문자로 만들고싶다.
<div id="app">
<ul>
<li v-for="todo in upperTodos">{{ todo.title }}</li>
</ul>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
todos: []
}
},
created() {
fetch("https://jsonplaceholder.typicode.com/todos")
.then(res => res.json())
.then(res => {
console.log(res);
this.todos = res;
})
},
computed: {
upperTodos() {
return this.todos.map(todo => ({
...todo,
title: todo.title.toUpperCase()
})
)
}
}
};
const vm = createApp(App).mount("#app");
</script>
정확히 upperTodos
메서드가 계속 실행되는게 아니라
한번만 실행되고 upperTodos
라는 변수에 할당된다.
또한 캐싱이 되어서 종속대상이 변경되지 않는 한 데이터를 유지한다고 한다.
=> this.todos
를 기반으로 데이터를 조작하니 this.todos
가 종속 대상이다!
정확히 종속성이 무엇일까?
리액트의 useEffect
2번째 매개변수로 받아오는 배열 내부 인자와 같다고 볼수있다.
<div id="app">
<h1>{{ fullName }}</h1>
<h1>{{ firstName }}</h1>
<h1>{{ lastName }}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
firstName: 'Leon',
lastName: 'Miller'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
};
const vm = createApp(App).mount("#app");
</script>
fullName은 종속성으로 firstName, lastName을 갖고있다.
따라서 fullName을 변경하여도 아무일도 일어나지 않는다.
(readOnly라서 사실 의미가 없다)
또한 computed
내 메서드는 기본적으로 getter
만 지니고 있어 할당이 필요하다면 setter
를 직접 설정해주어야함.
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newName) {
const spliitedName = newName.split(" ");
[this.firstName, this.lastName] = spliitedName;
}
}
}
computed와 비슷하다.
참고로 메서드의 이름을 감시할 데이터와 일치시켜야함.
=> 메서드의 이름을 감시한다!
<div id="app">
<h1>{{ fullName }}</h1>
<h1>{{ firstName }}</h1>
<h1>{{ lastName }}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
firstName: 'Leon',
lastName: 'Miller'
}
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newName) {
const spliitedName = newName.split(" ");
[this.firstName, this.lastName] = spliitedName;
}
}
},
watch: {
firstName() {
console.log('watch:', this.firstName)
}
}
};
const vm = createApp(App).mount("#app");
</script>
감시할 데이터가 바뀐다면, 메서드가 실행된다.
computed와 뭐가 다른걸까? 공식문서에 따르면 이렇다.
가급적 computed를 사용하라.
다만 데이터 변경에 대한 응답으로 비동기식 또는 시간이 많이 소요되는 조작을 수행하려는 경우에 가장 유용하다.
=> 예를들면 비동기, 디바운스 작업 등!
<div id="app">
<h1>{{user.age}}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
user: {
name: "kim",
age: 22
}
}
},
watch: {
user(newValue, oldValue) {
console.log(newValue, oldValue)
}
}
};
const vm = createApp(App).mount("#app");
</script>
위의 코드는 user
데이터를 감시한다. 그때문에 user
자체를 바꾸어야만 감지메서드가 동작한다.
user
를 바꿨을땐 동작하지만, 내부 프로퍼티인user.age
를 바꿨을땐 동작하지 않는다.
데이터 내부를 감시하려면 어떻게해야할까?
...
watch: {
user: {
handler(newValue, oldValue) {
console.log(newValue, oldValue)
},
deep: true
}
}
바로 이렇게 데이터 내부 handler
라는 키워드에 메서드를 추가해주고 deep
이라는 키워드를 선언해준다.
=> 객체의 프로퍼티까지 감시하겠다는 의미다.
내부 프로퍼티를 바꾸면 감시메서드가 실행된다.
참고로 배열도 결국 객체니 똑같이 사용하면 된다.
새로고침(페이지 로드)됐을때도 핸들러를 바로 실행할 수 있게 만들수 도 있음.
deep
키워드처럼 immediate
키워드를 추가하면 됨.
<style>
.title {
font-size: 60px;
color: blue;
}
.active {
color: red
}
</style>
<div id="app">
<button @click="toggle">toggle</button>
<h1 class="title" :class="isActive ? 'active' : '' ">{{msg}}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
msg: " hello", isActive: false,
}
},
methods: {
toggle() {
this.isActive = !this.isActive;
}
}
}; const vm = createApp(App).mount("#app"); </script>
이런식으로 동적바인딩을 이용 할수도 있다.
삼항연산자를 조금 더 간단하게 만들 수도 있다.
<h1 class="title" :class="{active: isActive}">{{msg}}</h1>
isActive가 false면 바인드되지 않는다.
아예 클래스 이름을 데이터로 만들면, 객체 키-값 같을 시 생략가능한 기능을 이용할 수 도 있다.
<div id="app">
<button @click="toggle">toggle</button>
<h1 class="title" :class="{active}">{{msg}}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
msg: " hello", active: false,
}
},
methods: {
toggle() {
this.active = !this.active;
}
}
}; const vm = createApp(App).mount("#app"); </script>
{active}
는 {active:active}
와 같다.
<style>
.title {
font-size: 60px;
color: blue;
}
.active {
color: red
}
.color--orange {
color: orange
}
</style>
<div id="app">
<button @click="toggle">toggle</button>
<h1 class="title" :class="styleObject">{{msg}}</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
msg: " hello",
active: false,
small: true
}
},
computed: {
styleObject() {
return {
active: this.active,
'color--orange': this.small
}
}
},
methods: {
toggle() {
this.active = !this.active;
}
}
}; const vm = createApp(App).mount("#app"); </script>
data에 선언하지 않는 이유는 반응형으로 만들기 위해서이다.
<style>
.title--large {
color: gray;
}
.active {
color: red
}
</style>
<div id="app">
<h1 @click="changeTitle" :class="[actvie, title]">Hi bros</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
active: "active",
title: "title"
}
},
methods: {
changeTitle() {
this.title = 'title--large'
}
}
}; const vm = createApp(App).mount("#app"); </script>
참고로 클래스 내부의 배열 요소들에게 작은따옴표를 씌워주면 문자열로 취급된다.
['acitve']
=> "active"와 동일
클래스와 유사한 방식으로 사용하면된다.
하지만 인라인 스타일은 명시도가 아주 높으니 보통 권장하지않음.알아두기만 하자.
참고로 여러개 메소들를 바인딩할땐 "method1(); method2(); method3()"
이런식으로 바인딩 해주어야한다.
<style>
h1 {
border: 4px solid;
}
</style>
<div id="app">
<h1 @click="toBlue(); increaseWidth();" :style="{color, width:`${width}px`}">hello vue</h1>
</div>
<script>
const { createApp } = Vue;
const App = {
data() {
return {
color: "red",
width: 200
}
},
methods: {
toBlue() {
this.color = "blue"
},
increaseWidth() {
this.width += 10;
}
}
}; const vm = createApp(App).mount("#app"); </script>
이렇게 작성해도 되고 클래스처럼 데이터 내부에 객체 생성후 할당하여도 된다. 또한 computed
키워드를 이용하여도 좋다.
클래스 방식과 다른 게 없으니 생략함.
뷰가 리액트보다 더 간단해보인다. 아직 배운 게 없어서 착각인가 싶기도하고?
또한 Computed와 Watch를 보며 상태를 감시하는 기능에 대해 실마리를 얻었다.
나중에 Proxy도 써보면 좋을것 같음ㅎㅎ