상세 컨텐츠

본문 제목

[리액트 스타터2] 5장. 리액트의 기본 기능 다루기(1)

23-24/React.js 2

by YUZ 유즈 2023. 11. 3. 17:35

본문

728x90


1.  컴포넌트

 

<실습 환경 설정하기>

  • 리액트 앱 만들기
  1. 문서(Documents) 폴더 아래에 chapter5 폴더를 만든 다음 비주얼 스튜디오 코드에서 생성한 폴더를 연다.
  2. 단축키 <Ctrl>+<J>를 눌러 터미널을 열고, 다음 명령어로 새로운 리액트 앱을 만든다.
npx create-react-app .
  • 사용하지 않는 파일 삭제하기: 혼동을 피하고자 src 폴더에서 실습에 사용하지 않을 파일 일부를 제거한다.
    • src/App.test.js
    • src/logo.svg
    • src/reportWebVitals.js 
    • src/setupTest.js

이 파일들은 Create React App이 자동으로 생성한 파일들로 테스트 코드를 작성하거나 리액트 앱의 성능을 살필 때 사용한다. 진행할 실습에서는 사용하지 않으므로 삭제한다.

  • 사용하지 않을 코드 삭제하기: Create React App이 자동으로 생성하지만, 실습에서 사용하지 않을 코드를 모두 삭제한다.
    • index.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 />);

 

  • App.js의 불필요한 코드를 제거한 후의 코드
import "./App.css";

function App() {
  return <div className="App"></div>;
}

export default App;

 

<첫 컴포넌트 만들기>

리액트 컴포넌트는 주로 자바스크립트의 클래스나 함수를 이용해 만든다.

클래스로 컴포넌트를 만드는 방식은 함수로 만드는 컴포넌트에 비해 단점이 많아 함수로 만드는 것을 권장한다.

  • 함수 컴포넌트 만들기: App.js에서 리액트 컴포넌트를 만든다.
import "./App.css";

function Header() { ①
  return (
    <header>
      <h1>header</h1>
    </header>
  ); ②
}

function App() {
  return <div className="App"></div>;
}

export default App;

① 함수를 이용해 Header라는 이름의 컴포넌트를 App 컴포넌트 밖에서 만든다.

② Header 컴포넌트는 HTML을 반환한다. 여러 줄로 이루어진 HTML을 반환할 때는 return문에서 반환할 HTML을 소괄호로 감싼 다음 세미콜론(;)을 꼭 붙여 주어야 한다.

 

함수 선언식이 아니라 화살표 함수로도 컴포넌트를 만들 수 있다.

import "./App.css";

const Header = () => { ①
  return (
    <header>
      <h1>header</h1>
    </header>
  );
};

function App() {
  return <div className="App"></div>;
}

export default App;

① 화살표 함수를 이용해 Header 컴포넌트를 만든다.

  • 컴포넌트를 페이지에 렌더링하기: Header 컴포넌트를 페이지에 렌더링 하려면 App에서 이 컴포넌트를 자식 요소로 배치해야 한다. App 컴포넌트를 다음과 같이 수정한다.
import "./App.css";

const Header = () => {
  (...)
};

function App() {
  return (
    <div className="App">
      <Header /> ①
    </div>
  );
}
export default App;

① App의 return 문에서 Header 컴포넌트를 마치 HTML처럼 태그로 감싸 작성한다.

 

리액트는 다른 컴포넌트를 태그로 감싸 사용한다. 이때 App처럼 다른 컴포넌트를 return 문 내부에 포함하는 컴포넌트를 '부모 컴포넌트'라고 한다. 반대로 Header처럼 App의 return 문에 포함된 컴포넌트를 '자식 컴포넌트'라고 한다. 이렇게 부모의 return문에 자식을 포함하는 행위를 "자식 컴포넌트를 배치한다"라고 표현한다.

 

Header를 App의 자식 컴포넌트로 배치하고 저장하면 페이지가 다음과 같이 변화한다.

