Javascript/React

[React] 리액트 state

dev23 2025. 3. 24. 21:37
반응형

- 리액트에서 state 컴포넌트 내부에서 바뀔 있는 의미한다.

- props 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 이며, 컴포넌트 자신은 해당 props 읽기 전용으로만 사용 있다.

- props 바꾸려면 부모 컴포넌트에서 바꿔 줘야 한다.

 

- 리액트에는 가지 종류의 state 있다.

- 하나는 클래스형 컴포넌트가 지니고 있는 state이고, 다른 하나는 함수 컴포넌트에서 useState라는 함수를 통해 사용하는 state 이다.

 

클래스형 컴포넌트의 state

- 새로운 컴포넌트를 만들자. Counter.js 파일을 src 디렉터리에 생성하여 다음 코드를 작성해 보자.

 

* Counter.js

import { Component } from 'react';

class Counter extends Component {
    constructor(props){
        super(props);
        // state의 초기값 설정하기
        this.state = {
            number: 0
        };
    }

    render() {
        const { number } = this.state;  // state를 조회할 때는 this.state로 조회한다.
        return(
            <div>
                <h1>{number}</h1>
                <button
                    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
                    onClick={()=>{
                        // this.setState를 사용해 state에 새로운 값을 넣을 수 있다.
                        this.setState({ number: number + 1});
                    }}>+1</button>
            </div>
        );
    }

}

export default Counter;

- 파일에서 코드가 어떤 역할을 하는지 알아 보자.

- 컴포넌트에 state 설정할 때는 다음과 같이 constructor 메서드를 작성하여 설정한다.

constructor(props){
   super(props);
   // state의 초기값 설정하기
   this.state = {
   number: 0
   };
}

- 이는 생성자 메서드인데, 클래스형 컴포넌트에서 constructor 작성할 때는 반드시 super(props) 호출 주어야 한다.

- 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해 준다.

- 그다음에는 this.state 값에 초기값을 설정해 주었다. 컴포넌트의 state 객체 형식이어야 한다.

 

 - 이제 render 함수를 확인해 보자.

render() {
   const { number } = this.state;  // state를 조회할 때는 this.state로 조회한다.
   return(
       <div>
            <h1>{number}</h1>
            <button
               // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
               onClick={()=>{
                   // this.setState를 사용해 state에 새로운 값을 넣을 수 있다.
                   this.setState({ number: number + 1});
            }}>+1</button>
        </div>
   );
}

- render 함수에서 현재 state 조회할 때는 this.state 조회하면 된다. 그리고 button 안에 onClick이라는 값을 props 넣어 주었는데, 이는 버튼이 클릭될 호출시킬 함수를 설정할 있게 준다. 이를 이벤트를 설정한다고 한다.

- 이벤트로 설정할 함수를 넣어 때는 화살표 함수 문법을 사용하여 넣어 주어야 한다.

- 함수 내부에서는 this.setState라는 함수를 사용했는데, 함수가 state 값을 바꿀 있게 준다.

 

- 코드 작성을 완료 Counter 컴포넌트를 App에서 불러와 렌더링하도록 한다.

 

* App.js

import Counter from './Counter';

const App = () => {
    return (
        <Counter />
    );
}

export default App;

- 브라우저에서 숫자와 버튼이 나타났는가? 버튼을 눌러 보면 숫자가 1 올라갈 것이다.

 

 

state 객체 안에 여러 값이 있을

- state 객체 안에는 여러 값이 있을 있다. Counter 컴포넌트를 다음과 같이 수정해 보자.

 

* Counter.js

import { Component } from 'react';

class Counter extends Component {
    constructor(props){
        super(props);
        // state의 초기값 설정하기
        this.state = {
            number: 0,
            fixedNumber: 0
        };
    }

    render() {
        const { number, fixedNumber } = this.state;  // state를 조회할 때는 this.state로 조회한다.
        return(
            <div>
                <h1>{number}</h1>
                <h2>바뀌지 않는 값: {fixedNumber}</h2>
                <button
                    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
                    onClick={()=>{
                        // this.setState를 사용해 state에 새로운 값을 넣을 수 있다.
                        this.setState({ number: number + 1});
                    }}>+1</button>
            </div>
        );
    }
}

export default Counter;

- 현재 state 안에 fixedNumber라는 다른 값을 추가했다. 버튼이 클릭될 fixedNumber 값은 그대로 두고 number 값만 바꿀 것인데, 그렇다고 해서 this.setState 함수를 사용할 인자로  전달되는 개체 내부에 fixedNumber 넣어 주지는 않았다.

- this.setState 함수는 인자로 전달된 개체 안에 들어 있는 값만 바꾸어 준다.

