5 할 일 관리 앱 - 완료 기능 구현
source: categories/study/vue-beginner-lv5/vue-beginner-lv5_5.md
5.1 할 일 완료 기능 마크업 및 스타일링
5.1.1 vue에 scss 기능 추가하기
yarn add -D sass sass-loader@10
sass-loader
10 버전을 설치하는 이유는 vue 2.x
에선 11 이상의 sass-loader
가 호환되지 않아서이다.
위와 같이 설치하면 자동으로 vue
에서 scss
를 인식한다.
webpack.config.js
에 이미 다 설정되어있기 때문.
git diff
5.2 할 일 목록의 데이터 타입 정의 및 할 일 추가 기능에 적용
- 현재
todoItems
각 요소는String
- 이 요소들을
Object
로..name
,status
,id
이렇게 3가지 값을 가지고 있는 객체 형태로..
- 이 요소들을
git diff
vue.runtime.esm.js?2b0e:619 [Vue warn]: Invalid prop: type check failed for prop "todoItem". Expected String, got Object
found in
---> <TodoListItem> at src/components/TodoListItem.vue
<App> at src/App.vue
<Root>
위 에러 해결은 다음 시간에..
5.3 props 타입 정의 방법
git diff
5.4 할 일 완료 기능 구현
git diff
5.5 할 일 완료 클래스 바인딩 및 computed 타입 정의 방식 안내
FE CONF 2019
5.5.1 목차
- 반응성 - 왜 내 화면은 다시 그려지지 않는 걸까?
- DOM 조작 - 오래된 습관 버리기
- 라이프사이클 - 나는 인스턴스를 얼마나 알고 있나?
- ref 속성 - 만들다가만 ref 속성
- computed 속성 - 간결한 템플릿의 완성
5.5.2 발표 대상
- Vue.js의 기본 문법을 알고 있는 웹 개발자
- Vue.js로 막 서비스 개발을 시작한 주니어 개발자
- Vue.js로 서비스를 제작중인 웹 개발자
- Vue.js의 특징이 궁금한 프론트엔드 개발자
"Vue.js를 소개하는 발표가 아닙니다"
5.5.3 기대 효과
PR 보냈을 때 사수(시니어)한테 칭찬 받기
5.5.4 발표 자료 및 예제 소스는 아래 링크에서 확인
https://github.com/joshua1988/vue-five-common-mistakes
5.5.5 반응성
- 반응성
- 왜 내 화면은 다시 그려지지 않는 걸까?
5.5.5.1 뷰의 반응성이란
- 데이터의 변화에 따라 화면이 다시 그려지는 뷰의 성질
var vm = new Vue({
data: {
count: 0,
}
})
vm.count += 1; // count 값이 증가하면 화면에 표시된 count도 증가
5.5.5.2 반응성은 언제 설정될까?
- 인스턴스가 생성될 때
data
의 속성들을 초기화 : 뷰 라이브러리 동작- new Vue() : 인스턴스 생성
- Init Events & LifeCycle
- beforeCreate
- Init injections & reactivity
- created - 여기부터 반응성이 주입된 데이터 속성들을 다루실 수 있음
5.5.5.3 반응성에 대해 알아야 할 점
- 생성하는 시점에 없었던
data
는 반응하지 않는다.
var vm = new Vue({
data: {
user: {
name: 'Captain',
}
}
})
vm.user.age += 1; // age 값이 변하더라도 화면은 갱신되지 않음
5.5.5.4 반응성을 이해하지 못했을 때의 실수 1
- 화면에서만 필요한 UI 상태 값을 다룰 때
- 체크박스
- 라디오 버튼
- 예시 코드
export default {
data() {
return {
users: [],
}
},
methods: {
fetchUsers() {
fetch('https://jsonplaceholder.typicode.com/users')
.then(response => response.json())
.then(data => {
this.users = data;
this.$set(this.users[0], 'checked', false);
})
.catch(error => console.log(error));
},
}
}
- REST로 받아온 유저 목록 10개
- 유저 목록 중 첫번째 유저를 체크하는 함수를 만들고 그 버튼을 만듦
- 버튼을 클릭하면 유저 목록 중 첫번째 유저를 체크 (
checked = true
) - 분명 자바스크립트 상으론
checked
가true
로 되었지만 화면에선 갱신이 안됨 - 그 반대로
checked = false
도 화면 갱신이 안됨 -
vue developer 확장 프로그램으로 봐도 해당
data
는true
,false
잘 바뀌지만 화면엔 갱신이 안됨 - 이런 이슈 해결 방법
this.$set(this.users[0], 'checked', false)
- root 수준의 data는
this.$set()
으로 해도 안되지만, root 수준의 data 안에 이렇게 정의를 하면 반응성이 적용된다.
- 위와 같이 수정하면
checked
값 변화에 따라 화면이 잘 갱신된다.
이런 UI 상태 값은 보통 DB에서 관리 안하고 화면에서 관리해줘야하는 값들이라 이런 실수를 종종 내곤한다.
5.5.5.5 반응성을 이해하지 못했을 때의 실수 2
- 백엔드에서 불러온 데이터에 임의 값을 추가하여 사용하는 경우
const user = {
address: {
},
company: {
},
email: 'Sincere@april.biz',
id: 1,
name: 'Leanne Graham',
phone: '1-770-736-8031 x56442',
username: 'Bret',
website: 'hildegard.org',
}
- 위 코드에
region
이라는 새로운 프로퍼티를 추가해서 화면과 연동region
이란 값은 자바스크립트 상에서 잘 바뀌나 화면에 갱신되지 않음- 해당 프로퍼티들에
reactivity
가 주입되었는지 아닌지 확인하는 방법reactivity
가 주입되어 있다면get
,set
이 설정되어 있음reactivity
가 주임 안된region
은get
,set
이 설정되어 있지 않다.
- 이슈 해결 방법
- 아까와 같이
this.$set()
함수 이용 - 또는 팩토리 펑션 비슷하게 속성을 반환해주는 함수를 만들어놓고 호출하거나
- 아니면
data
속성 안에 일일이 지정해놓고 사용하거나
- 아까와 같이
5.5.5.6 [참고] 뷰엑스의 state도 data와 동일하게 취급
state: {
user: { name: 'Captain' }
},
mutations: {
// 생성하는 시점에 없었던 데이터는 반응성이 없음
setUserAge: function(state) { state.user.age = 23; }, // x
// 객체 속성을 임의로 추가 또는 삭제하는 경우 뷰에서 감지하지 못함
deleteName: function(state) { delete state.user.name; } // x
}
5.5.5.7 뷰 3.0에서는 괜찮아요
Object.defineProperty()
에서Proxy
기반으로 변화
var obj = {};
// Vue 2.x
Object.defineProperty(obj, 'str', { ... });
// Vue 3.x
new Proxy(obj, { ... });
Vue framework reactivity
의 한계가 사실 프레임워크 상의 한계가 아니라..
사실 Object
에도 Object.observe()
라고 하는 API가 있는데.. 이게 deprecated
되면서 defineProperty
밖에 할 수가 없었다. 반응성 주입할 때.
하지만 Vue 3.x
에서는 Proxy
베이스로 가기 때문에 데이터에 어떤 속성이 동적으로 추가/삭제되는 거에 대해 다 감지할 수 있게 된다.
5.5.6 DOM 조작
-
오래된 습관 버리기
-
(기존) 화면 조작을 위한 DOM 요소 제어 방법
- 특정
DOM
을검색
해서 제어하는 방법
- 특정
// 네이티브 자바스크립트
document.querySelector('#app');
// 제이쿼리 라이브러리
$('#app');
- 사용자의 입력 이벤트를 기반으로 한 DOM 요소 제어
// 버튼 요소 검색
var btn = document.querySelector('#btn');
// 사용자의 클릭 이벤트를 기반으로 가장 가까운 태그 요소를 찾아 제거
btn.addEventListener('click', function(event) {
event.target.closest('.tag1').remove();
})
- 이런식의 사고방식이 잘못되었다기 보단
- 이런거에 너무 익숙해져서 Vue를 100% 활용을 못한다는 느낌
- 이런거를 Vue에선 어떻게 제어할 수 있을까?
5.5.6.1 (Vue.js 방식) ref 속성을 활용한 DOM 요소 제어
- 뷰에서 제공하는 ref 속성
<!-- HTML 태그에 ref 속성 추가 -->
<div ref="hello">Hello Ref</div>
<script>
// 인스턴스에서 접근 가능한 ref 속성
this.$refs.hello; // div 엘리먼트 정보
</script>
-
이런
ref
속성은 뷰 뿐만 아니라 리액트, 앵귤러에서도 많이 사용함 -
뷰 디렉티브에서 제공되는 정보를 최대한 활용
- 뷰 디렉티브에서 제공하는 속성들을 충분히 활용하면 대부분의 경우 커버 가능
<ul>
<li v-for="(item, index) in items">
<span v-bind:id="index">{{ item }}</span>
</li>
</ul>
5.5.6.2 DOM 제어 사고 전환이 필요한 실제 사례
<ul>
<li @click="removeItem">
<span>메뉴 1</span>
<div class="child hide">메뉴 설명</div>
</li>
<li @click="removeItem">
<span>메뉴 2</span>
<div class="child hide">메뉴 설명</div>
</li>
<!-- ... -->
</ul>
<script>
export default {
methods: {
removeItem(event) {
event.target.lastChild.classList.toggle('hide');
}
}
}
</script>
- 위 코드 수정
<ul>
<li v-for="item in items" @click="removeItem">
<span>{{ item }}</span>
<div class="child hide">메뉴 설명</div>
</li>
</ul>
<script>
export default {
data() {
return {
items: ['메뉴 1', '메뉴 2', '메뉴 3'],
}
},
methods: {
removeItem(event) {
event.target.lastChild.classList.toggle('hide');
}
}
}
</script>
ref
속성 적용
<ul>
<li v-for="(item, index) in items" @click="removeItem(index)">
<span>{{ item }}</span>
<div class="child hide" ref="listItem">메뉴 설명</div>
</li>
</ul>
<script>
export default {
data() {
return {
items: ['메뉴 1', '메뉴 2', '메뉴 3'],
}
},
methods: {
removeItem(index) {
this.$refs.listItem[index].classList.toggle('hide');
}
}
}
</script>
5.5.7 인스턴스 라이프 사이클
- 나는 인스턴스를 얼마나 알고 있나?
5.5.7.1 인스턴스 라이프 사이클이란?
- 뷰 인스턴스가 생성되고 소멸되기까지의 생애 주기
- new Vue(): 인스턴스 생성
- 이벤트 및 라이프사이클 초기화
- beforeCreate
- 화면에 반응성 주입
- created
- el, template 속성 확인
- tempalte 속성 내용을 render로 변환
- beforeMount
- vm.$el 생성 후 el 속성 값을 대입
- mounted
- 인스턴스를 화면에 부착
- 인스턴스의 데이터 변경
- beforeUpdate
- 화면 재 렌더링 및 데이터 갱신
- updated
- 인스턴스 내용 갱신
- 인스턴스 접근 가능
- beforeDestroy
- 컴포넌트, 인스턴스, 디렉트브 등 모두 해제
- destroyed
- 인스턴스 소멸
5.5.7.2 뷰의 template 속성
- 뷰의 템플릿 속성에 대해 이해하고 있으면 좋다.
- 왜냐하면 템플릿 속성이랑 뷰 인스턴스 라이프 사이클이랑 밀접한 관계가 있기 때문
- 템플릿 속성이란 인스턴스, 컴포넌트의 표현부를 정의하는 속성
<-- 싱글 파일 컴포넌트 -->
<template>
<div>{{ str }}</div>
</template>
--- 위 방식으로 정의하든 아래 방식으로 정의하든 같은 template 속성이다.
--- 정의하는 방법만 다를 뿐 같은 template 속성이다.
<script>
// 인스턴스 옵션 속성
new Vue({
data: { str: 'Hello World' },
template: '<div>{{ str }}</div>,
});
</script>
여튼 template
속성은 어떤 역할을 하는가?
5.5.7.3 뷰 template 속성의 정체
- 실제 DOM 엘리먼트가 아니라
Virtual DOM(자바스크립트 객체)
<!-- 사용자가 작성한 코드 -->
<template>
<div>{{ str }}</div>
</template>
위와 같이 자성된 코드에서 template
태그는 실제 DOM
엘리먼트가 아니다.
위는 뷰 라이브러리에서 아래처럼 변환이 된다.
// 라이브러리 내부적으로 변환한 모습
function render() {
with(this) {
return _c('div', [_v(_s(str))]);
}
}
- 구글에
vue template explorer
라고 검색하시면 아래와 같이template
태그가 어떻게 변환되는지를 볼 수 있다. - vue template explorer
5.5.7.4 template 속성이 실제로 유효한 시점
- template 속성이 실제로 유효한 시점에 대해서도 제대로 이해하고 계셔야합니다.
- 인스턴스가 화면에 부착(
mounted
)되고 난 후 mounted
가 되고 난 후template
에 작성한 태그(요소)에 접근 가능
- 인스턴스가 화면에 부착(
…
- beforeMount
- Create vm.$el and replace el with it
- mounted - 여기부터 작성한 태그(요소)에 접근 가능 …
5.5.7.5 인스턴스 부착 시점을 이해하지 못한 사례
<!-- 템플릿 속성 -->
<template>
<canvas id="myChart"></canvas>
</template>
<script>
// 인스턴스 옵션
new Vue({
created: function() {
var ctx = document.querySelector('#myChart'); // null
new Chart(ctx, chartOptions);
}
})
</script>
- 위과 같이 적용 후 안되다 보니..
nextTick
이란 걸 검색해서 발견 후 적용하게됨mounted
에서 확인할 수 있는 것은mounted
에서..nextTick
은 최후의 수단
<!-- 템플릿 속성 -->
<template>
<canvas id="myChart"></canvas>
</template>
<script>
// 인스턴스 옵션
new Vue({
created: function() {
this.$nextTick(function() { // 업데이트 시점 혼란 야기 및 코드 복잡도 증가
var ctx = document.querySelector('#myChart');
new Chart(ctx, chartOptions);
})
}
})
</script>
- 적절한 수정 방법
<!-- 템플릿 속성 -->
<template>
<canvas id="myChart"></canvas>
</template>
<script>
// 인스턴스 옵션
new Vue({
mounted: function() {
var ctx = document.querySelector('#myChart');
new Chart(ctx, chartOptions);
}
})
</script>
5.5.8 ref 속성
- 만들다가 만 ref 속성
- 규칙이 조금 존재함
- 알고있으면 좋을만한 규칙들
5.5.8.1 ref 속성이란?
- 특정
DOM
엘리먼트나 하위컴포넌트
를 가리키기 위해 사용 DOM 엘리먼트
에 사용하는 경우DOM 정보
를 접근하위 컴포넌트(커스텀 컴포넌트)
에 지정하는 경우 컴포넌트인스턴스 정보
접근v-for
디렉티브에 사용하는 경우Array
형태로 정보 제공
특정 DOM
요소를 조작하고 싶을 때 사용하는 속성
5.5.8.2 ref 속성 사용할 때 주의할 점 1
- ref 속성은
template 코드
를render 함수
로 변환하고 나서 생성 - 접근할 수 있는 최초의 시점은
mounted
라이프사이클 훅
<p ref="pTag">Hello</p>
<script>
created: function() {
this.$refs.pTag; // undefined
},
mounted: function() {
this.$refs.pTag; // <p>Hello</p>
}
</script>
5.5.8.3 ref 속성 사용할 때 주의할 점 2
v-if
디렉티브와 사용하는 경우 화면에 해당 영역이 그려지기 전까진DOM
요소 접근 불가- 간단한 해결 방법
v-show
활용
<div v-if="isUser">
<p ref="w3c">W3C</p>
</div>
<script>
new Vue({
data: { isUser: false, },
mounted: function() {
this.$refs.wrc; // undefined
}
})
</script>
5.5.8.4 ref 속성 사용할 때 주의할 점 3
- 하위 컴포넌트의 내용을 접근할 순 있지만 남용하면 안된다
- 아래 방법은 지양하는 것이 좋은게
- 컴포넌트간 데이터 통신 방식
props
를 내리고event emit
으로 통신 - 아래 처럼 접근하게되면 나중에 디버깅할 때 어려워짐
- 컴포넌트간 데이터 통신 방식
<div id="app">
<TodoList ref="list"></TodoList>
</div>
<script>
new Vue({
el: '#app',
methods: {
// 상위 컴포넌트에서 불필요하게 하위 컴포넌트를 제어하는 코드
fetchItems: function () {
this.$refs.list.fetchTodos();
}
}
})
</script>
- 특히 위와 같은 코드는 하위 컴포넌트인
TodoList
의 라이프사이클 훅을 활용해 해결 가능함
// 하위 컴포넌트인 TodoList에서 라이프 사이클 훅을 활용해 해결 가능
var TodoList = {
created: function() {
this.fetchTodos();
},
methods: {
fetchTodos: function() { ... }
},
}
이렇게 커스텀 컴포넌트에 ref
속성을 적용하는 경우는
써드 파티 라이브러리를 사용하실 때, 그 라이브러리에서 생성된 컴포넌트에 접근하실 때 사용하시는 것이 더 좋음.
5.5.9 computed 속성
- 간결한 템플릿의 완성
5.5.9.1 computed 속성이란?
- 간결하고 직관적인 템플릿 표현식을 위해 뷰에서 제공하는 속성
<p>{{ 'hello' + str + '!!' }}</p><!-- 템플릿 표현식만 이용하는 경우 -->
<p>{{ greetingStr }}</p><!-- computed 속성을 활용하는 경우 -->
<script>
new Vue({
data: { str: 'world', },
computed: {
greetingStr: function () {
return 'hello' + this.str + '!!';
}
}
})
</script>
5.5.9.2 computed 속성 활용처 1
- 조건에 따라 HTML 클래스를 추가, 변경할 때
<li :class="{ disalbed: isLastPage }"></li>
<script>
computed: {
isLastPage: function () {
var lastPageCondition = this.paginationInfo.current_page >= this.paginationInfo.last_page;
var nothingFetched = Object.keys(this.paginationInfo).length === 0;
return lastPageCondition || nothingFetched;
}
}
</script>
- computed return 값을 아예 Object로..
<li :class="listItemClass"></li>
<script>
computed: {
listItemClass: function () {
// ...
}
}
</script>
5.5.9.3 computed 속성 활용처 2
- 스토어(Vuex)의 state 값을 접근할 때
<div>
<p>{{ this.$store.state.module1.str }}</p>
<p>{{ module1Str }}</p>
</div>
<script>
new Vue({
computed: {
moduleStr: function () {
return this.$store.state.module1.str;
}
}
})
</script>
5.5.9.4 computed 속성 활용처 3
Vue i18n
과 같은다국어 라이브러리
에도 활용 가능
<p>{{ 'userPage.common.filter.input.label' }}</p>
<p>{{ inputLabel }}</p>
<script>
computed: {
inputLabel: function () {
return $t('userPage.common.filter.input.label');
}
}
</script>
- 특정 컴포넌트에
computed
가 많아질 경우, 중간에 레이어 하나(컴포넌트)를 더 줘서props
를 내려서 관리하는.. - 그런 식으로도 코드를 줄일 수 있을 것이다.
다시 돌아와서…
이제 브라우저 콘솔창을 안봐도 해당 데이터의 타입을 알 수 있다.
위와 같이 에디터에서 바로바로 어떤 속성 및 데이터 타입은 어떤지 바로 알려준다.
위와 같은 기능들이 타입스크립트를 썼기 때문에 에디터에서 제공하는 인텔리 센스라는 기능이다.