[React]리액트 기초 - state

Inung_92·2023년 1월 29일
1

React

목록 보기
2/15
post-thumbnail

State란?

📖컴포넌트 내부에서 선언되어 렌더링 결과물에 영향을 주는 데이터를 저장하는 객체 또는 상태

state는 나중에 배울 props와 같이 리액트에서 중요한 역할을 하는 객체라고 이해하면 좋을 것 같습니다. 간단하게 props는 매개변수 형태로 외부로부터 데이터를 전달받지만 state컴포넌트 내부에서 데이터를 다루는 역할을 하는 것이 차이입니다.

⚡️사용목적

state를 사용하는 가장 큰 목적은 공식 자습서에도 나와있듯이 컴포넌트가 사용하는 데이터의 불변성을 확보하는 것에 있습니다. 그렇다면 불변성을 확보하기 위하여 state를 사용하면 어떤 효과가 있는지 알아보겠습니다.

  • 복잡한 특징들을 단순하게 만듬 : 직접적인 데이터 변이를 피하고, 이전 버전의 이력을 유지하여 나중에 재사용할 수 있도록 도와줌.
  • 변화를 감지 : 객체가 직접적으로 수정되면 복제가 가능한 객체에서 변화를 감지하는 것은 어려움. 따라서 불변 객체를 사용하여 참조하고 있는 불변 객체가 이전 객체와 다르면 객체가 변한 것으로 판단할 수 있음.(시간단축)
  • 리액트에서 리-렌더링 하는 시기를 결정 : state 사용을 통해 순수 컴포넌트를 만들어 변하지 않는 데이터의 변경 여부를 쉽게 판단하고, 이를 근거로 컴포넌트가 리-렌더링할 타이밍을 결정

위와 같은 이유로 state를 사용하여 데이터의 직접적인 데이터 변이를 피해 객체의 변화를 판단하여 변화가 있으면 리-렌더링을 해주는데에 도움을 주는 것입니다.

⚡️주의사항

그렇다면 state를 사용하는데 주의해야 할 사항은 무엇인지 알아보겠습니다.

  • 직접 state 수정 X : state를 지정 할 수 있는 유일한 공간은 클래스형일 경우 constructor, 함수형일 경우 함수 중괄호{}와 return 사이입니다. state의 데이터를 수정해줄 수 있는 지정된(사용자정의) 함수를 통해 수정해 주어야 렌더링 타이밍을 포착할 수 있음.

    //잘못된 예시(데이터 직접 수정)
    this.state.comment = 'Hello';
    
    //올바른 예시(함수를 통한 데이터 수정)
    this.setState({comment: 'Hello'});
  • state 업데이트는 비동기적 : 리액트는 성능을 위해 setState() 호출을 단일 업데이트로 한꺼번에 처리가 가능함을 인지해야하며, 업데이트될 때 다음 state의 값에 의존해서는 안됨.

    //잘못된 예시(현재 데이터를 계산)
    this.setState({
        counter: this.state.counter + this.props.increment
        //위와 같이 계산을하면 비동기적으로 변경된 데이터가 반영될 수 없음
    });
    
    //올바른 예시(화살표 함수를 일반함수로 사용해도 가능)
    this.setState((state, props) => ({
        //state의 업데이트가 적용된 시점에 props를 두번째 인자로 받아들임
        counter: state.counter + props.increment
    }));
  • state 업데이트는 병합됨 : state는 다양한 독립적 변수를 포함할 수 있다. 이러한 특성으로 각각 setState() 호출을 통해 독립적으로 변수를 업데이트 가능하다. 아래 코드를 보자.

    constructor (props){
        super(props);
        this.state = {
            //변수를 독립적으로 선언
            posts: [],
            comments: []
        }
    }
    
    //독립적 변수에 각각의 setState() 선언
    componentDidMount(){
        fetchPosts().then(response => {
            this.setState({
              posts: response.posts
            });
        });
        fetchComments().then(response => {
            this.setState({
              comments: response.comments
            });
        });
    }

    병합은 얕게 이루어지기 때문에 각각의 setState() 호출은 해당 변수외에 영향을 주지 않는다. 즉, posts를 setState()할 경우 comments는 영향을 받지 않는다.

  • state는 일반적인 JavaScript 객체이어야 한다.


