-
Notifications
You must be signed in to change notification settings - Fork 7
✌️함수형 프로그래밍 부분적으로 도입하기
인피니티 스크롤을 구현할 경우 새로 불러와지는 메시지들의 높이의 합을 알아야합니다. 그래야 새로 불러와진 후 스크롤을 새로운 메시지들의 높이의 합만큼 아래로 내려줘야했습니다. 그런데 메시지들의 높이가 일정하지 않았던 것이 문제였습니다. 그 이유는 이미지가 로딩이 될 경우도 있고 이미지가 여러장이면 세로로 길어질 경우도 있었기에 이미지가 모두 로드된 후에 높이를 구해야했습니다. 따라서 비동기 처리를 해야했고 그에 따라 코드의 depth가 너무 깊어지는 것이 코드의 가독성 및 재사용성을 위해서 함수형 프로그래밍을 도입하게 되었습니다.
const go = (...args: any) => reduce((a: any, f: any) => f(a), args, undefined);
go 함수는 과정을 실행하는 함수입니다. 가장처음의 인자를 두번째 함수의 인자로 넣고 그 결과를 세번째 함수의 인자로 넣어서 끝까지 실행하는 함수입니다.
const curry =
(f: any) =>
(a: any, ..._: any) =>
_.length ? f(a, ..._) : (..._: any) => f(a, ..._);
curry라는 함수는 인자를 모두 받았을 때 실행되게 해주는 함수입니다. 예를 들자면
const plus = curry((a,b) => a+b)
위의 함수를 plus(1)(3)이라고 실행시킬 수 있게 해주는 것입니다.
그렇다면 본격적으로 함수들을 만들어보겠습니다.
const map = curry((f: (a: HTMLElement) => Promise<any>, iter: any) => {
const res = [];
for (const a of iter) {
res.push(f(a));
}
return res;
});
첫번째는 map 함수입니다. 기존의 map이 JS에 내장이 되어있는데 이것은 HTMLTagList에서는 사용할 수 없기에 iterator를 이용해서 map을 구현했습니다. 저희가 아는 기존 map과 똑같습니다!
const filter = curry((chatsList: NodeListOf<Element> | undefined, length: number) => {
const res: Element[] = [];
chatsList?.forEach((v, i) => {
if (i < length) res.push(v);
});
return res;
});
두번째는 filter함수입니다. filter함수는 앞에서부터 특정length까지 자르는 함수입니다. 원래 알던 filter함수와는 다르지만 간결성을 위해 별도의 함수로 분리했습니다.
const mapAsync = curry((f: (a: Promise<any>) => Promise<any>, iter: any) => {
const res = [];
for (const a of iter) {
res.push(a.then(f));
}
return res;
});
새번째 함수는 mapAsync라는 함수입니다. 이 함수는 Promise로 이루어진 배열에서 그 결과들을 담아서 반환합니다.
const reduce = curry((f: (a: any, b: any) => any, acc: any, iter: any) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = iter.next().value;
}
for (const a of iter) {
acc = f(acc, a);
}
return acc;
});
const reduceAsync = curry(async (f: (a: number, b: number) => number, acc: any, iter: any) => {
if (!iter) {
iter = acc[Symbol.iterator]();
acc = await iter.next().value;
}
for await (const ab of iter) {
acc = f(acc, ab);
}
return acc;
});
다음은 replace관련 함수입니다. reduce함수는 iterator를 이용해서 함수를 재귀로 진행한 결과값을 반환합니다. 그리고 reduceAsync
함수는 비동기적 처리 상황을 모두 await for of
라는 문법을 이용해서 진행된 결과를 반환합니다.
const add = curry(async (a: number, b: Promise<number>) => {
const totalHeight = await b;
return totalHeight + a;
});
마지막으로는 Promise와 숫자를 더해주는 함수입니다. 위의 함수들을 바탕으로 기능 구현을 한 결과물은 다음과 같습니다.
const result = go(
target,
map(loadOneChatItemImages),
mapAsync(getOneElement),
reduceAsync((a: number, b: number) => a + b),
add(dayPillHeights),
);
일단은 target이 되는 태그들을 가져오고 거기안에 있는 이미지들을 모두 로드합니다. 그 후 하나 하나의 태그들의 높이를 구하고 그 후 그것을 더하고 거기에 추가적으로 생기는 날짜표시 부분의 높이를 더해줍니다.위에서부터 잘 읽히니까 아주 좋네요~!