if(재능이 없으면 시간으로 극복하라){return 성공;}

React 05 - State란 무엇이고 어떻게 사용할까?

state란 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props 같은 경우는 부모 컴포넌트가 넘겨 주는 값을 읽기 전용으로만 사용할 수 있다. 하지만 개발을 하는데에 있어서 값은 유동적으로 바뀔 수 있다. props의 값을 바꾸려면 그 때 마다 부모의 컴포넌트의 넘겨주는 속성을 바꿔야 하는데 이건 실질적으로 값이 바뀔 때마다 재배포를 해야한다는 의미로도 볼 수 있다. 이러한 부분을 해결 하기 위해 사용하는게 바로 state이다. state는 2가지 유형으로 분류 할 수 있다.

  1. 클래스형 컴포넌트가 지니고 있는 stete
  2. 함수형 컴포넌트에서 사용하는 useState

클래스형 컴포넌트의 state

아래 소스를 확인해보자

import { Component } from 'react';

class StateComponent extends Component {
    constructor(props){
        super(props);                       //부모 클래스의 생성자를 호출
        this.state = {                      //state가 기본적으로 내장되어 있어서 별도로 선언을 하거나 import를 하지 않는다.
            number: 0
        };
    }
    render(){
        const { number } = this.state;      //state 호출할 때는 this.state로!
        return (
            <div>
                <h1>{number}</h1>
                <button 
                    onClick={() =>{
                        this.setState({number: number+1})
                    }}>increase!</button>
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}

export default StateComponent;

increase 버튼을 누르거나 decrease 버튼을 눌러도 화면에 깜빡임없이 데이터가 바뀐다.

image

값이 여러개 일때는 위에 state에 값을 추가해주면 된다.

import { Component } from 'react';

class StateComponent extends Component {
    constructor(props){
        super(props);           //부모 클래스의 생성자를 호출
        this.state = {          //state가 기본적으로 내장되어 있어서 별도로 선언을 하거나 import를 하지 않는다.
            number: 0,
            number2: 0
        };
    }
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState({number: number+1})
                    }}>increase!</button>
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}

export default StateComponent;

image

state안에 여러개를 넣고 사용해도 괜찮다.

그리고 많은 사람들이 constructor(생성자)나 super() 같은 부분들에 대해서 자연스럽게 사용하지 못하고 있다고 생각한다.

위와 같은 번거로운 과정이 없더라도 사용할 수 있는 방법이 있다.

import { Component } from 'react';

class StateComponent extends Component {
    state = {
        number: 0,
        number2: 0
    };
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState({number: number+1})
                    }}>increase!</button>
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}

export default StateComponent;

사실 그냥 state만 선언해줘도 된다.

누가 그럼 생성자를 써서 사용할까?

그렇다면 함수안에 여러번 setState를 여러개를 넣거나 (실제론 보통 이런 일은 없을테지만..) this.state로 직접 값을 업데이트 해버리면 어떻게 될까?

import { Component } from 'react';

class StateComponent extends Component {
    state = {
        number: 0,
        number2: 0
    };
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState({number: number+1});
                        this.setState({number: number+2});
                    }}>increase!</button>
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}
export default StateComponent;

위처럼 코딩을 해버린다면

image

3이 증가하는게 아니라 2씩 증가해버린다. 그 이유는 this.setState는 비동기적으로 업데이트를 해버리기 때문이다. 그래서 setState를 여러번 들어간다면 하나 빼고 누락이 되는 것처럼 보일 수 있다는 말이다. 윗줄 / 아랫줄 랜덤으로 보여줄 줄 알았는데 아랫줄만 적용이 되는것처럼 보인다. –> 이 이유는 값이 갱신되기 이전에 값을 업데이트 쳐버리니까 그 이전의 값이 필요없다고 생각하여 react에서 알아서 연산을 안해버린다고 한다.

그렇다면 여러번 사용해야할 경우는 어떻게 해야할까?

(물론 함수내의 연산식에서 한번에 해결하는게 더욱 깔끔할 것이고.. 이런 수학적인 사고가 필요한 덕분에 코딩은 수학을 잘하는 사람들이 더 잘할거라는 낭설도 생긴것이라 생각한다.) (우리의 주목적은 위처럼 쓴다면 비동기 방식으로 값이 업데이트 된다는 것정도만 알면 될 것 같다.)

import { Component } from 'react';

class StateComponent extends Component {
    state = {
        number: 0,
        number2: 0
    };
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState(prevState => {
                            return {number: prevState.number + 1}
                        });
                        this.setState(prevState => {
                            return {number: prevState.number + 2}
                        });
                    }}>increase!</button> 
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}

export default StateComponent;

위 방식처럼 사용하게 된다면 정상적으로 3씩 증가하게 된다. image

그렇다면 우리는 prevState라는 인자값에 대해서 알 필요가 있다. 이전 값을 참조하여 새로운 값을 업데이트를 친다는 의미로 알면 될 것 같다.

근데 위 문법이 너무나도 눈에 잘 안들어온다..

import { Component } from 'react';

class StateComponent extends Component {
    state = {
        number: 0,
        number2: 0
    };
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState(prevState => ({
                            number: prevState.number + 1
                        }));
                        this.setState(prevState => ({
                            number: prevState.number + 2
                        }));
                    }}>increase!</button> 
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}