자식인 Header 컴포넌트를 페이지에 표시

Header 컴포넌트를 페이지에 렌더링했다.

 

<컴포넌트의 계층 구조>

리액트가 컴포넌트를 페이지에 렌더링 하는 과정을 되짚어 보며 App에서 Header 컴포넌트를 자식으로 배치했더니 페이지에서 Header를 렌더링 한 이유를 이해해 보자. index.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 />); ①

① 리액트의 루트 요소 아래에 App 컴포넌트를 배치해 렌더링 한다.

 

위의 index.js를 보면 페이지에 렌더링 하는 컴포넌트는 App 하나뿐이다. 따라서 새로운 컴포넌트를 페이지에 렌더링 하려면 이 컴포넌트를 App의 자식으로 배치해야 한다. 단순히 컴포넌트를 생성한다고 해서 바로 페이지에 렌더링 하지는 않는다.

 

리액트에서 부모는 자식 컴포넌트의 모든 HTML을 함께 반환한다. 예를 들어 chapter5 앱이라면 App는 Header 컴포넌트의 HTML도 함께 반환한다. 따라서 Header를 자식으로 배치한 App 컴포넌트의 예는 HTML로 작성한 다음 코드와 의미상으로 동일하다.

import "./App.css";

function App() {
  return (
    <div className="App">
      <header> ①
        <h1>header</h1>
      </header>
    </div>
  );
}

export default App;

① 자식 컴포넌트 Header의 반환값과 동일한 HTML 코드이다.

 

리액트는 자식으로 배치한 컴포넌트를 부모와 함께 렌더링 한다. 만약 페이지에 렌더링한 컴포넌트가 다음 그림처럼 3개가 필요하다면 각각을 컴포넌트로 만든 다음 App의 자식으로 배치해야 한다.

부모 App에 3개의 자식 컴포넌트 Header, Body, Footer 배 치하기

리액트에서 컴포넌트를 페이지에 렌더링 하려면 App의 자식으로 배치하거나 Header처럼 자식으로 이미 배치된 컴포넌트의 또 다른 자식으로 배치해야 한다. 

 

리액트 컴포넌트는 위 그림처럼 부모-자식 관계라는 계층 구조를 형성한다. 컴포넌트의 계층 구조를 다른 말로 '컴포넌트 트리'라고 한다. 컴포넌트 트리에서 App는 항상 최상위에 존재하므로 루트 컴포넌트라고 부른다.

 

<컴포넌트별로 파일 분리하기>

리액트에서는 보통 하나의 파일에 하나의 컴포넌트를 만든다. 하나의 파일에 여러 컴포넌트를 만들면 코드의 가독성이 떨어지기 때문이다. 

이번에는 컴포넌트를 여러 파일로 나누고 App.js에서 불러와 App 컴포넌트의 자식으로 배치해 보자. 

  1. 리액트 앱 chapter5의 src에 component라는 이름으로 폴더를 만든다.
  2. Header 컴포넌트를 담당할 Header.js를 생성한다.
  3. Header.js에 컴포넌트를 작성한다.
  4. App.js에서 App 컴포넌트 바깥에 만들었던 Header 코드는 삭제한다.
  5. App.js에서 Header 컴포넌트를 불러온다.
  6. 페이지의 몸통 역할을 수행할 Body와 페이지 정보를 표시할 Footer 컴포넌트도 Header 컴포넌트와 동일하게 만들고 App.js에서 불러와 준다.
//3. Header.js에 컴포넌트를 작성한다.
function Header() {
  return (
    <header>
      <h1>header</h1>
    </header>
  );
}

export default Header; ①

① Header 컴포넌트를 다른 파일에서 사용할 수 있도록 내보낸다. 이때 원하는 이름으로 불러올 수 있도록 모듈의 기본값으로 내보낸다.

 

//5. App.js에서 Header 컴포넌트를 불러온다.
import "./App.css";
import Header from "./component/Header"; ①

