컴포넌트를 선언하는 방식은 두 가지가 있습니다.
// 함수형 컴포넌트
import React from 'react';
function App() {
const name = '리액트';
return <div className='react'>{name}</div>;
}
export default App;
// 클래스형 컴포넌트
import React, { Component } from 'react';
class App extends Component {
render() {
const name = 'react';
return <div className='react'>{name}</div>;
}
}
export default App;
함수형 컴포넌트와 클래스형 컴포넌트의 차이점은 클래스형 컴포넌트의 경우 state 기능 및 라이프사이클 기능을 사용할 수 있다는 것과 임의 메서드를 정의할 수 있다는 것입니다.
클래스형 컴포넌트에는 render 함수가 꼭 있어야 하고, 그 안에서 보여 주어야 할 JSX를 반환해야 합니다.
함수형 컴포넌트의 장점
함수형 컴포넌트의 단점
리액트 공식 매뉴얼에서는 컴포넌트를 새로 작성할 때 함수형 컴포넌트와 Hooks를 사용하도록 권장하고 있습니다.
import React from 'react';
const MyComponent = () => {
return <div>나의 새롭고 멋진 컴포넌트</div>;
};
export default MyComponent;
(...)
// 맨 아래 코드
**export default MyComponent;**
App컴포넌트에서 MyComponent 컴포넌트를 불러와서 사용합니다.
import './App.css';
**import MyComponent from './MyComponent';**
function App() {
return <MyComponent />;
}
export default App;
props는 컴포넌트 속성을 설정할 때 사용하는 요소입니다. props값은 해당 컴포넌트를 불러와 사용하는 부모 컴포넌트에서 설정할 수 있습니다.
props 값은 컴포넌트 함수의 파라미터로 받아 와서 사용할 수 있습니다.
import React from 'react';
const MyComponent = (props) => {
return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};
export default MyComponent;
import './App.css';
import MyComponent from './MyComponent';
function App() {
return <MyComponent **name='React'** />;
}
export default App;
import React from 'react';
const MyComponent = (props) => {
return <div>안녕하세요, 제 이름은 {props.name}입니다.</div>;
};
MyComponent.defaultProps = {
name: '기본 이름',
};
export default MyComponent;
리액트 컴포넌트를 사용할 때 컴포넌트 태그 사이의 내용을 보여 주는 props를 children이라고 합니다.
import React from 'react';
const MyComponent = (props) => {
return (
<div>
안녕하세요, 제 이름은 {props.name}입니다.
<br />
children 값은 {props.children}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
export default MyComponent;
import './App.css';
import MyComponent from './MyComponent';
function App() {
return <MyComponent>리액트</MyComponent>;
}
export default App;
현재 MyComponent에서 props 값을 조회할 때마다 props.name, props.children과 같이 props. 이라는 키워드를 앞에 붙여 주고 있는데, 이러한 작업을 더 편하게 하기 위해 내부 값을 바로 추출하는 방법이 있습니다.
import React from 'react';
const MyComponent = (props) => {
**const { name, children } = props;**
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
export default MyComponent;
객체에서 값을 추출하는 문법을 비구조화 할당이라고 부릅니다. 이 문법은 구조 분해 문법이라고도 불리며, 함수의 파라미터 부분에서도 사용할 수 있습니다. 만약 함수의 파라미터가 객체라면 그 값을 바로 비구조화해서 사용하는 것입니다.
import React from 'react';
const MyComponent = **({ name, children }**) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
export default MyComponent;
컴포넌트의 필수 props를 지정하거나 props의 타입을 지정할 때는 propTypes를 사용합니다. 컴포넌트의 propTypes를 지정하는 방법은 defaultProp을 설정하는 것과 비슷합니다. 우선 propTypes를 사용하려면 코드 상단에 import구문을 사용하여 불러와야 합니다.
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = ({ name, children }) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
MyComponent.propTypes = {
name: PropTypes.string,
};
export default MyComponent;
이렇게 설저해 주면 name 값은 무조건 문자열 형태로 전달해야 된다는 것을 의미합니다. App 컴포넌트에서 name 값을 문자열이 아닌 숫자로 전달한 뒤 개발자 도구의 Console 탭을 열러서 확인하면 경고 메시지를 출력합니다.
propTypes를 지정하지 않았을 때 경고 메시지를 띄어주는 작업은 propTypes를 지정할 때 뒤에 isRequired를 붙여주면 됩니다.
import React from 'react';
import PropTypes from 'prop-types';
const MyComponent = ({ name, favoriteNumber, children }) => {
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
<br />
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
};
MyComponent.defaultProps = {
name: '기본 이름',
};
MyComponent.propTypes = {
name: PropTypes.string,
**favoriteNumber: PropTypes.number.isRequired,**
};
export default MyComponent;
PropTypes에서는 여러 가지 종류를 설정할 수 있습니다.
더 자세한 정보는 https://github.com/facebook/prop-types 에서 확인할 수 있습니다.
클래스형 컴포넌트에서 Props를 사용할 때는 render함수에서 this.props를 조회하면 됩니다. MyComponent를 다음과 같이 클래스형 컴포넌트로 변환할 수 있습니다.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class MyComponentClass extends Component {
render() {
const { name, favoriteNumber, children } = this.props; // 비구조화 할당
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
<br />
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
}
}
MyComponentClass.defaultProps = {
name: '기본 이름',
};
MyComponentClass.propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired,
};
export default MyComponentClass;
클래스형 컴포넌트에서 defaultProps와 propTypes를 설정할 때 class 내부에서 지정하는 방법도 있습니다.
import React, { Component } from 'react';
import PropTypes from 'prop-types';
class MyComponentClass extends Component {
static defaultProps = {
name: '기본 이름',
};
static propTypes = {
name: PropTypes.string,
favoriteNumber: PropTypes.number.isRequired,
};
render() {
const { name, favoriteNumber, children } = this.props; // 비구조화 할당
return (
<div>
안녕하세요, 제 이름은 {name}입니다.
<br />
children 값은 {children}입니다.
<br />
제가 좋아하는 숫자는 {favoriteNumber}입니다.
</div>
);
}
}
export default MyComponentClass;
리액트에서 state는 컴포넌트 내부에서 바뀔 수 있는 값을 의미합니다. props는 컴포넌트가 사용되는 과정에서 부모 컴포넌트가 설정하는 값이며, 컴포넌트 자신은 해당 props를 읽기 전용으로만 사용할 수 있습니다. props를 바꾸려면 부모 컴포넌트에서 바꾸어 주어야 합니다.
리액트에는 두 가지 종류의 state가 있습니다. 하나는 클래스형 컴포넌트가 지니고 있는 state이고, 다른 하나는 함수형 컴포넌트에서 useState라는 함수를 통해 사용하는 state입니다.
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;
컴포넌트에 state를 설정할 때는 constructor 메서드를 작성합니다. 이는 컴포넌트의 생성자 메서드인데 클래스형 컴포넌트에서 constructor를 작성할 때는 반드시 super(props)를 호출해 주어야 합니다. 이 함수가 호출되면 현재 클래스형 컴포넌트가 상속받고 있는 리액트의 Component 클래스가 지닌 생성자 함수를 호출해 줍니다.
그다음에는 this.state 값에 초깃값을 설정해 줍니다. 컴포넌트의 state는 객체 형식이어야 합니다.
render 함수에서 현재 state를 조회할 때는 this.state를 조회하면 됩니다. button 안에 onClick이라는 값을 props로 넣어 주어, 버튼이 클릭될 때 호출시킬 함수를 설정할 수 있게 해줍니다.
import React, { 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;
import React, { 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를 사용하여 state 값을 업데이트할 때는 상태가 비동기적으로 업데이트됩니다.
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState({ number: number + 1 });
this.setState({ number: this.state.number + 1 });
}}>
onClick에 설정한 함수 내부에서 this.state를 두 번 호출해도 버튼을 클릭할 때 숫자가 1씩 더해집니다. this.state를 사용한다고 해서 state 값이 바로 바뀌지는 않기 때문입니다.
이에 대한 해결책은 this.setState를 사용할 때 객체 대신에 함수를 인자로 넣어 주는 것입니다.
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState((prevState, props) => {
return {
// 업데이트 하고 싶은 내용
}
})
}}>
여기서 prevState는 기존 상태이고, props는 현재 지니고 있는 props를 가리킵니다. 만약 업데이트하는 과정에서 props가 필요하지 않다면 생략해도 됩니다.
import React, { 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((prevState) => {
return {
number: prevState.number + 1,
};
});
// 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
// 아래 코드는 함수에서 바로 객체를 반환한다는 의미입니다.
this.setState((prevState) => ({
number: prevState.number + 1,
}));
}}>
{'+1 '}
</button>
</div>
);
}
}
export default Counter;
setState를 사용하여 값을 업데이트하고 난 다음에 특정 작업을 하고 싶을 때는 setState의 두 번째 파라미터로 콜백 함수를 등록하여 작업을 처리할 수 있습니다.
onClick={() => {
// this.setState를 사용하여 state에 새로운 값을 넣을 수 있습니다.
this.setState(
{
number: number + 1,
},
() => {
console.log('방금 setState가 호출되었습니다.');
console.log(this.state);
},
);
// 위 코드와 아래 코드는 완전히 똑같은 기능을 합니다.
// 아래 코드는 함수에서 바로 객체를 반환한다는 의미입니다.
this.setState((prevState) => ({
number: prevState.number + 1,
}));
}}
const array = [1, 2];
const one = array[0];
const two = array[1];
// array 안에 있는 값을 one, two에 담아주는 코드입니다.
// 배열 비구조화 할당을 사용하면 아래와 같이 표현할 수 있습니다.
const [one, two] = array;
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에서는 반드시 객체가 아니어도 상관없습니다.
함수를 호출하면 배열이 반환되는데, 배열의 첫 번째 원소는 현재 상태이고, 두 번째 원소는 상태를 바꾸어 주는 함수입니다.
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 값을 바꾸어야 할 때는 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 })); // id가 1인 항목의 value를 false로 설정
객체에 대한 사본을 만들 때는 spread 연산자라 불리는 ...을 사용하여 처리하고, 배열에 대한 사본을 만들 때는 배열의 내장 함수들을 활용합니다.
- 해당 코드는 GitHub 에서 볼 수 있습니다.
- 본 글은 [리액트를 다루는 기술]을 참고하여 작성되었음을 밝힙니다.
- React를 공부하는 중이고, 복습하는 겸 포스팅을 하고 있기 때문에, 틀린 정보가 있을 수 있습니다.
잘못된 정보가 있다면 댓글로 알려주세요 :)
컴포넌트를 만들어서 내보내고 불러오는 방법과 props 및 state를 사용하는 방법을 배워 보았습니다. props와 state는 둘 다 컴포넌트에서 사용하거나 렌더링할 데이터를 담고 있으므로 비슷해 보일 수 있지만, 그 역할은 매우 다릅니다. props는 부모 컴포넌트가 설정하고, state는 컴포넌트가 자체적으로 지닌 값으로 컴포넌트 내부에서 값을 업데이트할 수 있습니다. 앞으로 새로운 컴포넌트를 만들 때는 useState를 사용할 것을 권장합니다.