[React Docs 번역] Lifting State Up

김승엽
Berkbach
12 min readApr 23, 2020

--

Photo by Mae Mu on Unsplash

🏋️‍♂️ State 끌어올리기

때로 같은 데이터에 대해 여러 개의 컴포넌트가 반응해야 합니다. 이럴때는 가까운 조상 컴포넌트에게 state 를 공유하는 방법을 추천합니다. 이 섹션에서 state를 끌어올릴 때 어떻게 작동하는지 알아보겠습니다.

이번 섹션에서는 온도를 줬을 때 물이 끓는 온도인지 알려주는 온도 계산기를 만들겠습니다. BoilingVerdict 라는 컴포넌트로 시작하겠습니다. 이것은 celsius 온도를 props 로 받아서 물이 끓는지 보여줍니다.

다음 Calculator 라는 컴포넌트를 만들겠습니다. 온도를 입력할 수 있는 <input> 을 렌더링 하고 this.state.temperature 로 보관합니다. 추가로 BoilingVerdict 를 렌더링 합니다.

⌨️ 두 번째 입력 값 추가하기

현재 섭씨 입력 필드만 있지만 화씨 입력을 제공하고 두 입력을 동기화 하도록 하는 새로운 요구사항이 생겼습니다. Calculator 에서 TemperatureInput 으로 분리해서 시작하겠습니다. “c” 또는 “f” 로 될 수 있는 scale 이라는 새로운 props 를 추가하겠습니다.

Calculator 가 두 가지 온도 입력 필드 렌더링을 하도록 하겠습니다.

이제 두 가지 입력 필드가 있습니다. 하지만 둘 중 어떤 것도 온도를 입력했을 때 다른 입력 필드의 업데이트가 일어나지 않습니다. 이것은 동기화하려는 요구사항과 맞지 않습니다. 또한 TemperatureInput 안에 숨겨져 있는 현재 온도를 Calculator 는 알 수 없기 때문에 CalculatorBoilingVerdict 를 보여줄 수 없습니다.

🔄 변환 함수 작성하기

일단 섭씨 에서 화씨로, 화씨에서 섭씨로 바꾸는 두 가지 함수를 만들겠습니다.

이 두 가지 함수는 숫자를 변환합니다. 문자열 temperature 을 받아서 다른 문자열을 리턴하는 함수를 작성하겠습니다. 한 입력 값을 기반으로 다른 입력 값을 계산하기 위해 사용하겠습니다.

유효하지 않은 temperature 는 빈 문자열을 리턴하고 유효한 문자열은 출력 값을 소수점 3자리까지 반올림해서 리턴합니다.

예로 tryConvert(‘abc’, toCelsius) 는 빈 문자열을 리턴하고 tryConvert(‘10.22’, toFahrenheit)‘50.396’ 을 리턴합니다.

🏗️ State 끌어올리기

현재 두 가지 TemperatureInput 컴포넌트 모두 state를 독립적으로 관리합니다.

하지만 우리는 두 가지가 동기화되길 원합니다. 섭씨 입력 필드가 업데이트 됐을 때 화씨 입력 필드에도 변환된 온도가 반영되어야 합니다. 그 반대의 경우도 마찬가지 입니다. React에서 state를 공유하는 일은 각 컴포넌트가 필요로 할 때 가까운 조상에게 끌어올려서 해결할 수 있습니다. 이런 방법을 “state 끌어올리기 ( lifting state up )” 로 불립니다. TemperatureInput 의 state를 지우고 대신 Calculator 로 옮기겠습니다.

Calculator 가 공유할 state를 가지고 있다면 그것은 두 가지 입력의 현재 온도의 “진실의 근원 ( source of truth )” 가 됩니다. 이것은 두 입력 모두 일치하는 값을 가지도록 지시할 수 있습니다. 두 가지 TemperatureInput 컴포넌트 모두 같은 Calculator 컴포넌트로 부터 props 를 받기 때문에 항상 동기화 됩니다.

동작하는 방법을 순서대로 보겠습니다.

먼저 TemperatureInput 컴포넌트의 this.state.temperaturethis.props.temperature 로 바꾸겠습니다. 지금은 this.props.temperature 의 데이터가 존재한다고 가정하겠습니다. 나중에 Calculator 에서 전달할 것 입니다.

알다시피 props 은 읽기 전용 입니다. temperature 가 state 였을 때 TemperatureInputthis.setState() 를 호출해서 변경할 수 있었습니다. 하지만 지금은 temperature 가 부모로부터 props 로 받기 때문에 TemperatureInput 는 더 이상 temperature 를 제어할 수 없습니다.

React에서 대게 컴포넌트를 “제어" 가능하게 만들어서 해결합니다. DOM의 <input>valueonChange 를 props 로 받는 것처럼 TemperatureInputtemperatureonTemperatureChangeCalculator 로 부터 props 로 받을 수 있습니다. 이제 TemperatureInput 가 온도 업데이트를 원할 때 this.props.onTemperatureChange 를 호출합니다.

onTemperatureChange props 는 temperature props 와 같이 Calculator 에서 제공됩니다. 이것은 자신의 state가 바뀔 때 마다 두 가지 입력이 재 렌더링 (re-rendering) 이 일어납니다. Calculator 의 변경사항을 보기전에 TemperatureInput 컴포넌트의 변경사항을 먼저 요약하겠습니다. TemperatureInput 의 state를 지웠습니다. 그리고 this.state.temperaturethis.props.temperature 로 바꿨습니다. this.setState() 호출 대신 변화를 원할 때 Calculator 로 받은 this.props.onTemperatureChange() 를 호출합니다.