function App() {
  return (
    <div className="App">
      <Header />
    </div>
  );
}

export default App;

① Header.js에서 기본값으로 내보낸 Header 컴포넌트를 App.js로 불러온다. 이때 경로 './component/Header'의  '.'은 지금 작성하고 있는 파일(App.js)이 있는 위치를 나타날 때 사용하는 표현이다. Header.js는 component 폴더에 있으므로 현재 위치를 기준으로 경로를 표시해 불러와야 한다.

 

//6. 페이지의 몸통 역할을 수행할 Body컴포넌트를 작성한다.
function Body() {
  return (
    <div>
      <h1>body</h1>
    </div>
  );
}
export default Body;

 

//6. 페이지 정보를 표시할 Footer 컴포넌트를 작성한다.
function Footer() {
  return (
    <footer>
      <h1>footer</h1>
    </footer>
  );
}
export default Footer;

 

//6. Body와 Footer 컴포넌트도 App.js에서 불러와 준다.
import "./App.css";
import Header from "./component/Header";
import Body from "./component/Body";
import Footer from "./component/Footer";

function App() {
  return (
    <div className="App">
      <Header />
      <Body />
      <Footer />
    </div>
  );
}

export default App;

 

파일을 저장하고 페이지에서 Header, Body, Footer 컴포넌트를 잘 렌더링 하는지 확인한다.

Header, Body, Footer 컴포넌트를 페이지에서 렌더링하기


 

2. JSX

<JSX란>

리액트에서 컴포넌트는 자바스크립트 함수로 만드는데 이 함수는 HTML 값을 반환한다. 이렇듯 자바스크립트와 HTML 태그를 섞어 사용하는 문법을 JSX(자바스크립트 XML)라고 한다. JSX는 자바스크립트의 확장 문법이다.

JSX 문법을 이용하면 HTML 태그에서 자바스크립트의 표현식을 직접 사용할 수 있다. 

 

component 폴더의 Body.js를 수정해 보자.

function Body() {
  const number = 1; ①
  return (
    <div>
      <h1>body</h1>
      <h2>{number}</h2> ②
    </div>
  );
}
export default Body;

① 상수 number를 선언하고 값 1을 저장한다.

② 상수 number의 값을 <h2> 태그로 감싸 렌더링 한다. 이때 상수 number가 자바스크립트 표현식이라는 것을 표현하기 위해 중괄호({})를 사용한다.

 

<JSX와 자바스크립트 표현식>

표현식이란 값으로 평가되는 식이다. JSX는 자바스크립트 표현식을 HTML 태그와 함께 사용할 수 있어 가독성 있는 코드를 작성할 수 있다.

 

  • 산술 표현식: 숫자로 표현되는 식이다.
function Body() {
  const numA = 1;
  const numB = 2;
  return (
    <div>
      <h1>body</h1>
      <h2>{numA + numB}</h2> ①
    </div>
  );
}
export default Body;

① numA + numB는 두 변수를 더한 연산식이다. 이 연산식은 숫자로 평가되는 산술 표현식이므로 JSX 문법과 함께 사용한다. 페이지를 확인하면 1 + 2를 계산한 값 3을 렌더링한다.

 

  • 문자열 표현식: 문자열 또는 문자열로 평가되는 식이다.
function Body() {
  const strA = "안녕";
  const strB = "리액트";
  return (
    <div>
      <h1>body</h1>
      <h2>{strA + strB}</h2> ①
    </div>
  );
}
export default Body;

① strA + strB는 문자열과 문자열을 이어 붙인다. 문자열을 반환하는 표현식이므로 JSX 문법과 함께 사용한다. 페이지를 확인하면 "안녕리액트"를 렌더링한다.

 

  • 논리 표현식: 참이나 거짓으로 평가되는 식이다.
function Body() {
  const boolA = true;
  const boolB = false;
  return (
    <div>
      <h1>body</h1>
      <h2>{boolA || boolB}</h2> ①
    </div>
  );
}
export default Body;

