Skip to content

✌️함수형 프로그래밍 부분적으로 도입하기

김서진 edited this page Dec 2, 2021 · 4 revisions

image

왜 사용했을까?

인피니티 스크롤을 구현할 경우 새로 불러와지는 메시지들의 높이의 합을 알아야합니다. 그래야 새로 불러와진 후 스크롤을 새로운 메시지들의 높이의 합만큼 아래로 내려줘야했습니다. 그런데 메시지들의 높이가 일정하지 않았던 것이 문제였습니다. 그 이유는 이미지가 로딩이 될 경우도 있고 이미지가 여러장이면 세로로 길어질 경우도 있었기에 이미지가 모두 로드된 후에 높이를 구해야했습니다. 따라서 비동기 처리를 해야했고 그에 따라 코드의 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 Promise.all(res);
});

새번째 함수는 mapAsync라는 함수입니다. 이 함수는 Promise로 이루어진 배열에서 그 결과들을 Promise.all담아서 반환합니다.

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

다음은 replace관련 함수입니다. reduce함수는 iterator를 이용해서 함수를 재귀로 진행한 결과값을 반환합니다.

const add = curry((a: number, b: number) => a + b);

마지막으로는 숫자를 더해주는 함수입니다. 위의 함수들을 바탕으로 기능 구현을 한 결과물은 다음과 같습니다.

await go(
  target, 
  map(addLoadEvent), 
  mapAsync(loadImage)
);

return go(
  target,
  map((chatItem: HTMLElement) => chatItem.clientHeight),
  reduce(add),
  add(dayPillHeights),
);

일단은 타겟들의 모든 이미지에 onload를 걸어주고 그것이 다 로드 되는 것을 기다립니다. 그 후에 각각을 돌면서 clientHeight를 구하고 그 후에 그것을 더하고 마지막으로 날짜필들을 더해줘서 반환합니다! 함수형으로 짜니 이해가 쉽네요!

Clone this wiki locally