cover

computed 속성은 다른 데이터 값을 변형해서 사용하도록 하고 있습니다. 오로지 값을 바꾸는 것에 집중하고 있기 때문에 DOM 변형하거나 HTTP 요청 등은 다른 작업을 하지않는 것을 권장합니다. 하지만 값이 변함에 따라 특정 동작을 하길 원할 수도 있습니다. 이 때 사용하는 컴포넌트 속성, watch에 대해 알아보겠습니다.


watch

watch 문법

watch 속성도 computed와 마찬가지로 함수를 만들지만, 값을 리턴하지 않습니다.

javascript
      watch: {
	함수명(새로운 값, 이전 값) {
		// ...
	}
}
    

여기서 중요한 특징은 함수명은 변화를 감지할 컴포넌트 데이터입니다. 만약 해당 값이 바뀌면 그 함수가 실행됩니다.

javascript
      export default {
	data() {
		return {
			question: "",
		}
	},
	watch: {
		question(newVal, oldVal) {
			// question 값이 바뀔 때 실행
		}
	}
}
    

watch 중첩된 값 deep

그런데 단순한 값이 아니라 객체의 내부 값이 바뀌는 것일 수도 있습니다. 이 경우 함수명을 객체에 접근하는 방식으로 문자열을 만듭니다.

javascript
      export default {
	data() {
		return {
			obj: {
				inner: {
					count: 0,
				}
			},
		}
	},
	watch: {
		'obj.inner.count'(newVal, oldVal) {
			// obj의 inner의 count가 바뀔 때 실행
		},
		obj() {
			// count가 바뀌어도 실행되지 않음
		}
	},
}
    

obj의 내부에 있는 값이니 당연히 obj()함수도 실행될 것 같지만 그렇지 않습니다. 만약 내부 값들에 대한 변화도 모두 감지하고 싶다면 함수가 아닌 객체로 만들고handlerdeep으로 분리해서 설정하면 됩니다.

javascript
      export default {
  watch: {
    obj: {
      handler(newVal, oldVal) {
        // 처리할 내용
      },
      deep: true // 내부 중첩된 값도 변화 감지
    }
  }
}
    
warn
      객체의 모든 값이 변했는지 확인하기 때문에 거대한 데이터일 수록 성능 이슈가 발생할 수 있습니다.
    

handler는 이전에 계속 다루었던 처리 함수를 나타낸 것입니다. 이렇게 watch에서는 함수 뿐만 아니라 객체의 형태로 만들 수도 있습니다. 그 객체 안에서 사용하는 몇 가지 값이 있습니다.

handler (Function): 처리할 내용
deep (Boolean): 해당 값의 내부 값도 변화를 감지할지 결정합니다.
immediate (Boolean): 처음에 한 번 실행할지 결정합니다.
flush (String): DOM이 업데이트되고 난 뒤 실행하게 합니다. (pre, post, sync)
javascript
      export default {
	watch: {
		데이터값: {
			handler(newVal, oldVal) {
				// ...
			},
			deep: false,
			immediate: false,
			flush: false
		}
	}
}
    

나머지 immediate와 flush도 자세히 알아보겠습니다.

watch 처음에 한 번 immediate

watch로 만든 handler 메소드들은 처음엔 값이 변하지 않기 때문에 실행되지 않습니다. 그런데 값이 변하기 전 처음에 무조건 한 번 실행되길 바라는 경우가 있습니다. 이 때 immediate 값을 true로 바꿔주면 됩니다.

javascript
      export default {
  watch: {
    obj: {
      handler(newQuestion) {
				// 처리 내용
      },
      // 처음에 한 번 실행합니다.
      immediate: true
    }
  }
}
    

예를 들면, 언어를 바꿀 때마다 해당 언어의 데이터를 불러오는 기능을 만들었는데 처음에는 값이 바뀌지 않으니 실행되지 않습니다.

javascript
      export default {
	data() {
		return {
			lang: 'kr'
		}
	},
	watch: {
		lang(n) {
			// 처음에는 fetch를 하지 않음
			fetch(`...${n}...`).then(...)
		}
	}
}
    

immediate를 사용하면 값이 변하기 전에 실행됩니다.

