React Component란?
- 리액트로 만들어진 앱을 이루는 최소한의 단위
- 밑 사진 빨간 박스 하나하나가 모두 컴포넌트
리액트 컴포넌트 특징
-
데이터가 주어졌을 때 이에 맞추어 UI를 생성.
-
라이프 사이클 API를 이용해 컴포넌트가 화면에 Mount, Update, Unmount 될 때 주어진 작업 처리 가능
-
기존의 웹 프레임워크는 MVC방식으로 분리하여 관리하여 각 요소의 의존성이 높아 재활용이 어렵다는 단점이 있지만, 리액트 컴포넌트는 MVC의 V(View)를 독립적으로 구성하여 재사용을 할 수 있고 이를 통해 새로운 컴포넌트 생성 가능 → 타 프레임워크와 달리 뷰(View) 하나만 바뀌어도 나머지 Control, Model 영역에 영향을 주지 않습니다.
-
props 라는 속성을 이용해 같은 컴포넌트여도 다르게 렌더링 가능합니다.
-
위에 언급했듯이 컴포넌트를 통해 UI를 재사용 가능한 개별적인 여러 조각으로 나누고, 각 조각을 재사용할 수 있습니다. → 컴포넌트 사용의 가장 큰 목적
예시
밑 사진의 컴포넌트를 재사용해
다음과 같은 컴포넌트를 화면을 구상할 수 있습니다.
컴포넌트 선언 방법
리액트에서 컴포넌트를 선언하는 방법은 클래스형 컴포넌트와 함수형 컴포넌트가 있습니다.
클래스형 컴포넌트 예시
import react, { Component } from "react";
//Component로 부터 상속
class App extends Component() {
render() {
//render 함수가 반드시 있어야합니다.
//render 함수는 JSX 값을 반환해줍니다.
const name = "react";
return <div className="react">{name}</div>;
}
}
함수형 컴포넌트 예시
import React from "react";
import "./App.css";
export default function App() {
const name = "react";
return <div className="react">{name}</div>;
}
함수형 컴포넌트는 이름 그대로 함수 형태, 클래스형 컴포넌트는 이름 그대로 클래스 형태를 하고 있다는 점이 직관적인 차이점이라 할 수 있습니다.
과거에는 클래스형 컴포넌트를 사용했다고 합니다.
클래스형 컴포넌트 특징
- 리액트의 기능을 최대로 끌어 올릴 수 있습니다.
- 함수형 컴포넌트는 라이프사이클 API 사용, 컴포넌트 내부의 state를 만들 수 없습니다.
하지만 현재 리액트 시장은 함수형 컴포넌트 사용을 선호하고 있습니다. 공식문서에서도 함수형 컴포넌트와 hook을 함께 사용하기를 권장하고 있습니다.
함수형 컴포넌트 장점
- 함수의 문법만 알면 사용할 수 있기 때문에 편리.
- 클래스형 컴포넌트는 로직과 상태를 컴포넌트 내에서 구현하기 때문에 함수형 컴포넌트보다 가독성이 떨어집니다.
- 2019년에 출시된 최신 리액트(v16.8)에서 hook이 도입되면서 내부적으로 state 관리, 라이프 사이클 api 사용 등 함수형 컴포넌트의 한계를 극복할 수 있는 기능들이 등장했기 때문. → 클래스형 컴포넌트 위주에서 함수형 컴포넌트 위주로 넘어가게된 가장 큰 원인. 4.메모리 자원을 덜 사용하기 때문에 프로젝트를 완성하여 빌드한 후 배포할 때도 함수형 컴포넌트를 사용하는 것이 결과물의 파일 크기가 더 작습니다. (사실 성능과 파일 크기 면에서 큰 차이는 없다고 합니다.)
- 제공되는 Hook 함수뿐만 아니라 커스텀 훅을 생성하여 사용할 수 있습니다.
커스텀 훅
Custom Hook?
- useState와 useEffect들과 같은 특정 상태관리나 라이프사이클 로직들을 추상화하여 묶어서 재사용이 가능하도록 제작이 가능한 함수
커스텀 훅 사용 이유
- 반복되는 로직을 하나로 묶어 재사용하기 위함
간략한 사용법
//custom hook 정의
const useToggle = (initialState = false) => {
const [state, setState] = useState(initialState);
const toggle = useCallback(() => setState((state) => !state), []);
return [state, toggle];
};
// 사용예시
import { useCallback, useState } from "react";
function App() {
const [isTextChanged, setIsTextChanged] = useToggle();
return (
<button onClick={setIsTextChanged}>
{isTextChanged ? "Toggled" : "Click to Toggle"}
</button>
);
}
위처럼 useToggle을 호출하면 현재 토글상태과, 이 토글상태를 변경할 수 있는 함수가 리턴이 됩니다. 훅을 호출하면 useToggle함수의 return값이 [state, toggle] 이므로 분해구조 문법을 통해 리턴값을 특정 변수(state)와 바인딩하여 사용할 수 있습니다.
커스텀 훅의 강점은 불필요하게 반복되는 로직을 하나의 함수로 묶어서 추상화함으로써 재활용이 가능하도록 만든다는 점에서 효율적 측면이 있습니다.
하지만 클래스형 컴포넌트도 개념은 익혀놔야합니다. 어떠한 기업은 클래스형 컴포넌트를 사용할 수 있고(Legacy) 그 기업에 근무를 하게 될 수도 있기 때문입니다. 그 기업 프로젝트의 유지보수를 위해서는 클래스형 컴포넌트에 대한 개념은 알고 있어야 당황하지 않겠죠.
children props 고찰
3장 내용 중 children props가 나오길래 잘 모르고 사용한 것 같아서 딥다이브 하기로 했습니다.
First.js
, Second.js
를 모두 포함하는 컴포넌트 (House.js)
가 필요한 상황이라고 가정하겠습니다. 아래처럼 First
,second
컴포넌트를 감싸는 <House/>
컴포넌트가 있습니다.
// House.js
import React from "react";
function House() {
const style = {
border: "4px solid green",
padding: "16px",
};
return <div style={style}></div>;
}
export default House;
//Second.js
import React from "react";
function Second(props) {
return <div> 둘 째 {props.name} 입니다</div>;
}
export default Second;
// First.js
import React from "react";
function First() {
return <div>첫 째입니다</div>;
}
export default First;
// App.js
import React from "react";
import First from "./First";
import Second from "./Second";
import House from "./House";
function App() {
return (
<House>
<First />
<Second name="동2" color="blue" />
</House>
);
}
export default App;
렌더링 결과
House 컴포넌트는 정상적으로 초록색 박스가 그려지는데, 내부의 컴포넌트인 First, Second는 작동하지 않는 것을 볼 수 있습니다.
컴포넌트 태그(House) 사이의 컴포넌트(First, Second)의 값을 조회하고 싶을 때 props.children
을 사용해야 합니다.
// House.js
import React from "react";
function House({ children }) {
const style = {
border: "4px solid green",
padding: "16px",
};
return <div style={style}>{children}</div>;
}
export default House;
정상적으로 렌더링 되는 것을 확인할 수 있습니다.
즉 컴포넌트 사이에 컴포넌트가 있을 경우 children을 사용합니다.
children props를 활용한 렌더링 최적화
import React, { useState } from "react";
const ChildComponent = () => {
console.log("ChildComponent is rendering!");
return <div>Hello World!</div>;
};
const ParentComponent = () => {
console.log("ParentComponent is rendering!");
const [toggle, setToggle] = useState(false);
return (
<>
<ChildComponent />
<button
onClick={() => {
setToggle(!toggle);
}}
>
re-render
</button>
</>
);
};
const Container = () => {
return (
<div>
<ParentComponent />
</div>
);
};
Container > ParentComponent > ChildComponent 구조로 프로젝트가 구성되있습니다. 이 상황에서 만약 re-render 버튼을 눌러 ParentComponent의 리렌더링을 유발한다면 당연히 ParentComponent가 리렌더 되는 순간 ChildComponent도 함께 리렌더 될 것입니다. 실제로 콘솔을 살펴보면 두 컴포넌트가 모두 렌더링 되고 있음을 확인할 수 있습니다.
아시다시피 이 현상은 아주 비효율적입니다. ParentComponent의 toggle state 변경은 ChildComponent와는 무관함에도 무의미하게 ChildComponent가 리렌더링 되는 것이기 때문입니다. 즉 ChildComponent는 리렌더링될 필요가 없음에도 불구하고 리렌더링되는 것입니다.
이러한 문제를 해결하기 위해 React.memo
를 사용하기도 하지만 children props를 사용해도 해결할 수 있습니다.
import React, { useState } from "react";
const ChildComponent = () => {
console.log("ChildComponent is rendering!");
return <div>Hello World!</div>;
};
const ParentComponent = ({ children }) => {
console.log("ParentComponent is rendering!");
const [toggle, setToggle] = useState(false);
return (
<div>
{children}
<button
onClick={() => {
setToggle(!toggle);
}}
>
re-render
</button>
</div>
);
};
const Container = () => {
return (
<div>
<ParentComponent>
<ChildComponent />
</ParentComponent>
</div>
);
};
ParentComponent 내부에 직접 ChildComponent를 배치하는 대신 children prop을 통해 간접적으로 ChildComponent를 return하고 있음을 확인할 수 있습니다. 이 상태에서 아까와 동일하게 re-render 버튼을 눌러 ParentComponent의 리렌더를 유발하게 하게 되면 콘솔에는 "ParentComponent is rendering!"이라는 문구만 찍히게 됩니다. 즉 ChildComponent가 렌더링 되지 않음으로써 최적화한 것입니다.
즉 children prop을 사용하면 React.memo 등의 도구를 사용하지 않고도 구조적으로 렌더링 최적화를 달성할 수 있습니다.
그렇다며 이유가 뭘까요??
이유는 아직 이해를 못했습니다... 담주까지 알아와서 마저 설명드리도록 하겠습니다ㅠ
웹팩 코드 검색 확장자
import React from "react";
import Header from "./header";
//웹팩 코드 검색 확장자가 있기 때문에
//파일을 import 할 때 파일 확장자를 적지않아도 됩니다.
export default function App() {
return (
<div>
<Header name="변재정" />
<p>안녕하세요 에픽입니다.</p>
</div>
);
}
"웹팩 코드 검색 확장자(webpack module resolution)" 기능 덕분에 import시 js, jsx 등 파일 확장자를 생략해도 자동으로 찾을 수 있습니다. 확장자가 파일 이름에 없는 경우 웹팩의 확장자 옵션(extentions)에 정의된 확장자 목록을 통해 확장자 이름을 포함한 파일이 있는지 확인하여 자동으로 import 한다.
3줄 요약
- 리액트의 컴포넌트 선언방법은 함수형 컴포넌트와 클래스형 컴포넌트가 있으며, 현재 함수형 컴포넌트와 react hook을 조합해 사용하는 것을 권장하고있다.
- children props는 부모컴포넌트가 자식 컴포넌트로 접근하기 위한 리액트 props이다.
- 접근뿐만 아니라 불필요한 리렌더링을 방지하는 최적화에도 도움이 된다.