리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미한다. props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사요할 수 있다. props를 바꾸려면 부모 컴포넌트에서 바꾸어야 한다. 예를 들어 App 컴포넌트에서 MyComponent 를 사용할 때 props를 바꿔야 값이 변경될 수 있다. 반면 MyComponent에서는 전달받은 name 값을 직접 바꿀 수 없다. 리액트에는 두가지 종류의 state가 있다. 하나는 클래스형 컴포넌트가 지니고 있는 state이고, 다른 하나는 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state이다.
클래스형 컴포넌트의 state
Counter.js 파일을 src 디렉터리에 생성한다.
import React, { 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({ number: number + 1});
}}
> +1
</button>
</div>
);
}
export default Counter;
컴포넌트에 state를 설정할 때는 다음과 같이 constructor 생성자 메서드를 작성해야 한다.
클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해야 한다. 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출한다.
그 다음에는 this.state 값에 초기값을 설정한다. 컴포넌트의 state는 객체 형식이어야 한다.
render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 된다. 그리고 button 안에 onClick이라는 값을 props로 넣어 주었는데, 이는 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 한다. 이벤트로 설정할 함수를 넣어 줄 때는 화살표 함수 문법을 사용하여 넣어야 한다. 함수 내부에서는 this.setState라는 함수를 사용했는데, 이 함수가 state값을 바꿀 수 있게 한다.
import React from 'react';
import Counter from './Counter';
const App = () => {
return <Counter />;
};
export default App;
state 객체 안에 여러 값이 있을 때
state 객체 안에는 여러 값이 있을 수 있다. Counter 컴포넌트를 수정한다.
import React, { Component } from 'react';
class Counter extends Component {
constructor(props) {
super(props);
// state의 초기값 설정
this.state = {
number: 0,
fixed: 0
};
}
}
render() {
const { number, fixed } = this.state; // state를 조회할 때 this.state로 조회한다.
return (
<div>
<h1>{ number }</h1>
<h1>바뀌지 않는 값: { fixed }</h1>
<button
// onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
onClick = {() => {
this.setState({ number: number + 1});
}}
> +1
</button>
</div>
);
}
export default Counter;
state를 constructor에서 꺼내기
import React, { Component } from 'react';
class Counter extends Component {
state = {
number: 0,
fixed: 0
}
}
render() {
const { number, fixed } = this.state; // state를 조회할 때 this.state로 조회한다.
return (
<div>
<h1>{ number }</h1>
<h1>바뀌지 않는 값: { fixed }</h1>
<button
// onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정한다.
onClick = {() => {
this.setState({ number: number + 1});
}}
> +1
</button>
</div>
);
}
export default Counter;
이렇게 하면 constructor 메서드를 선언하지 않고도 state 초기값을 설정할 수 있다.
this.setState에 객체 대신 함수 인자 전달하기
onClick = {() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있다.
this.setState({ number: number + 1 });
this.setState({ number: this.state.number + 1 });
}}
코드를 위와 같이 작성하면 this.setState를 두번 사용하는 것임에도 불구하고 버튼을 클릭할 때 숫자가 1씩 더해진다. this.setState를 사용한다고 해서 state값이 바로 바뀌지 않기 때문이다.
이에 대한 해결책은 this.setState를 사용할 때 객체 대신에 하수를 인자로 넣어주는 것이다. this.setState의 인자로 함수를 넣어 줄 때는 코드를 다음과 같은 형식으로 작성한다.
this.setState((prevState, props) => {
return {
//업데이트하고 싶은 내용
}
})
여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 의미한다. 업데이트 과정에서 props가 없다면 생략가능한다.
<button
onClick = { () => {
this.setState((prevState) => {
return {
number: prevState.number + 1
};
});
// 위 코드와 아래 코드는 완전히 똑같은 기능을 한다.
this.setState(prevState => ({
number: prevState + 1
}));
}}
>1+
</button>
화살표 함수에서 값을 바로 변환하고 싶다면 코드 블록 { }를 생략하면 된다. 예를 들어, 파라미터 a와 b를 받아 와서 합을 구하는 함수를 작성한다면 된다.
const sum = ( a, b ) => a + b;
this.setState가 끝난 후 특정 작업 실행하기
setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때는 setStae의 두번째 파라미터로 콜백 함수를 등록하여 작업을 처리할 수 있다.
<button
onClick = { () => {
this.setState(
{
number: number + 1
},
() => {
console.log(' setState 호출됨!! ');
console.log( this.state );
}
);
}}
>1+
</button>
함수형 컴포넌트에서 useState 사용하기
배열 비구조화 할당
Hooks를 사용하기 전에 배열 비구조화 할당이라는 것을 알아보자. 배열 비구조화 할당은 객체 비구조화 할당과 비슷하다. 즉, 배열 안에 들어 있는 값을 쉽게 추출할 수 있도록 하는 문법이다.
const array = [ 1, 2 ];
const [ one, two ] = array;
useState 사용하기
import React, { useState } from 'react';
const Say = () => {
const [ message, setMessage ] = useSate('');
const onClickEnter = () => setMessage('안녕!');
const onClickLeave = () => setMessage('잘가!');
return (
<div>
<h1>{ message }</h1>
<button onClick = { onClickEnter }>입장</button>
<button onClick = { onClickLeave }>퇴장</button>
</div>
);
}
export default Say;
useState 함수의 인자에 상태의 초기값을 넣는다. 클래스형 컴포넌트에서 state 초기값은 객체 형태를 넣어야 하지만 useState에서는 반드시 객체가 아니어도 괜찮다. 함수를 호출하면 배열이 반환되는데, 배열의 첫번째 원소는 현재 상태이고, 두 번째 원소는 상태를 바꾸는 함수다. 이 함수를 setter 함수라고 부른다. 그리고 배열 비구조화 할당을 통해 이름을 자유롭게 정할 수 있다.
import React from 'react';
import Say from './Say';
const App = () => {
return <Say />;
};
export default App;
한 컴포넌트에서 useState 여러 번 사용하기
useState는 한 컴포넌트에서 여러번 사용해도 상과없다.
import React, { useState } from 'react';
const Say = () => {
const [ message, setMessage ] = useSate('');
const onClickEnter = () => setMessage('안녕!');
const onClickLeave = () => setMessage('잘가!');
const [ color, setColor ] = useState('black');
return (
<div>
<h1 style = {{ color }}>{ message }</h1>
<button onClick = { onClickEnter }>입장</button>
<button onClick = { onClickLeave }>퇴장</button>
<button style = {{ color: 'red' }} onClick = { () => setColor('red') }>빨강색</button>
<button style = {{ color: 'blue' }} onClick = { () => setColor('blue') }>파랑색</button>
<button style = {{ color: 'green' }} onClick = { () => setColor('green') }>초록색</button>
</div>
);
}
export default Say;
state를 사용할 때 주의 사항
state값을 바꾸어야 할 때는 setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 한다.
아래는 잘못된 코드이다.
// 클래스형 컴포넌트에서
this.state.number = this.state.number + 1;
this.state.array= this.array.push(2);
this.state.object.value = 1;
// 함수형 컴포넌트에서
const [ object, setObject ] = useState({ a: 1, b: 2});
object.b = 3;
사본을 만들어서 업데이트 하자.
// 객체 다루기
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로 변경