처음 만난 리액트 문서


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

지금부터는 사용자로부터 온도를 입력받아서 각각 섭씨와 화씨로 표현해주고, 해당 온도에서 물이 끓는지 안 끓는지 여부를 출력해주는 컴포넌트를 만들어 보면서 State를 공유하는 방법에 대해서 더 자세히 살펴 보도록 하겠습니다.

12.2.1 물의 끓음 여부를 알려주는 컴포넌트

먼저 섭씨 온도값을 props로 받아서 물이 끓는지 안 끓는지 여부를 문자열로 출력해주는 컴포넌트를 만들어 보도록 하겠습니다.

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

위 코드는 BoilingVerdict라는 이름을 가진 굉장히 간단한 컴포넌트입니다. 섭씨 온도 값을 props로 받아서 100도 이상이면 물이 끓는다는 문자열을 출력하고, 그 외에는 물이 끓지 않는다는 문자열을 출력합니다. 이제 이 컴포넌트를 실제로 사용하는 부모 컴포넌트를 만들어 보도록 하겠습니다.

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

위 코드에 나온 Calculator라는 컴포넌트는 State로 온도 값을 하나 갖고 있습니다. 그리고 사용자로부터 입력을 받기 위해서 <input>태그를 사용하여 앞에서 배운 Controlled Component형태로 구현되어 있습니다. 사용자가 온도 값을 변경할 때마다 handleChange()함수가 호출되고 setTemperature()함수를 통해 온도 값을 갖고 있는 temperature라는 이름의 State를 업데이트 합니다. 그리고 State에 있는 온도 값은 앞에서 만든 BoilingVerdict컴포넌트에 celsius라는 이름의 props로 전달됩니다.

12.2.2 입력 컴포넌트 추출하기

다음으로는 Calculator컴포넌트 안에 온도를 입력하는 부분을 별도의 컴포넌트로 추출하도록 하겠습니다. 이렇게 하는 이유는 섭씨온도와 화씨온도를 각각 따로 입력받을 수 있도록 하려고 하는데, 재사용 가능한 형태로 컴포넌트를 만들어서 사용하는게 효율적이기 때문입니다.

const scaleNames = {
    c: '섭씨',
    f: '화씨'
};

function TemperatureInput(props) {
    const [temperature, setTemperature] = useState('');

    const handleChange = (event) => {
        setTemperature(event.target.value);
    }

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

위 코드는 온도를 입력받기 위한 TemperatureInput컴포넌트입니다. Calculator컴포넌트에서 온도를 입력받는 부분을 추출해서 별도의 컴포넌트로 만든 것입니다. 추가적으로 props에 단위를 나타내는 scale을 추가하여 온도의 단위를 섭씨 또는 화씨로 입력 가능하도록 만들었습니다. 이제 이렇게 추출한 컴포넌트를 사용하도록 Calculator컴포넌트를 변경하면 아래 코드와 같이 됩니다.

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

총 두 개의 입력을 받을 수 있도록 되어있으며, 하나는 섭씨 온도를 입력받고 다른 하나는 화씨 온도를 입력받습니다. 그런데 여기서 한 가지 문제가 발생합니다. 사용자가 입력하는 온도값이 TemperatureInput의 State에 저장되기 때문에, 섭씨와 화씨 온도 값을 따로 입력 받으면 두 개의 값이 다를 수 있습니다. 이를 해결하기 위해서 값을 동기화 시켜줘야 합니다.

12.2.3 온도 변환 함수 작성하기

먼저 섭씨와 화씨 온도 값을 동기화 시키기 위해서 각각 변환하는 함수를 작성해야 합니다. 아래 함수는 섭씨 온도를 화씨 온도로 변환하는 함수와 화씨 온도를 섭씨 온도로 변환하는 함수입니다.

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()함수는 온도 값과 변환하는 함수를 파라미터로 받아서 값을 변환시켜서 리턴해주는 함수입니다. 만약 숫자가 아닌 값을 입력하면 empty string을 리턴하도록 예외처리를 했습니다. 위 함수를 실제로 사용하는 방법은 아래와 같습니다.

tryConvert('abc', toCelsius)        // empty string을 리턴
tryConvert('10.22', toFahrenheit)   // '50.396'을 리턴

12.2.4 Shared State 적용하기

이제 다음으로는 하위 컴포넌트의 State를 공통된 부모 컴포넌트로 올려서 Shared State를 적용해야 합니다. 여기서 State를 상위 컴포넌트로 올린다는 것을 영어로 Lifting State Up이라고 표현합니다. 영어 단어 Lifting은 올린다는 뜻을 갖고 있는데, 말 그대로 State를 위로 올린다는 의미입니다. 이를 위해서 먼저 TemperatureInput컴포넌트에서 온도값을 가져오는 부분을 아래와 같이 수정해야 합니다.

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

이렇게 하면 온도값을 컴포넌트의 State에서 가져오는 것이 아닌 props를 통해서 가져오게 됩니다. 그리고 컴포넌트의 State를 사용하지 않게 되기 때문에, 입력값이 변경 되었을 때 상위 컴포넌트로 변경된 값을 전달해주어야 합니다. 이를 위해서 handleChange()함수를 아래와 같이 변경합니다.

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

이제 사용자가 온도 값을 변경할 때마다 props에 있는 onTemperatureChange()함수를 통해 변경된 온도 값이 상위 컴포넌트로 전달됩니다. 최종적으로 완성된 TemperatureInput컴포넌트의 모습은 아래와 같습니다. State는 제거되었고 오로지 상위 컴포넌트에서 전달받은 값만을 사용하고 있습니다.

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

12.2.5 Calculator 컴포넌트 변경하기

이제 마지막으로 변경된 TemperatureInput컴포넌트에 맞춰서 Calculator컴포넌트를 변경해주어야 합니다. 아래 코드는 변경된 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>
    );
}

우선 State로 temperaturescale을 선언하여, 온도 값과 단위를 각각 저장하도록 하였습니다. 그리고 이 온도와 단위를 이용하여 변환함수를 통해 섭씨 온도와 화씨 온도를 구해서 사용합니다. TemperatureInput컴포넌트를 사용하는 부분에서는 각 단위로 변환된 온도 값과 단위를 props로 넣어주었고, 값이 변경되었을 때 업데이트 하기 위한 함수를 onTemperatureChange에 넣어주었습니다. 그래서 섭씨 온도가 변경되면 단위가 'c'로 변경되고, 화씨 온도가 변경되면 단위가 'f'로 변경됩니다. 이렇게 최종적으로 완성된 구조를 그림으로 나타내면 아래와 같습니다.

Calculator

상위 컴포넌트인 Calculator에서 온도 값과 단위를 각각의 State로 가지고 있으며, 두 개의 하위 컴포넌트는 각각 섭씨와 화씨로 변환된 온도 값과 단위, 그리고 온도를 업데이트하기 위한 함수를 props로 갖고 있습니다. 이처럼 각 컴포넌트가 State에 값을 갖고 있는 것이 아니라, 공통된 상위 컴포넌트로 올려서 공유하는 방법을 사용하면 리액트에서 더욱 간결하고 효율적으로 개발을 할 수 있습니다.


마지막 업데이트: 2025년 08월 21일 09시 05분

이 문서의 저작권은 이인제(소플)에 있습니다. 무단 전재와 무단 복제를 금합니다.

On this page