상세 컨텐츠

본문 제목

[React.js 3] 5장. 리액트의 기본 기능 다루기 1

23-24/React.js 3

by 롱롱😋 2023. 11. 3. 10:00

본문

728x90

 

컴포넌트

실습 환경 설정하기

본격적인 실습에 앞서 5장에서 사용할 새 리액트 앱을 만든다.

  1. 리액트 앱 만든다. npx create-react-app .
  2. 사용하지 않는 파일 삭제한다.  → Create React App이 자동으로 생성한 파일들로 테스트 코드를 작성하거나 리액트 앱의 성능을 살필 때 사용한다. 진행할 실습에서는 사용하지 않으므로 삭제한다.
    • src/App.test.js
    • src/logo.svg
    • src/reportWebVitals.js
    • src/setupTest.js
  3. 사용하지 않을 코드를 삭제한다.
    • index.js → 주석으로 표시된 코드는 모두 삭제한다.
      import React from 'react';
      import ReactDOM from 'react-dom/client';
      import './index.css';
      import App from './App';
      // import reportWebVitals from './reportWebVitals'; // 앞서 삭제한 파일을 불러오는 문장, 리액트 앱의 성능 척도 측정용도로 사용
      
      const root = ReactDOM.createRoot(document.getElementById('root'));
      root.render(
      //    // 리액트 앱 내부의 잠재적인 문제를 검사하는 도구, 리액트 입문자에 혼란을 줄 수 있음
          
      //  
      );
      
      // If you want to start measuring performance in your app, pass a function
      // to log results (for example: reportWebVitals(console.log))
      // or send to an analytics endpoint. Learn more: <https://bit.ly/CRA-vitals>
      // reportWebVitals();
      ​
    • App.js
      import './App.css';
      
      function App() {
        return (
          <div className="App"></div>
        );
      }
      
      export default App;
      ​
  4. 불필요한 파일과 코드를 모두 제거했다면, 리액트 앱을 시작한다. →  npm run start

 

 

첫 컴포넌트 만들기

리액트 컴포넌트는 주로 자바스크립트의 클래스함수를 이용해 만든다. 클래스로 컴포넌트를 만드는 방식은 기본 설정 코드를 작성하는 등 함수로 만드는 컴포넌트에 비해 단점이 많아 지금은 선호하지 않는다.

따라서 함수로 컴포넌트를 만드는 방법을 알아보도록 하자.

  1. 함수 컴포넌트 만들기
    • App.js
      • 함수를 선언하고 해당 함수가 HTML 요소를 반환하도록 만든다.
    const Header = () => {
      return (
        <header>
          <h1>header</h1>
        </header>
      );
    };
    
    📌 컴포넌트 이름은 HTML 태그와 구분하기 위해 항상 대문자로 시작한다.
  2. 컴포넌트를 페이지에 렌더링하기
    • App.js
      • App에서 해당 컴포넌트를 자식 요소로 매치합니다.
    function App() {
      return (
        <div className="App">
          <Header />
        </div>
      );
    }
    

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

 

 

컴포넌트의 계층 구조

리액트 컴포넌트는 부모-자식 관계라는 계층 구조를 형성하며  ‘컴포넌트 트리’라고 한다. 그리고 컴포넌트 트리에서 App은 항상 최상위에 존재하므로 ‘루트 컴포넌트’라고 한다.

  • index.js에서는 App 컴포넌트를 리액트의 루트 요소 아래에 배치해 렌더링한다.
    • root.render(<App />); → 페이지에 렌더링하는 컴포넌트가 하나.
    • 새로운 컴포넌트를 페이지에 렌더링하려면 이 컴포넌트를 App의 자식으로 배치해야 한다.
  • 단지 컴포넌트를 생성한다고 해서 바로 페이지에 렌더링하진 않는다.
    • 부모는 자식 컴포넌트의 모든 HTML을 함께 반환한다.
    • 자식으로 배치한 컴포넌트를 부모와 함께 렌더링한다. → 페이지에 렌더링할 컴포넌트가 3개 필요하다면 각각을 컴포넌트로 만들어 App의 자식으로 배치해야 한다.

 

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

 리액트에서는 가독성을 위해 보통 하나의 파일에 하나의 컴포넌트를 만든다.

