state

컴포넌트에서 관리하는 데이터를 저장하는 state에 대해서 살펴본다.

state

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

state 선언

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

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

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

state 출력

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

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

글자 색상도 state로 설정할 수 있다.

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>
            </>
        );
    }
}

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

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

page구조 분해 할당

state 변경

state는 직접 변경을 금지한다.

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

단일 항목 변경

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

state = {
    number : 10
}

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

this.setState({number : 20});

객체 변경

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

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

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

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

객체 일부 변경

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

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

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

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

객체의 일부 변경 시 주의사항

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

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

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

배열 변경

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

  • 배열 전체 교체

  • 배열 특정 인덱스 값 변경

  • 배열 데이터 추가

  • 배열 데이터 삭제

배열 전체 교체

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

  • 기존 배열의 위치를 랜덤으로 뒤섞는(shuffle) 경우

  • 기존 배열에서 검색(filter)하여 새로운 배열을 만드는 경우

단일 항목 변경과 동일하게 진행한다

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

배열을 복사하는 방법은 여러 가지가 있으며, 예제 코드에서는 전개 연산자(spread operator)를 사용하였다.

예제 : 배열 섞기

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

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/>
);

예제 : 목록 필터링

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/>
);

배열 특정 인덱스 값 변경

(작성 예정)

배열 데이터 추가

state에 있는 배열에 데이터를 추가할 경우 다음과 같이 전개 연산자와 콜백 함수를 사용하여 작성한다. prev 사용 이유는 객체의 일부 변경 시 주의사항 참조.

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

배열 데이터 삭제

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

  • 인덱스와 범위를 알 때 - splice(index, range)

  • 데이터를 알 때 - filter()

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

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

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

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

깊은 복사와 얕은 복사에 대한 차이 및 방법은 다음 문서를 참고한다.

Last updated