[React] Lifting State Up - Shared State

    Shared State


    리액트로 개발을 하다 보면, 하나의 데이터를 여러 개의 컴포넌트에서 표현해야 하는 경우가 생긴다.

    이 경우, 각 컴포넌트의 state에서 데이터를 각각 보관하는 것이 아니라

    가장 가까운 공통된 부모 컴포넌트의 state를 공유해 사용하는 것이 더 효율적이다. 

    이를 Shared State라고 하며, state에 있는 데이터를 여러 개의 하위 컴포넌트에서 공통적으로 사용하는 경우를 말한다.

     

     

    Shared State

    자식 컴포넌트들이 각각 값을 가지고 있을 필요가 없고, 부모 컴포넌트의 state에 연산(*2)를 해주면 된다.

     

     

     

    하위 컴포넌트에서 State 공유하기


    1. 섭씨온도를 props로 받아 물이 끓는지 안 끓는지 문자열로 출력해주는 컴포넌트를 만든다.

    function BoilingVerdict(props) {
      if(props.celsius>=100) {
        return <p>물이 끓습니다.</p>
      }
      return <p>물이 끓지 않습니다.</p>
    }

     

    2. 이 컴포넌트를 사용하는 부모 컴포넌트를 만든다.

    function Calculator(props) {
      const [temperature, setTemperature] = useState('');
    
      const handleChange = (event) => {
        setTemperature(event.target.value);
      }
    
      return (
        <fieldset>
          <legend>섭씨 온도를 입력하세요. :</legend>
          <input 
            value={temperature}
            onChange={handleChange}
          />
          <BoilingVerdict 
            celsius={parseFloat(temperature)}
          />
        </fieldset>
      )
    }

    3. Calculator 안에 온도를 입력하는 부분을 별도로 추출한다.

    ⏩ 섭씨온도와 화씨온도를 각각 따로 입력받아, 재사용 가능한 컴포넌트를 만들기 위해

    const scaleName = {
      c: '섭씨',
      f: '화씨'
    };
    
    function TemperatureInput(props) {
      const [temperature, setTemperature] = useState("");
    
      const handleChange = (event) => {
        setTemperature(event.target.value);
      }
    
      return(
        <fieldset>
          <legend>
            온도를 입력해주세요(단위:{scaleName[props.scale]});
          </legend>
          <input value={temperature} onChange={handleChange} />
        </fieldset>
      )
    }

     

    Calculator 수정

    function Calculator(props) {
      return (
        <div>
          <Temperature scale="c"/>
          <Temperature scale="f"/>
        </div>
      )
    }

    이 경우, 사용자가 입력하는 온도가 TemperatureInput의 state에 저장되므로,

    섭씨온도값과 화씨온도값을 따로 입력받으면 두 개의 값이 다를 수 있다.

    (개별로 존재하는 2개의 컴포넌트에 대해, 입력값이 각각의 state에 따로 저장된다.)

    이를 해결하기 위해서는 값을 동기화시켜 주어야 한다.

     

    4. 온도 변환 함수 작성하기

    function toCelsius(fahrenheit) {
      return (fahrenheit - 32) * 5 / 9
    }
    
    function toFahrenheit(celsius) {
      return (celsius * 9 / 5) + 32
    }

     

    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();
    }
    tryConvert('abc', toCelsius); // empty string 리턴
    tryConvert('10.22', toFahrenheit); //'50.396' 리턴

     

    5. Shared State 적용하기

    하위 컴포넌트의 state를 공통된 부모 컴포넌트의 state로 끌어올린다. ( Lifting State Up )

    function TemperatureInput(props) {
    ...
      return(
        ...
          // 변경전 : <input value={temperature} onChange={handleChange} />
          <input value={props.temperature} onChange={handleChange} />
        ...
      )
    }

    온도값을 컴포넌트의 state에서 가져오는 것이 아닌, props를 통해서 가져온다. (상위 컴포넌트로부터 전달받는다.)

    컴포넌트의 state를 사용하지 않기 때문에, 입력값이 변경되었을 때 상위 컴포넌트로 변경된 값을 전달해야 한다.


    const handleChange = (event) => {
    	// 변경 전 : setTemperatureChange(event.target.value);
    	props.onTemperatureChange(event.target.value);
    }

     

    사용자가 온도값을 변경할 때마다, onTemperature 함수를 통해 변경된 온도값이 상위 컴포넌트로 전달된다.

    이 때 onTemperature 함수는 상위 컴포넌트에 있어 props로 전달 받는다.


     최종적으로 완성된 TemperatureInput 컴포넌트의 모습은 아래와 같다.

    function TemperatureInput(props) {
      const handleChange = (event) => {
        props.onTemperatureChange(event.target.value);
      }
    
      return (
        <fieldset>
          <legend>
            온도를 입력해주세요. (단위 : {scaleNames[props.scale]})
          </legend>
          <input value={props.temperature} onChange={handleChange} />
        </fieldset>
      )
    }

    6. Calculator 컴포넌트 변경하기

    변경된 TemperatureInput 컴포넌트에 맞춰, Calculator 컴포넌트를 변경한다.

    function Calculator(props) {
      const [temperature, setTemperature] = useState("");
      const [scale, setScale] = useState("c");
      
      const handleCelsiusChange = (temperature) => {
        setTemperature(temperature);
        setScale('c');
      }
    
      const handleFahrenheitChange = (temperature) => {
        setTemperature(temperature);
        setScale('f');
      }
    
      const celsius = scale === 'f' ? tryConvert(temperature, toCelsius) :  temperature;
      const fahrenheit = scale === 'c' ? tryConvert(temperature, toFahrenheit) :  temperature;
    
      return (
        <div>
          <TemperatureInput 
            scale="c"
            temperature={celsius}
            onTemperatureChange={handleCelsiusChange}
          />
          <TemperatureInput 
            scale="f"
            temperature={fahrenheit}
            onTemperatureChange={handleFahrenheitChange}
          />
          <BoilingVerdict 
            celsius={parseFloat(celsius)}
          />
        </div>
      )
    }

    컴포넌트 최종 구조



    🚀 참고

    댓글