state의 사용(간단한 예제)

이제 state에 대한 부분을 간단하게 알아보았으니 예제를 통해서 위에서 설명한 부분들을 알아보겠습니다. 예제에 대한 설명은 다음과 같습니다.

  • select태그 : option 2개 구성 -> 분/시간 & km/miles 선택 시 해당 UI출력
  • input태그 : 상단 인풋태그와 하단 인풋태그로 구성하여 각 값을 전환
  • button 태그 : reset은 데이터 초기화, flip은 input태그 상하단 역할 스위치

한번 진행해보도록 하겠습니다.
(출처 : 노마드코더 리액트 기초 강의에서 실습했던 예제입니다.)

🖥️select 태그 컴포넌트

function App(){
  	//state 선언(배열 첫번째 : 현재상태, 두번째 Setter())
  	//useState()의 파라미터는 state의 초기값. 즉 index=0
  	const [index, setIndex] = React.useState(0);
  	//event 함수 선언
  	const onChange = (event) => {
      	//선택된 select option의 value를 state로 변경
    	setIndex(event.target.vaule);
    }
	return(
    //반환될 UI가 작성되는 영역
    <div>
		<h1>Super Converter</h1>
		//select의 현재 value 상태로 출력할 컴포넌트 결정
		//onChange 이벤트를 통해 선택한 시점에 컴포넌트 출력
		<select value={index} onChange={onChange}>
			<option value="1">Minutes & Hours</option>
			<option value="2">Km & Miles</option>
		</select>
    </div>
    )
}

선택된 옵션에 따라 해당하는 컴포넌트를 출력하기 위한 App 컴포넌트 코드이다. 여기서 좀 의아한 부분은 const [index, setIndex] 이 부분이 아닐까 싶다. 왜 저렇게 선언한 것인가? 이유는 useState()를 호출하면 배열이 반환되는데 이 때 배열의 첫번째는 현재 state, 두번째는 state를 변경할 수 있는 Setter를 반환해준다. 이어서 가보자.

🖥️ Minutes & Hours 컴포넌트

function MinuteToHour(){
  	//state 선언
  	const [amount, setAmount] = React.useState(0); //입출력 데이터
  	const [flip, setFlip] = React.useState(false); //기능 전환 논리값
  	//입력 시마다 state 수정
  	const onChange = (event) => {
    	setAmount(event.target.value);
    }
  	const reset = () => setAmount(0);
  	//기능 전환 시 데이터 초기화 및 논리값 전환
  	const onFlip = () => {
    	reset();
      	setFlip((flip) => !flip); //논리값 전환
    }
	return(
      <div>
      
      	<div>
      		//label 클릭 시 해당 input태그로 커서이동
      		<label htmlFor="minutes">Minutes</label>
      		//초기 입력 value를 받을 input
      		<input type="number"
      				value={amount ? Math.round(amount * 60) : amount} 
					id="minutes"
					onChange={onChange}
					disabled={flip} //true일 경우 입력불가
			/>
        </div>
		//flip 상태 - true : hours->minutes, false : minutes->hour 출력
		<div>
      		//label 클릭 시 해당 input태그로 커서이동
        	<label htmlFor="hours">Hours</label>
			//변환된 값을 받으며, flip 클릭 시 입력을 받을 input
      		<input type="number"
					value={amount ? amount : Math.round(amount / 60)}
					id="hours"
					onChange={onChange}
					disabled={!flip} //false일 경우 입력불가
			/>
        </div>

        //클릭 시 입력 데이터 0으로 초기화
        <button onClick={reset}>reset</button>
        //클릭 시 input 입출력 기능 전환
        <button onClick={onFlip}>flip</button>

      </div>
    )
}