- 코드를 저장하고 브라우저를 열어서 버튼을 눌러 보자. 위에 있는 숫자만 업데이트되고 하단의 숫자는 고정 있을 것이다.

 

state constructor에서 꺼내기

- 앞에서 state 초깃값을 지정하기 위해 constructor 메서드를 선언해 주었는데, 다른 방식으로도 state 초기값을 지정해 있다.

- 코드를 다음과 같이 수정한다. 

 

* Counter.js

import { Component } from 'react';

class Counter extends Component {
  state = {
    number: 0,
    fixedNumber: 0,
  };

  render() {
    const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회한다.
    return (
      <div>
        <h1>{number}</h1>
        <h2>바뀌지 않는 값: {fixedNumber}</h2>
        <button
          // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
          onClick={() => {
            // this.setState를 사용해 state에 새로운 값을 넣을 수 있다.
            this.setState({ number: number + 1 });
          }}
        >
          +1
        </button>
      </div>
    );
  }
}

export default Counter;

- 이렇게 하면 constructor 메서드를 선언하지 않고도 state 초기값을 설정할 있다.

 

this.setState 객체 대신 함수 인자 전달하기

- this.setState 사용해 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트된다.

- 만약 다음과 같이 onClick 설정한 함수 내부에서 this.setState 호출하면 어떻게 될까?

 

* Counter.js

<button
    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
    onClick={()=>{
    // this.setState를 사용해 state에 새로운 값을 넣을 수 있다.
    this.setState({ number: number + 1});
    this.setState({ number: this.state.number + 1});
}}>+1</button>

- 코드를 위와 같이 작성하면 this.setState 사용하는 것임에도 불구하고 버튼을 클릭할 숫자가 1 더해진다.

- this.setState 사용한다고 해서 state 값이 바로 바뀌지는 않기 때문이다.

 

- 위에 대한 해결책은 this.setState 사용할 객체 대신에 함수를 인자로 넣어 주는 이다.

- this.setState 인자로 함수를 넣어 때는 코드를 다음과 같은 형식으로 작성한다.

this.setState((prevState, props) => {
   return {
   // 업데이트할 내용
   }
})

- 여기서 prevState 기존 상태이고, props 현재 지니고 있는 props 가리킨다.

- 만약 업데이트 과정에서 props 필요하지 않다면 생략해도 된다.

 

- 기존 코드를 다음과 같이 작성한다.

 

* Counter.js - button

<button
     // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
     onClick={()=>{
        this.setState(prevState => {
            return {
                       number: prevState.number + 1
            };
        });

        // 위 코드와 아래 코드는 완전히 똑같은 기능을 한다.
        // 아래 코드는 함수에서 바로 객체를 반환한다는 의미다.
        this.setState(prevState =>({
            number: prevState.number + 1
         }));
             }}
>+1</button>

- 화살표 함수에서 값을 바로 반환하고 싶으면 코드 블록 { } 생략하면 된다.

- 예를 들어, 파라미터 a b 받아 와서 합을 구해 주는 함수를 작성하고 싶다면 다음과 같이 작성할 있다.

 

const sum = (a, b) => a + b;

 

- onClick에서 번째로 this.setState 함수를 사용할 때는 화살표 함수에서 바로 객체를 반환하도록 했기 때문에 prevState => ({ }) 같은 형태로 코드가 이루어진다.

 

this.setState 끝난 특정 작업 실행하기

- setState 사용하여 값을 업데이트하고 다음에 특정 작업을 하고 싶을 때는 setState 번째 파라미터로 콜백(callback) 함수를 등록하여 작업을 처리 있다.

- onClick 함수를 다음과 같이 수정한다.

 

* Counter.js - button

<button
    // onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
    onClick={()=>{
        this.setState(
            {
                number: number + 1
            },
            () => {
                console.log('방금 setState가 호출되었습니다.');
                console.log(this.state);
             }
        );
}}>+1</button>

- 이렇게 콜백 함수를 등록한 브라우저를 열어서 버튼을 누르고 개발자 도구의 [Console] 탭을 확인해 보자.

 

 

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

- 리액트 16.8 이전 버전에서는 함수 컴포넌트에서 state 사용할 없었지만, 이후 버전에서는 useState라는 함수를 사용하여 함수 컴포넌트에서도 state 사용 있게 되었다.

 

 

배열 비구조화 할당

 

- Hooks 사용하기 전에 배열 비구조화 할당이라는 것을 알아보자.

- 배열 비구조화 할당은 객체 비구조화 할당과 비슷하며, 배열 안에 들어 있는 값을 쉽게 추출할 있도록 주는 문법이다.

- 다음 코드를 확인하자.

 

const array = [1, 2];

const one = array[0];

const two = array[1];

 

