이전까지는 자바스크립트와 DOM을 이용해 페이지 단위로 개발을 다뤘다면, 드디어 프론트엔드 라이브러리 리액트를 활용해 컴포넌트 단위의 개발을 접하게 되었다.
처음에는 페이지 단위와 컴포넌트 단위 개발의 차이점과 장단점이 와닿지 않았는데, ajax를 통한 동적 웹페이지의 연장선에서 생각해보니 조금은 이해가 되는 것 같았다. 하나의 화면에서도 유저와의 인터랙션이 다양하게 발생하게 되는데, 이 때 자유롭게 변할 수 있는 ui, 데이터 등을 보다 편리하게 변경 가능하도록 유지하기 위해서는 컴포넌트 단위로 나누어 개발하는 것이 정말 좋은 방법이겠다고 느꼈다. 어느 한 구석이 바뀌었다고 화면 전체를 다시 리로드하는 것은 생각만해봐도 비효율적이니까.
컴포넌트란 웹,앱의 ui에서 특정 기능을 하는 부분을 따로 떼어서 구분지어놓는 단위(관심사 분리) 라고 이해했는데, 이렇게 구분을 해두면 다른 곳에서 그 컴포넌트의 형태와 로직을 재사용하기가 쉬워진다. 또한, 에러가 났을 때 컴포넌트 단위별로 핸들링을 하면 되니까 또 좋다. 그리고 전체 코드가 모두 꽉 짜여진 구조라면 어느 한 부분만을 수정하기가 어려울텐데, 보다 독립적인 형태인 컴포넌트 단위의 코드는 부분적으로만 업데이트도 가능할 것이다.
사실 그동안 이미 어느정도는 컴포넌트 단위로 생각하고, 코드를 작성해왔다고 할 수 있다. 예를 들면, 트위틀러(트위터클론)를 만들 때, 새 글을 입력하는 부분, 섭밋버튼, 트윗피드 각각의 기능별로 DOM과 자바스크립트 함수를 작성하고 콜백함수를 호출하는 식으로.
이렇듯, 리액트의 컴포넌트도 기본적으로 자바스크립트 함수를 기반으로 한다.
//함수형 컴포넌트
function Welcome(props) {
return <h1>Hello, {props.name}</h1>;
}
//클래스 컴포넌트
class Welcome extends React.Component {
render() {
return <h1>Hello, {this.props.name}</h1>;
}
}
JSX는 리액트와 함께 사용하는 자바스크립트를 확장한 태그 문법이다. 반드시 사용해야하는 것은 아니지만, 훨씬 더 간결하고 가독성 좋게 코드를 작성할 수 있다.
JSX로는 모든 자바스크립트 표현식을 {}
로 감싸서 작성할 수 있다. 또한, 태그 어트리뷰트에도 삽입할 수 있는데, 이때는 camelCase로 작성하는 것을 규칙으로 한다.
function getGreeting(user) {
if (user) {
return <h1>Hello, {formatName(user)}!</h1>;
}
return <h1>Hello, Stranger.</h1>;
}
const element = (
<h1 className="greeting"> //class --> className
Hello, world!
</h1>
);
<div>
를 만들고 클래스명을 따로 지정해줘야하는 불편함에서 벗어나 조금더 직관적으로 앨리먼트명을 사용자정의해 사용할 수 있다는 장점이 생긴다.const element = <Welcome name="Joy Kim" />;
리액트는 사용자정의 컴포넌트로 작성한 엘리먼트를 발견하면 JSX 어트리뷰트와 자식을 해당 컴포넌트에 단일 객체로 전달하는데, (함수의 인자로 전달된다고 보면 된다.) 이를 props 라고 한다.
props는 읽기 전용이며, 외부로부터 받아오는 값이라고 보면된다.
컴포넌트는 자신의 props를 다룰 때 반드시 순수 함수처럼 작동해야한다. 즉, 전달된 props의 값을 자체적으로 중간에 수정하면 안된다는 뜻이다.immutable한 값으로 다루어져야 한다.
function Welcome(props) {
return <h1>Hello, {props.name} !</h1>;
}
const element = <Welcome name="Joy Kim" />;
ReactDOM.render(
element,
document.getElementById('root')
);
// --> Hello, Joy Kim !
state는 컴포넌트 내부에서 변화하는 상태값을 말한다. 즉, 웹앱에서 변화할 수 있는 데이터를 상태로 두어야한다.
리액트에서 state를 사용하려면 아래와 같이 클래스 컴포넌트를 이용해야한다.
기본 constructor에서 super()키워드로 props를 전달받고, 초기 state를 객체 형태로 설정해주는 것이 일반적이다.
class App extends React.Component {
constructor(super) {
super(super)
this.state = {}
}
render() {
}
}
state는 직접 수정되어서는 안되며, setState() 메소드를 통해서만 접근할 수 있다. 이는 리액트 생명주기와도 관련이 있다.
부모 컴포넌트의 state는 props를 통해 내려받아 사용할 수 있다. 이는 리액트의 가장 중요한 단방향 데이터흐름의 특징이라고 할 수 있다. 서로 유기적으로 변화해야하는 형제 컴포넌트들은 서로의 state에 직접 접근하지 못한다. 대신 공통의 상위 부모 컴포넌트에 공통의 데이터인 state를 위치시키고 이를 각각 props로 전달받아 사용한다.
하위 컴포넌트에서 상위 컴포넌트의 state 값을 변경하고 싶을 때는, 이벤트 함수를 통해 변경이 가능하다. 상위 컴포넌트에 이벤트 함수를 설정한 후 이 이벤트 함수 자체를 하위 컴포넌트의 props로 전달해 연결해주면 된다.
이때 주의할 것은 constructor에 만들어준 이벤트 함수를 바인딩해주어야한다.
이는 render() 메소드에서 해당 이벤트 함수를 호출했을 때 함수의 this가 undefined 혹은 window가 되어버리기 때문이다.
또한가지 이벤트를 연결하면서 놓치기 쉬운 부분은, onClick같은 이벤트에 함수를 할당할 때 함수 자체를 할당해야한다는 것이다. 함수를 호출하여 함수의 값을 할당해버리면 이벤트가 무한실행되는 오류가 발생한다. 이는 wrapper 함수를 사용하고 바인딩해주거나, 화살표함수를 사용해 해결할 수 있다.
(다음 포스트에 이어서)