728x90
반응형
728x170

Render Props 패턴

JSX 엘리먼트를 props를 통해 컴포넌트에게 전달한다

 

📜 원문: patterns.dev - render props pattern

📜 번역: https://patterns-dev-kr.github.io/design-patterns/render-props-pattern/


고차 컴포넌트 섹션에서 여러 컴포넌트가 동일한 데이터나 동일한 로직을 포함해야 할 때 컴포넌트의 로직을 재사용할 수

있게 되면 편해질 수 있다는 것을 알았습니다.


컴포넌트를 재사용 가능하게 할 수 있는 또 다른 방법으로, render prop 패턴을 사용하는 방법이 있습니다.

 

render prop은 컴포넌트의 prop으로 함수이며 JSX 엘리먼트를 리턴합니다. 컴포넌트 자체는 아무런 것도 렌더링하지 않지

만 render prop함수를 호출하죠.


Title컴포넌트가 있다고 생각해 볼까요? Title컴포넌트는 prop으로 넘어온 함수를 호출하여 반환하는 것 외에는 아무런

동작을 하지 않습니다. Title컴포넌트에 render prop을 아래와 같이 넣어 봅시다.

 

<Title render={() => <h1>I am a render prop!</h1>} />

Title컴포넌트 내에서는 단순히 prop의 render함수를 호출하여 반환합니다.

 

const Title = props => props.render()

컴포넌트 엘리먼트에 React엘리먼트를 반환하는 render라는 이름의 prop을 넘깁니다.

 

render prop패턴의 장점은 prop을 받는 컴포넌트가 재사용성이 좋다는 점입니다.

Title컴포넌트는 이제 render prop만 바꿔가며 여러번 사용할 수 있죠.

 

이 패턴의 이름이 render prop이지만 넘기는 prop의 이름을 꼭 render로 할 필요는 없습니다. 

JSX를 렌더하는 어떤 prop이던 render prop으로 볼 수 있습니다. 아래 예제에서는 이름을 변경하여 사용하고 있죠.

 

위의 예제에서 render prop패턴을 사용하여 컴포넌트를 재사용 가능하게 만들었습니다. 

하지만 이 패턴은 이보다 더 유용하게 쓰일 수 있죠.


render prop을 받는 컴포넌트는 단순히 함수를 호출해 JSX엘리먼트를 렌더링하는 것 외에도 많은 동작을 할 수 있습니다.

단지 함수를 호출하는 것 대신에 render prop 함수를 호출할 때 인자를 전달할 수 있습니다.

 

function Component(props) {
  const data = { ... }

  return props.render(data)
}

위처럼 인자를 넘기게 구현하면 render prop은 이제 아래 코드와 같이 데이터를 인자로 받을 수 있습니다.

<Component render={data => <ChildComponent data={data} />} />

아래 예제는 텍스트박스에 섭씨 온도를 받아서 켈빈과 화씨 온도로 표현해주는 단순한 앱입니다.

위의 예제를 보면 Input 컴포넌트는 값 입력을 받기 위해 state를 갖고 있는데 Fahrenheit컴포넌트와 Kelvin 컴포넌트는

이 state를 전달 받을 방법이 없습니다.


상태를 부모 컴포넌트로 올리기

Fahrenheit컴포넌트와 Kelvin 컴포넌트가 사용자가 입력한 값을 전달받기 위한 방법 중 하나는 상태를 부모 컴포넌트로 올려보내는 방법이 있습니다.


아래 예제에서 상태를 가지고 있는 Input 컴포넌트가 있지만 형제 컴포넌트인 Fahrenheit, Kelvin 컴포넌트도 이 값에 접근

할 수 있어야 변환된 값을 보여줄 수 있습니다. 이 때 Input 자체가 상태를 갖는 것 대신 세 컴포넌트의 부모 컴포넌트로

상태를 올려보내는 것인데 아래 예제에서는 App 컴포넌트가 될 것입니다.

 

function Input({ value, handleChange }) {
  return <input value={value} onChange={e => handleChange(e.target.value)} />
}

export default function App() {
  const [value, setValue] = useState('')

  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input value={value} handleChange={setValue} />
      <Kelvin value={value} />
      <Fahrenheit value={value} />
    </div>
  )
}

이 방법도 유효하긴 하지만 규모가 큰 앱에서 컴포넌트가 여러 자식 컴포넌트를 가지고 있는 경우 이 작업을 하기란 까다로

운 일입니다. 상태의 변경은 모든 자식 컴포넌트의 리렌더링을 유발할 수 있고 이런 상황이 쌓이면 앱의 전체적인 성능을

떨어트릴 수 있습니다.

 


Render props

그 대신에 render props 패턴을 활용할 수 있다. Input 컴포넌트가 render prop을 받도록 리펙토링 해 보죠.

function Input(props) {
  const [value, setValue] = useState('')

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.render(value)}
    </>
  )
}

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input
        render={value => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      />
    </div>
  )
}

이로써 Kelvin과 Fahrenheight 컴포넌트는 사용자의 입력 값을 받을 수 있게 되었습니다.


자식 컴포넌트를 함수로 받아보자

일반적인 JSX컴포넌트에 자식 엘리먼트로 React 엘리먼트를 반환하는 함수를 전달할 수 있습니다.

해당 컴포넌트에서 이 함수는 children prop으로 사용 가능하며 이것도 역시 render prop에 해당합니다.

Input 컴포넌트에 명시적으로 render prop을 넘기는 대신 자식 컴포넌트를 함수로 넘기도록 수정해 보죠.

 

