-
리액트 주요개념 - State 끌어올리기IT/리액트 2022. 1. 16. 19:28728x90
동일한 데이터에 대한 변경사항을 여러 컴포넌트에 반영해야 할 때가 온다면 가장 가까운 공통 조상으로 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'IT > 리액트' 카테고리의 다른 글
리액트 주요개념 - React로 사고하기 (0) 2022.01.16 리액트 주요개념 - 합성 vs 상속 (0) 2022.01.16 리액트 주요개념 - 폼 (0) 2022.01.16 리액트 주요개념 - 리스트와 key (0) 2022.01.16 리액트 주요개념 - 조건부 렌더링 (0) 2022.01.16