리액트를 처음 접하면 당황스러운 일이, 바로 state
와 props
이다. 리액트는 선언적 특징을 가지고 있기 때문에 직접 DOM
요소를 조작하지 않고 state
의 변화를 통해서 UI
를 리-렌더링한다. 그렇기 때문에 이전에 명령을 통해서 조작하던 방식과는 사고의 흐름이 다르다고 느껴졌다. 하지만, state
와 props
은 알면 알수록 재밌고 흥미로운 요소이다. 이에 대해서 정리해보려고 한다.
state
는 컴포넌트가 가지고 있는 상태값을 의미한다. 더 정확히는 UI
에 대한 정보를 담고 있는 객체라고 볼 수 있다. state
는 불변값이 아니며 언제든지 사용하고 변경될 수 있다.
class Main extends Component {
state = {
feeds: [],
};
render() {
return (
<main className="main>
<Nav />
<section className="main__feeds">
{this.state.feeds.map(feed => (
<Feed key={feed.id} feed={feed} comments={feed.comments} />
))}
</section>
<Aside />
</main>
);
}
}
export default Main;
위의 예시는 메인 페이지의 컴포넌트에서 state
를 가지고 있는 모습이다. 메인 페이지는 state
에 feeds
라는 배열을 가지고 있다. 이 배열 안에는 또 각 피드의 정보를 담고 있는 객체들이 들어갈 예정이다. 지금은 생성하면서 초기화만 진행했기 때문에 feeds
에 빈 배열이 들어와 있다.
// mock data 받아오기
componentDidMount = () => {
fetch('http://localhost:3000/data/CommentDataCJ.json', {
method: 'GET',
})
.then(result => result.json())
.then(feeds => {
this.setState({
feeds,
});
});
};
위의 코드는 componentDidMount()
를 통해서 mock data
를 받아오고 이를 이용해서 setState
를 진행했다. 여기서 setState
를 사용해서 업데이트를 한 점을 보자. state
는 UI
에 대한 정보를 가지고 있고 이를 렌더링 하기 위해서 존재한다고 볼 수 있다. 만약, state
의 값을 직접 조작하는 경우에는 state
의 변화는 있겠지만 render()
가 호출되지 않아서 리-렌더링이 생겨나지 않는다. 그렇기 때문에 setState()
를 호출하고 이 때, render()
가 호출되기 때문에 정상적으로 UI
를 그릴 수 있게 된다.
state
를 사용하면서 겪었던 어려움은 setState()
가 비동기적으로 작동하는 점이었다. state
가 잘 업데이트 되는지 확인하기 위해서 console.log(this.state)
를 사용하는 과정에서 한 박자 느린 반응을 보게 된다거나 전혀 예상하지 못한 흐름으로 동작한다거나 하는 오류들을 겪었다. 특히, ID
와 PASSWORD
를 입력하는 상황에서 한 쪽이 사라지는 경우를 겪기도 했다.
this.setState({
counter: this.state.counter + this.props.increment,
});
만약 위와 같은 코드를 사용하게 된다면 카운터가 제대로 업데이트 되지 않는 상황을 겪을 수도 있다. 되도록 this.state
와 this.props
을 사용할 때 비동기적으로 업데이트 될 수 있음을 명심하고 사용하는 것이 좋다.
state
가 컴포넌트 본인이 가지고 있는 상태값이라면 props
은 부모로부터 받은 속성 객체라고 볼 수 있다. 여기에는 배열, 객체 등이 들어갈 수 있고 메소드도 가능하다. state
는 본인이 가지고 있는 값이기 때문에 얼마든지 변경이 가능하지만 props
는 읽기 전용이기 때문에 자식 컴포넌트에서 수정할 수가 없다.
class Comment extends Component {
handleDelete = e => {
this.props.onDelete(this.props.reply);
};
handleLike = e => {
this.props.onLike(this.props.reply);
};
render() {
const { userName, comment, isUser, isLike } = this.props.reply;
return (
<li className="comment-cheoljin">
<span>{userName}</span>
<span>{comment}</span>
<button
type="button"
className="comment__heart"
onClick={this.handleLike}
>
<i className={isLike ? 'fas fa-heart' : 'far fa-heart'} />
</button>
<button
type="button"
className="comment__delete"
onClick={this.handleDelete}
>
<i
className={isUser ? 'far fa-trash-alt' : 'far fa-trash-alt none'}
/>
</button>
</li>
);
}
}
export default Comment;
위의 예시를 보자. props
를 구조분해할당을 통해서 userName
, comment
, isUser
, isLike
로 나눴다. 그 후에 각 필요한 부분에 {}
을 통해 사용했다. 또한, handleDelete
등의 함수를 보면 부모 컴포넌트로부터 전달 받은 props
객체의 메소드인 onDelete
를 호출하는 함수 임을 알 수 있다.
이처럼 props
는 부모 컴포넌트와 자식 컴포넌트를 연결해주는 다리의 역할을 한다.
더 설명해보자면 자식 컴포넌트인 Comment
에 있는 <button>
에서 onClick
이벤트가 발생하게 되면 handleDelete
함수가 호출되고 handleDelete
함수는 props
로 전달받은 onDelete
함수를 호출한다. 이때 인자로 props
인 reply
를 전달할 수 있다.
위와 같은 방식으로 자식 요소에서 발생한 이벤트를 부모 컴포넌트로 전달해서 state
를 업데이트 할 수 있다.
오늘은 state
와 props
에 대해서 간단하게 정리했다. 리액트를 처음 접하게 되는 경우에는 어떻게 이용해야 좋을지 막막한 기분을 느끼는 경우가 많은데 사용하는 방법에 대해서 잘 이해한다면 바닐라 자바스크립트만을 사용하는 경우보다 훨씬 편함을 알 수 있다.
앞으로 평생 함께해야 할 state
와 props
. 막히는 부분이 있는 경우에는 꼭 리액트 공식 문서에서 답을 찾으며 계속 공부해 볼 예정이다.