이번에는 컴포넌트 파일만을 따로 모아 보관할 폴더를 만들고 컴포넌트를 여러 파일로 나누어 보도록 하자.

  • src/component/Header.js
    const Header = () => {
        return (
            <header>
            <h1>header</h1>
            </header>
        );
    };
    
    export default Header;  // Header 컴포넌트를 다른 파일에서 사용할 수 있도록 내보냄
    ​
  • import Header from "./component/Header";
    • src/App.js에 Header 컴포넌트를 불러오는 구문을 추가한다.
    • 추가하지 않는다면 App컴포넌트의 자식으로 배치한 Header를 찾을 수 없으므로 오류가 발생한다.
  • 추가적으로 Body와 Footer 컴포넌트를 각각 만들고 App.js에서 두 컴포넌트를 불러와 자식으로 배치합니다.
    • src/component/Body.js
    const Body = () => {
        return (
            <div>
               <h1>body</h1>
            </div>
        );
    };
    
    export default Body;
    
    • src/component/Footer.js
    const Footer = () => {
        return (
            <div>
    	        <h1>footer</h1>
            </div>
        );
    };
    
    export default Footer;
    
  • src/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;
    ​

 

 

JSX

 

JSX란?

 리액트에서 컴포넌트는 자바스크립트 함수로 만들고 HTML 값을 반환한다. JSX는 자바스크립트와 HTML 태그를 섞어 사용하는 문법이다. 공식 자바스크립트 문법은 아니지만, 자바스크립트의 확장 문법이며 다수의 리액트 개발자가 사용한다. 또한, 리액트 공식문서와 리액트 개발팀 또한 JSX 문법 사용을 적극 권장한다.

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

const number = 1;
    return (
        <div>
            <h2>{number}</h2>
        </div>
    );

 

JSX와 자바스크립트 표현식

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

자주 사용하는 표현식을 살펴보도록 하자.

  1. 산술표현식: 산술표현식은 숫자로 표현되는 식이다.
    const numA = 1;
    const numB = 2;
        return (
            <div>
                <h2>{numA + numB}</h2>
            </div>
        );
    
  2. 문자열 표현식: 문자열 표현식은 문자열 또는 문자열로 평가되는이다.
    const strA = "안녕";
    const strB = "리액트";
        return (
            <div>
                <h2>{strA + strB}</h2>  
            </div>
        );
    
  3. 논리 표현식: 논리 표현식은 참이나 거짓으로 평가되는 식이다.
    • || 연산자를 이용해 참 또는 거짓인 불리언 값을 반환하는 표현식을 작성했다. 하지만 페이지에는 렌더링되지 않는다. → 오류가 아니다.
    const boolA = true;
    const boolB = false;
        return (
            <div>
                <h2>{boolA || boolB}</h2>
            </div>
        );
    
    • 만일 페이지에 불리언 값을 렌더링하고 싶다면 다음과 같이 형 변환 함수를 이용해 문자열로 바꿔줘야 한다.       → <h2>{String(boolA || boolB)}</h2>
  4. 사용할 수 없는 값

 JSX는 값을 반환하는 자바스크립트 표현식을 사용할 수 있지만, 모든 값을 사용할 수 있는 것은 아니다. 원시 자료형에 해당하는 숫자, 문자열, 불리언, null, undefined를 제외한 값을 사용하면 오류가 발생한다.  

  • 객체 자료형을 JSX 문법으로 작성한 경우 “Object are not vaild as a React Child”라는 오류 메세지를 콘솔창에서 확인할 수 있다.
    • 만일 객체 자료형의 값을 페이지에 렌더링하고 싶다면, 다음과 같이 프로퍼티 접근 표기법으로 값을 원시 자료형으로 바꿔줘야 한다.
      const objA = {
       a: 1,
       b: 2,
      };
      
      return (
       <div>
        <h2>a: {objA.a}</h2>
        <h2>b: {objA.b}</h2>
       </div>
      );

 

JSX 문법에서 지켜야 할 것들

  1. 닫힘 규칙
    • JSX의 모든 태그는 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다.
    • <img>, <input>은 HTML에서 닫힘 태그 없이도 사용할 수 없지만, JSX는 이를 허용하지 않으므로 <img />, <input />과 같이 닫힘 태그를 반드시 병기해야 한다.
  2. 최상위 태그 규칙
    • JSX가 반환하는 모든 태그는 반드시 최상위 태그로 감싸야 한다.
    • HTML 태그를 최상위 코드로 사용하지 않을 경우 <React.Fragment> 태그를 사용한다.
      • 리액트가 제공하는 기능이면서 컴포넌트이므로 react 라이브러리에서 이 객체를 불러와야 한다.                  → import React from "react";
      • <React.Fragment> 태그는 최상위 태그를 대체하는 효과가 있지만 렌더링되지는 않는다.
      • <React.Fragment> 태그 대신 <></> 빈 태그를 사용하기도 한다.
      • 오류가 발생하는 경우
      const Body = () => {
          return (
              <div>div 1</div>
              <div>div2</div>
          );
      };
      • 수정
      const Body = () => {
       return (
        <React.Fragment>
         <div>div 1</div>
        </React.Fragment>
       );
      };

 

 

조건부 렌더링

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

 

  1. 삼항 연산자를 활용한 조건부 렌더링
    • if 조건문은 표현식에 해당하지 않으므로 JSX와 함께 사용할 수 없지만, 표현식인 삼항 연산자를 이용하면 조건에 따라 다른 값을 렌더링할 수 있다.
    • 코드가 매우 간결하지만, 자주 사용할 경우 가독성을 해칠 우려가 있다.
    • 다중 조건을 작성하기 힘들다.
import React from "react";

const Body = () => {
    const num = 19;
    return (
     <>
      <h2>
	   {num}은 {num % 2 === 0 ? "짝수" : "홀수"}입니다.
	  </h2>
     </>
    );
};

export default Body;

 

   2.  조건문을 이용한 조건부 렌더링

  • 위에서 언급했듯 if 조건문은 표현식이 아니므로 JSX와 함께 사용할 수 없다.
  • 가독성은 좋으나 기본적으로 작성해야 할 코드가 많고 중복 코드가 발생할 우려가 있다.
import React from "react";

const Body = () => {
    const num = 200;

    if (num % 2 === 0) {
	 return <div>{num}은 짝수입니다. </div>
	 } else {
	  return <div>{num}은 홀수입니다. </div>
	 }

};

export default Body;

 

 

 

JSX 스타일링

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

JSX로 리액트 컴포넌트를 스타일링하는 방법을 알아보도록 하자.

 

  1. 인라인 스타일링 : JSX 문법 중 하나로, HTML의 style 속성을 이용해 직접 스타일을 정의하는 방법이다.
    • 장점: 하나의 파일 안에서 UI 표현을 위한 HTML과 스타일을 위한 CSS 규칙을 함께 작성할 수 있다.
    • 단점: 페이지가 스타일을 계산할 때 불필요한 연산을 수행할 가능성이 있고, 스타일 규칙이 많으면 코드가 복잡해져 가독성이 떨어진다.
    • 문자열로 작성하는 HTML의 인라인 스타일링과는 달리, JSX에선 객체를 생성한 다음 각각의 스타일을 프로퍼티 형식으로 작성한다.
    • 카멜 표기법을 사용한다. → backgroundColor
      • 스네이크 표기법 : CSS에서 속성을 표시할 때 사용 → background-color
     return (
            <div style={{ backgroundColor: "red", color: "blue" }}>
                <h1>body</h2>
            </div>
        );
    
  2. 스타일 파일 분리: 별도의 CSS 파일을 만들고 이를 불러와 스타일을 적용한다.
    • 요소의 이름을 지정할 때 class 선택자가 아닌 className을 사용한다.
      • class는 자바스크립트의 예약어이다.
    • src/component/Body.js
    import "./Body.css";
    
    const Body = () => {
    
        return (
            <div className="body">
                <h1>body</h1>
            </div>
        );
    };
    
    export default Body;
    • src/component/Body.css
    .body{
        background-color: green;
        color: blue;
    }
    

 

 