javascript
      export default {
	data() {
		return {
			lang: 'kr'
		}
	},
	watch: {
		lang: {
			handler(n) {
				fetch(`...${n}...`).then(...)
			},
			immediate: true // 값이 바뀌는 것과 관계없이 처음에 실행
		}
	}
}
    

watch flush

데이터가 변경되었을 때, DOM 업데이트와 watch에서 감지하는 것 중 어떤 게 먼저 실행될까요? 답은 watch가 먼저 감지되고 DOM이 업데이트 됩니다. 그래서 만약 watch에서 DOM을 읽거나 업데이트할 때 문제가 생길 수 있습니다.

예를 들어, 버튼을 클릭하면 고세구라고 글자가 바뀌고 뒤에 킹아 라고 문자열을 붙이고 싶다고 하겠습니다.

html
      <template>
	<button @click="msg = '고세구'">{{ msg }}</button>
</template>
    
info
      @click은 Vue에서 사용하는 디렉티브 중에 하나로, 태그에 대한 이벤트처리를 할 수 있도록 도와줍니다.
    
javascript
      export default {
  data() {
    return {
      msg: "안녕하세요",
    };
  },
  watch: {
    msg(n) {
      document.querySelector("button").innerText = n + " 킹아";
    },
  },
}
    

그냥 고세구라는 글자가 나타날겁니다.

1. watch에서 고세구 킹아라고 DOM을 업데이트
2. msg 값 고세구를 읽고 DOM 렌더링

그래서 DOM이 업데이트 된 뒤에 watch의 메소드가 실행되길 바란다면 flush: 'post'로 설정해주시면 됩니다.

javascript
      watch: {
  msg: {
		handler(n): {
			document.querySelector("button").innerText = n + " 킹아";
		},
		flush: 'post'
  },
},
    

기본값은 pre, 순서를 바꾼 post가 있습니다. 하지만 둘 다 싫고 값이 바뀌는 즉시 실행되길 바랄 때가 있는데 이 경우 sync를 사용하면 됩니다. 여기서 즉시라는 것은 값이 바뀌고 다음 줄이 실행되지 않고 watch handler 함수가 먼저 실행됩니다.

javascript
      click() {
	this.msg = "아이네";
	// watch 함수 (msg) 먼저 실행
	this.msg = "릴파";
	// watch 함수 (msg) 먼저 실행
}
    

this.$watch

watch는 해당 변수가 바뀔 때마다 실행되는 메소드들을 정의하기 때문에 항상 실행되면, 성능 이슈가 발생할 수 있습니다. 그래서 상황에 따라 생성하거나 제거하고 싶을 수 있습니다. 그 때 사용하는 것이 컴포넌트의 $watch 함수입니다.

javascript
      export default {
	// 컴포넌트가 만들어졌을 때 watch 메소드를 만들고 싶은 경우
  created() {
    this.$watch('question', (newQuestion) => {
      // ...
    })
  }
}
    
info
      created는 컴포넌트의 라이프사이클 훅 중 하나로 컴포넌트가 생성되었을 때 실행되는 함수입니다.
    

보통 컴포넌트가 마운트 해제되면 watch 메소드들도 같이 멈추어서 대부분의 경우 신경 안 써도 됩니다. 하지만 굳이 멈추고 싶다면 $watch 함수가 반환한 함수를 실행해주세요.

javascript
      const unwatch = this.$watch('foo', ()=>{ ... })

// watch 더이상 안쓰겠다!!
unwatch()
    

마무리하며

computedwatch는 둘 다 변수의 변화를 감지하고 실행되는 메소드들을 정의하는 컴포넌트 속성들입니다. 다만 다른 점이 있습니다.

computed: 데이터 변화에 따른 새로운 데이터를 만든다.
watch: 데이터가 변화할 때마다 특정 동작을 한다.

물론 함수 안의 내용은 얼마든지 자유롭게 쓸 수 있지만, Vue에서 의도한 대로 코드를 구현하는게…좋겠죠?

이번 글에서 created라는 속성과 디렉티브에 대해 슬쩍 알게되었는데요. 다음 글부터는 디렉티브에 대해 다루고 그 다음 라이프사이클 훅에 대해 다뤄보겠습니다.