My Boundary As Much As I Experienced

함수 커링을 활용하여 props를 순차적으로 주입하기 (한 번에 prop을 결정하지 못할 때의 해결법) 본문

FrontEnd/React

함수 커링을 활용하여 props를 순차적으로 주입하기 (한 번에 prop을 결정하지 못할 때의 해결법)

Bumang 2024. 8. 17. 13:26

함수 커링이란?

함수가 인자를 하나씩 받아가며 최종 결과를 도출하는 함수 패턴이다.

아래와 같은 형식이 된다고 생각하면 된다.

(params1) => (params2) => {
  return <Component params1={params1} params2={params2}>
}

도대체 이걸 왜 쓰나.. 싶었는데 가끔 나름 요긴하게 쓰게된다.

주로 한 번에 prop을 결정짓지 못할 때 사용하게 된다.

(이 단계에서는 이 prop이 결정되고, 저 단계에서는 저 prop이 결정되고...)

예시를 들어보겠다.

 

내가 직면한 문제 - 기존 컴포넌트에 prop을 추가적으로 넣어야하는데 못하는 상황(?)

나는 최근에 리액트네이티브의 FlatList를 다뤄야하는 일이 있었다.

FlatList는 리액트네이티브가 제공하는 대규모 데이터 배열을 처리하기 위한 스크롤 뷰이다.

(뷰포트에 있지 않는 데이터는 렌더하지 않는 windowing 기법이 적용되어있다.)

 

이 컴포넌트는 prop으로 renderItem을 제공받아야하고, 나는 일기를 표현하는 아이템 컴포넌트를 제공해야되는 상황이었다.

const MyClassesDiaries = () => {
  return (
      <FlatList
        data={example} // 배열 데이터와 
        renderItem={ClassItem} // 렌더 아이템을 제공받으면 데이터를 map함수로 돌려 만들어준다.
      />
  );
};

 

그러나 이 renderItem 컴포넌트를 쓰는 페이지는 3곳이 있었고(ex. 내 일기, 공개 일기, 우리반 일기...)

어떤 페이지에서 렌더되는지에 따라 약간씩 UI를 바꿔야 했다.

그래서 약간씩 UI를 다르게 표현하기 위해 조금씩 레이아웃을 수정한 컴포넌트를 3개 만들어야할까?

공개 일기에서 볼 때의 레이아웃
내 일기에서 볼때의 레이아웃

 

 

보통은 아니라고 할 것이다.

살짝만 다른 레이아웃을 표현하기 위해서 아예 쌩 컴포넌트를 다시 짜는게 아니라,

'어디서 사용되는지'를 props로 제공해주면 되는거 아니야?'라는 생각이 먼저 든다.

보통 아래처럼 타입을 나눠서 컴포넌트에 전달하는 방식을 많이 쓰니까.

type DiaryType = "PUBLIC" | "MY" | "MYCLASS";


// 이런 식으로 활용
<Component type="PUBLIC">

 

그러나 위처럼 사용할 수 없는 문제 발생. flatList의 renderItem은 JSX형태의 패러미터를 받을 수 없게 설계되어 있기 때문이다.

const MyClassesDiaries = () => {
  return (
      <FlatList
        data={exampleTriple} // 이 prop은 renderItem의 prop으로 전달된다.
        renderItem={ClassItem} // jsx형태는 주입할 수 없다.
        keyExtractor={...}
      />
  );
};


즉, Component라는 컴포넌트가 있다고 치면, 꼭

renderItem={Component} 형태로 전달해야지,

renderItem={<Component locatedAt={...} />} 형태로 전달할 수 없던 것이다.

 

그러므로 이 컴포넌트를 사용하는 페이지에서 임의로 다른 데이터를 주입하기가 힘든 상황이었다.

이때 해결 방법은 3가지 정도 있는거 같다.

1. data로 제공할 데이터를 가공하기

data로 제공할 배열을 map으로 조작해서 내가 원하는 데이터를 주입해주는 거다.

const newExample = example.map((item) => {...item, locatedAt: "MyDiaries"})

 

그리고 이걸 data로 제공하는 것이다.

  return (
    <View style={styles.container}>
      <FlatList
        data={newExample} // 각각의 아이템마다 locatedAt이 추가된 새로운 데이터

 

이 방법은 비효율적이다. 안그래도 대규모 배열을 처리하기 위한 컴포넌트여서 인자도 되게 많을텐데..

각각의 인자에 필드 하나 추가해주려고 map을 돌린다? 좋지 않은 방법이다.

 

 

2. 그냥 컴포넌트 다 따로 만들기

렌더할 내용은 똑같고 페이지 위치에 따라 UI만 차이있는거라

한 컴포넌트안에 조건적으로 렌더하려했던건데.. 쿨하게 포기한다!

각 페이지의 FlatList에 각각 다른 UI의 컴포넌트를 제공한다.

물론 이 방법도 중복코드가 많아진다는 점에서 좋지 않다.

 

 

3. 함수 커링으로 원하는 정보를 Item컴포넌트에 주입하기 like 고차함수..(선택한 방법)

나는 Item 컴포넌트를 한 번 더 함수로 감싸서 nested function으로 만들었다. 함수 커링을 사용한 것이다.

 

({data}) => <JSX>...</JSX> 구조인 DiaryItem컴포넌트를

(additionalInfo) => ({data}) => <JSX>{additionalInfo && ...}</JSX>로 바꿨다.

additionalInfo를 받는 함수 레이어로 컴포넌트를 wrapping하여 최종 return 컴포넌트에 주입할 수 있도록 만들었다.

const DiaryItem =
  (additionalInfo: string) =>
  ({ data }: DiaryItem) => {
    // ...
    return (
      <Button onPress={handleClick} style={styles.container} pressedShrink={1}>
        <View key={data.id}>
          <View style={styles.metaData}>
            {additionalInfo === "PublicDiaries" && ( // 호출 위치에 따라 바뀌는 정보
              <Text style={[styles.username]}>{data.username}</Text>
            )}
            {additionalInfo === "MyDiaries" && ( // 호출 위치에 따라 바뀌는 정보
              <Text style={[styles.badge]}>
                {CONTENTTYPE[item.type as CONTENTTYPE_KEY]}
              </Text>
            )}
            ...
      </Button>
    );
  };

 

그리고 FlatList에는 locatedAt이라는 정보를

DiaryItem(locatedAt)에 주입하면서 wrapped된 함수를 한 꺼풀 벗긴다.

그러면 결과적으로 renderItem에 locatedAt이 주입된 DiaryItem을 제공하게 되는 것이다.

  const locatedAt = "MyDiaries";
  
  return (
      // ...
      <FlatList
        data={exampleTriple}
        renderItem={DiaryItem(locatedAt)}
      />
  )

 

+함수커링 패턴은 고차함수(HoC)인가?

나는 최근까지 이게 함수커링 기법이 아니라 고차함수인줄 알았다.

고차함수도 nested구조로 순차적으로 props을 주입할 수 있는 구조긴 해서 헷갈릴 수 있다.

(철학은 비슷하게 공유하는거라고 볼수 있다.)

그러나 고차함수는 패러미터로 컴포넌트를 받는다는 차이가 있다.

나중에 고차함수와 함수커링을 비교해보도록 하겠다.