각 input은 분과 시를 입력할 수 있으나 지정된 논리값으로 state가 변경된 상태에서만 가능토록 구성된 코드이다. 여기서 논리값이 수정되는 시점은 flip버튼을 클릭하고 onClick()을 통해 setFlip()가 호출된 시점에 수정된다. 다음 코드는 위와 동일한 코드에서 각 계산식만 바꾼내용이니 주석없이 코드만 작성하겠다.

🖥️ Km & Miles 컴포넌트

function KmToMiles() {
  const [distance, setDistance] = React.useState(0);
  const [flag, setFlag] = React.useState(true);
  const onChange = (event) => {
    setDistance(event.target.value);
  }
  const reset = () => setDistance(0);
  const onConvert = () => {
    reset();
    setFlag((current) => !current);
  }
  return (
    <div>
        <div>
        	<label htmlFor="km">Km</label>
        	<input type="number"
    				id="km"
    				placeholder="Km"
    				value={flag ? distance : Math.round(distance * 1.6)}
					onChange={onChange}
					disabled={!flag}
			/>
        </div>

   		<div>
	     	<label htmlFor="miles">Miles</label>
  			<input type="number"
					id="miles"
					placeholder="Mailes"
					value={flag ? Math.round(distance / 1.6) : distance}
                    onChange={onChange}
					disabled={flag}
			/>
      	</div>

		<button onClick={reset}>Reset</button>
		<button onClick={onConvert}>Convert</button>
    </div>
  )
}

이렇게해서 각 컴포넌트들은 완성이 됐다. 이제 select 컴포넌트의 가장 하단부에 선택된 컴포넌트를 출력하는 코드를 추가하겠다.

function APP(){
...state 코드는 생략
return(
	//반환될 UI가 작성되는 영역
	<div>
		<h1>Super Converter</h1>
		<select value={index} onChange={onChange}>
			<option value="1">Minutes & Hours</option>
			<option value="2">Km & Miles</option>
		</select>
		<hr/>
        //인덱스 값에 따라 출력되는 컴포넌트 지정
        {index === 1 ? <MinuteToHour />: null}
        {index === 2 ? <KmToMile />: null}
    </div>
    )
}

//작성 완료 후 render() 호출
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App />);

이렇게 렌더링까지 완료해주면 된다. 실행되는 모습을 아래에서 확인해보자.


마무리

이번 게시글에서는 state를 이용하여 컴포넌트 내에서 데이터를 직접 수정하지 않고 메서드를 통해 수정하는 방법 등에 대해서 알아보았다.

느낀점

  • state는 컴포넌트 내에 정의되고 컴포넌트가 사용할 데이터를 저장하는 객체로 직접수정하는 것보다 메서드를 통해 수정해야 리액트가 리-렌더링하는 타이밍을 포착할 수 있다는 것을 알게됨.
  • 데이터의 수정 시 현재 값과 수정할 값의 변경된 점을 인지하는데에 있어 데이터 자체를 수정하는 것보다 불변 객체를 사용하여 이전 객체와의 차이점을 인식하는 것이 더 빠르고 정확하다는 것을 알게됨.
  • 컴포넌트 내에서 변수를 독립적으로 선언하고 독립적으로 선언된 변수를 조작할 수 있는 각각의 메서드를 사용할 수 있다는 점은 좋으나 다루는 state가 많아질수록 코드가 복잡해질 수 있다는 부분이 우려됨.

리액트를 사용하면서 느끼는 점은 무언가 정리가 되지않던 서랍장에 라벨을 부착해서 구분하여 정리하는 듯한 느낌을 많이 받았음. 하지만 사용하는 요령이나 주의사항을 제대로 알지 못하고 사용하면 오히려 정리를 안하느니만 못한 것 같다는생각을 함. 계속해서 동작원리나 사용하면서 고려해야하는 부분들을 확실히 알아가야겠다는 생각을 하였음.

참고
https://ko.reactjs.org/

profile
서핑하는 개발자🏄🏽

0개의 댓글