본문 바로가기

Frontend

Prop Drilling

원문 : https://kentcdodds.com/blog/prop-drilling#why-is-prop-drillinggood

 

이 글의 목표는 Prop Drilling이 무엇인지 이해하는데 도움을 줄 뿐만 아니라, 언제 문제가 될 수 있으며 이를 회피하거나 피하기 위해 사용할 수 있는 메커니즘에 대해 설명하는 것입니다. 

What is prop drilling?

Prop Drilling("스레딩"이라고 함)은 React 컴포넌트 트리의 일부로 데이터를 가져오기 위해 거쳐야 하는 프로세스를 말합니다. 상태를 가지고 있는 컴포넌트의 아주 간단한 예시를 살펴보겠습니다.

 

function Toggle() {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(o => !o)
  return (
    <div>
      <div>The button is {on ? 'on' : 'off'}</div>
      <button onClick={toggle}>Toggle</button>
    </div>
  )
}

 

이제 이를 두 가지 컴포넌트로 리팩터링해 보겠습니다.

 

function Toggle() {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(o => !o)
  return <Switch on={on} onToggle={toggle} />
}

function Switch({on, onToggle}) {
  return (
    <div>
      <div>The button is {on ? 'on' : 'off'}</div>
      <button onClick={onToggle}>Toggle</button>
    </div>
  )
}

 

스위치 컴포넌트에서는 토글 및 켜짐 상태에 대한 참조가 필요하므로 몇 가지 prop을 보내겠습니다. 컴포넌트 트리에 다른 레이어를 추가하기 위해 다시 한 번 리팩터링해보겠습니다. 

 

function Toggle() {
  const [on, setOn] = React.useState(false)
  const toggle = () => setOn(o => !o)
  return <Switch on={on} onToggle={toggle} />
}

function Switch({on, onToggle}) {
  return (
    <div>
      <SwitchMessage on={on} />
      <SwitchButton onToggle={onToggle} />
    </div>
  )
}

function SwitchMessage({on}) {
  return <div>The button is {on ? 'on' : 'off'}</div>
}

function SwitchButton({onToggle}) {
  return <button onClick={onToggle}>Toggle</button>
}

 

이게 Prop Drilling입니다. 켜짐 상태와 토글 핸들러를 올바른 위치로 가져 오려면 Switch 컴포넌트를 통해 Prop Drilling(또는 스레드)해야 합니다. Switch 컴포넌트 자체는 실제로 작동하기 위해 해당 값이 필요하지 않지만, 자식 컴포넌트에는 해당 값이 필요하기 때문에 해당 프로퍼티를 수락하고 전달해야 합니다. 

 

Why is prop drilling good?

전역 변수를 사용하는 애플리케이션에서 작업한 적이 있나요? 분리되지 않은 $scope 상속을 활용하는 AngularJS 애플리케이션은 어떤가요? 커뮤니티에서 이러한 방법론을 대체로 거부하는 이유는 필연적으로 애플리케이션에서 매우 혼란스러운 데이터 모델을 초래하기 때문입니다. 데이터가 어디에서 초기화되고, 어디에서 업데이트되고, 어디에서 사용되는지 누구도 찾기가 어려워집니다. 이 코드를 아무 문제 없이 수정/삭제 할 수 있는가?라는 질문에 답하기란 쉽지 않습니다. 이것이 바로 코딩할 때 최적화해야하는가에 대한 질문입니다.

 

전역 변수보다 ESModule을 선호하는 이유 중 하나는 값이 어디에 사용되는지 더 명확하게 표시할 수 있어 값을 추적하기가 훨씬 쉬워지고 변경 사항이 애플리케이션의 나머지 부분에 어떤 영향을 미칠지 결정하는 과정이 더 간편해지기 때문입니다.

 

기본적인 수준의 Prop Drilling은 애플리케이션의 뷰 전체에 명시적으로 값을 전달하는 것입니다. 위의 토글에서 on 상태를 열거형으로 리팩터링하기로 결정한 경우 코드를 실행할 필요 없이 정적으로 코드를 따라가면 해당 코드가 사용된 모든 위치를 쉽게 추적하고 업데이트할 수 있기 때문에 이 방법이 좋습니다. 여기서 핵심은 불명확성보다 명확성입니다.

 

