state는 컴포넌트 내부에서 선언하며 내부에서 값을 변경할 수 있습니다.
아래 코드는 클해스형 컴포넌트의 state 선언방식입니다.
Counter.js
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를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 });
}}
>
+1
</button>
</div>
);
}
}
export default Counter;
constructor(props){}
는 컴포넌트의 생성자 메서드입니다.
클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props);
를 호출해 주어야 합니다.
이 함수가 호출되면 현재 클래스형 컴포넌트가 상속하고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해 줍니다.
this.state = {};
값에 초기값을 설정하며 컴포넌트의 state는 객체 형식이어야 합니다.
render(){}
함수에서 현재 state를 조회할 때는 this.state를 조회하면 됩니다.
App.js
import React from 'react';
import Counter from './Counter';
const App = () => {
return <Counter />;
};
export default App;
Counter 컴포넌트를 App에서 불러와 렌더링을 하면 다음과 같은 결과가 나옵니다.
state 객체 안에는 여러 값이 있을 수 있습니다.
state의 초깃값을 지정하기 위해 constructor 메서드를 선언해 주었는데요.
또 다른 방식으로도 state의 초깃값을 지정해 줄 수 있습니다.
Counter.js
import React, { Component } from 'react';
class Counter extends Component {
state = {
number: 0,
fixedNumber: 0
};
render() {
const { number, fixedNumber } = this.state; // state를 조회할 때는 this.state로 조회합니다.
return (...);
}
}
export default Counter;
이와 같이 코드를 작성하면 constructor 메서드를 선언하지 않고도 state 초기값을 설정할 수 있습니다
this.setState를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트됩니다.
Counter.js - button onClick
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가 필요하지 않다면 생략해도 됩니다.
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 => ({ })와 같은 형태로 코드가 이루어집니다.
setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때는
setState의 두 번째 파라미터로 콜백(callback) 함수를 등록하여 작업을 처리할 수 있습니다.
Counter.js - button
<button
// onClick을 통해 버튼이 클릭되었을 때 호출할 함수를 지정합니다.
onClick={() => {
this.setState(
{
number: number + 1
},
() => {
console.log('방금 setState가 호출되었습니다.');
console.log(this.state);
}
);
}}
>
+1
</button>
콜백 함수를 등록한 뒤 브라우저를 열어 개발자 도구의 Console탭을 확인한 결과입니다.
리액트 16.8 이전 버전에서는 함수형 컴포넌트에서 state를 사용할 수 없었습니다. 하지만 16.8 이후부터는 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 사용 방법을 쉽게 이해할 수 있습니다.
Say.js
import React, { 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에서는 반드시 객체가 아니어도 상관없습니다.
값의 형태는 자유입니다. 숫자일 수도, 문자열일 수도, 객체일 수도, 배열일 수도 있습니다.
함수를 호출하면 배열이 반환되는데요. 배열의 첫 번째 원소는 현재 상태이고,
두 번째 원소는 상태를 바꾸어 주는 함수입니다. 이 함수를 세터(Setter) 함수라고 부릅니다.
그리고 배열 비구조화 할당을 통해 이름을 자유롭게 정해 줄 수 있습니다.
현재 message와 setMessage라고 이름을 설정해 주었는데요.
text와 setText라고 이름을 자유롭게 바꾸어 주어도 상관없습니다.
App.js
import React from 'react';
import Say from './Say';
const App = () => {
return <Say />;
};
export default App;
useState는 한 컴포넌트에서 여러 번 사용해도 상관없습니다.
Say.js
import React, { 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 값을 바꾸어야 할 때는 setState 혹은 useState를 통해 전달받은 세터 함수를 사용해야 합니다.
배열이나 객체를 업데이트해야 할 때는 배열이나 객체 사본을 만들고 그 사본에 값을 업데이트한 후, 그 사본의 상태를 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 연산자라 불리는 ...을 사용하여 처리하고, 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용합니다.