- array 안에 있는 값을 one two 담아 주는 코드인데, 코드는 배열 비구조화 할당을 사용하면 다음과 같이 표현할 있다.

 

const array = [1, 2];

const [one, two] = array;

 

- 코드가 훨씬 깔끔하다.

 

useState 사용하기

- 배열 비구조화 할당 문법을 알고 나면 useState 사용 방법을 쉽게 이해할 있다.

- 컴포넌트를 만들어서 useState 사용해 보자.

- src 디렉터리에 Say.js 파일을 생성하고 다음 코드를 작성한다.

 

* Say.js

import { useState } from 'react';

const Say = () => {
    const [message, setMessage] = useState('');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');
    return(
        <div>
            <button onClick={onClickEnter}>입장</button>
            <button onClick={onClickLeave}>퇴장</button>
            <h1>{message}</h1>
        </div>
    );
};

export default Say;

- useState 함수의 인자에는 상태의 초기값 넣어 준다.

- 클래스형 컴포넌트에서의 state 초기값은 객체 형태를 넣어 주어야 한다고 배웠는데, useState에서는 반드시 객체가 아니어도 상관없다.

- 값의 형태는 자유이다. 숫자일 수도, 문자열일 수도, 배열일 수도 있다.

 

- useState( ) 함수를 호출하면 배열이 반환되는데, 배열의 번째 원소는 현재 상태이고, 번째 원소는 상태를 바꾸어 주는 함수이다. 함수를 세터(Setter)라고 한다.

- 그리고 배열 비구조화 할당을 통해 이름을 자유롭게 정할 있다.

- 현재 message setMessage라고 이름을 설정했는데, text setText라고 이름을 자유롭게 바꾸어 주어도 상관없다.

 

- Say 컴포넌트를 App에서 렌더링해 보고 [입장] 버튼과 [퇴장] 버튼을 눌러 보자.

 

* App.js

import Say from './Say';

const App = () => {
    return (
        <Say />
    );
}

export default App;

 

컴포넌트에서 useState 여러 사용하기

- useState 여러 사용해도 상관없다. 다른 상태를 useState 관리해 보자.

 

* Say.js

import { useState } from 'react';


const Say = () => {
    const [message, setMessage] = useState('');
    const onClickEnter = () => setMessage('안녕하세요!');
    const onClickLeave = () => setMessage('안녕히 가세요!');


    const [color, setColor] = useState('black');


    return(
        <div>
            <button onClick={onClickEnter}>입장</button>
            <button onClick={onClickLeave}>퇴장</button>
            <h1 style={{ color }}>{message}</h1>
            <button style= {{ color: 'red' }} onClick={() => setColor('red')}>빨간색</button>
            <button style= {{ color: 'green' }} onClick={() => setColor('green')}>초록색</button>
            <button style= {{ color: 'blue' }} onClick={() => setColor('blue')}>파란색</button>
        </div>
    );
};


export default Say;

- 코드를 저장하고 [입장] 버튼을 눌러서 텍스트를 띄워 보자. 그리고 색상이 표시되어 있는 버튼을 눌러 보자. 텍스트 색상이 바뀌는가?

 

state 사용 주의사항

- 클래스형 컴포넌트든 함수 컴포넌트든 state 사용할 때는 주의해야 사항이 있다.

- state 값을 바꾸어야 때는 setState 혹은 useState 통해 전달받은 세터 함수를 사용해야 한다.

- 예를 들어 다음 코드는 잘못된 코드이다.

// 클래스형 컴포넌트에서...
this.state.number = this.state.number + 1;
this.state.array = this.array.push(2);
this.state.object.value = 5;

// 함수 컴포넌트에서...
const [object, setObject] = useState({ a: 1, b: 1});
object.b = 2;

- 배열이나 객체를 업데이트해야 경우에는 어떻게 해야 할까?

- 이런 상황에서는 배열이나 객체 사본을 만들고 사본에 값을 업데이트한 , 사본의 상태를 setState 혹은 세터 함수를 통해 업데이트한다.

- 사본을 만들어서 업데이트하는 예시는 다음과 같다.

 

// 객체 다루기
const object = { a: 1, b: 2, c: 3};
const nextObject = { ...object, b: 2}; // 사본을 만들어서 b 값만 덮어 쓰기

// 배열 다루기
const array = [
    { id: 1, value : true },
    { id: 2, value : true },
    { id: 3, value : false },
];
let nextArray = array.concat({ id: 4});     // 새 항목 추가
nextArray.filter(item => item.id !== 2);    // id 2인 항목 제거
nextArray.map(item => (item.id === 1 ? { ...item, value: false } : item));  // id 1인 항목의 value false로 설정

- 객체에 대한사본을 만들 때는 spread 연산자라 불리는 ... 사용하여 처리하고, 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용한다.

반응형