컴포넌트에 값 전달하기

Props란?

 리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있는데, 이 객체를 Props라고 한다. Props는 Properties의 줄임말로 속성이라는 뜻이다.

  • Props 객체가 이런 이름을 갖게 된 이유
    • 리액트에서는 보통 재사용하려는 요소 즉, 내용은 다르지만 구조가 같은 요소를 주로 컴포넌트로 만들며 컴포넌트의 공통 기능이 아닌 세부 기능을 표현할 때 Props를 사용한다.                                                                               ex) 샌드위치의 빵: 컴포넌트, 속 재료: Props → Props로 햄을 전달하면 햄 샌드위치
    • 보통 리액트에서 컴포넌트에 값을 전달하는 경우는 세부 사항들, 즉 컴포넌트의 속성을 지정하는 경우가 대부분이다. 따라서 컴포넌트에 값을 전달하는 속성들이라는 점에서 해당 이름이 붙었다.                                                                     

 

Props로 값 전달하기

  • Props는 부모만이 자식 컴포넌트에 전달할 수 있다. 그 역은 성립하지 않으며 Body 컴포넌트에 Props를 전달하려면 부모인 App 컴포넌트에서 전달해야 한다.
  • Props를 전달하려는 자식 컴포넌트 태그에서 이름={값} 형식으로 작성한다.
  1. Props로 하나의 값 전달하기

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

  • src/App.js
import Body from "./component/Body";

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

export default App;
  • src/component/Body.js
    • 부모 컴포넌트에서 전달된 객체 Props는 함수의 매개변수 형태로 저장된다.
 import "./Body.css";

function Body(props){
    console.log(props);  // 값 확인을 위한 콘솔 창 출력
    return <div className="body">{props.name}</div>;  // 렌더링
}

export default Body;

 

    2.  Props로 여러 개의 값 전달하기

  • src/App.js
    • 변수를 미리 선언하지 않아도 location={"부천시"} 처럼 객체 Props에 프로퍼티를 전달할 수 있다.
import Body from "./component/Body";

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

export default App;
  • src/component/Body.js
 import "./Body.css";

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

export default Body;

 

    3. 구조 분해 할당으로 여러 개의 값 사용하기

        자식 컴포넌트에서 Props로 전달된 값이 많을 경우, 이 값을 사용할 때마다 객체의 점 표기법을 사용해야하는 불편함 이 있기 때문에 구조 분해 할당을 통해 편하게 사용할 수 있다.  → Props도 객체이기 때문!

  • src/component/Body.js
// 매개변수 props에 있는 프로퍼티를 구조 분해 할당하여 같은 이름의 상수에 저장

function Body(props){
    const {name, location} = props;
    console.log(name, location);
    return <div className="body">
        {name}은 {location}에 거주합니다.
    </div>;
}
// 매개변수에 구조 분해 할당하기 -> 더 간단한 코드 작성

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

 

 

     4.  스프레드 연산자로 여러 개의 값 쉽게 전달하기

        3.과 반대로 부모 컴포넌트에서 Props로 전달할 값이 많은 경우, 값을 일일이 명시해야하므로 불편하고 가독성이 떨어진다.  Props로 값을 하나의 객체로 만든 다음, 스프레드 연산자를 활용해 전달하여 간결하게 작성한다.

  • src/App.js
import Body from "./component/Body";

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

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

export default App;
  • src/component/Body.js
 import "./Body.css";

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

export default Body;

 

     5. 기본값 설정하기

           기본값 설정을 통해 컴포넌트가 받을 Props의 기본값을 미리 설정오류를 미연에 방지하도록 한다.

  • 오류가 발생하는 경우는?
    • App에서 실수로 favorList를 전달하지 않으면, Body 컴포넌트의 배열 favorList의 값은 undefined가 된다.
    • Body 컴포넌트에서는 favorList를 배열로 예상하고 length 프로퍼티로 접근하기 때문에 **“undefined 프로퍼티를 읽을 수 없습니다”**의 오류가 발생한다.
  • src/App.js
