https://www.inflearn.com/course/%ED%95%A8%EC%88%98%ED%98%95_ES6_%EC%9D%91%EC%9A%A9%ED%8E%B8f 를 수강하며 내용을 정리한 글입니다.
이미지 받아와서 화면에 뿌리기
서버에서 이미지를 받아와서 화면에 뿌려주는 코드 작성해본다.
const Images = {}
Images.fetch = () => new Promise(resolve => setTimeout(() => resolve([
{ name: "HEART", url: "https://s3.marpple.co/files/m2/t3/colored_images/45_1115570_1162087.png" },
{ name: "하트", url: "https://s3.marpple.co/f1/2019/1/1235206_1548918825999_78819.png" },
{ name: "2", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076769146_28397.png" }, { name: "6", url: "https://s3.marpple.co/f1/2018/1/1054966_1516076919028_64501.png"},{"name":"도넛","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918758054_55883.png"},{"name":"14","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077199329_75954.png"},{"name":"15","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077223857_39997.png"},{"name":"시계","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918485881_30787.png"},{"name":"돈","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918585512_77099.png"},{"name":"10","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077029665_73411.png"}
]), 200));
Images.tmpl = imgs => `
<div class="images">
${
_.map(img => `<div class="image">
<div class="box"><img src="${img.url}"></div>
<div class="name">${img.name}</div>
</div>`, imgs)
}
</div>
`;
Images.fetch 함수를 실행하면 서버로부터 데이터를 받아온다고 가정한다. 위의 Images.tmpl의 결과는 _.map의 결과로 배열이 출력될 것이므로 이를 합쳐줘야한다.
Images.tmpl = imgs => `
<div class="images">
${
string(_.map(img => `
<div class="image">
<div class="box"><img src="${img.url}"></div>
<div class="name">${img.name}</div>
</div>${'\n'}`, imgs))
}
</div>
`;
const string = iter => _.reduce((a,b) => `${a}${b}`, iter);
_.go(
Images.fetch(),
Images.tmpl,
console.log
)
엘리먼트로 만들어주는 함수와 body에 붙여주는 함수를 만든다.
$.qs = (sel, pa) => document.querySelector(sel, pa);
//$.qs = document.querySelector.bind(document);
$.append = _.curry((p, c) => p.appendChild(c));
$.el = html => {
const wrap = document.createElement('div');
wrap.innerHTML = html;
return wrap.children[0];
}
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
console.log
)
화면에 이쁘게 렌더링 된다. 이제는 삭제하는 것을 만들어본다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
$.findAll('.remove'),
console.log
)
findAll이라는 함수를 통해 remove 클래스를 가진 엘리먼트를 모두 찾아올 것이다. findAll은 아래와 같이 구현한다.
$.qs = (sel, pa = document) => document.querySelector(sel, pa);
$.qsa = (sel, pa = document) => document.querySelectorAll(sel, pa);
$.find = _.curry($.qs);
$.findAll = _.curry($.qsa);
qs와 qsa는 가변적인 인자를 받으므로 쿼리를 적용하기 위해 따로 find와 findAll 함수를 만든다. 이제 찾아온 remove 엘리먼트들에 이벤트를 달아줄 것이다.
$.closest = _.curry((sel, el) => el.closest(sel));
$.removeIt = el => el.parentNode.removeChild(el)
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
$.findAll('.remove'),
_.each(el => el.addEventListener('click', (e) => _.go(
e.currentTarget,
$.closest('.image'),
$.removeIt,
console.log
))),
console.log
)
그리고 자주사용될 법한 함수는 따로 빼서 만들어본다. _.each 함수를 한번 빼볼 것이다.
$.on = (event, f) => els => _.each(el => el.addEventListener(event, f), _.isIterable(els) ? els : [els]);
함수형을 통해 추상화 구현
우선 알림창을 만들어본다. 이미지에 삭제버튼을 눌렀을때 confirm 창과 삭제가 완료되었음을 알리는 alert 창을 만들것이다.
const UI = {};
UI.confirm = msg => new Promise(resolve => _.go(
//알림창 만드는 코드,
//버튼 클릭시에 resolve
))
async function f() {
await UI.confirm('??');
console.log('hi!');
}
f();
윈도우가 제공하는 기본 confirm 창처럼 확인 버튼을 눌러야 다음 로직으로 넘어가는 것을 구현하려면 위와 같이 커스텀 confirm 창을 프로미스로 만들고 마지막에 resolve를 해서 넘어가도록 구현한다. 세부적인 코드는 아래와 같다.
UI.confirm = msg => new Promise(resolve => _.go(
`
<div class="confirm">
<div class="body">
<div class="msg">${msg}</div>
<div class="button">
<button type="button" class="cancel">취소</button>
<button type="button" class="ok">확인</button>
</div>
</div>
</div>
`,
$.el,
$.append($.qs('body')),
_.tap(
$.find('.ok'),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.removeIt,
_ => resolve(true)
))),
_.tap(
$.find('.cancel'),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.removeIt,
_ => resolve(false)
))),
))
ok를 눌렀을 경우엔 프로미스가 true로 평가되고 cancel을 누르면 false로 평가된다. 이 함수는 기존의 go 함수에서 x 버튼을 클릭했을때 실행되도록 해야한다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
$.findAll('.remove'),
$.on('click', async e => {
if(await UI.confirm('?')) { // 내부에서 e = null
_.go(
e.currentTarget,
$.closest('.image'),
$.removeIt)
}
}
))
하지만 이는 문제가 발생한다. if문 내부에서 e가 null로 변해서 currentTarget을 찾을 수 없다는 에러가 나올 것이다. 이를 방지하기 위해 구조분해를 사용한다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
$.findAll('.remove'),
$.on('click', async ({currentTarget : ct}) => {
if(await UI.confirm('?')) {
_.go(
ct,
$.closest('.image'),
$.removeIt)
}
}))
이제 confirm창을 그대로 따와서 alert 창을 만들어보자
UI.alert = msg => new Promise(resolve => _.go(
`
<div class="confirm">
<div class="body">
<div class="msg">${msg}</div>
<div class="button">
<button type="button" class="ok">확인</button>
</div>
</div>
</div>
`,
$.el,
$.append($.qs('body')),
_.tap(
$.find('.ok'),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.removeIt,
_ => resolve(true)
)))
))
이렇게 둘 다 만들어놓고 보니 confirm과 alert 간에는 중복이 정말 많이 있다. 객체지향 프로그래밍에서 클래스를 상속받아 하위 클래스를 구현하는 것처럼 상위 클래스의 역할을 하는 message 라는 함수를 만들고 이 message 함수에 적절한 인자를 넘겨서 confirm과 alert가 동작하도록 할 것이다.
UI.message = (메세지, 버튼) => {}
UI.confirm = UI.messge(인자들 넘김)
UI.alert= UI.messge(인자들 넘김)
위와 같은 방식으로 클래스를 대신해서 함수로 추상화를 해낸 것이다. UI.message를 구현한 코드를 보면
UI.message = _.curry((btns, msg) => new Promise(resolve => _.go(
`
<div class="confirm">
<div class="body">
<div class="msg">${msg}</div>
<div class="buttons">
${_.strMap(btn => `
<button type="button" class="${btn.type}">${btn.name}</button>
`, btns)}
</div>
</div>
</div>
`,
$.el,
$.append($.qs('body')),
..._.map(btn => _.tap( //map 결과를 펼침
$.find(`.${btn.type}`),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.removeIt,
_ => resolve(btn.value)
))), btns)
)))
이렇게 추상화를 이루어냈다. 나머지 코드는 기존의 confirm과 비슷하고 아래에 ...map 하는 부분을 주목해볼만 하다.
해당 map의 결과는 btn에 클릭 이벤트를 달아주는 '함수' 들의 배열이다. 그러므로 전개연산자를 통해 펼쳐서 각 함수들이 순차적으로 go 함수의 내부에서 실행되도록 해야한다. 이를 좀 더 쉽게 풀어보면
UI.message = btns => _.go(
_.map(btn => _.tap(f), btns) // [_.tap(), _.tap(), _.tap(), _.tap(), ...]
)
//전개 연산자를 붙이면
UI.message = btns => _.go(
_.tap(f),
_.tap(f),
_.tap(f),
_.tap(f)
)
//_.tap 함수는 받은 인자를 내부의 로직을 수행한 후 그대로 다음 함수에 전달하는 함수이다.
'프론트엔드 > 자바스크립트' 카테고리의 다른 글
var를 쓰면 안되는 이유 (0) | 2021.08.19 |
---|---|
함수형 프로그래밍2 - 추상화 레벨 높이기 (0) | 2021.08.10 |
함수형 프로그래밍2 - 명령형에서 선언형으로 (0) | 2021.08.06 |
this 와 클로저 (0) | 2021.07.30 |
jQuery 엘리먼트 제어 기초 (0) | 2021.01.27 |