React 입문자라면 꼭 알아야 할 기초 개념 5가지! Component, Props, State, Life Cycle, Lifting State Up
프론트엔드 개발자 필수 기술 React 개념 정리
프론트엔드 개발자를 준비하고 있다면 꼭 들어봤을 React. 오늘은 React가 대체 뭔지, 그 기본 개념에 대해 알아보려고 한다. 하지만 코드스테이츠에서 재차 강조하듯, React에 대해 가장 잘 아는 방법은 직접 해보기이다. 공식문서를 읽는 것은 기본 중의 기본이지만 눈으로 읽고 보는 것 만으로는 그 안에서 돌아가는 상호작용이 깊게 와닿지 않아서 코드의 모양새만 익숙해지는 불상사가 일어난다. 우선 기본개념을 살펴보고 다음 포스팅에서 간단한 ToDoList를 만들어 볼 예정이다.
그럼 먼저, React 리액트가 대체 뭔지 부터 살펴보자.
React공식 홈페이지에 있는 예시들을 사용했다.
What is React?
“A JavaScript library for building user interfaces"
"UI를 만들기 위한 JavaScript라이브러리" — made by 갓페이스북
생활코딩의 정의를 살짝 빌려오자면, React는 사용자 인터페이스를 만들 때 자주 사용하는 자바스크립트 기능들을 모아놓고 필요할 때 가져다 쓸 수 있도록 한 저장소 같은 것이다. (라이브러리의 종류는 엄청 많고 그 중 하나)
React는 컴포넌트 Component 라는 것을 이용해 기능들을 구축하는데, 다양한 레고 블럭들을 만들어서 사용하는 느낌이었다.
사실 웹페이지는 HTML, CSS 를 이용해서 만들 수 있고 동적이 요소들도 JavaScript를 이용해서 DOM조작을 할 수 있지만, 요즘 웹페이지들은 유저들과 수많은 상호작용이 일어난다. 이미지 하나를 바꾸려해도, 바꿀 이미지를 찾고, 기존이미지의 소스를 새로 바꿀 소스로 바꾸고 또 이 바뀐 이미지를 화면에 띄워주는 등 여러 상태들이 변경된다.
요즘 웹페이지는 상호작용이 많이 일어나게끔 만들어져 있기 때문에 상호작용이 많다면 모든 기능 하나하나에 관리해줘야 하는 상태들이 많아서 웹페이지를 관리하는 일이 아주 어려울 것이다.
React, Angular, Vue 같은 프레임워크들은 이 상태관리를 최소화 해주고 DOM조작을 최소화하여 웹페이지의 개발, 유지, 보수를 더 뛰어나게 할 수 있게 만들어준다.
Component
React의 가장 큰 특징이자 장점은 Component-based, 컴포넌트 기반이라는 것이다.
컴포넌트는 "하나의 의미를 가진 독립적인 단위의 모듈" 인데 쉽게 말해서 나만의 HTML 태그라고 생각할 수 있다.
예를 들어 화면에 “Hello, {name}"을 띄운다고 해보자.
function Welcome(props) {
return <h1>Hello, {props.name}</h1>
}
이렇게 <h1>Hello, {props.name}</h1>
엘리먼트를 반환하는 Welcome
이라는 컴포넌트를 정의해주고
const element = <Welcome name="Jason" />;
ReactDOM.render(
element,
document.getElementById('root')
);
ReactDOM.render()
를 사용하여 만들어준 Welcome
컴포넌트를 element
라는 변수에 담아 호출해서 렌더링해준다.
여기서 주의할 것은 컴포넌트의 이름은 항상 대문자로 시작한다는 것이다.
또한, 컴포넌트는 위와 같은 함수형으로 만들 수 있고, ES6 문법인 class를 이용하여 만들 수 있다.
클래스 컴포넌트:
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
추가적으로 element는 React 앱의 가장 작은 단위로, 우리가 알고있는 일반 객체이다. React element는 불변객체이기 때문에 자식이나 속성을 변경할 수 없다. 그렇기 때문에 h1태그를 이용하여 만든 작은 엘리먼트를 컴포넌트의 구성요소로 넣었고, 렌더링 해주기 위해 이 컴포넌트를 또 다른 element로 생성하여 ReactDOM.render로 전달했다.
Props
props는 "상위 컴포넌트가 하위 컴포넌트에게 내려주는 데이터" 이다.
props는 객체다.
위 예시에서 element라는 엘리먼트를 만들 때 우리는 이미 props를 전달해줬다. <Welcome name=”Jason” />
에서 {name: “Jason”}
이라는 객체, props를 하위Welcome
컴포넌트에 전달해줬고 그로 인해 화면에 Hello, Jason이 보이게 되는 것이다.
Props 컴포넌트는 읽기 전용이다. 하위 컴포넌트는 props를 변경할 수 없고 사용만 할 수 있다. 이 점을 이용해서 우리는 변경되지 말아야 할 데이터를 효율적으로 관리할 수 있다.
State
state는 "컴포넌트가 독립적으로 갖는 상태" 이다. 이 역시 객체의 형태로, 컴포넌트 안에서만 제어되어 보관, 관리된다.
welcome 메세지 뒤에 메세지가 렌더링 될 때의 현재 시각을 알려주는 메세지를 같이 렌더링 해보자.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}ReactDOM.render(
<Clock name="Jason"/>,
document.getElementById('root')
);
Clock 이라는 컴포넌트를 만들고, data이라는 state를 지정해주면 아래와 같이 렌더링 된다. Codepen 에서 해보기
함수형 컴포넌트가 아닌 클래스형 컴포넌트로 만든 것을 주목해야한다. state는 class component만 가질 수 있다. 그래서 함수형 컴포넌트는 stateless component, 클래스형 컴포넌트는 stateful component라고 부르기도 한다. 상태가 필요한 컴포넌트와 필요하지 않은 컴포넌트를 잘 구분하여 기능에 맞게 설계해주는게 중요하다.
이제 시계가 스스로 매 초마다 시각을 업데이트 하도록 state를 변경해주도록 해보겠다. this.state는 constructor 안에 지정한다.
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = {date: new Date()};
}componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}componentWillUnmount() {
clearInterval(this.timerID);
}tick() {
this.setState({
date: new Date()
});
}render() {
return (
<div>
<h1>Hello, {this.props.name}</h1>
<h2>It is {this.state.date.toLocaleTimeString()}.</h2>
</div>
);
}
}ReactDOM.render(
<Clock name="Jason"/>,
document.getElementById('root')
);
Clock
컴포넌트가 매초마다 작동하도록 하는 tick()
이라는 메소드를 만들고 state를 업데이트 해주기 위해 setState()
를 사용했다. Codepen에서 해보기
여기서 주목해야할 점은 state를 직접 변경할 수 없고, React Component의 내장메소드인
setState()
를 사용해야한다는 점이다.
setState()
가 실행 될 때 마다 React는state
가 변경된 것을 이지하고 표시될 내용을 알아내기 위해render()
메소드를 다시 호출한다.render()
메소드 안의this.state.date
달라지고 React는 이에 따라 DOM을 업데이트 한다.
즉 setState를 사용 해야 life cycle을 사용할 수 있는 것이다.
컴포넌트는 자신의 state를 자식 컴포넌트에 props로 전달할 수 있다.
Life Cycle 생명주기
Life Cycle은 "컴포넌트가 생성, 업데이트, 삭제 될 때 일어나는 일련의 과정"들이고, 각 단계의 전, 후로 미리 지정되어 있는 특정 생명주기 메소드들이 실행된다.
위에서 ComponentDidMount
, ComponentWillUnmount
라는 생소한 메소드를 봤다. 이 메소드들이 바로 생명주기 메소드이다.
컴포넌트가 생성될 때를 예를 들면,
1. 컴포넌트가 생성되면 생성자가 먼저 호출된다.
2. 그리고 컴포넌트 안에 있는 render
메소드가 실행되면서 화면에 값이 렌더링된다.
3. 그 다음 componentDidMount
메소드가 실행된다.
위 Clock 컴포넌트를 살펴보면,
Clock
컴포넌트가 호출되면서constructor
생성자가 먼저 호출되고render()
함수가 실행되면서 안에 있는 내용물을return
함으로서 화면에 결과물이 출력된다- 그리고 아래
componentDidMount
메소드가 실행되는데, 그 안에서 1초마다Clock
컴포넌트를 업데이트 해주는 메소드가 불려온다. 즉, 화면에 렌더링 된 후 부터 시계가 작동하게 해준 것이다.
componentDidMount() {
this.timerID = setInterval(
() => this.tick(),
1000
);
}
그리고 만약 Clock 컴포넌트가 삭제된다면 tick 함수가 중지될 수 있도록 componentWillUnmount
생명주기 메소드를 아래와 같이 실행시켜준다.
componentWillUnmount() {
clearInterval(this.timerID);
}
Life Cycle 을 잘 알고 각 단계마다 실행되는 메소드들을 이용하여 원하는 타이밍에 원하는 기능이 실행되도록 구현할 수 있다.
중요한 점은 Life cycle를 이용하려면 반드시 class Component로 작성해야한다는 것이다. state를 이용하는 것을 생각하면 왜 그런지 알 수 있다.
또한, 우리가 작성해주지 않아도 생명주기 메소드들은 자동으로 실행된다. 아무것도 지정해주지 않았다면 그냥 빈 함수가 실행된다는 소리이다.
Lifting State Up
React는 단방향 데이터 플로우를 가지고 있다. 즉, 부모만 자식에게 데이터를 줄 수 있고 자식은 부모에게 데이터를 줄 수 없다는 뜻이다.
하지만, 자식이 부모의 상태를 변경해야 한다면?
이 때 우리는 Lifting state up 이라는 것을 이용한다.
- 상위 컴포넌트에서 state을 변경시키는 함수를 만든다.
예를 들어,handleState = () => {this.setState()}
- 그리고 이 함수를 자식에게 props로 넘긴다.
- 자식은 이 함수를 받아 사용하면 부모의 상태가 변경된다. 이 때 상태가 변경하므로
render
가 다시 실행된다.
이 때 중요한 것은, 상위 컴포넌트에서 this를 꼭 bind해서 넘겨야 한다는 것이다. 그래야 부모의 상태를 변경한다. arrow function을 사용하면 실행 context를 만들지 않으므로 binding이 필요 없다는 점도 기억하면 좋다.
포스팅이 너무 길어질 것 같아서 자세한 예시는 공식홈페이지를 참고하면 좋을 것 같다.
참고하면 좋은 소스들: