> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/web/develop-page/js/reactjs/cdn/component/class-component/state.md).

# state

## state

state는 컴포넌트 내부에서 화면과 상호 작용하는 데이터를 저장하기 위한 도구이다. 화면에 해당하는 핵심 데이터들을 state로 저장하고 관리하여 변동 사항을 화면에 반영하도록 구현한다.

<div align="left"><figure><img src="/files/LZLl7LxPivPyuPbxhrfz" alt=""><figcaption><p>state는 화면 구성을 위한 값의 모음이다</p></figcaption></figure></div>

## state 선언

클래스 컴포넌트에서 state는 다음과 같이 선언한다.

```javascript
class MyComponent extends React.Component {
    state = {
        //key : value
    };
}
```

클래스 내부에 state라는 이름으로 객체를 선언하며, 다른 이름으로 생성할 경우 setState 함수 등 부가적으로 제공되는 기능을 사용할 수 없으므로 이름을 지켜야 한다.

## state 출력

render 함수에서 state를 출력할 수 있다. 또한 클래스 내부의 모든 메소드에서 `this.state` 키워드로 state 항목에 접근할 수 있다.

```jsx
class MyComponent extends React.Component {
    state = {
        text : "This is react component"
    };
    
    render(){
        return {
            <>
                <h1>{this.state.text}</h1>
            </>
        }
    }
}
```

{% embed url="<https://codepen.io/hiphop5782/pen/LYrYqZN>" %}

글자 색상도 state로 설정할 수 있다.&#x20;

```javascript
class MainComponent extends React.Component {
    state = {
        text : "This is react component",
        color:"blue",
    };
    
    render(){
        return (
            <>
                <h1 style={{color:this.state.color}}>{this.state.text}</h1>
            </>
        );
    }
}
```

{% embed url="<https://codepen.io/hiphop5782/pen/RwJwvGV>" %}

매번 `this.state`를 출력하는 것은 번거롭기 때문에 다음과 같이 ES6의 구조 분해 할당 구문으로 사용할 수 있다.

```javascript
class MainComponent extends React.Component {
    state = {
        text : "This is react component",
        color:"blue",
    };
    
    render(){
        const {text, color} = this.state;
        return (
            <>
                <h1 style={{color:color}}>{text}</h1>
            </>
        );
    }
}
```

this.state에서 필요한 내용들을 구조 분해 할당하여 사용하면 코드가 비교적 간단해진다. 자세한 원리에 대해서는 다음 문서를 참고한다.

{% content-ref url="/pages/9EUJxuEYZipKPiveOctM" %}
[구조 분해 할당](/web/develop-page/js/syntax/destructuring-assignment.md)
{% endcontent-ref %}

{% embed url="<https://codepen.io/hiphop5782/pen/BaVaMQY>" %}

## state 변경

{% hint style="info" %}
state는 직접 변경을 금지한다.
{% endhint %}

state를 변경해야 하는 경우 setState 함수를 사용하여 변경한다. state를 직접 변경하면 render 함수가 호출되지 않아 화면이 데이터와 맞지 않게 되므로 **state는 불변성(immutable)을 유지**해야 한다.

<div align="left"><figure><img src="/files/78boDIxrWi3Y083vn9zg" alt=""><figcaption></figcaption></figure></div>

### 단일 항목 변경

단일 항목의 경우 클래스 내부의 state에 다음과 같이 선언되어 있다.

```javascript
state = {
    number : 10
}
```

위와 같이 선언된 state의 number를 20으로 변경하려면 다음과 같이 작성한다.

```javascript
this.setState({number : 20});
```

### 객체 변경

state에 객체가 있을 경우는 다음 형태와 같다.

```javascript
state = {
    monster : {
        no : 25,
        name : "피카츄",
        type : "전기"
    }
};
```

이 경우 객체 전체를 바꾸고 싶을 때는 다음과 같이 setState 함수를 호출한다.

```javascript
this.setState({
    monster : {
        no : 1,
        name : "이상해씨",
        type : "풀"
    }
});
```

### 객체 일부 변경

객체에서 특정 항목만 변경하고 싶다면 this.setState에 객체가 아닌 함수를 넣어서 이전 상태 객체(prevState) 정보를 사용해야 한다.

```javascript
this.setState(prev=>({ /* 변경할 내용 작성 */}));
```

위 코드에서 prev는 이전 상태 객체를 말한다. prev를 사용하여 몬스터의 번호만 바꾸면 다음과  같다.

```javascript
this.setState(prev=>({
    monster : {
        ...prev.monster,
        no : 100
    }
}));
```

### <mark style="color:red;">객체의 일부 변경 시 주의사항</mark>

prevState 대신 this.state를 사용하지 않아야 한다.

```javascript
this.setState(()=>({
    ...this.state.monster,
    no : 100
}));
```

setState 함수는 비동기로 실행되며 연속 실행될 경우 마지막에 렌더링이 되기 때문에 this.state를 사용하여 setState를 연속 실행할 경우 마지막 결과로 덮어쓰기 되어 렌더링 될 수 있다. prevState를 사용하여 변경할 경우 직전 실행 결과에 대한 콜백 인수를 사용하기 때문에 setState 연속 실행 시 상태가 이어져 문제가 생기지 않는다. (하단 그림 참조)

