리액트는 페이지의 모든 요소를 컴포넌트 단위로 쪼개어 개발하고, 완성된 컴포넌트를 마치 레고 조립하듯이 하나로 합쳐 페이지를 구성합니다. 리액트의 핵심 개념인 컴포넌트에 대해 자세히 알아보겠습니다.
앞에서 Create React App으로 리액트 앱을 생성했던 방법과 동일합니다.
npx create-react-app .
Documents 폴더 아래에 chapter5 폴더를 만든 다음, 해당 폴더 안에서 단축키 <Ctrl>+<J>를 눌러 터미널을 열고, 다음 명령어로 새로운 리액트 앱을 만듭니다.
npm run start
터미널에서 npm run start 명령으로 리액트 앱을 시작합니다.
리액트 컴포넌트는 주로 자바스크립트의 클래스나 함수를 이용해 만듭니다. 클래스로 컴포넌트를 만드는 방식은 기본 설정 코드를 작성하는 등 함수로 만드는 컴포넌트에 비해 단점이 많아 지금은 선호하지 않습니다. 리액트 공식 문서에서도 클래스보다는 함수로 컴포넌트를 만들 것을 권장하고 있습니다.
함수를 이용해 App.js에서 첫 번째 리액트 컴포넌트를 만들겠습니다. App.js를 다음과 같이 수정해보세요.
import "./App.css";
function Header() { ①
return (
<header>
<h1>header</h1>
</header>
); ②
}
function App() {
return <div className="App"></div>;
}
export default App;
페이지에서 헤더 역할을 담당할 Header 컴포넌트를 만들었습니다. 이렇듯 함수를 이용하면 매우 간단하게 리액트 컴포넌트를 만들 수 있습니다. 즉, 함수를 선언하고 해당 함수가 HTML 요소를 반환하도록 만들면 됩니다. 함수를 사용해 만든 컴포넌트를 특별히 함수 컴포넌트라고 합니다.
함수 컴포넌트를 만들 때 한 가지 주의할 점이 있습니다. 컴포넌트 함수 이름의 첫 글자는 항상 영어 대문자여야 합니다. 그 이유는 리액트 컴포넌트를 HTML 태그와 구분하기 위해서입니다. 컴포넌트 이름의 첫 글자를 대문자로 작성하지 않아도 에러가 발생하지는 않습니다. 그러나 정상적인 리액트 컴포넌트로 인식하지 않기 때문에 의도치 않은 결과가 나타날 수 있으며, 리액트가 제공하는 여러 유용한 기능도 사용할 수 없습니다.
Header 컴포넌트를 페이지에 렌더링하려면 App에서 이 컴포넌트를 자식 요소로 배치해야 합니다. App 컴포넌트를 다음과 같이 수정합니다.
import "./App.css";
const Header = () => {
(...)
};
function App() {
return (
<div className="App">
<Header /> ①
</div>
);
}
export default App;
단지 컴포넌트를 생성한다고 해서 바로 페이지에 렌더링하지는 않습니다. 리액트는 자식으로 배치한 컴포넌트를 부모와 함께 렌더링합니다. 만약 페이지에 렌더링할 컴포넌트가 다음 그림처럼 3개가 필요하다면, 각각을 컴포넌트로 만든 다음 App의 자식으로 배치해야 합니다.
리액트에서 컴포넌트를 페이지에 렌더링하려면 App의 자식으로 배치하거나 Header처럼 자식으로 이미 배치된 컴포넌트의 또 다른 자식으로 배치해야 합니다. 리액트 컴포넌트는 위 그림처럼 부모-자식 관계라는 계층 구조를 형성합니다. 컴포넌트의 계층 구조를 다른 말로 '컴포넌트 트리'라고 합니다. 그리고 컴포넌트 트리에서 App는 항상 최상위에 존재하므로 이를 루트 컴포넌트라고 부릅니다.
하나의 파일에 여러 컴포넌트를 만들면 코드의 가독성이 떨어지기 때문에 보통 하나의 파일에 하나의 컴포넌트를 만들도록 합니다.
리액트에서 컴포넌트는 자바스크립트 함수로 만드는데, 특이하게도 이 함수는 HTML 값을 반환합니다. 이렇듯 자바스크립트와 HTML 태그를 섞어 사용하는 문법을 JSX라고 합니다. JSX는 자바스크립트의 확장 문법입니다. JSX는 공식 자바스크립트 문법은 아니지만, 대다수 리액트 개발자가 사용하는 문법이며, 리액트 공식 문서의 예제로도 사용합니다. 우리는 JSX문법을 이용해 HTML 태그에서 자바스크립트의 표현식을 직접 사용할 수 있습니다.
표현식이란 값으로 평가되는 식입니다. JSX는 자바스크립트 표현식을 HTML 태그와 함께 사용할 수 있어 가독성 있는 코드를 작성할 수 있습니다.
산술표현식이란 숫자로 표현되는 식을 말합니다. component 폴더의 Body.js를 다음과 같이 수정하면, 1+2를 계산한 값 3을 렌더링합니다.
function Body() {
const numA = 1;
const numB = 2;
return (
<div>
<h1>body</h1>
<h2>{numA + numB}</h2> ①
</div>
);
}
export default Body;
문자열 표현식이란 문자열 또는 문자열로 평가되는 식을 말합니다. component 폴더의 Body.js를 다음과 같이 수정하면, 안녕리액트를 렌더링한 것을 확인할 수 있습니다.
function Body() {
const strA = "안녕";
const strB = "리액트";
return (
<div>
<h1>body</h1>
<h2>{strA + strB}</h2> ①
</div>
);
}
export default Body;
논리 표현식이란 참이나 거짓으로 평가되는 식입니다. component 폴더의 Body.js를 다음과 같이 수정합니다. 아무런 결과도 렌더링하지 않게 되는데, 이는 오류가 아닙니다.
function Body() {
const boolA = true;
const boolB = false;
return (
<div>
<h1>body</h1>
<h2>{boolA || boolB}</h2> ①
</div>
);
}
export default Body;
논리 표현식의 결과인 불리언 값을 페이지에 렌더링하고 싶다면, 다음과 같이 형 변환 함수를 이용해 문자열로 바꿔 주어야 합니다.
function Body() {
const boolA = true;
const boolB = false;
return (
<div>
<h1>body</h1>
<h2>{String(boolA || boolB)}</h2> ①
</div>
);
}
export default Body;
JSX는 값을 반환하는 자바스크립트 표현식을 사용할 수 있습니다. 그러나 모든 값을 사용할 수 있는 것은 아닙니다. 원시 자료형에 해당하는 숫자, 문자열, 불리언, null, undefined를 제외한 값을 사용하면 오류가 발생합니다.
JSX의 모든 태그는 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다는 규칙입니다.
JSX가 반환하는 모든 태그는 반드시 최상위 태그로 감싸야 합니다.
function Body() {
return (
<div>div 1</div>
<div>div 2</div>
)
}
export default Body;
이 코드를 실행하면 Body 컴포넌트의 return문 안에 최상위 태그가 존재하지 않아 오류가 발생합니다. HTML 태그를 최상위 태그로 사용하지 않으려면, 다음과 같이 <React.Fragment> 태그를 사용하면 됩니다.
import React from "react";
function Body() {
return (
<React.Fragment> ①
<div>div 1</div>
<div>div 2</div>
</React.Fragment>
);
}
export default Body;
<React.Fragment>로 다른 태그를 감싸면 최상위 태그를 대체하는 효과가 있습니다. 단 페이지에서 <React.Fragment> 태그는 렌더링되지 않습니다.
리액트 컴포넌트가 조건식의 결과에 따라 각기 다른 값을 페이지에 렌더링하는 것입니다.
삼항 연산자를 이용해 변수 num의 값이 2로 나누어 떨어지면 짝수, 그렇지 않으면 홀수를 반환합니다.
import React from "react";
function Body() {
const num = 19;
return (
<>
<h2>
{num}은(는) {num % 2 === 0 ? "짝수" : "홀수"}입니다. ①
</h2>
</>
);
}
export default Body;
if 조건문은 표현식에 해당하지 않기 때문에 JSX와 함께 사용할 수 없지만, 표현식인 삼항 연산자를 이용하면 조건에 따라 다른 값을 렌더링할 수 있습니다.
조건문은 자바스크립트의 표현식이 아니기 때문에 JSX와 함께 사용할 수 없지만, 다음과 같이 조건에 따라 컴포넌트가 반환하는 값을 다르게 표시하도록 만들 수 있습니다. Body 컴포넌트를 다음과 같이 수정해보세요.
import React from "react";
function Body() {
const num = 200;
if (num % 2 === 0) {
return <div>{num}은(는) 짝수입니다</div>;
} else {
return <div>{num}은(는) 홀수입니다</div>;
}
}
export default Body;
삼항 연산자를 이용하는 방법과 조건문을 이용하는 방법은 각기 장단점이 있습니다. 삼항 연산자는 코드가 매우 간결하지만, 자주 사용할 경우 가독성을 해칠 우려가 있습니다. 그리고 삼항 연산자는 다중 조건을 작성하기 힘듭니다. 반면 조건문은 가독성은 좋으나 기본적으로 작성해야 할 코드가 많고 중복 코드가 발생할 우려도 있습니다. 각자 처한 상황에 맞게 적절히 선택해 사용하면 되겠습니다.
스타일링이란 CSS와 같은 스타일 규칙을 이용해 요소의 크기, 색상 등을 결정하는 일입니다.
JSX 문법으로, HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법입니다.
function Body() {
return (
<div style={{ backgroundColor: "red", color: "blue" }}> ①
<h1>body</h1>
</div>
);
}
export default Body;
JSX의 인라인 스타일링은 style={{스타일 규칙들}}과 같은 문법으로 작성합니다. 문자열로 작성하는 HTML의 인라인 스타일링과는 달리, JSX의 인라인 스타일링은 객체를 생성한 다음 각각의 스타일을 프로퍼티 형식으로 작성합니다. 인라인 스타일링은 하나의 파일 안에서 UI 표현을 위한 HTML과 스타일을 위한 CSS 규칙을 함께 작성할 수 있다는 장점이 있습니다. 그러나 페이지가 스타일을 계산할 때 불필요한 연산을 수행할 가능성이 있고, 스타일 규칙이 많으면 코드가 복잡해져 가독성이 떨어집니다.
HTML에서는 스타일을 정의한 CSS 파일을 따로 작성한 다음, <link rel='stylesheet' href='css 파일 경로'> 형식으로 불러와 사용합니다. 리액트의 JSX도 마찬가지로 별도의 CSS 스타일 파일을 만들고 이를 불러와 스타일을 적용할 수 있습니다.
리액트 앱을 만들다 보면 컴포넌트가 다른 컴포넌트에 값을 전달해야 하는 상황이 생깁니다. 컴포넌트 간에 값을 주고받는 방법을 알아보겠습니다.
리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있습니다. 이 객체를 리액트에서는 Props(Properties)라고 합니다. Props 객체가 왜 이런 이름을 갖게 되었을까요? 컴포넌트가 어떤 상황에서 자식에게 값을 전달하는지 알아봅시다. 리액트에서는 보통 재사용하려는 요소를 컴포넌트로 만듭니다. 내용은 다르지만 구조가 같은 요소를 주로 컴포넌트로 만든다고 생각하면 쉽습니다. 컴포넌트의 공통 기능이 아닌 세부 기능을 표현할 때 바로 Props를 사용하는 것입니다. 리액트의 컴포넌트와 Props를 샌드위치 제조에 비유한다면 샌드위치의 겉을 둘러싸고 있는 빵은 컴포넌트이고 샌드위치의 속은 Props와 같습니다. 다 똑같은 샌드위치지만 Props로 햄을 전달하면 햄 샌드위치가 되고, 야채를 넣으면 야채 샌드위치가 되는 원리와 흡사합니다. 보통 리액트에서 컴포넌트에 값을 전달하는 경우는 세부 사항들, 즉 컴포넌트의 속성을 지정하는 경우가 대부분입니다. 따라서 컴포넌트에 값을 전달하는 속성들이라는 점에서 Properties라고 부르며, 이를 간단히 줄여 Props라고 합니다.
Props는 부모만이 자식 컴포넌트에 전달할 수 있다는 것을 주의하세요. 그 역은 성립하지 않습니다.
App.js를 다음과 같이 수정하세요. Props를 전달하려는 자식 컴포넌트 태그에서 이름={값} 형식으로 작성하면 됩니다.
(...)
function App() {
const name = "이덕성";
return (
<div className="App">
<Header />
<Body name={name} /> ①
<Footer />
</div>
);
}
export default App;
App 컴포넌트를 다음과 같이 수정해보세요.
(...)
function App() {
const name = "이덕성";
return (
<div className="App">
<Header />
<Body name={name} location={"부천시"} /> ①
<Footer />
</div>
);
}
export default App;
Body 컴포넌트에서 Props로 전달된 2개의 값을 사용해봅니다.
function Body(props) {
console.log(props);
return (
<div className="body">
{props.name}은 {props.location}에 거주합니다
</div>
);
}
export default Body;
Props로 전달된 값이 많으면, 이 값을 사용할 때마다 객체의 점 표기법을 사용해야 하는 것이 불편합니다. 그런데 Props는 객체이므로 구조 분해 할당하면 간편하게 사용할 수 있습니다. 다음과 같이 Body 컴포넌트를 수정해보세요.
function Body(props) {
const { name, location } = props; ①
console.log(name, location); ②
return (
<div className="body">
{name}은 {location}에 거주합니다 ③
</div>
);
}
export default Body;
Body 컴포넌트의 매개변수에서 구조 분해 할당하면 더 간결한 코드를 작성할 수 있습니다.
function Body({ name, location }) {
console.log(name, location);
return (
<div className="body">
{name}은 {location}에 거주합니다
</div>
);
}
export default Body;
반대로 부모 컴포넌트에서 Props로 전달할 값이 많으면, 값을 일일이 명시해야 하므로 불편할 뿐만 아니라 가독성도 떨어집니다. 이때 Props로 값을 하나의 객체로 만든 다음, 스프레드 연산자를 활용해 전달하면 훨씬 간결하게 코드를 작성할 수 있습니다.
(...)
function App() {
const BodyProps = { ①
name: "이덕성",
location: "부천시",
};
return (
<div className="App">
<Header />
<Body {...BodyProps} /> ②
<Footer />
</div>
);
}
export default App;
Props로는 자바스크립트 뿐만 아니라 컴포넌트도 전달할 수 있습니다. App에서 Body로 컴포넌트를 하나 전달하겠습니다. App.js를 다음과 같이 수정해보세요. 새로운 컴포넌트 ChildComp를 만들고, ChildComp를 Body 컴포넌트의 자식 요소로 배치했습니다. 리액트에서는 자식 컴포넌트에 또 다른 컴포넌트를 배치하면, 배치한 컴포넌트는 자동으로 Props의 children 프로퍼티에 저장되어 전달됩니다.
(...)
function ChildComp() { ①
return <div>child component</div>;
}
function App() {
return (
<div className="App">
<Header />
<Body>
<ChildComp /> ②
</Body>
<Footer />
</div>
);
}
export default App;
children 프로퍼티에 저장된 자식 컴포넌트를 사용해보세요. Body 컴포넌트를 다음과 같이 수정합니다.
function Body({ children }) { ①
console.log(children); ②
return <div className="body">{children}</div>; ③
}
export default Body;
Props의 children 프로퍼티로 전달되는 자식 컴포넌트는 값으로 취급하므로 JSX의 자바스크립트 표현식으로 사용할 수 있습니다. children에는 컴포넌트 ChildComp가 저장되어 있기 때문에 해당 컴포넌트를 렌더링합니다. 저장하고 결과를 콘솔과 페이지에서 확인해보세요. 컴포넌트를 개발자 도구의 콘솔에서 출력하면 객체 형식의 값을 출력합니다. JSX에서는 자바스크립트의 표현식이 객체를 평가할 경우 오류가 발생하지만, 이 객체의 경우 리액트 컴포넌트를 표현한 것이므로 오류가 발생하지 않습니다.
1. 컴포넌트 함수 이름의 첫 글자는 항상 ( )여야 한다.
2. 컴포넌트의 계층 구조를 다른 말로 ( )라고 한다.
3. 자바스크립트와 HTML 태그를 섞어 사용하는 문법을 ( )라고 한다.
4. JSX가 반환하는 모든 태그는 반드시 ( ) 태그로 감싸야 하는데, HTML 태그를 최상위 태그로 사용하지 않으려면
( ) 태그를 사용하면 된다.
5. if 조건문은 표현식에 해당하지 않기 때문에 JSX와 함께 사용할 수 없지만, 표현식인 ( )를 이용하면 조건에 따라 다른 값을 렌더링할 수 있다.
6. ( )은 JSX 문법으로, HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법이다.
7. Props로 전달된 값이 많으면, 이 값을 사용할 때마다 객체의 점 표기법을 사용해야 하는 것이 불편하다. 그런데 Props는 객체이므로 ( )하면 간편하게 사용할 수 있다.
8. "MyApp" 함수 컴포넌트를 만들고, 해당 컴포넌트에서 "Hello, React!"를 화면에 렌더링하도록 코드를 작성해보세요.
function Header() {
return (
<header>
<h1>Hello, React!</h1>
</header>
);
}
function MyApp() {
return (
<div>
<Header />
</div>
);
}
export default MyApp;
9.
// App.js
import "./App.css";
function App() {
const personInfo = {
name: "김덕성",
age: 20,
city: "Seoul",
};
return (
<div className="App">
<Header />
<Body {...personInfo} />
<Footer />
</div>
);
}
export default App;
// Body.js
function Body(props) {
return (
<div>
<h2>Name: {props.name}</h2>
<p>Age: {props.age}</p>
<p>City: {props.city}</p>
</div>
);
}
export default Body;
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023)
Corner React.js 1
Editor: nini
[React.js 1] p1. 카운터 앱 만들기, 6장. 라이프 사이클과 리액트 개발자 도구 (0) | 2023.11.17 |
---|---|
[React.js 1] 5장. 리액트의 기본 기능 다루기(2) (0) | 2023.11.10 |
[React.js 1]3장, 4장 Node.js, 리액트 시작하기 (0) | 2023.10.13 |
[React.js 1] 2장 자바스크립트 실전 (0) | 2023.10.06 |
[React.js 1] 1장. 자바스크립트 기초 (0) | 2023.09.29 |