이미지 로딩
이전의 실습에서 이미지가 로딩 될때 굉장히 버벅거렸는데 이러한 부분들을 개선해볼 것이다.
이미지가 로딩이 다 되면 화면에 렌더링되도록 코드를 작성해보자.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img); // onload
img.src = img.getAttribute('lazy-src');
})),
_.each($.addClass('fade-in'))
),
$.findAll('.remove'),
$.on('click', async ({currentTarget : ct}) => {
if(await UI.confirm('?')) {
_.go(
ct,
$.closest('.image'),
$.removeIt,
_ => UI.alert('삭제 완료'))
}
}))
findAll('remove') 전에 tap 함수를 넣어서 해당 함수에서 이미지들을 깔끔하게 렌더링 해주는 코드를 넣는다. each에서 한번에 평가를 시도하면서 순차적으로 렌더링 될 것이다.
만약 한번에 렌더링하고 싶다면 어떻게 해야할까? (1) 프로미스이고 시간이 걸리는 작업이니 병렬적으로 처리해야하고 (2) 모든 값을 가져와야하니 정답은 C.takeAll을 사용하는 것이다.
_.go(
Images.fetch(),
Images.tmpl,
$.el,
$.append($.qs('body')),
_.tap(
$.findAll('img'),
L.map(img => new Promise(resolve => {
img.onload = () => resolve(img);
img.src = img.getAttribute('lazy-src');
})),
C.takeAll, // 이 부분 추가
_.each($.addClass('fade-in'))
),
$.findAll('.remove'),
$.on('click', async ({currentTarget : ct}) => {
if(await UI.confirm('?')) {
_.go(
ct,
$.closest('.image'),
$.removeIt,
_ => UI.alert('삭제 완료'))
}
}))
이번엔 부하를 조절하기 위해 그룹을 나눠서 4개씩 렌더링 해보자 C.takeAll과 addClass 해주는 부분 대신에 아래의 코드를 넣으면 된다.
lazy => { //그룹을 지어줌으로써 부하를 조절함
let r = L.range(Infinity);
return _.go(
lazy,
_.groupBy(_ => Math.floor(r.next().value / 4)), // 인덱스 : [배열] 형태의 객체
L.values, //value인 배열만 뽑아냄
L.map(L.map(f => f())),
L.map(C.takeAll),
_.each(_.each($.addClass('fade-in')))
)
}
_.groupBy 라는 함수를 이용한다. n 개씩 묶어내어 2차원 배열을 만들어냈으므로 L.map을 두개 중첩해서 사용하도록 한다. 하지만 이렇게만 해두면 제대로 동작하지 않는다. 이유는 groupBy 내부에서 프로미스가 풀려버리기 때문이다. 풀린다면 L.map(f⇒f()) 에서 f가 함수가 아니라는 에러가 나올 것이다. 이를 방지하기 위해 다음과 같이 코드를 변경한다.
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(_ => {
return 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 : ct}) => {
if(await UI.confirm('?')) {
_.go(
ct,
$.closest('.image'),
$.removeIt,
_ => UI.alert('삭제 완료'))
}
}))
위와 같이 함수를 한단계 더 추가해도 자바스크립트의 클로저에 의해 img 변수가 저장된 상태로 프로미스가 선언된다. loader 함수도 따로 만들어서 limit 값을 받도록 했기때문에 원하는 그룹 사이즈를 조정해서 한번에 렌더링 되는 갯수를 조절할 수 있게 되었다.
함수들의 추상화 레벨 높이기
고차함수들을 사용하면 추상화레벨이 높은 함수들을 쉽게 만들 수 있다. 위의 Image.loader 함수를 더 작게 나누는 연습을 해보자. 먼저 알아둬야 할 점은 고차함수의 특징은 도메인, 데이터 형이 없다는 점이다. 그러므로 데이터형과 상관이 없는 추상화된 부분을 찾아야한다.
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(_ => {
return Math.floor(r.next().value / limit)
}),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll)
)
},
_.each(_.each($.addClass('fade-in')))
);
addClass를 해주는 부분은 특정 데이터와 연관이 있으니 밖으로 빼줘보면 lazy ⇒ f 는 모두 추상화 레벨이 높은 섹션이다. 이 부분을 따로 함수로 빼낸다.
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(_ => {
return Math.floor(r.next().value / limit)
}),
L.values,
L.map(L.map(f => f())),
L.map(C.takeAll)
)
})
이렇게 빼면 C.takeAllWithLimit은 평가를 기다리는 여러개의 값들을 그룹으로 나눠서 한번에 처리하도록하는 추상화레벨이 높은 함수가 된다. 여기서 한번 더 나눠볼 수 있는데 어떻게 해야할까??
먼저 변수 r 에 주목해야한다. r 은 평가가 지연된 상태로 무한하게 평가될 수 있는 range이다. 해당 변수는 _.groupBy 내부에서만 사용되므로 저 둘을 밖으로 빼서 함수로 만들 수 있다.
_.groupBySize = _.curry((size, iter) => {
let r = L.range(Infinity);
return _.groupBy(_ => Math.floor(r.next().value / size), iter)
})
위와 같이 가능하다. 이 groupBySize 함수를 사용하는 C.takeAllWithLimit 함수는 보다 더 간단해질 것이다.
C.takeAllWithLimit = _.curry((limit, 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')),
Images.loader(4),
$.findAll('.remove'),
$.on('click', async ({currentTarget : ct}) => {
if(await UI.confirm('?')) {
_.go(
ct,
$.closest('.image'),
$.removeIt,
_ => UI.alert('삭제 완료'))
}
}))
이미지를 fetch하고 그룹으로 나눠서 로딩하고 삭제버튼 등록하는 등의 작업을 하는 함수이다. 이 함수에서도 고차함수를 뽑아낼 수 있다.
UI.remover = (btnSel, targetSel, parent) => _.go(
parent,
$.findAll(btnSel),
$.on('click', async ({currentTarget : ct}) => {
await UI.confirm('?') &&
_.go(
ct,
$.closest(targetSel),
$.removeIt,
_ => UI.alert('삭제 완료'))
}))
parent 의 자식노드 중 btnSel을 찾아서 클릭됐을 경우 targetSel을 삭제하도록하는 함수이다. 클로저를 이용해서 꼭 3개의 인자를 한번에 다 받을 필요없이 parent만 나중에 따로 받도록 할 수 있다.
UI.remover = (btnSel, targetSel) => _.pipe(
$.findAll(btnSel),
$.on('click', async ({currentTarget : ct}) => {
await UI.confirm('?') &&
_.go(
ct,
$.closest(targetSel),
$.removeIt,
_ => UI.alert('삭제 완료'))
}))
이제 어떤 목록이든 지울 수 있는 보다 더 추상화레벨이 높은 함수가 되었다!!
마무리
고차함수를 만드는 사례들을 통해 함수를 분리하는 것을 실습해봤다. 함수형 프로그래밍에서는 이렇듯 일어나야되는 일을 이터러블로 바라보고 이 이터러블한 대상을 여러 보조함수를 사용해서 다형성 및 추상화레벨을 높여준다.
수업에서 실습해온 것처럼 잘게 나누는 연습을 하고 이름을 잘 붙여나가면 생산성을 많이 높일 수 있을 것이다.
관련글
'프론트엔드 > 자바스크립트' 카테고리의 다른 글
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 |