export default function App() {
  return (
    <div className="App">
      <h1>☃️ Temperature Converter 🌞</h1>
      <Input>
        {value => (
          <>
            <Kelvin value={value} />
            <Fahrenheit value={value} />
          </>
        )}
      </Input>
    </div>
  )
}

Input컴포넌트는 props.children을 통해 이 함수에 접글할 수 있습니다. props.render를 쓰는 대신에 props.children함수

를 호출하며 인자를 넘기도록 수정합니다.

 

function Input(props) {
  const [value, setValue] = useState('')

  return (
    <>
      <input
        type="text"
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Temp in °C"
      />
      {props.children(value)}
    </>
  )
}

이렇게 하여 render prop의 이름을 어떻게 지을까 고민하지 않고 Kelvin과 Fahrenheit 둘 다 사용자의 입력 값에 접근할 수 

있습니다.


Hooks

몇몇 상황에 render props 패턴은 hooks로 대체될 수 있다. Apollo Client가 좋은 예시입니다.

아래 예제 코드를 이해하는데 Apollo Client에 대한 지식은 필요하지 않습니다.

Apollo Client를 사용하는 방법 중 하나는 Mutation과 Query 컴포넌트를 사용하는 것입니다.

아래 예시는 HOC Pattern 의 Input컴포넌트 예시와 동일한데 graphql() HOC를 사용하는 대신 Mutation 컴포넌트가

render prop을 받는것을 알 수 있습니다.

 

Mutation컴포넌트가 자식 엘리먼트에게 데이터를 전달할 수 있도록 하기 위해 컴포넌트를 렌더하는 함수를 자식 요소로 

제공했습니다. 이 함수에서 인자로 데이터를 받을 수 있죠.

 

<Mutation mutation={...} variables={...}>
  {addMessage => <div className="input-row">...</div>}
</Mutation>

render prop 형태는 HOC에 비교하여 조금 더 선호되긴 하지만 단점이 존재합니다.

 

첫번째 단점은 트리가 깊어진다는 것입니다. 컴포넌트가 여러 개의 mutation을 사용해야 하는 경우 Mutation 컴포넌트나

Query 컴포넌트를 중첩해 사용해야 합니다.

 

<Mutation mutation={FIRST_MUTATION}>
  {firstMutation => (
    <Mutation mutation={SECOND_MUTATION}>
      {secondMutation => (
        <Mutation mutation={THIRD_MUTATION}>
          {thirdMutation => (
            <Element
              firstMutation={firstMutation}
              secondMutation={secondMutation}
              thirdMutation={thirdMutation}
            />
          )}
        </Mutation>
      )}
    </Mutation>
  )}
</Mutation>

React에 훅이 추가되고 나서 Apollo에도 훅을 지원하기 시작했습니다. 

Mutation 혹은 Query 컴포넌트를 사용하는 대신 개발자는 훅을 사용하여 직접 필요한 값을 참조할 수 있게 되었습니다.

아래 예시에서는 Query 컴포넌트를 render prop과 함께 사용하는 대신 useQuery훅을 사용하고 있습니다.

 

useQuery훅을 사용하여 꽤 많은 양의 코드를 줄이면서 필요한 데이터를 사용할 수 있게 되었습니다.


장점

render prop을 사용하여 몇몇 컴포넌트간 데이터를 공유하는것은 간단합니다. 

children prop을 활용하는 것으로 해당 컴포넌트를 재사용할 수 있게 됩니다.

HOC패턴도 마찬가지로 재사용성과 데이터의 공유 부분에서 같은 이슈를 해결할 수 있습니다. render prop은 HOC를 사용할 때 마주칠 수 있는 몇 가지 이슈들을 해결할 수 있죠.

 

props를 자동으로 머지하도록 구현하지 않기 때문에 HOC패턴을 사용할 때 prop이 어디서 만들어져 어디서 오는지 구별하기 힘들었던 이슈가 없습니다. 부모 컴포넌트로부터 받은 prop을 명시적으로 받아 처리하기 때문이죠.

 

함수의 인자에서 명시적으로 prop이 전달되기 때문에 HOC를 사용할 때 prop이 모호한 문제가 해결됩니다.

이 덕분에 prop이 어디로부터 오는지 확실히 알 수 있죠.

 

render props를 활용하여 렌더링 컴포넌트와 앱의 로직을 분리할 수 있습니다. 상태를 가진 컴포넌트는 render prop을

받고. 상태가 없는 컴포넌트를 렌더할 수 있습니다.


단점

위에서 render props로 해결하려 한 문제는 React hooks로 대체되었습니다. Hooks는 컴포넌트에 재사용성과 데이터 공유를 위한

방법 자체를 바꿔놓았죠. 대부분의 render props는 Hooks로 대체 가능합니다.

render prop 내에서는 생명 주기 함수를 사용할 수 없기 때문에 render prop 패턴은 받은 데이터를 수정할 필요가 없는 컴포넌트들

에 대하여 사용할 수 있습니다.

 

참조

728x90
반응형
그리드형

'프로그래밍(Basic) > 디자인 패턴(JS)' 카테고리의 다른 글

[바미] Compound 패턴  (1) 2022.10.07
[바미] - Hooks 패턴  (2) 2022.10.04
[바미] HOC 패턴  (0) 2022.09.24
[바미] Mediator/Middleware 패턴  (0) 2022.09.23
[바미] Mixin 패턴  (0) 2022.09.22
Bami