이제 Calculator 컴포넌트를 보겠습니다. 우리는 현재 입력한 temperaturescale 을 state 에 저장할 것 입니다. 이것은 입력 필드에서 끌어올린 state 입니다. 그리고 TemperatureInput 컴포넌트에게 진실의 근원으로 작동합니다. 또한 두 입력창을 렌더링 하기 위해서 알아야 하는 모든 데이터를 최소한으로 표현한 것이기도 합니다.

예로 섭씨 입력창에 37을 입력하면 Calculator 컴포넌트의 state 는 이렇게 됩니다.

화씨로 212를 입력하면 이렇게 됩니다.

두 가지 입력 값을 저장할 수 있습니다. 하지만 이것은 중요하지 않습니다. 가장 최근에 바뀐 값과 그 값을 나타내는 scale 을 저장하면 충분합니다. 그렇다면 현재 temperaturescale 에 기반하는 다른 입력 값을 추론할 수 있습니다. 입력 값이 같은 state 에서 계산되기 때문에 입력은 동기화 상태로 있습니다.

이제 Calculator 안에 this.state.temperaturethis.state.scale 가 업데이트 됩니다. 두 개의 입력 중 하나에 값을 입력하면 다른 입력창에 계산이 된 값이 표시됩니다.

입력 값을 수정 했을 때 무슨 일이 일어나는지 요약해보겠습니다.

  • React가 DOM의 <input> 에서 onChange 로 명시된 함수를 호출합니다. 위 예제의 경우는 TemperatureInput 컴포넌트 안에 있는 handleChange 메소드 입니다.
  • TemperatureInput 컴포넌트 안 handleChange 메소드는 새로운 값을 인자로 this.props.onTemperatureChange() 호출합니다. props 는 Calculator 부모 컴포넌트에서 왔습니다.
  • Calculator 에서 섭씨 TemperatureInputonTemperatureChangehandleCelsiusChange 메소드, 화씨 TemperatureInputonTemperatureChangehandleFahrenheitChange 메소드를 명시했습니다. 따라서 입력 필드에 따라서 두 메소드 중 하나를 호출됩니다.
  • 메소드를 살펴보면 Calculator 컴포넌트는 React에게 새로운 입력 값과 함께 this.setState() 를 호출해서 재 렌더링 (re-rendering) 요청합니다.
  • React는 UI의 모양을 보여주기 위해서 Calculator 컴포넌트의 render 메소드를 호출합니다. 두 가지 입력 값 모두 temperaturescale 에 기반해서 다시 계산됩니다. 온도 변환은 여기서 일어납니다.
  • React가 각 TemperatureInput 컴포넌트의 render 메소드를 Calculator 에 명시된 새로운 props 과 함께 호출합니다. 여기서 그들의 UI가 어떤 모습인지 알 수 있습니다.
  • React가 섭씨 온도를 props 로 BoilingVerdictrender 메소드를 호출합니다.
  • React DOM이 입력 값과 맞는 BoilingVerdict 컴포넌트의 결과 값을 DOM에 업데이트합니다. 작성한 입력창과 다른 입력창은 계산이 끝난 온도를 업데이트합니다.

입력창이 동기화 되있기 때문에 모든 업데이트는 같은 순서로 작동합니다.

🤔 교훈

React 애플리케이션 안에서 변경이 일어나는 데이터에 대해서는 “진실의 근원” 을 하나만 두어야 합니다. 보통 state 는 렌더링에 필요할 때 처음 추가됩니다. 만약 다른 컴포넌트도 필요로 할 때 가장 가까운 공통조상에게 올려줍니다. 다른 컴포넌트 사이에 state 를 동기화하려고 하지 말고 하향식 데이터 흐름 에 의존해야 합니다.

state 를 끌어올리는 작업은 양방향 바인딩 접근 방식보다 더 많은 “보일러 플레이트” 코드를 유발하지만, 버그를 찾고 격리하기 더 쉽게 만든다는 장점이 있습니다. 어떤 state 든 간에 특정 컴포넌트 안에서 존재하기 마련이고 그 컴포넌트가 자신의 state 를 스스로 변경할 수 있기 때문에 버그가 존재할 수 있는 범위가 크게 줄어듭니다. 또한 사용자의 입력을 거부하거나 변형하는 자체 로직을 구현할 수도 있습니다.

props 나 state 중 어떤거든 파생할 수 있다면 이것은 state 가 되지 않을 것입니다. 예로 celsiusValuefahrenheitValue 를 저장하는 것 대신 마지막에 작성한 temperaturescale 만 저장할 수 있습니다. 다른 입력 값은 render() 안에서 항상 계산될 수 있습니다. 이를 통해 사용자 입력값의 정밀도를 유지한 채 다른 필드의 입력값에 반올림을 지우거나 적용할 수 있게 됩니다.

UI에서 무언가 잘못된 부분이 있을 경우, React Developer Tools를 이용하여 props를 검사하고 state를 갱신할 수 있는 컴포넌트를 찾을 때까지 트리를 따라 탐색해보세요. 이렇게 함으로써 소스 코드에서 버그를 추적할 수 있습니다.

📚 정리 및 참고 자료

  1. React에서 state를 공유하려면 공통 조상 컴포넌트까지 state를 올려서 해결할 수 있습니다.
  2. 조상 컴포넌트에서 Event handler 를 props로 전달하면 하위 컴포넌트에서 상위 컴포넌트의 state를 제어를 할 수 있습니다.
  3. React 애플리케이션 안에서 변경이 일어나는 데이터에 대해서는 “진실의 근원” 을 하나만 두어야 합니다
  4. 보일러 플레이트란 반드시 작성해야하지만 반복적인 코드를 의미합니다.
  5. React 보일러 플레이트 예시

--

--