래거시 코드
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<button onclick="counter++; countDisplay()">증가</button>
<span id="counter-display">0</span>
<script>
var counter = 0;
function countDisplay() {
var el = document.getElementById('counter-display');
el.innerHTML = counter;
}
</script>
</body>
</html>
개선된 코드
index.html
<html>
<body>
<span id="counter-display"></span>
<button id="btn-increase">Increase</button>
<script src="ClickCounter.js"></script>
<script src="ClickCountView.js"></script>
<script>
(() => {
const clickCounter = App.ClickCounter();
const updateEl = document.querySelector("#counter-display");
const triggerEl = document.querySelector("#btn-increase");
const view = App.ClickCountView(clickCounter, {updateEl, triggerEl});
view.updateView();
})()
</script>
</body>
</html>
ClickCountView.js
var App = App || {}
App.ClickCountView = (clickCounter, options) => {
if (!clickCounter) throw Error(App.ClickCountView.message.noClickCounter);
if (!options.updateEl) throw Error(App.ClickCountView.message.noUpdateEl);
const view = {
updateView() {
options.updateEl.innerHTML = clickCounter.getValue();
},
increaseAndUpdateView() {
clickCounter.increase();
this.updateView();
}
}
options.triggerEl.addEventListener('click', () => {
view.increaseAndUpdateView();
})
return view;
}
App.ClickCountView.message = {
noClickCounter: 'clickCounter를 주입해야 합니다.',
noUpdateEl : 'updateEl을 주입해야 합니다.'
}
ClickCounter.js
var App = App || {}
App.ClickCounter = () => {
let value = 0;
return {
getValue() {
return value;
},
increase() {
value++;
},
}
}
UI에 완벽히 독립적이고 전역변수 사용을 없앴으며 재사용성이 좋게 만들었다.
확장시켜보자.
증가 버튼뿐만 아니라 감소버튼도 추가하거나 한번 클릭하면 2씩 증가하는 기능이 필요하다면?
세번째 스펙
"ClickCounter 모듈은 '데이터'를 주입 받는다"
Red
it('초기값을 주입하지 않으면 에러를 던진다', () => {
const actual = () => (counter = App.ClickCounter());
expect(actual).toThrowError();
})
beforeEach(() => {
data = {value : 0}
counter = App.ClickCounter(data);
});
Green
var App = App || {}
App.ClickCounter = _data => {
if(!_data) throw Error('_data');
const data = _data;
data.value = data.value || 0;
return {
getValue() {
return data.value;
},
increase() {
data.value++;
},
}
}
이외에 테스터를 조금씩 조작해주었다.
네번째 스펙
"ClickCounter 모듈의 increase 함수는 대체될 수 있다"
값을 올릴수도 내릴수도 있어야 한다.
이름변경: increase -> count
Red
describe('setCountFn()', () => {
it('인자로 함수를 넘기면 count()를 대체한다', () => {
const add2 = value => value + 2;
const expected = add2(data.value); // 함수 체이닝
counter.setCountFn(add2).count();
const actual = counter.getValue();
expect(actual).toBe(expected);
})
})
})
함수 체이닝 기법에 주목하자
Green
var App = App || {}
App.ClickCounter = _data => {
if(!_data) throw Error('_data');
const data = _data;
data.value = data.value || 0;
return {
getValue() {
return data.value;
},
count() {
data.value++;
},
setCountFn(fn){
this.count = () => (data.value = fn(data.value))
return this;
}
}
}
index.html
<html>
<body>
<button id="btn-desc">-</button>
<span id="counter-display"></span>
<button id="btn-inc">+</button>
<script src="ClickCounter.js"></script>
<script src="ClickCountView.js"></script>
<script>
(() => {
const data = { value : 0 }
const counterDesc = App.ClickCounter(data).setCountFn(v => v - 1)
const counterInc = App.ClickCounter(data)
const updateEl = document.querySelector("#counter-display");
const btnDesc= document.querySelector("#btn-desc");
const btnInc= document.querySelector("#btn-inc");
const descCounterView = App.ClickCountView(counterDesc, {updateEl, triggerEl: btnDesc});
const incCounterView = App.ClickCountView(counterInc, {updateEl, triggerEl: btnInc});
descCounterView.updateView();
})()
</script>
</body>
</html>
Reference
'개발 > JavaScript' 카테고리의 다른 글
[Javascript] Method Chaining 메서드 체이닝 (0) | 2022.04.12 |
---|---|
[Javascript] 이벤트 위임(Event delegation) (0) | 2022.04.11 |
[Vanilla Javascript] Jasmine을 이용해 테스트 코드 작성해보기 -2 (0) | 2022.04.10 |
[Vanilla Javascript] Jasmine을 이용해 테스트 코드 작성해보기 -1 (0) | 2022.04.10 |
[Javascript] Closure 클로저 (0) | 2022.03.30 |