ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 리액트 주요개념 - State 끌어올리기
    IT/리액트 2022. 1. 16. 19:28
    728x90

    동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 때가 온다면 가장 가까운 공통 조상으로 state를 끌어올리는 것이 좋습니다.

     

     

    예제: 주어진 온도에서 물의 끊는 여부를 추정하는 온도 계산기

     

    BoilingVerdict: 섭씨온도를 의미하는 celsius prop를 받아 물이 끓기에 충분한 온도인지에 대한 여부를 출력

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

     

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

    class Caculator 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 추가하기

    섭씨 입력 필드 + 화씨 입력 필드(추가!) => 두 필드 간에 동기화 상태를 유지하도록 하겠습니다.

     

    Calculator에서 TemperatureInput 컴포넌트 추출

    // 온도 단위 이름 저장하는 prop
    const scaleName = {
        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 {scaleName[scale]}</legend>
                    <input
                        value={temperature}
                        onChange={this.handleChange} />
                </fieldset>
            );
        }
    }

     

    Calculator에서 온도 입력 필드 렌더링

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

     

    두 개의 입력 필드를 생성하였지만 두 입력 필드 간에 동기화 상태가 유지되지 않는 문제가 생깁니다. 또한 온도 정보가 TemperatureInput Caluclator안에 숨겨져 있어 Calculaor가 그 값을 알 수 없어 BoilingVerdict도 보여줄 수 없습니다.

     

     

    변환 함수

    섭씨 -> 화씨, 화씨 -> 섭씨 로 변환해주는 함수

    //화씨 -> 섭씨
    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();
    }

     

     

    State 끌어올리기

    두 입력 컴포넌트가 독립적인 데이터를 저장하는 것이 아니라 서로 동기화된 상태로 있길 바랍니다. 따라서  두 컴포넌트 간의 가장 가까운 공통 조상으로 State를 끌러올리는 State 끌어올리기(lifting state up)를 사용해야합니다.

    TemperatureInput에 개별적으로 가지고 있던 지역 state를 지우는 대신 Caculator로 그 값을 옮겨놓을 것입니다.

     

    Calculator가 공유될 state를 소유하고 있으면 이 컴포넌트는 두 입력 필드의 현재 온도에 대한 source of truth가 됩니다. 두 TemperatureInput 컴포넌트의 props가 같은 부모인 Calculator로부터 전달되기 때문에, 두 입력 필드는 항상 동기화된 상태를 유지할 수 있습니다.

     

    this.state.temperature를 this.props.temperature로 대체

    props는 읽기 전용이기 때문에 그 값을 temperatureInput에서 제어할 수 없습니다. 따라서 부모인 Calculator로부터 temperature와 onTemperatureChange props를 전달받고 온도를 갱신하고 싶을 때 this.props.onTemperatureChange를 호출합니다.

    onTemperatureChange가 호출된 Calcutaor는 지역 state를 수정해서 변경사항을 저장합니다. 새 값을 전달받은 두 입력 필드는 모두 리렌더링됩니다.

    // 온도 입력받기
    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 {scaleName[scale]}</legend>
                    <input
                        value={temperature}
                        onChange={this.handleChange} />
                </fieldset>
            );
        }
    }

    Calculator 컴포넌트

    temperature와 scale의 현재 입력값을 이 컴포넌트의 지역 state에 저장하고 이것은 입력 필드로부터 끌어올린 state이며 그들에 대한 source of truth로 작용할 것입니다.

    두 입력 필드에 모두 값을 저장하는 것도 가능하지만 가장 최근에 변경된 입력값과 그 값이 나타내는 단위를 저장하는 것만으로도 충분합니다. 그 후 현재의 temperature와 scale에 기반해 다른 입력 필드의 값을 추론할 수 있습니다.

    {
      temperature: '37',
      scale: 'c'
    }
    or
    {
      temperature: '227',
      scale: 'f'
    }

    두 입력 필드의 값이 동일한 state로부터 계산되기 때문에 이 둘은 항상 동기화된 상태를 유지할 수 있습니다.

    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>
            );
        }
    }

    728x90

    댓글

Designed by Tistory.