[React] State 끌어올리기

MihyunCho·2021년 5월 11일
0

[Front-end] React / Redux

목록 보기
5/11
post-thumbnail

Lifting state up

상태 끌어올리기
현재 상태를 가지고 있는 컴포넌트로부터, 그 컴포넌트에 가장 가까운 부모 컴포넌트에게 State를 끌어 올린다

각각 다른 컴포넌트가 State를 각자 가지고 있는데, 이 상태 끌어올려 부모 컴포넌트에서 통합하고 관리하겠다는 뜻이 담겨있다. 그렇게 관리하면 부모 컴포넌트로부터 props를 통해 받은 데이터를 자식들끼리 연관지어서 렌더링 할 수 있게 되는것이다.

React 공식문서 가져온 예시

주어진 온도에서 물의 끓는 여부를 추정하는 온도 계산기를 예시로 Lifting state up을 정리해보자.

BoilingVerdict

function BoilingVerdict(props) {
  if (props.celsius >= 100) {
    return <p>The water would boil.</p>;
  }
  return <p>The water would not boil.</p>;
}

이 컴포넌트는 섭씨온도를 의미하는 celsius prop를 받아서 이 온도가 물이 끓기에 충분한지 여부를 출력한다.

Calculator

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    return (
      <fieldset>
        <legend>Enter temperature in Celsius:</legend>
        <input value={temperature} onChange={this.handleChange} />
        <BoilingVerdict celsius={parseFloat(temperature)} />
      </fieldset>
    );
  }
}

이 컴포넌트는 온도를 입력할 수 있는 <input> 을 렌더링하고 그 값을 this.state.temperature에 저장하고 현재 입력값에 대한 BoilingVerdict 컴포넌트를 렌더링한다.

새 요구사항으로써 섭씨 입력 필드뿐만 아니라 화씨 입력 필드를 추가하고 두 필드 간에 동기화 상태를 유지하도록 Calculator에서 TemperatureInput 컴포넌트를 빼내는 작업을 한다.
또한 "c" 또는 "f"의 값을 가질 수 있는 scale prop를 추가한다

TemperatureInput

const scaleNames = {
  c: 'Celsius',
  f: 'Fahrenheit'
};

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature}
               onChange={this.handleChange} />
      </fieldset>
    );
  }
}

이제 Calculator가 분리된 두 개의 온도 입력 필드를 렌더링하도록 변경할 수 있다.

Calculator

class Calculator extends React.Component {
  render() {
    return (
      <div>
        <TemperatureInput scale="c" />
        <TemperatureInput scale="f" />
      </div>
    );
  }
}

이제 두 개의 입력 필드를 갖게 되었지만, 둘 중 하나에 온도를 입력하더라도 다른 하나는 갱신되지 않는 문제가 있다. Calculator에서 BoilingVerdict도 보여줄 수 없는 상황이다.
현재 입력된 온도 정보가 TemperatureInput 안에 숨겨져 있으므로 Calculator는 그 값을 알 수 없기 때문이다.

섭씨를 화씨로, 화씨를 섭시로 변환하는 함수를 써준다

function toCelsius(fahrenheit) {
  return (fahrenheit - 32) * 5 / 9;
}

function toFahrenheit(celsius) {
  return (celsius * 9 / 5) + 32;
}

이제 temperature 문자열과 변환 함수를 인수로 취해서 문자열을 반환하는 또 다른 함수를 작성한다

function tryConvert(temperature, convert) {
 const input = parseFloat(temperature);
 if (Number.isNaN(input)) {
   return '';
 }
 const output = convert(input);
 const rounded = Math.round(output * 1000) / 1000;
 return rounded.toString();
}

result
tryConvert('abc', toCelsius)는 빈 문자열을 반환
tryConvert('10.22', toFahrenheit)는 '50.396'을 반환.

현재는 두 TemperatureInput 컴포넌트가 각각의 입력값을 각자의 state에 독립적으로 저장하고 있다.

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
    this.state = {temperature: ''};
  }

  handleChange(e) {
    this.setState({temperature: e.target.value});
  }

  render() {
    const temperature = this.state.temperature;
    // ...

원하는 바는 섭씨온도 입력값을 변경할 경우 화씨온도 입력값 역시 변환된 온도를 반영할 수 있어야 하며, 그 반대의 경우에도 마찬가지여야 한다.

React에서 state를 공유하는 일은 그 값을 필요로 하는 컴포넌트 간의 가장 가까운 공통 조상으로 state를 끌어올림으로써 이뤄낼 수 있다. 이렇게 하는 방법을 state 끌어올리기라고 부른다.

TemperatureInput이 개별적으로 가지고 있던 지역 state를 Calculator로 옮겨야한다.
그렇게하면 두 TemperatureInput 컴포넌트의 props가 같은 부모인 Calculator로부터 전달되기 때문에, 두 입력 필드는 항상 동기화된 상태를 유지할 수 있게 된다.

TemperatureInput

class TemperatureInput extends React.Component {
  constructor(props) {
    super(props);
    this.handleChange = this.handleChange.bind(this);
  }

  handleChange(e) {
    this.props.onTemperatureChange(e.target.value);
  }

  render() {
    const temperature = this.props.temperature;
    const scale = this.props.scale;
    return (
      <fieldset>
        <legend>Enter temperature in {scaleNames[scale]}:</legend>
        <input value={temperature} onChange={this.handleChange} />
      </fieldset>
    );
  }
}

Calculator

class Calculator extends React.Component {
  constructor(props) {
    super(props);
    this.handleCelsiusChange = this.handleCelsiusChange.bind(this);
    this.handleFahrenheitChange = this.handleFahrenheitChange.bind(this);
    this.state = {temperature: '', scale: 'c'};
  }

  handleCelsiusChange(temperature) {
    this.setState({scale: 'c', temperature});
  }

  handleFahrenheitChange(temperature) {
    this.setState({scale: 'f', temperature});
  }

  render() {
    const scale = this.state.scale;
    const temperature = this.state.temperature;
    const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) : temperature;
    const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) : temperature;

    return (
      <div>
        <TemperatureInput
          scale="c"
          temperature={celsius}
          onTemperatureChange={this.handleCelsiusChange} />
        <TemperatureInput
          scale="f"
          temperature={fahrenheit}
          onTemperatureChange={this.handleFahrenheitChange} />
        <BoilingVerdict celsius={parseFloat(celsius)} />
      </div>
    );
  }
}

이제 어떤 입력 필드를 수정하든 간에 Calculator의 this.state.temperature와 this.state.scale이 갱신된다.
입력 필드 중 하나는 있는 그대로의 값을 받으므로 사용자가 입력한 값이 보존되고, 다른 입력 필드의 값은 항상 다른 하나에 기반해 재계산된다.

profile
Sic Parvis Magna 🧩

0개의 댓글