import Body from "./component/Body";

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

...
  • src/component/Body.js
import "./Body.css";

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로 문자열이나 숫자 같은 자바스크립트 값을 전달해 보았다면, 이번엔 컴포넌트 값도 전달하는 방법을 알아보자.

  • src/App.js
    • 자식 컴포넌트에 또 다른 컴포넌트를 배치하면, 배치된 컴포넌트는 자동으로 Props의 children 프로퍼티에 저장되어 전달된다.
import Body from "./component/Body";

function ChildComp(){              // 새로운 Component 생성
  return <div>child component</div>
}

function App() {
  return (
    <div className="App">
      <Body>
        <ChildComp />  // 자식요소로 배치
      </Body>
    </div>
  );
}

export default App;
  • src/component/Body.js
    • Props의 children 프로퍼티로 전달되는 자식 컴포넌트는 값으로 취급하므로 JSX의 자바스크립트 표현식으로 사용할 수 있다.
import "./Body.css";

function Body({children}) {
    console.log(children);
    return (
        <div className="body">
            {children} 
        </div>
    );
}

export default Body;
  • console.log

컴포넌트가 콘솔에서 객체 형식의 값을 출력한다.

이 객체는 리액트 컴포넌트를 표현한 것이므로 오류가 발생하지 않는다.

✔️ JSX에서는 자바스크립트 표현식이 객체를 평가할 경우 오류가 발생한다.

 

 


Quiz

  1. 리액트 컴포넌트는 주로 자바스크립트의 (클래스)나 (함수)를 이용해 만들지만, (함수)로 만드는 컴포넌트를 더 선호한다.
  2. 다른 컴포넌트를 return문 내부에 포함하는 컴포넌트를 (부모 컴포넌트)라고 하며, 부모의 return문에 포함된 컴포넌트를 (자식 컴포넌트)라고 한다.
  3. (JSX)는 자바스크립트와 HTML 태그를 섞어 사용하는 문법이다. 
  4. [3번 정답]에서 (원시 자료형)에 해당하는 숫자, 문자열, (불리언), (null), (undefined)를 제외한 값을 사용하면 오류가 발생한다.
  5. 리액트 컴포넌트가 조건식의 결과에 따라 각기 다른 값을 페이지에 렌더링하는 것을 (조건부 렌더링)이라고 한다.
  6. [3번 정답] 스타일링 중 인라인 스타일링의 프로퍼티 형식은 (카멜) 표기법을 사용한다.
  7. 부모가 자식 컴포넌트에 단일 (객체 형태)로 값을 전달할 수 있는데, 이것을 Props라고 한다. 

 

 

 

<프로그래밍 문제>

1. 다음 코드는 JSX 문법에서 어떤 규칙을 위반한 것인지 주석으로 쓰고 오류가 나지 않게 수정하시오. (복수 정답 인정) 

const Body = () => {
    return (
        <div>div 1</div>
    );
};

 

 

2. 다음 App.js에 새로 생성된 ChildComp 컴포넌트를 Body 컴포넌트의 자식 컴포넌트에 배치하고 Body 컴포넌트를 작성하시오.

import Body from "./component/Body";

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

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

export default App;

 

 


1. 최상위 태그 규칙을 위반함.

const Body = () => {
 return (
  <React.Fragment>
   <div>div 1</div>
  </React.Fragment>
 );
};

  또는 빈태그 사용

const Body = () => {
 return (
  <>
   <div>div 1</div>
  </>
 );
};

 

 

2. 

- App.js

import Body from "./component/Body";

function ChildComp(){              // 새로운 Component 생성
  return <div>child component</div>
}

function App() {
  return (
    <div className="App">
      <Body>
        <ChildComp />  // 자식요소로 배치
      </Body>
    </div>
  );
}

export default App;

- Body.js

function Body({children}) {
    console.log(children);
    return (
        <div className="body">
            {children} 
        </div>
    );
}

export default Body;

 

 

 

 

 

 


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

Corner React.js 3 

Editor: so2

728x90

관련글 더보기