export default StateComponent;

이렇게하면 조금 더 눈에 잘 보이게 표현할 수 있다. 모든 값처리가 끝나면 실행하도록 하는 콜백 함수는 어떻게 사용할까?

import { Component } from 'react';

class StateComponent extends Component {
    state = {
        number: 0,
        number2: 0
    };
    render(){
        const { number, number2 } = this.state;
        return (
            <div>
                <h1>{number}</h1>
                <h1>{number2}</h1>
                <button 
                    onClick={() =>{
                        this.setState(prevState => ({
                            number: prevState.number + 1
                        }),
                        () => {
                            console.log("콜백 1 끝!")
                        });
                        this.setState(prevState => ({
                            number: prevState.number + 2
                        }),
                        () => {
                            console.log("콜백 2 끝!")
                        });
                    }}>increase!</button> 
                <button
                    onClick={() =>{
                        this.setState({number: number-1})
                    }}>decrease!</button>
            </div>
        )
    }
}
export default StateComponent;

setState내에서 콜백 함수를 ,로 이어주면 된다.

image

이 정도면 클래스형 컴포넌트 내에서 state를 사용한건 마무리해도 될 것 같다.

함수형 컴포넌트에서 사용하는 useState

원래는 함수형 컴포넌트에서는 state를 사용하지 못했다고 한다. 리액트가 버전업이 되면서 state를 사용할 수 있게 되었다고 하는데 우리는 앞서 함수형 컴포넌트와 클래스 컴포넌트의 가장 큰 차이점을 state와 props의 사용 여부라고 학습했었다.

클래스 컴포넌트를 사용하면 모든게 해결될 것 같지만, 예전에 함수형 컴포넌트로 개발되었던 부분들은 어떻게 수정할 것인가에 대해서도 우리는 고민을 해봐야한다.

그러한 이유로 함수형 컴포넌트에서는 state를 어떻게 쓰는지 알아보자~!

import { useState } from "react";
const ButtonComp = () => {
    const [message, setMessage] = useState('');
    const onClick1 = () => setMessage('1번 버튼 클릭');
    const onClick2 = () => setMessage('2번 버튼 클릭');

    return (
        <>
            <button onClick={onClick1}>버튼1</button>
            <button onClick={onClick2}>버튼2</button>
            <h1>{message}</h1>
        </>
    );
};

export default ButtonComp;

위와 같이 버튼 두개를 만들고 버튼을 누를 때 h1태그에 값을 삽입해주는 코드를 만들어 보았다.

onClick에 onClick1이나 onClick2함수가 바인딩 되어서실행되는거 까지는 알겠는데, setMessage가 message변수에 어떻게 값을 할당하는 것일까?

책에서 아무리 찾아도 관련 내용은 없었다..

그래서 구글링을 통해 useState를 찾아보니

useState는 배열을 return하고 배열의 첫 번째 요소는 <상태 값="" 저장="" 변수="">(이하 state), 두 번째 요소는 <상태 값="" 갱신="" 함수="">(이하 setState)을 반환한다고 한다.

위 과정을 설명해보자면

const messageState = useState('');      //messageState라는 useState는 배열을 반환하는데
const message = messageState[0];        //messageState의 0번째 값은 ''으로 변수를 저장하고
const setMessage = messageState[1];     //messageState의 값을 셋팅하는(이하 Setter) 함수이자 함수명이 setMessage로

와 같은 구조로 만들어줘야 한다. 이러한 구조를 배열 비구조화 할당을 하게 된다면

const [message, setMessage] = userState('');

위와 같이 간편하게 바꿀 수 있다는 말이다.

덧붙여 개발을 할 때 매번 한가지 변수로 개발을 진행하지 않는다.

이와 같은 경우는 값을 여러개 관리 해야하는데 useState를 여러개 사용해보자.

import { useState } from "react";
const ButtonComp = () => {
    const [message, setMessage] = useState('');
    const onClick1 = () => setMessage('1번 버튼 클릭');
    const onClick2 = () => setMessage('2번 버튼 클릭');
    const [message2, setMessage2] = useState('');
    const onClick3 = () => setMessage('3번 버튼 클릭');
    const onClick4 = () => setMessage('4번 버튼 클릭');
    return (
        <>
            <button onClick={onClick1}>버튼1</button>
            <button onClick={onClick2}>버튼2</button>
            <h1>{message}</h1>
            <button onClick={onClick3}>버튼3</button>
            <button onClick={onClick4}>버튼4</button>
            <h1>{message2}</h1>
        </>
    );
};

export default ButtonComp;

image

State의 개념이나 사용 방법에 대해서 쭉 알아보았다. State는 값을 바꾸려 할 때 setState(클래스 컴포넌트에서) setter(함수형 컴포넌트에서)를 이용해서 바꿔야한다.

일반적인 변수의 값을 바꾸는 것처럼 바꿔서는 안된다. setState를 사용하거나 setter를 사용하면 값을 바로 바꿀 수 있지만 배열같은 형태의 내부값을 바꾸고 싶으면 어떻게 해야할까?

처음으로 셋팅한 state값을 사본으로 복사하고 사본의 값을 변경 한 뒤에 setState나 setter를 통하여 객체 자체를 다시 셋팅해주면 된다.

다음은 Event 관련 포스팅에서 뵙겠습니다.

Event Handling 해보자