<div align="left"><figure><img src="/files/fKdUBZy8LzfZgTv3mv32" alt=""><figcaption></figcaption></figure></div>

### 배열 변경

배열의 경우 여러 가지 변경이 있을 수 있다.

* 배열 전체 교체
* 배열 특정 인덱스 값 변경
* 배열 데이터 추가
* 배열 데이터 삭제

### 배열 전체 교체

배열을 전체 교체하는 상황은 다음과 같을 수 있다.

* 기존 배열의 위치를 랜덤으로 뒤섞는(shuffle) 경우
* 기존 배열에서 검색(filter)하여 새로운 배열을 만드는 경우

[#undefined](#undefined "mention")과 동일하게 진행한다

```javascript
const copyArr = [...this.state.arr];
//copyArr에 대한 처리 수행
this.setState({arr:copyArr});
```

{% hint style="info" %}
배열을 복사하는 방법은 여러 가지가 있으며, 예제 코드에서는 **전개 연산자(spread operator)**&#xB97C; 사용하였다.
{% endhint %}

### 예제 : 배열 섞기

섞기 버튼을 누르면 기존에 저장된 5마리의 몬스터 이름의 위치가 무작위로 변경되는 예제이다.

```jsx
class MainComponent extends React.Component {
    state = {
        monsters : ["피카츄", "라이츄", "파이리", "꼬부기", "버터플"]
    };

    shuffle = e=>{
        const monstersCopy = [...this.state.monsters];
        for(var i=0; i < monstersCopy.length; i++){
            const pos = Math.floor(Math.random() * monstersCopy.length);
            const temp = monstersCopy[pos];
            monstersCopy[pos] = monstersCopy[i];
            monstersCopy[i] = temp;
        }
        this.setState({monsters:monstersCopy});
    };
 
    render(){
        
        const {monsters} = this.state;

        const monsterList = monsters.map((monster, index) => {
            return (
                <li key={monster}>{monster}</li>
            );
        });

        return (
            <>
                <h1>포켓몬스터 목록</h1>
                <button onClick={this.shuffle}>섞기</button>
                <ul>{monsterList}</ul>
            </>
        );
    }
}

const app = ReactDOM.createRoot(document.querySelector("#app"));
app.render(
    //class 이름을 태그로 사용
    <MainComponent/>
);
```

{% embed url="<https://codepen.io/hiphop5782/pen/abKzYdB>" %}

### 예제 : 목록 필터링

```jsx
class MainComponent extends React.Component {
    state = {
        monsters : ["피카츄", "라이츄", "파이리", "꼬부기", "버터플", "리자드", "리자몽", "어니부기", "거북왕", "야도란", "또가스", "뮤", "뮤츠"],
        results:[],
    };
    
    search = e=>{
        const keyword = e.target.value;
        if(!keyword) return;
    
        const newResult = this.state.monsters.filter(monster=>monster.indexOf(keyword) >= 0);
        this.setState({results:newResult});
    };
    
    render(){
        
        const {monsters, results} = this.state;
    
        const resultList = results.map((monster, index) => {
            return (
                <li key={monster}>{monster}</li>
            );
        });
    
        return (
            <>
                <h1>포켓몬스터 도감</h1>
                <input onInput={this.search} placeholder="이름 입력"/>
                <ul>{resultList}</ul>
            </>
        );
    }
    }
    
    const app = ReactDOM.createRoot(document.querySelector("#app"));
    app.render(
    //class 이름을 태그로 사용
    <MainComponent/>
);
```

{% embed url="<https://codepen.io/hiphop5782/pen/poKvLEo>" %}

### 배열 특정 인덱스 값 변경

<mark style="color:red;">(작성 예정)</mark>

### 배열 데이터 추가

state에 있는 배열에 데이터를 추가할 경우 다음과 같이 전개 연산자와 콜백 함수를 사용하여 작성한다. prev 사용 이유는 [#undefined-3](#undefined-3 "mention") 참조.

```javascript
this.setState(prev=>({
    arr : [...prev.arr , 신규값]
}));
```

### 배열 데이터 삭제

&#x20;배열에서 데이터를 지우는 방법은 여러 가지가 있다.

* 인덱스와 범위를 알 때 - splice(index, range)
* 데이터를 알 때 - filter()

filter()의 경우 데이터를 지우는 명령이 아니라 원하는 데이터를 추려내는 명령이지만 깊은 복사(Deep copy)를 하므로 추려낸 뒤 state에 재 설정한다면 데이터를 삭제하는 것과 같은 효과가 발생한다.

splice()는 원본을 변경하므로 별도의 깊은 복사(Deep copy) 코드가 필요하다.

```javascript
const deleteItem = 30;
this.setState(prev=>({
    arr : prev.arr.filter(value=>value !== deleteItem)
});
```

arr은 가상의 state 항목이며, deleteItem에는 다른 값이 들어가도 무방하다.

{% hint style="info" %}
깊은 복사와 얕은 복사에 대한 차이 및 방법은 다음 문서를 참고한다.

[Object copy](/web/develop-page/js/syntax/object-copy.md#deep-copy-vs-shallow-copy)
{% endhint %}
