[실습 환경 설정]
실습 환경 설정을 위해 새 폴더 chapter5를 만들고 터미널에서 npx create-react-app . 명령어로 새로운 리액트 앱을 만든다. 그리고 src 폴더에서 실습에 사용하지 않을 아래의 파일들을 제거한다.
- src/App.test.js
- src/logo.svg
- src/reportWebVitals.js
- src/setupTest.js
Create React App이 자동으로 생성하지만 실습에는 사용하지 않는 코드를 모두 삭제한다.
각각 index.js와 App.js의 파일이 아래와 같은 코드들만 남도록 불필요한 코드를 모두 삭제한다.
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<App />
);
src/index.js
import './App.css';
function App() {
return <div className="App"></div>;
}
export default App;
src/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';
function Header() {
return (
<header>
<h1>header</h1>
</header>
);
}
function App() {
return (
<div className="App">
<Header />
</div>
);
}
export default App;
src/App.js
이때 App처럼 다른 컴포넌트를 return 문 내부에 포함하는 컴포넌트를 ‘부모 컴포넌트’라고 한다. 반대로 Header 처럼 App의 return 문에 포함된 컴포넌트를 ‘자식 컴포넌트’라고 한다.
npm run start 명령어로 렌더링 하면 아래와 같이 Header 컴포넌트가 잘 렌더링 된 모습을 확인할 수 있다.
[컴포넌트의 계층구조]
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(<App/>);
index.js
index.js를 보면 페이지에 렌더링하는 컴포넌트는 App 하나뿐이기 때문에 새로운 컴포넌트를 페이지에 렌더링하려면 이 컴포넌트를 App의 자식으로 배치해야 한다.
리액트 컴포넌트는 위 그림처럼 부모-자식 관계라는 계층 구조를 형성한다. 컴포넌트의 계층 구조를 다른 말로 ‘컴포넌트 트리’라고도 한다. 그리고 컴포넌트 트리에서 App는 항상 최상위에 존재하므로 '루트 컴포넌트'라고 부른다.
[컴포넌트별로 파일 분리하기]
하나의 파일에 여러 컴포넌트를 만들면 코드의 가독성이 떨어지기 때문에 리액트에서는 보통 하나의 파일에 하나의 컴포넌트를 만든다.
chapter5의 src 폴더 아래에 component 이름의 컴포넌트 파일만 따로 보관할 폴더를 만들고 Header 컴포넌트를 담당할 Header.js 파일을 만든다.
function Header() {
return (
<header>
<h1>header</h1>
</header>
);
}
export default Header;
src/component/Header.js
이후 App.js에서 작성한 Header 컴포넌트는 삭제하고 저장한다.
하지만 이대로 저장 후 실행하면 다음과 같은 오류가 발생한다. App 컴포넌트의 자식으로 저장한 Header를 찾을 수 없기 때문이다. Header 컴포넌트를 App.js 파일에서 선언하지도 불러오지도 않았기 때문에 오류가 발생한다. 해결하기 위해서 아래와 같은 import문을 App.js에 추가로 입력해준다.
(...)
import Header from "./component/Header";
(...)
src/App.js
앞으로 실습에서 추가로 사용할 몸통 역할을 수행하는 Body 컴포넌트 Body.js와 페이지 정보를 표시할 Footer 컴포넌트 Footer.js를 component 폴더 아래에 작성한다.
function Body(){
return (
<div>
<h1>body</h1>
</div>
);
}
export default Body;
src/component/Body.js
function Footer(){
return(
<div>
<h1>footer</h1>
</div>
);
}
export default Footer;
src/component/Footer.js
각 파일을 작성했으면 마지막으로 루트 디렉터리인 App.js에서 불러오는 코드를 작성하고 자식 컴포넌트로 배치한다.
import "./App.css";
import Header from "./components/Header";
import Body from "./components/Body";
import Footer from "./components/Footer";
function App() {
return (
<div className="App">
<Header />
<Body />
<Footer />
</div>
);
}
export default App;
src/App.js
저장 후 렌더링하면 Header, Body, Footer 컴포넌트가 페이지에서 잘 실행되는 화면을 볼 수 있다.
[JSX와 자바스크립트 표현식]
자바스크립트와 HTML 태그를 섞어 사용하는 문법을 JSX(자바스크립트 XML)라고 한다. 표현식은 값으로 평가되는 식을 의미하고 JSX에서 주로 사용하는 표현식은 산술 표현식, 문자열 표현식, 논리 표현식이 있다. 이때 자바스크립트 표현식이라는 걸 표현하기 위해 중괄호({})를 반드시 사용해야 한다.
function Body(){
const numA = 10;
const numB = 20;
const strA = "Hello";
const strB = "React !";
const boolA = true;
const boolB = false;
return (
<div>
<h1>body</h1>
<h2>산술 표현식 : {numA + numB}</h2>
<h2>문자열 표현식 : {strA + strB}</h2>
<h2>논리 표현식 : {boolA || boolB}</h2>
<h2>논리 표현식의 문자열 형 변환 : {String(boolA || boolB)}</h2>
</div>
);
}
export default Body;
src/component/Body.js
각 표현식을 사용해 결과를 확인해 보도록하기 위해 Body.js 파일을 위와 같이 수정하고 저장해 실행해본다.
논리 표현식의 결과인 불리언 값은 숫자나 문자열과 달리 페이지에 렌더링되지 않기 때문에 그냥 논리 표현식은 페이지에 아무런 값이 뜨지 않는다. 불리언 값을 페이지의 렌더링 하고 싶다면 바로 아래인 14번째줄에 있는 코드 처럼 형 변환 함수String()을 이용해 문자열로 바꿔주면 페이지에 렌더링된다.
[사용할 수 없는 값]
원시 자료형에 해당하는 숫자, 문자열, 불리언, null, undefined를 제외한 값을 사용하면 오류가 발생한다.
왼쪽 코드와 같이 객체 자료형 값을 반환하는 표현식을 JSX 문법으로 작성한 다음 저장하면, “Object are not valid as a React Child” 라는 메세지가 뜬다. JSX에서는 객체 자료형을 지원하지 않는다. 객체 자료형을 페이지에 렌더링 하고 싶다면 프로퍼티 접근 표기법을 사용해 값을 원시 자료형으로 바꿔주면 된다.
function Body(){
const objA = {
a: 1,
b: 2,
};
return (
<div>
<h1>body</h1>
<h2>a : {objA.a}</h2>
<h2>b : {objA.b}</h2>
</div>
);
}
export default Body;
src/component/Body.js
다음과 같이 Body.js 코드를 수정하고 렌더링하면 아래와 같이 페이지에 객체의 프로퍼티가 정상적으로 렌더링 된 모습을 확인할 수 있다.
[JSX 문법에서 지켜야 할 것들]
1. 닫힘 규칙
JSX의 모든 태그는 예외 없이 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다.
2. 최상위 태그 규칙
JSX가 반환하는 모든 태그는 최상위 태그로 감싸야 한다. HTML 태그를 최상위 태그로 사용하지 않으려면<React.Fragment></React.Fragment> 태그를 사용하거나 빈 태그인 <></ >를 사용할 수 있다.
(단, <React.Fragment>태그는 리액트가 제공하는 기능이면서 컴포넌트이기 때문에 import React from "react"; 문장을 Body.js 파일에 작성해서 react 라이브러리에서 불러와야 사용할 수 있다.)
[조건부 렌더링]
● 삼항 연산자를 사용한 조건부 렌더링
component 폴더의 Body.js를 다음과 같이 수정하고 렌더링 해보면 결과를 확인할 수 있다. 표현식이 아니라 JSX에서는 사용하지 못하는 if문과 달리 삼항 연산자는 표현식이기 때문에 위와 같이 사용할 수 있다.
● 조건문을 사용한 조건부 렌더링
if문은 표현식이 아니라서 JSX에서 사용할 수 없지만, 조건에 따라 컴포넌트가 반환하는 값을 다르게 표시하도록 만들면 사용할 수 있다.
삼항 연산자는 코드가 매우 간결하지만, 자주 사용할 경우 가독성을 해칠 우려가 있고 다중 조건을 작성하기 힘들다. 반면 조건문은 가독성은 좋으나 기본적으로 작성해야 할 코드가 많고 중복 코드가 발생할 우려도 있다. 따라서 상황에 맞게 적절히 선택해 사용해야 한다.
[JSX 스타일링]
● 인라인 스타일링
JSX 문법으로 HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법을 인라인 스타일링이라고 한다.
function Body() {
return (
<div style={{ backgroundColor: "skyBlue", color: "white" }}>
<h1>body</h1>
</div>
);
}
export default Body;
JSX의 인라인 스타일링은 style={{스타일 규칙들}}과 같은 문법을 사용하고, JSX의 인라인 스타일링은 객체를 생성한 다음 각각의 스타일을 프로퍼티 형식으로 작성한다. 또한 background-color처럼 CSS에 서 속성을 표시할 때 사용하는 스네이크 케이스 표기법 대신 JSX에서는 backgroundColor와 같이 카멜 표기법으로 작성한다.
● 스타일 파일 분리
JSX에서도 별도의 CSS 스타일 파일을 만들고 이를 불러와 스타일을 적용할 수 있다. component폴더 아래에 Body.css 파일을 만들고 아래와 같이 배경색과 글자색을 지정해주는 코드를 작성한다.
.body {
background-color: darkslateblue;
color: lightcyan;
}
src/component/Body.css
Body.css에서 작성한 스타일을 적용시키기 위해 Body.js를 아래와 같이 수정한다.
import "./Body.css";
function Body() {
return (
<div className="body">
<h1>body</h1>
</div>
);
}
export default Body;
src/component/Body.js
이떄, class가 자바스크립트의 예약어이기 때문에 HTML 문법과는 달리 요소의 이름을 지정할 때 class 선택자가 아닌 className을 사용한다. 저장 후 렌더링 해보면 아래와 같이 잘 적용된 것을 확인할 수 있다.
[Props란?]
리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있는데, 이 객체를 "Props" 라고 한다. Props는 Properties의 줄임말로 속성이라는 뜻이다. 리액트에서 내용은 다르지만 구조가 같은 요소를 재사용하기 위해 주로 컴포넌트를 만든다. 이때 컴포넌트의 공통 기능이 아닌 세부 기능을 표현할 때 Props를 사용한다.
[Props로 값 전달하기]
● Props로 하나의 값 전달하기
Props로 하나의 값을 전달하기 위해 src/App.js 파일을 아래와 같이 수정하고 저장한다.
Props를 전달하려는 자식 컴포넌트 태그에서 이름={값} 형식으로 작성한다. 전달하는 Props는 단일 객채이기 떄문에 객체 Props에는 name 프로 퍼티가 추가되고 이 과정을 그림으로 나타내면 위의 오른쪽 그림과 같다.
function Body(props) {
console.log(props);
return <div className="body">{props.name}</div>;
}
export default Body;
src/component/Body.js
부모 컴포넌트에서 전달된 객체 Props라는 이름의 함수의 매개변수 형태로 저장된다. 이때, 개발자 도구의 콘솔에 출력된 값을 보면 매개변수 props의 값이 (name : "이정환")이고 props.name의 값이 "이정환"으로 페이지에 잘 렌더링 된 것을 확인할 수 있다.
● Props로 여러 개의 값 전달하기
Props에 프로퍼티를 추가해 전달하기 위해서 App.js를 오른쪽과 같이 수정하고 이 과정을 그림으로 나타내면 왼쪽 그림과 같다. 변수를 미리 선언하지 않아도 location={"부천시"} 처럼 객체 Props에 프로퍼티를 추가해 전달 할 수 있다.
Body 컴포넌트에서 Props로 전달된 값 두 개를 사용하기 위해 src/component/Body.js를 수정하고 저장해 렌더링 해 콘솔을 열어보면 Props에 값이 잘 전달 되었는 지 알 수 있다.
● 구조 분해 할당으로 여러 개의 값 사용하기
Props는 객체이므로 구조 분해 할당하면 간편하게 사용할 수 있다. Body 컴포넌트의 매개변수에서 구조 분해 할당해서 작성하면 더 편리하고 간단한 코드를 만들 수 있다. 콘솔의 결과를 보면 결괏값은 객체가 아닌 상수값임을 알 수 있다.
● 스프레드 연산자로 여러 개의 값 쉽게 전달하기
부모 컴포넌트에서 Props로 전달할 값이 많으면, 값을 일일이 명시해야 해서 불편하고 가독성도 떨어진다. 이때 Props로 값을 하나의 객체로 만든 다음에, 스프레드 연산자를 활용해 전달하면 훨씬 간결하게 코드를 작성할 수 있다.
import "./App.css";
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from "./component/Footer";
function App() {
const BodyProps = {
name: "이정환",
location: "부천시",
};
return (
<div className="App">
<Header />
<Body {...BodyProps} />
<Footer />
</div>
);
}
export default App;
src/App.js
●기본값 설정하기
App에서 Body 컴포넌트에 전달할 값인 favorList 배열을 추가하기 위해 아래와 같이 App.js 그리고 Body.js 파일을 수정하고 콘솔을 열어 정상적으로 렌더링 되었는지 확인한다.
하지만 만약, favorList를 전달받지 못했다면(가정하기 위해 주석처리 했다.) Body 컴포넌트의 배열 favorList의 값은 undefined가 되는 오류가 발생한다. Body 컴포넌트에서는 favorList를 배열로 예상하고, 배열의 길이를 렌더링하기 위해 length 프로퍼티로 접근하기 떄문이다.
따라서 아래와 같이 defaultProps를 이용해 컴포넌트가 받을 Props의 기본값을 미리 설정하면 위와 같은 오류를 방지할 수 있다. 아래는 favorList의 기본값을 빈 배열로 지정해 두었기 때문에 만일 favorList를 전달하지 않아도 오류가 발생하지 않는다.
function Body({ name, location, favorList }) {
console.log(name, location, favorList);
return (
<div className="body">
{name}은 {location}에 거주합니다.
<br />
{favorList.length}개의 음식을 좋아합니다.
</div>
);
}
Body.defaultProps = {
favorList: [],
};
export default Body;
[Props로 컴포넌트 전달하기]
앞서서 컴포넌트 간에 숫자나 문자열과 같은 자바스크립트 값을 Props로 전달할 수 있었는데, 이 뿐만아니라 Props를 통해 컴포넌트도 전달할 수있다. App에서 Body로 컴포넌트를 전달하기 위해 App.js 파일에 ChildComp()라는 새로운 컴포넌트를 만들고 Body 컴포넌트의 자식 요소로 배치한다.
import "./App.css";
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from "./component/Footer";
function ChildComp() {
return <div>child component</div>;
}
function App() {
return (
<div className="App">
<Header />
<Body>
<ChildComp />
</Body>
<Footer />
</div>
);
}
export default App;
src/App.js
리액트에서 자식 컴포넌트에 또 다른 컴포넌트를 배치하면, 배치된 컴포넌트는 자동으로 Props의 children 프로퍼티에 저장되어 전달된다. children 프로퍼티에 저장된 자식 컴포넌트를 사용하기 위해 Body 컴포넌트를 아래와 같이 수정한다.
function Body({ children }) {
console.log(children);
return <div className="body">{children}</div>;
}
export default Body;
src/component/Body.js
Props의 children 프로퍼티로 전달되는 자식 컴포넌트는 값으로 취급하므로 JSX의 자바스크립트 표현식으로 사용할 수 있다. 저장 후 렌더링한 다음 콘솔을 열어보면 children에 저장되어 있는 컴포넌트 ChildComp가 렌더링 된 것을 확인할 수 있다.
1. 리액트에서 컴포넌트를 만들 때 주로 사용하는 방식은 _ _ _ 함수이다.
2. 리액트의 컴포넌트의 이름은 _ 글자를 _ 문자로 시작해야 한다.
3. JSX에서 모든 태그가 __ 태그가 있어야 하며 이를 __ 규칙이라고 한다.
4. _ _ _ _ _ 는 부모 컴포넌트가 자식 컴포넌트에게 값을 전달하기 위한 객체이다.
5. _ _문은 JSX에서 사용할 순 없지만 삼항 연산자 또는 조건에 따라 컴포넌트가 반환하는 값을 다르게 표시하도록 조건문을 사용하면 조건부 렌더링에 사용할 수 있다.
6. JSX에서 class는 예약어이므로 _ _ _ _ _ _ _ _ _ (으)로 사용해야 한다.
7. JSX에서 자바스크립트 표현식을 사용할 때 반드시 _ _ _를 사용해야 한다.
8. 리액트 컴포넌트에서 Props는 자식 컴포넌트에서 수정할 수 있다. (O/X)
9. Body 컴포넌트에 기본값을 설정하고자 한다. 주석으로 처리된 부분의 코드를 완성해라.
function Body({ name, location, favorList }) {
console.log(name, location, favorList);
return (
<div /*요소 이름 지정 선택자*/="body">
{name}은 {location}에 거주합니다.
<br />
{ /*favorList의 개수*/ }개의 음식을 좋아합니다.
</div>
);
}
/*Body 컴포넌트의 기본값 설정*/ = {
favorList: [],
};
export default Body;
10. 아래의 코드와 같이 App.js에 Zero 컴포넌트를 만들어 Body 컴포넌트의 자식으로 배치했다. Zero 컴포넌트가 정상적으로 렌더링 될 수 있도록 Body.js 파일을 작성해라.
import "./App.css";
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from "./component/Footer";
function Zero() {
return <div>제주청귤 아이스티 녹차</div>;
}
function App() {
return (
<div className="App">
<Header />
<Body>
<Zero />
</Body>
<Footer />
</div>
);
}
export default App;
퀴즈 답안
1번 ~ 8번: 함수형 / 첫,대 / 닫는,닫힘/ Props / if / className / 중괄호({}) / X
9번 : className / favor.length / Body.defaultProps
10번 :
function Body({ children }) {
console.log(children);
return <div className="body">{children}</div>;
}
export default Body;
출처 : 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), p184-220, https://reactjs.winterlood.com
Corner React.js 2
Editor: ahyohyo
[React.js 2팀] 3장. Node.js ~ 4장. 리액트 시작하기 (10) | 2024.10.11 |
---|---|
[React.js 2팀] 1장. 자바스크립트 기초 -함수 ~ 2장. 자바스크립트 실전 -반복문 응용하기 (0) | 2024.09.27 |