What problems can prop drilling cause?

위의 인위적인 예시에서는 전혀 문제가 없습니다. 하지만 애플리케이션이 성장함에 따라 여러 계층의 컴포넌트를 사용하게 될 수도 있습니다. 처음에 코드를 작성할 때는 일반적으로 큰 문제가되지 않지만, 몇 주 동안 코드를 작성하고 나면 몇 가지 사용 사례에서 다루기 어려워지기 시작합니다.

 

- 일부 데이터의 모양을 리팩터링합니다. (ie: {user: {name: 'Joe West'}} -> {user: {firstName: 'Joe', lastName: 'West'}})

- 일부 prop이 필요했지만 더 이상 필요하지 않은 컴포넌트를 이동하여 prop을 과도하게 전달(필요 이상으로 많은 prop 전달)하는 경우

- prop과소 전달 + defaultProps를 남용하여 누락된 prop을 인식하지 못하는 경우(컴포넌트 이동으로 인해)

- 중간에 prop이름을 바꾸면(예: <Toggle on={on} /> -> <Switch toggleIsOn={on}/>.) 머리속에서 이를 추적하기 어렵습니다.

 

특히 리팩토링 과정에서 prop drilling이 실제 고통을 유발할 수 있는 상황은 여러 가지가 있습니다. 

 

How can we avoid problems with prop drilling?

prop drilling 문제를 악화시키는 것 중 하나는 렌더링 메서드를 불필요하게 여러 컴포넌트로 분리하는 것입니다. 가능한한 많이 인라인 시키면 렌더링 메서드는 매우 간단해집니다. 너무 성급하게 분리할 필요가 없습니다. 정말 블록을 재사용해야 할 때까지 기다렸다가 블록을 분리하세요. 그러면 prop 전달을 할 필요가 없습니다. 

 

재미있는 사실은 기술적으로 전체 애플리케이션을 하나의 리액트 컴포넌트로 작성하는 것을 막을 수는 없다는 것입니다. 전체 애플리케이션의 상태를 관리할 수 있고 하나의 거대한 렌더링 메서드를 가지게 되니까요. 하지만 저는 이것을 옹호하는 것은 아닙니다. 그냥 생각해볼만한 것일 뿐입니다.

 

prop drilling의 영향을 완화하기 위해 할 수 있는 방법은 필수 prop에 defaultProp을 사용하지 않는 것입니다. 컴포넌트가 작동하는데 실제로 필요한 것에 defaultProp을 사용하면 중요한 오류를 숨기고 조용히 실패하게 만들 뿐입니다. 따라서 필요하지 않은 것에만 기본값 프로퍼티만 사용하세요.

 

상태를 가능한 한 관련성이 있는 위치에 가깝게 유지하세요. 앱의 한 섹션에만 특정 상태가 필요한 경우 앱의 최상위 레벨에 배치하지 말고 해당 컴포넌트의 가장 일반적인 부모에서 관리하세요.

 

리액트 트리 깊숙한 곳에서 정말 필요한 것에는 리액트의 컨텍스트 API를 사용하세요. 애플리케이션의 모든 곳에 필요한 것일 필요는 없습니다. 이렇게하면 prop drilling과 관련된 몇 가지 문제를 피할 수 있습니다. 컨텍스트가 글로벌 변수의 시대로 돌아아고 있다는 지적이 있습니다. 차이점은 API가 설계된 방식 덕분에 컨텍스트의 소스와 모든 소비자를 비교적 쉽게 정적으로 찾을 수 있다는 점입니다. 

 

Conclusion

prop drilling은 좋은 일이 될 수도 있고 나쁜 일이 될 수도 있습니다. 위에서 언급한 몇 가지 모범 사례에 따라 애플리케이션의 유지 관리성을 높이는 기능으로 활용할 수 있습니다.