① boolA || boolB는 참 또는 거짓인 불리언 값을 반환하는 표현식으로 JSX 문법과 함께 사용한다. 페이지를 확인하면 아무런 결과도 렌더링 하지 않았지만 오류는 아니다.

 

논리 표현식의 결과인 불리언 값은 숫자나 문자열과 달리 페이지에 렌더링 되지 않는다. 만일 불리언 값을 렌더링 하고 싶다면 다음과 같이 형 변환 함수를 이용해 문자열로 바꿔 주어야 한다.

function Body() {
  const boolA = true;
  const boolB = false;
  return (
    <div>
      <h1>body</h1>
      <h2>{String(boolA || boolB)}</h2> ①
    </div>
  );
}
export default Body;

String()은 숫자나 불리언 값을 문자열로 변환하는 형 변환 함수로, 자바스크립트에서 기본으로 제공한다. 페이지를 확인하면 "ture"를 렌더링 한다.

 

  • 사용할 수 없는 값: 원시 자료형(숫자, 문자열, 불리언, null, undefined를 제외한 값은 오류가 발생한다.

만약 객체 자료형을 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;

① 객체 objA의 a 프로퍼티 값 렌더링

② 객체 objA의 b 프로퍼티 값 렌더링

객체 objA의 프로퍼티 a, b의 값은 숫자이므로 objA.a와 objA.b는 산술 표현식이다. 따라서 JSX 문법으로 사용할 수 있다. 페이지를 확인하면 "a: 1"과 "b: 2"를 렌더링 한다.

 

<JSX 문법에서 지켜야 할 것들>

  • 닫힘 규칙: JSX의 모든 태그는 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다.
function Body() {
  return (
    <div>
      <h1>body ①
    </div>
  );
}
export default Body;

① <h1>은 여는 태그는 있지만 닫는 태그가 없다. 따라서 JSX의 닫힘 규칙을 위반했기 때문에 오류가 발생한다.

 

많이 사용하는 HTML 태그 중 <img>, <input>은 닫힘 태그 없이도 사용할 수 있는데 JSX에서는 이를 허용하지 않는다.

 

  • 최상위 태그 규칙: JSX가 반환하는 모든 태그는 반드시 최상위 태그로 감싸야한다. 
function Body() {
  return (
    <div>div 1</div>
    <div>div 2</div>
  )
}
export default Body;

이 코드를 실행하면 최상위 태그가 존재하지 않아 오류가 발생한다.

 

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> 태그는 리액트가 제공하는 기능이면서 컴포넌트이다. 따라서 Body 컴포넌트에서 이 객체를 react 라이브러리에서 불러와야 한다.

 

<React.Fragment>로 다른 태그를 감싸면 최상위 태그를 대체하는 효과가 있다. 단 페이지에서 <React.Fragment>  태그는 렌더링 되지 않는다.

 

<React.Fragment>  대신 빈 태그 '<></>'를 사용할 수도 있다.

 

<조건부 렌더링>

리액트 컴포넌트가 조건식의 결과에 따라 각기 다른 값을 페이지에 렌더링 하는 것을 조건부 렌더링이라 한다. 조건에 따라 페이지 요소의 모습이나 종류를 다르게 표시하고 싶을 때 조건부 렌더링을 사용한다.

  • 삼항 연산자를 활용한 조건부 렌더링
import React from "react";
function Body() {
  const num = 19;
  return (
    <>
      <h2>
        {num}은(는) {num % 2 === 0 ? "짝수" : "홀수"}입니다. ①
      </h2>
    </>
  );
}
export default Body;

① 삼항 연산자를 이용해 변수 num의 값이 2로 나누어 떨어지면 짝수, 그렇지 않으면 홀수를 반환한다.

 

if 조건문은 표현식에 해당하지 않기 때문에 JSX와 함께 사용할 수 없지만 표현식인 삼항 연산자를 이용하면 조건에 따라 다른 값을 렌더링 할 수 있다.

  • 조건문을 이용한 조건부 렌더링

조건문은 자바스크립트의 표현식이 아니기 때문에 JSX와 함께 사용할 수 없지만 다음과 같이 조건에 따라 컴포넌트가 반환하는 값을 다르게 표시하도록 만들 수 있다. 

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;

 

삼항 연산자를 이용하는 방법과 조건문을 이용하는 방법은 각기 장단점이 있다. 삼항 연산자는 코드가 매우 간결하지만 자주 사용할 경우 가독성을 해칠 우려가 있고 다중 조건을 작성하기 힘들다. 조건문은 가독성은 좋으나 기본적으로 작성해야 할 코드가 많고 중복 코드가 발생할 우려가 있다. 따라서 상황에 맞게 적절히 선택해서 사용하는 것이 좋다.

<JSX 스타일링>

스타일링이란 CSS와 같은 스타일 규칙을 이용해 요소의 크기, 색상 등을 결정하는 일이다.

  • 인라인 스타일링: JSX 문법으로 HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법이다.
function Body() {
  return (
    <div style={{ backgroundColor: "red", color: "blue" }}> ①
      <h1>body</h1>
    </div>
  );
}
export default Body;

JSX의 인라인 스타일링은 style={{스타일 규칙들}}과 같은 문법으로 작성한다. 문자열로 작성하는 HTML의 인라인 스타일링과는 달리, JSX의 인라인 스타일링은 객체를 생성한 다음 각각의 스타일을 프로퍼티 형식으로 작성한다. 또한 리액트의 JSX는 background-color처럼 CSS에서 속성을 표시할 때 사용하는 스네이크 케이스 표기법 대신 backgroundColor와 같이 카멜 표기법으로 작성해야 한다.

JSX의 인라인 스타일링 페이지

인라인 스타일링은 하나의 파일 안에서 UI 표현을 위한 HTML과 스타일을 위한 CSS 규칙을 함께 작성할 수 있다는 장점이 있다. 그러나 페이지가 스타일을 계산할 때 불필요한 연산을 수행할 가능성이 있고 스타일 규칙이 많으면 코드가 복잡해져 가독성이 떨어진다.

  • 스타일 파일 분리

HTML에서는 스타일을 정의한 CSS 파일을 따로 작성한 다음 <link rel='stylesheet' href='css 파일 경로'> 형식으로 불러와 사용한다. 리액트의 JSX도 마찬가지로 별도의 CSS 스타일 파일을 만들고 이를 불러와 스타일을 적용할 수 있다.

 

Body 컴포넌트에 스타일 규칙을 적용할 파일 Body.css를 component 폴더에 만들고 다음과 같이 작성한다.

.body {
  background-color: green;
  color: blue;
}

Body.css에 작성한 스타일 규칙을 컴포넌트에 적용하기 위해 Body.js를 다음과 같이 수정한다.

import "./Body.css"; ①

function Body() {
  return (
    <div className="body"> ②
      <h1>body</h1>
    </div>
  );
}

export default Body;

① CSS 파일은 import 문으로 경로만 명시하면 불러올 수 있다.

② JSX에서는 HTML 문법과는 달리 요소의 이름을 지정할 때 class 선택자가 아닌 className을 사용한다. class가 자바스크립트의 예약어이기 때문이다.

JSX에서 CSS 파일 스타일 적용하기


3. 컴포넌트에 값 전달하기

 

<Props란>

리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있다. 컴포넌트에 값을 전달하는 경우는 세부 사항들, 즉 컴포넌트의 속성을 지정하는 경우가 대부분이다. 따라서 컴포넌트에 값을 전달하는 속성들이라는 점에서 Properties라고 부르며, 이를 간단히 줄여 Props라고 한다.

 

<Props로 값 전달하기>

Props는 부모만이 자식 컴포넌트에 전달할 수 있다. 그 역은 성립하지 않는다. 따라서 Body 컴포넌트에 Props를 전달하려면 부모인 App 컴포넌트에서 전달해야 한다. 

  • Props로 하나의 값 전달하기: Body 컴포넌트에 변수 name을 Props로 전달해 보자.
//src/App.js 수정
(...)

function App() {
  const name = "이정환";
  return (
    <div className="App">
      <Header />
      <Body name={name} /> ①
      <Footer />
    </div>
  );
}

export default App;

① Props를 전달하려는 자식 컴포넌트 태그에서 이름={값} 형식으로 작성하면 된다.

 

전달하는 Props는 단일객체이다. 따라서 객체 Props에는 name 프로퍼티가 추가된다.

App에서 전달한 Props를 Body 컴포넌트에서 사용해 보자.

//src/component/Body.js 수정
function Body(props) { ①
  console.log(props); ②
  return <div className="body">{props.name}</div>; ③
}
export default Body;

부모 컴포넌트에서 전달된 객체 Props는 함수의 매개변수 형태로 저장된다. 이 코드에서는 props라는 이름의 매개변수에 저장된다. 

매개변수 props의 값을 확인하기 위해 개발자 도구의 콘솔에 출력한다. 

객체 props의 name 프로퍼티 값을 렌더링 한다.

 

전달된 Props에 어떤 값이 들어 있는지 콘솔에서 확인한다.

그리고 props.name 값을 페이지에서 잘 렌더링 하는지도 확인한다.

Props 사용하기

개발자 도구의 콘솔을 확인하면 App 컴포넌트에서 전달된 Props 값(name: "이정환")이 출력된다. 그리고 페이지에서도 props.name의 값 이정환을 잘 렌더링 하고 있다.

 

  • Props로 여러 개의 값 전달하기: App에서 Body 컴포넌트에 객체 Props로 여러 개의 값을 담아 전달해 보자.
//src/App.js 수정
(...)

function App() {
  const name = "이정환";
  return (
    <div className="App">
      <Header />
      <Body name={name} location={"부천시"} /> ①
      <Footer />
    </div>
  );
}

export default App;

App에서 Body 컴포넌트에 Props로 2개의 값 name, location을 전달한다. 변수를 미리 선언하지 않아도 location={"부천시"} 처럼 객체 Props에 프로퍼티를 추가해 전달할 수 있다.

 

Body 컴포넌트에서 Props로 전달된 2개의 값을 사용해 보자.

//src/component/Body.js 수정
function Body(props) {
  console.log(props);
  return (
    <div className="body">
      {props.name}은 {props.location}에 거주합니다
    </div>
  );
}
export default Body;

전달된 Props를 개발자 도구의 콘솔에 출력한다. 

Props로 전달된 name과 location을 페이지에 렌더링 한다.

Props로 전달한 2개의 값 확인하기

 

  • 구조 분해 할당으로 여러 개의 값 사용하기
//src/component/Body.js 수정
function Body(props) {
  const { name, location } = props; ①
  console.log(name, location); ②
  return (
    <div className="body">
      {name}은 {location}에 거주합니다 ③
    </div>
  );
}
export default Body;

매개변수 props에 있는 name, location 프로퍼티를 구조 분해 할당하여 같은 이름의 상수에 저장한다. 

name과 location의 값을 개발자 도구의 콘솔에 출력한다. 

props.name, props.location 대신 구조 분해 할당한 name, location 값을 페이지에 렌더링 한다.

구조 분해 할당한 값 사용하기

콘솔의 결과를 보면 결괏값이 객체가 아닌 상숫값임을 알 수 있다.

Body 컴포넌트의 매개변수에서 구조 분해 할당하면 더 간결한 코드를 작성할 수 있다.

//src/component/Body.js 수정
function Body({ name, location }) {
  console.log(name, location);
  return (
    <div className="body">
      {name}은 {location}에 거주합니다
    </div>
  );
}
export default Body;

매개변수에 전달된 Props 객체를 구조 분해 할당한다.

 

  • 스프레드 연산자로 여러 개의 값 쉽게 전달하기: 부모 컴포넌트에서 Props로 전달할 값이 많으면 Props로 값을 하나의 객체로 만든 다음, 스프레드 연산자를 활용해 전달하면 훨씬 간결하게 코드를 작성한다.
//src/App.js 수정
(...)

function App() {
  const BodyProps = { ①
    name: "이정환",
    location: "부천시",
  };

  return (
    <div className="App">
      <Header />
      <Body {...BodyProps} /> ②
      <Footer />
    </div>
  );
}
export default App;

Body 컴포넌트에 Props로 전달할 값을 객체 BodyProps로 만든다. 

스프레드 연산자로 객체 BodyProps 각각의 프로퍼티를 Props 값으로 전달한다.

스프레드 연산자를 이용해 객체의 값으로 전달하기

 

  • 기본값 설정하기

App에서 Body 컴포넌트에 전달할 값을 하나 더 늘려보자. 

//src/App.js 수정
(...)

function App() {
  const BodyProps = {
    name: "이정환",
    location: "부천시",
    favorList: ["파스타", "빵", "떡볶이"], ①
  };

  return (
    <div className="App">
      <Header />
      <Body {...BodyProps} />
      <Footer />
    </div>
  );
}
export default App;

좋아하는 음식을 담은 배열 favorList를 객체 BodyProps에 추가한다.

 

Body 컴포넌트에서 좋아하는 음식의 개수를 페이지에 렌더링 해보자.

function Body({ name, location, favorList }) { ①
  console.log(name, location, favorList); ②
  return (
    <div className="body">
      {name}은 {location}에 거주합니다.
      <br />
      {favorList.length}개의 음식을 좋아합니다. ③
    </div> 
  );
}
export default Body;

Props에서 구조 분해 할당할 값에 배열 favorList를 추가한다. 

favorList를 포함한 Props의 값을 콘솔에 출력한다. 

배열 favorList에 포함된 요소의 개수를 출력한다.

 

Props에 배열 favorList 추가하기

 

 

실수로 App 컴포넌트에서 Props의 값 중 favorList를 전달하지 않는 경우를 생각해 보자.

//src/App.js 수정
(...)

function App() {
  const BodyProps = {
    name: "이정환",
    location: "부천시",
    // favorList: ["파스타", "빵", "떡볶이"], ①
  };

  return (
    <div className="App">
      <Header />
      <Body {...BodyProps} />
      <Footer />
    </div>
  );
}

export default App;

favorList를 실수로 전달하지 않았다는 상황을 가정하기 위해 주석 처리한다.

 

그 결과 오류가 발생한다.

favorList를 실수로 전달하지 않아 오류 발생

App에서 실수로 favorList를 전달하지 않으면, Body 컴포넌트의 배열 favorList의 값은 undefined가 된다. Body 컴포넌트에서는 favorList를 배열로 예상하고, 배열의 길이를 렌더링 하기 위해 length 프로퍼티로 접근한다. 따라서 "undefined의 프로퍼티를 읽을 수 없다"라는 메시지와 함께 오류가 발생한다. 이런 경우를 대비해 defaultProps를 사용한다. defaultProps를 이용하면 컴포넌트가 받을 Props의 기본값을 미리 설정할 수 있기 때문에 오류를 미연에 방지할 수 있다.

//src/component/Body.js 수정
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;

Body 컴포넌트가 받을 Props에서 favorList의 기본값을 빈 배열로 설정한다.

 

결과를 확인하면 App 컴포넌트에서 실수로 배열 favorList를 전달하지 않아도 오류가 발생하지 않는다. Body 컴포넌트에서 favorList의 기본값을 빈 배열로 설정해 두었기 때문이다.

defaultProps로 Props의 기본값 설정하기

 

<Props로 컴포넌트 전달하기>

Props로 자바스크립트 값뿐만 아니라 컴포넌트도 전달할 수 있다.

//src/App.js 수정
(...)

function ChildComp() { ①
  return <div>child component</div>;
}

function App() {
  return (
    <div className="App">
      <Header />
      <Body>
        <ChildComp /> ②
      </Body>
      <Footer />
    </div>
  );
}

export default App;

새로운 컴포넌트 ChildComp를 만든다. 

ChildComp를 Body 컴포넌트의 자식 요소로 배치한다.

 

Body 컴포넌트의 자식 요소로 ChildComp를 배치했다. 리액트에서는 자식 컴포넌트에 또 다른 컴포넌트를 배치하면 배치된 컴포넌트는 자동으로 props의 children 프로퍼티에 저장되어 전달된다.

//src/component/Body.js 수정
function Body({ children }) { ①
  console.log(children); ②
  return <div className="body">{children}</div>; ③
}
export default Body;

App에서 Body 컴포넌트의 자식으로 배치한 ChildComp는 children 프로퍼티로 전달되어 매개 변수 children에 저장된다. 

children을 콘솔에 출력한다.

children을 자바스크립트 표현식을 사용하듯 렌더링 한다.

 

Props의 children 프로퍼티로 전달되는 자식 컴포넌트는 값으로 취급하므로 JSX의 자바스크립트 표현식으로 사용할 수 있다. chidren에는 컴포넌트 ChildComp가 저장되어 있기 때문에 해당 컴포넌트를 렌더링 한다.

children에 저장된 컴포넌트 렌더링

컴포넌트를 개발자 도구의 콘솔에서 출력하면 객체 형식의 값을 출력한다. 앞서 JSX에서는 자바스크립트 표현식이 객체를 평가할 경우 오류가 발생한다고 했지만 이 객체는 리액트 컴포넌트를 표현한 것이므로 오류가 발생하지 않는다.



Quiz

1. 리액트 컴포넌트는 주로 자바스크립트의 클래스나 (__)를 이용해 만든다. 클래스보다는 (__)로 컴포넌트를 만들 것을 권장한다.

2. 컴포넌트의 이름은 항상 (___)로 시작해야 한다.

3. 컴포넌트는 (__ __)를 가진다. 이는 다른 말로 '컴포넌트 트리'라고도 한다. 

4. JSX는 (___ ___)을 제외한 값을 사용하면 오류가 발생한다.

5. JSX 문법에서 지켜야 할 규칙에는 (__) 규칙과 (___ __) 규칙이 있다.

6.  Props로 컴포넌트에 값을 전달할 때 여러 개의 값을 전달한다면 (____ ___)로 간결하게 코드 작성이 가능하다. 

7. Props로 컴포넌트에 값을 전달할 때 여러 개의 값을 사용한다면 ()하면 간편하게 사용할 수 있다.


1. Footer 컴포넌트스타일을 정의한 파일을 분리해서 다음 화면을 출력해 보세요.

2. 스프레드 연산자를 이용해 여러 개의 값을 컴포넌트에 전달해 보세요.(name, age, mbti 전달)


함수 / 대문자 / 계층 구조 / 원시 자료형 / 닫힘, 최상위 태그 / 스프레드 연산자 / 구조 분해 할당

//Footer.css를 다음과 같이 작성
.footer {
	background-color: blue;
    color: green;
}

//Footer.js를 다음과 같이 작성
import "./Footer.css";

function Footer() {
  return (
    <div className="footer">
      <h1>footer</h1>
    </div>
  );
}

export default Footer;
//src/App.js 수정
(...)

function App() {
  const BodyProps = { ①
    name: "OOO",
    age: "20",
    mbit: "INFP",
  };

  return (
    <div className="App">
      <Header />
      <Body {...BodyProps} /> ②
      <Footer />
    </div>
  );
}
export default App;

출처: 이정환, 『한 입 크기로 잘라먹는 리액트』, 프로그래밍인사이트(2023), p208-244.

Editor: minyong

 
728x90

관련글 더보기