[ES6+: 응용] 프런트엔드에서 활용하기 (1) : https://opentogether.tistory.com/82
클래스 대신 함수로 하는 추상화
앞 챕터에서 배웠던 코드는 중복이 많았는데, 이를 줄여나가는 추상화를 시켜보자.
먼저, Ui.message를 "확인, 취소"창뿐 아니라, "경고"창도 코드 하나로 구현되게끔 해주자.
const Ui = {};
Ui.message = ({msg, btns}) => 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(
$.find(`.${btn.type}`),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.remove,
_ => resolve(btn.value)
))), btns)
));
/* 확인 창 */
Ui.confirm = msg => Ui.message({
msg,
btns: [
{name: '취소', type: 'cancel', value: false},
{name: '확인', type: 'ok', value: true}
]
});
/* 경고 창 */
Ui.alert = msg => Ui.message({
msg,
btns: [
{name: '확인', type: 'ok', value: true}
]
});
다음과 같은 표현도 가능하다.
const Ui = {};
Ui.message = (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(
$.find(`.${btn.type}`),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.remove,
_ => resolve(btn.value)
))), btns)
));
Ui.confirm = msg => Ui.message(
[{name: '취소', type: 'cancel', value: false},
{name: '확인', type: 'ok', value: true}],
msg);
Ui.alert = msg => Ui.message(
[{name: '확인', type: 'ok', value: true}], msg);
또 이것을 curry를 통해 더 재밌는 표현도 가능하다.
const Ui = {};
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(
$.find(`.${btn.type}`),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.remove,
_ => resolve(btn.value)
))), btns)
)));
Ui.confirm = Ui.message(
[{name: '취소', type: 'cancel', value: false},
{name: '확인', type: 'ok', value: true}
]);
Ui.alert = Ui.message(
[{name: '확인', type: 'ok', value: true}
]);
이미지 동시성 다루기
css코드
<style>
.fade {
opacity: 0;
}
.fade-in {
opacity: 1;
transition: opacity 0.3s;
}
</style>
이미지를 부를 때, 순서대로 불러오거나, 한 번에 불러오지 않고, 각 이미지마다 로딩되는 대로 따로 불러올 것이다. 이를 미리 이미지를 한 번에 대기시켜놓고, 로딩이 되면 한번에 불러오게 하는 이미지 동시성을 다뤄 볼 것이다.
Images.tmpl = imgs => `
<div class ="images">
${_.strMap( img => `
<div class="image">
<div class="box"><img src="" lazy-src="${img.url}" class="fade" alt=""></div>
<div class="name">${img.name}</div>
<div class="remove">×</div>
</div>
`, imgs)}
</div>
`;
위와 같이 "src"는 빼고, "lazy-src"라고 만들어주고, 향후에 다룰 것이다. 그리고 이처럼 해주면 일단 이미지는 나오지 않을 것이다.
이제 이미지를 로드할 때, tap함수를 통해 이미지 로드를 관리해보자.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img); /* onload가 일어난 후에 resolve */
img.src = img.getAttribute('lazy-src'); /* lazy-src 값을 src로 */
})),
console.log
),
...
/*
▼mapL {<suspended>}
▶__proto__: Generator
[[GeneratorLocation]]: fx.js:451
[[GeneratorStatus]]: "suspended"
▶[[GeneratorFunction]]: ƒ* mapL(f, iter)
[[GeneratorReceiver]]: undefined
▶[[Scopes]]: Scopes[4]
*/
이렇게 콘솔을 찍어보면, 지연적으로 평가할 준비가 된 상태가 되었음을 볼 수 있다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img); /* onload가 일어난 후에 resolve */
img.src = img.getAttribute('lazy-src'); /* lazy-src 값을 src로 */
})),
_.each(img => img.classList.add('fade-in')) /*로딩이 끝날때 해당 구문이 실행*/
),
...
이제 each를 통해 이미지를 "fade-in [투명도 제거]"해보면, 첫 이미지부터 순차적으로 이미지가 보인다. 만약 이미지 전체가 동시에 로딩되는 모습을 보고 싶다면, each 이전에 C.takeAll을 해준다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img); /* onload가 일어난 후에 resolve */
img.src = img.getAttribute('lazy-src'); /* lazy-src 값을 src로 */
})),
C.takeAll, /* 모든걸 한번에 받아서 each로 넘김 */
_.each(img => img.classList.add('fade-in')) /*로딩이 끝날때 해당 구문이 실행*/
),
...
이 역시도 curry를 통해 코드를 정리해주자.
$.addClass = _.curry((name, el) => el.classList.add(name));
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img); /* onload가 일어난 후에 resolve */
img.src = img.getAttribute('lazy-src'); /* lazy-src 값을 src로 */
})),
C.takeAll, /* 모든걸 한번에 받아서 each로 넘김 */
_.each($.addClass('fade-in')) /*로딩이 끝날때 해당 구문이 실행*/
),
$.findAll('.remove'),
$.on('click', async ({currentTarget}) => {
if(await Ui.confirm('정말 삭제하시겠습니까?')){
_.go(
currentTarget,
$.closest('.image'),
$.remove);
}
}));
동시성 부하 조절
이미지를 동시에 부를 때 부하를 조절해서 더 자연스러운 화면을 만들어볼 것이다.
먼저 groupBy 함수를 보자.
_.groupBy(a => Math.floor(a / 4), [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
/*
▼{0: Array(4), 1: Array(4), 2: Array(3)}
▶0: (4) [0, 1, 2, 3]
▶1: (4) [4, 5, 6, 7]
▶2: (3) [8, 9, 10]
▶__proto__: Object
*/
groupBy함수는 첫 인자로 온 함수로 그룹을 지어, 두 번째 인자 배열을 각 그룹 배열을 만들어 준다. 여기서 Math.floor은 다음과 같은 동작을 한다.
Math.floor(1/4)
// 0
Math.floor(4/4)
// 1
Math.floor(6/4)
// 1
Math.floor(8/4)
// 2
Math.floor(10/4)
// 2
즉 위와 같은 방식으로 동시에 평가해야 할 것들을 그룹을 지어서, 그 그룹으로 평가하면 부하를 줄일 수 있다.
_.go(
...
lazy => {
let r = L.range(Infinity); /* 0부터 증가하는 값이 필요함 */
return _.go(
lazy,
_.groupBy(_ => Math.floor(r.next().value / 4)),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll),
_.each(_.each($.addClass('fade-in')))
)
}
),
...
이렇게 하면, 4개씩 로딩이 될 때마다, 보여준다. 그리고 이걸 다음과 같이 정리해줄 수 있다.
Images.loader = limit => _.tap(
$.findAll('img'),
L.map(img => _ => new Promise(resolve => {
img.onload = () => resolve(img);
img.src = img.getAttribute('lazy-src');
})),
lazy => {
let r = L.range(Infinity);
return _.go(
lazy,
_.groupBy(_ => Math.floor(r.next().value / limit)),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll),
_.each(_.each($.addClass('fade-in'))));
}
);
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
Images.loader(4),
$.findAll('.remove'),
$.on('click', async ({currentTarget}) => {
if(await Ui.confirm('정말 삭제하시겠습니까?')){
_.go(
currentTarget,
$.closest('.image'),
$.remove);
}
}));
고차 함수로 더 작게 나누어 재사용성 높이기
이번에는 기존에 작성된 코드들에서 재사용될만한 코드들을 분리하고, 고차 함수를 만들어 재사용성을 높여볼 것이다.
Images.loader = limit => _.tap(
$.findAll('img'),
L.map(img => _ => new Promise(resolve => {
img.onload = () => resolve(img);
img.src = img.getAttribute('lazy-src');
})),
C.takeAllWithLimit(limit),
_.each(_.each($.addClass('fade-in')))
);
C.takeAllWithLimit = _.curry((limit, iter) => {
let r = L.range(Infinity);
return _.go(
iter,
_.groupBy(_ => Math.floor(r.next().value / limit)),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll));
});
이런 식으로 C.takeAllWithLimit 함수를 만들어놓으면, 이미지를 동시에 로딩하는 것 외에도, 이미지를 리사이징 하거나 여러 페이지에서 크롤링하는 것을 동시에 실행하는 등, 여러 곳에서 쓰일 수 있다.
이번엔 상위 스코프 변수를 사용하는 함수와 아닌 함수를 쪼개 볼 것이다.
Images.loader = limit => _.tap(
$.findAll('img'),
L.map(img => _ => new Promise(resolve => {
img.onload = () => resolve(img);
img.src = img.getAttribute('lazy-src');
})),
C.takeAllWithLimit(limit),
_.each(_.each($.addClass('fade-in')))
);
_.groupBySize = _.curry((size, iter) => {
let r = L.range(Infinity);
return _.groupBy(_ => Math.floor(r.next().value / size), iter);
});
C.takeAllWithLimit = _.curry((limit = Infinity, iter) => _.go(
iter,
_.groupBySize(limit),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll)));
이번에도 역시 groupBySize라는 함수로 이터러블을 받아서, 사이즈를 조절하게끔 하는 함수로 쪼갰다.
이미지 목록을 지우는 함수를 따로 빼내서 함수를 하나 더 만들어 보자.
Ui.remover = (btnSel, targetSel) => parent => _.go(
parent,
$.findAll(btnSel),
$.on('click', async ({currentTarget}) => {
if(await Ui.confirm('정말 삭제하시겠습니까?')){
_.go(
currentTarget,
$.closest(targetSel),
$.remove);
}
}));
Ui.remover를 생성해서 이미지 목록도 지울 수 있고, 유저 목록도 지울 수 있고, 글 목록도 지울 수 있으므로 훨씬 더 추상화가 되었다.
또 이 함수를 실행할 때, 서버에서 쿼리를 날리거나 따른 부가적인 처리하는 작업이 필요할 때는 다음과 같이, 코드를 해주면 좋다.
Ui.remover = (btnSel, targetSel, before = a => a, after = a => a) => _.tap(
$.findAll(btnSel),
$.on('click', async ({currentTarget}) => {
if(await Ui.confirm('정말 삭제하시겠습니까?')){
_.go(
currentTarget,
$.closest(targetSel),
_.tap(before),
$.remove,
_.tap(after));
}
}));
before나 after에 아무 값이 없다면, 아무 동작도 하지 않게끔 "a => a"를 설정하였고, 전체 코드와 before, after를 tap으로 감싸주었다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
Ui.remover('.remove', '.image', _ => console.log('서버 통신')),
Images.loader(4));
이제 위처럼 코드를 출력해보면, 이미지를 지울 때마다, '서버 통신' 출력과 같은 부가적인 동작을 넣을 수 있다.
전체 코드
const $ = {};
$.qs = (sel, parent = document) => parent.querySelector(sel);
$.qsa = (sel, parent = document) => parent.querySelectorAll(sel);
$.find = _.curry($.qs);
$.findAll = _.curry($.qsa);
$.el = html => {
const wrap = document.createElement('div');
wrap.innerHTML = html;
return wrap.children[0];
};
$.append = _.curry((parent, child) => parent.appendChild(child));
$.closest = _.curry((sel, el) => el.closest(sel));
$.remove = el => el.parentNode.removeChild(el);
$.on = (event, f) => els =>
_.each(el => el.addEventListener(event, f), _.isIterable(els) ? els : [els]);
$.addClass = _.curry((name, el) => el.classList.add(name));
const Ui = {};
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(
$.find(`.${btn.type}`),
$.on('click', e => _.go(
e.currentTarget,
$.closest('.confirm'),
$.remove,
_ => resolve(btn.value)
))), btns)
)));
Ui.confirm = Ui.message(
[{name: '취소', type: 'cancel', value: false},
{name: '확인', type: 'ok', value: true}
]);
Ui.alert = Ui.message(
[{name: '확인', type: 'ok', value: true}
]);
Ui.remover = (btnSel, targetSel, before = a => a, after = a => a) => _.tap(
$.findAll(btnSel),
$.on('click', async ({currentTarget}) => {
if(await Ui.confirm('정말 삭제하시겠습니까?')){
_.go(
currentTarget,
$.closest(targetSel),
_.tap(before),
$.remove,
_.tap(after));
}
}));
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"},{"name":"7","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076948567_98474.png"},{"name":"농구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918719546_22465.png"},{"name":"9","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077004840_10995.png"},{"name":"선물","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918791224_48182.png"},{"name":"당구공","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918909204_46098.png"},{"name":"유령","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918927120_12321.png"},{"name":"원숭이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919417134_80857.png"},{"name":"3","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076802375_69966.png"},{"name":"16","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077254829_36624.png"},{"name":"안경","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918944668_23881.png"},{"name":"폭죽","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919005789_67520.png"},{"name":"폭죽 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919027834_48946.png"},{"name":"박","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919062254_67900.png"},{"name":"톱니바퀴","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919302583_24439.png"},{"name":"11","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077078772_79004.png"},{"name":"핫도그","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919086961_23322.png"},{"name":"고기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919274214_33127.png"},{"name":"책","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919326628_13977.png"},{"name":"돋보기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919363855_26766.png"},{"name":"집","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919395033_19373.png"},{"name":"사람","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918696715_44274.png"},{"name":"연필","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919437239_32501.png"},{"name":"파일","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919468582_23707.png"},{"name":"스피커","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919495804_49080.png"},{"name":"트로피 ","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918438617_69000.png"},{"name":"카메라","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919847041_33220.png"},{"name":"그래프","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918521301_43877.png"},{"name":"가방","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918642937_11925.png"},{"name":"입술","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919886042_10049.png"},{"name":"fire","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920036111_19302.png"},{"name":"TV","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920054808_42469.png"},{"name":"핸드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920109727_43404.png"},{"name":"노트북","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920142776_26474.png"},{"name":"전구","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920181784_14964.png"},{"name":"장미","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920264149_78607.png"},{"name":"맥주","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920312701_18073.png"},{"name":"마이크","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920397855_39832.png"},{"name":"별","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920420823_49166.png"},{"name":"와이파이","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920438005_35247.png"},{"name":"헤드폰","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920468136_82088.png"},{"name":"peace","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920538719_19072.png"},{"name":"계산기","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920348341_40080.png"},{"name":"poo 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924259247_12839.png"},{"name":"poo 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924850867_72121.png"},{"name":"poo 4","url":"https://s3.marpple.co/f1/2019/1/1235206_1548925154648_40289.png"},{"name":"poo","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918988097_38121.png"},{"name":"모니터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769774483.png"},{"name":"talk","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927111573_76831.png"},{"name":"keyboard","url":"https://s3.marpple.co/f1/2018/1/1054966_1516330864360_25866.png"},{"name":"daily 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548926169159_58295.png"},{"name":"daily","url":"https://s3.marpple.co/f1/2018/7/1199664_1531814945451_49451.png"},{"name":"편지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548920087850_99421.png"},{"name":"sns 하단바 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917218903_88079.png"},{"name":"sns 하단바","url":"https://s3.marpple.co/f1/2019/1/1235206_1548917192465_28365.png"},{"name":"sns 이모지 6","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927313417_99007.png"},{"name":"sns 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548927219485_18861.png"},{"name":"13","url":"https://s3.marpple.co/f1/2018/1/1054966_1516077164559_59630.png"},{"name":"iphone","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769886837.png"},{"name":"아이패드","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769820297.png"},{"name":"컴퓨터","url":"https://s3.marpple.co/f1/2016/7/1043023_1469769802862.png"},{"name":"5","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076888018_74741.png"},{"name":"poo 1","url":"https://s3.marpple.co/f1/2019/1/1235206_1548924230868_28487.png"},{"name":"Sns icon_똥 안경","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211657799.png"},{"name":"Sns icon_똥 웃음","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211686108.png"},{"name":"4","url":"https://s3.marpple.co/f1/2018/1/1054966_1516076850148_36610.png"},{"name":"Sns icon_똥 놀림","url":"https://s3.marpple.co/f1/2017/2/1043404_1487211670017.png"},{"name":"달력","url":"https://s3.marpple.co/f1/2019/1/1235206_1548919531014_35045.png"},{"name":"자물쇠","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918410738_59815.png"},{"name":"손 이모지","url":"https://s3.marpple.co/f1/2019/1/1235206_1548918353391_54897.png"},{"name":"Sns icon_손바닥","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210472038.png"},{"name":"Sns icon_검지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210393226.png"},{"name":"Sns icon_롹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210522978.png"},{"name":"Sns icon_하이파이브","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210538695.png"},{"name":"Sns icon_브이","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210508758.png"},{"name":"Sns icon_중지","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210428137.png"},{"name":"Sns icon_주먹","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210372629.png"},{"name":"Sns icon_박수","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210444994.png"},{"name":"Sns icon_따봉","url":"https://s3.marpple.co/f1/2017/2/1043404_1487210488684.png"},{"name":"손 이모지 2","url":"https://s3.marpple.co/f1/2019/1/1235206_1548921736267_35010.png"},{"name":"손 이모지 3","url":"https://s3.marpple.co/f1/2019/1/1235206_1548922150829_10878.png"}
]), 200));
const string = iter => _.reduce((a, b) => `${a}${b}`, iter);
_.strMap = _.curry(_.pipe(L.map, string));
Images.tmpl = imgs => `
<div class ="images">
${_.strMap( img => `
<div class="image">
<div class="box"><img src="" lazy-src="${img.url}" class="fade" alt=""></div>
<div class="name">${img.name}</div>
<div class="remove">×</div>
</div>
`, imgs)}
</div>
`;
Images.loader = limit => _.tap(
$.findAll('img'),
L.map(img => _ => new Promise(resolve => {
img.onload = () => resolve(img);
img.src = img.getAttribute('lazy-src');
})),
C.takeAllWithLimit(limit),
_.each(_.each($.addClass('fade-in')))
);
_.groupBySize = _.curry((size, iter) => {
let r = L.range(Infinity);
return _.groupBy(_ => Math.floor(r.next().value / size), iter);
});
C.takeAllWithLimit = _.curry((limit = Infinity, iter) => _.go(
iter,
_.groupBySize(limit),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll)));
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
Ui.remover('.remove', '.image', _ => console.log('서버통신')),
Images.loader(4));
'Web[웹] > ES6+ 함수형 언어' 카테고리의 다른 글
[ES6+: 응용] 프론트엔드에서 활용하기 (1) (0) | 2019.11.04 |
---|---|
[ES6+: 응용] 시간을 이터러블로 다루기 (0) | 2019.11.01 |
[ES6+: 응용] 사용자 정의 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.11.01 |
[ES6+: 응용] 객체를 이터러블 프로그래밍으로 다루기 (0) | 2019.10.29 |
[ES6+: 응용] map과 filter로 하는 안전한 함수 합성 (0) | 2019.10.29 |