본문 바로가기

Web[웹]/ES6+ 함수형 언어

[ES6+: 응용] 프론트엔드에서 활용하기 (2)

 

 

[ES6+: 응용] 프런트엔드에서 활용하기 (1) : https://opentogether.tistory.com/82

 

[ES6+: 응용] 프론트엔드에서 활용하기 (1)

#1 프론트엔드에서 함수형/이터러블/동시성 프로그래밍 ES6 템플릿 리터럴 활용 이후에 템플릿 리터럴을 통해 프로그래밍을 해볼 것인데, 템플릿 리터럴의 사용법은 아래와 같다. const a = 10; const b = 5; con..

opentogether.tistory.com


클래스 대신 함수로 하는 추상화

 

앞 챕터에서 배웠던 코드는 중복이 많았는데, 이를 줄여나가는 추상화를 시켜보자.

먼저, 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 {

        opacity0;

      }

      .fade-in {

        opacity1;

        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));