on my way

한 입 크기로 잘라먹는 리액트 5장: 리액트의 기본 기능 다루기 본문

Computer Science/React

한 입 크기로 잘라먹는 리액트 5장: 리액트의 기본 기능 다루기

wingbeat 2024. 7. 9. 14:15
반응형

1. 컴포넌트

 

리액트는 컴포넌트 기반의 UI 라이브러리이다.

이는 페이지의 모든 요소를 컴포넌트 단위로 쪼개어 개발하고, 완성된 컴포넌트를 레고 조립하듯 하나로 합쳐 페이지를 구성하는 방식이다.

 

첫 컴포넌트 만들기

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

현재는 함수로 컴포넌트를 만드는 방식이 선호된다.

 

함수 컴포넌트 만들기:

App.js에서 첫 번째 리액트 컴포넌트를 만들었다.

import "./App.css";

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

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

export default App;
  • 함수를 이용해 Header라는 이름의 컴포넌트를 만들었다.
  • Header 컴포넌트는 HTML을 반환하며, 여러 줄로 이루어진 HTML을 반환할 때는 소괄호로 감싸고 세미콜론(;)을 붙여주어야 한다.

현재 Header 컴포넌트는 페이지에 렌더링하는 설정이 없으므로 저장해도 빈 페이지만 표시된다.

 

 

컴포넌트를 페이지에 렌더링하기

Header 컴포넌트를 페이지에 렌더링하려면 App 컴포넌트에서 자식 요소로 배치해야 한다.

import "./App.css";

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

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

export default App;
  • App 컴포넌트의 return 문에서 Header 컴포넌트를 마치 HTML처럼 태그로 감싸 작성했다. 리액트 컴포넌트를 반환하는 HTML 태그는 닫는 태그를 반드시 표기해야 한다.

컴포넌트의 계층 구조

리액트는 컴포넌트를 페이지에 렌더링할 때 부모 컴포넌트는 자식 컴포넌트의 모든 HTML을 함께 반환한다. 예를 들어, App 컴포넌트는 Header 컴포넌트의 HTML도 함께 반환한다. 이는 다음과 같이 HTML 코드와 동일한 의미를 가진다.

import "./App.css";

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

export default App;

리액트 컴포넌트는 부모-자식 관계라는 계층 구조를 형성한다.

이를 컴포넌트 트리라고 하며, 컴포넌트 트리에서 App은 항상 최상위에 존재하므로 이를 루트 컴포넌트라고 부른다.

 

 

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

리액트에서는 보통 하나의 파일에 하나의 컴포넌트를 만든다.

이는 코드의 가독성을 높이기 위함이다.

 

Header.js 생성:

component 폴더를 만들고, Header.js 파일을 생성했다.

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

export default Header;
  • Header 컴포넌트를 작성하고 내보냈다.

App.js 수정:

import "./App.css";
import Header from "./component/Header";

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

export default App;
  • Header.js에서 내보낸 Header 컴포넌트를 불러와 App 컴포넌트의 자식으로 배치했다.

이후, Body와 Footer 컴포넌트를 각각 생성하고 App.js에 추가했다.

// Body.js
function Body() {
  return (
    <div>
      <h1>body</h1>
    </div>
  );
}
export default Body;

// Footer.js
function Footer() {
  return (
    <footer>
      <h1>footer</h1>
    </footer>
  );
}
export default 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;

이렇게 파일을 분리하여 컴포넌트를 관리하면 코드의 가독성이 높아지고, 유지보수가 쉬워진다.

 

 

2. JSX

JSX란 무엇인가?

리액트에서 컴포넌트는 자바스크립트 함수로 만들어진다.

이 함수는 특이하게도 HTML 값을 반환하는데, 이러한 자바스크립트와 HTML 태그를 섞어 사용하는 문법을 JSX(자바스크립트 XML)라고 한다.

JSX는 자바스크립트의 확장 문법으로, 공식 자바스크립트 문법은 아니지만 대다수 리액트 개발자가 사용하는 문법이다.

리액트 개발팀 또한 JSX 문법의 사용을 적극 권장하고 있다.

 

JSX의 기본 사용법

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

를 들어, 다음과 같이 사용할 수 있다:

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

이 예제에서는 상수 number를 선언하고 <h2> 태그 내에서 자바스크립트 표현식 {number}를 사용해 렌더링하고 있다.

 

JSX와 자바스크립트 표현식

JSX에서 자주 사용하는 표현식에는 산술 표현식, 문자열 표현식, 논리 표현식 등이 있다.

산술 표현식

function Body() {
  const numA = 1;
  const numB = 2;
  return (
    <div>
      <h1>body</h1>
      <h2>{numA + numB}</h2>
    </div>
  );
}
export default Body;

위 예제는 numA + numB를 계산한 결과인 3을 렌더링한다.

 

문자열 표현식

function Body() {
  const strA = "안녕";
  const strB = "리액트";
  return (
    <div>
      <h1>body</h1>
      <h2>{strA + strB}</h2>
    </div>
  );
}
export default Body;

이 예제는 문자열을 결합하여 "안녕리액트"를 렌더링한다.

 

논리 표현식

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

이 예제는 불리언 값을 사용하며, boolA가 true이기 때문에 결과는 true가 된다.

단, 불리언 값은 JSX에서 직접 렌더링되지 않으므로 값을 문자열로 변환해줘야 한다.

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

객체 자료형 사용하기

JSX에서 객체 자료형을 사용할 수는 없다. 다음 예제를 보자:

function Body() {
  const objA = { a: 1, b: 2 };
  return (
    <div>
      <h1>body</h1>
      <h2>{objA}</h2>
    </div>
  );
}
export default Body;

이 코드는 오류를 발생시키며, "Object are not valid as a React Child"라는 메시지가 뜬다.

객체의 특정 프로퍼티 값을 사용해야 한다.

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;

이렇게 프로퍼티 값을 사용하면 오류 없이 렌더링할 수 있다.

 

JSX 문법 규칙

JSX를 사용할 때 반드시 지켜야 할 몇 가지 중요한 규칙이 있다.

 

닫힘 규칙

JSX의 모든 태그는 여는 태그가 있으면 반드시 닫는 태그도 있어야 한다.

function Body() {
  return (
    <div>
      <h1>body</h1>
    </div>
  );
}
export default Body;

 

최상위 태그 규칙

JSX가 반환하는 모든 태그는 반드시 최상위 태그로 감싸야 한다.

function Body() {
  return (
    <div>div 1</div>
    <div>div 2</div>
  );
}
export default Body;
이 코드는 오류가 발생한다. 최상위 태그로 감싸야 한다
import React from "react";

function Body() {
  return (
    <React.Fragment>
      <div>div 1</div>
      <div>div 2</div>
    </React.Fragment>
  );
}

export default Body;​
또는 빈 태그 <> </>를 사용해도 된다.
function Body() {
  return (
    <>
      <div>div 1</div>
      <div>div 2</div>
    </>
  );
}
export default Body;

<React.Fragment>는 보이지 않고 두 개의 <div> 태그만 렌더링하는 것을 확인할 수 있다.

 

조건부 렌더링

조건에 따라 다른 값을 렌더링할 수 있다.

 

삼항 연산자 사용

import React from "react";
function Body() {
  const num = 19;
  return (
    <>
      <h2>
        {num}은(는) {num % 2 === 0 ? "짝수" : "홀수"}입니다.
      </h2>
    </>
  );
}
export default 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;

 

JSX 스타일링

JSX로 리액트 컴포넌트를 스타일링하는 방법을 살펴보겠다.

 

인라인 스타일링

function Body() {
  return (
    <div style={{ backgroundColor: "red", color: "blue" }}>
      <h1>body</h1>
    </div>
  );
}
export default Body;


리액트에서 대괄호 {}는 JSX 문법에서 자바스크립트 표현식을 감싸기 위해 사용된다.

중괄호 안에 자바스크립트 표현식을 넣으면, 해당 표현식의 결과가 JSX에 삽입된다.

중괄호 두 개 {{}}는 일반적으로 JSX에서 인라인 스타일을 정의할 때 사용된다.

이때 중괄호 내부의 중괄호는 객체를 나타낸다.

즉, 첫 번째 중괄호는 JSX 표현식을 감싸기 위한 것이고, 두 번째 중괄호는 자바스크립트 객체를 나타낸다.

 

예를 들어, 다음과 같이 사용할 수 있다:

function App() {
  return (
    <div style={{ backgroundColor: 'blue', color: 'white' }}>
      Hello, world!
    </div>
  );
}

여기서 style={{ backgroundColor: 'blue', color: 'white' }} 부분이 중괄호 두 개를 사용하는 예시이다.

외부 중괄호는 JSX 표현식을 감싸고 있고, 내부 중괄호는 자바스크립트 객체를 정의하고 있다.

 

 

스타일 파일 분리

CSS 파일을 분리해서 사용할 수도 있다.

 

Body.css:

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

Body.js:

import "./Body.css";

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

export default Body;

이렇게 하면, JSX에서도 HTML처럼 스타일 파일을 분리해 관리할 수 있다.

JSX는 리액트의 강력한 도구로, 자바스크립트와 HTML을 결합해 보다 가독성 높은 코드를 작성할 수 있게 도와준다.

JSX를 잘 이해하고 활용하면 리액트 컴포넌트를 더 쉽게 만들고 관리할 수 있다.

 

 

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

리액트 앱을 만들다 보면 컴포넌트가 다른 컴포넌트에 값을 전달해야 하는 상황이 생긴다.

이번 포스팅에서는 컴포넌트 간에 값을 주고받는 방법을 알아보았다.

Props란?

리액트에서는 부모가 자식 컴포넌트에 단일 객체 형태로 값을 전달할 수 있다.

이 객체를 Props(Properties)라고 한다. Props는 Properties의 줄임말로, 속성을 의미한다.

 

리액트에서 재사용하려는 요소를 컴포넌트로 만들 때, 컴포넌트의 세부 기능을 표현하기 위해 Props를 사용한다.

예를 들어, 게시판 페이지를 리액트로 만든다면 게시물 리스트의 항목들을 컴포넌트로 만들고, 각각의 내용을 Props로 전달한다.

이를 통해 컴포넌트의 구조는 동일하지만, 내용은 다르게 표현할 수 있다.

 

Props로 값 전달하기

단일 값 전달하기:

App 컴포넌트에서 Body 컴포넌트로 name이라는 값을 Props로 전달했다.

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

// Body.js
function Body(props) {
  console.log(props);
  return <div className="body">{props.name}</div>;
}
export default Body;

App 컴포넌트는 name 값을 Body 컴포넌트에 전달하고, Body 컴포넌트는 전달된 name 값을 화면에 렌더링했다.

 

 

 

여러 값 전달하기:

App 컴포넌트에서 Body 컴포넌트로 name과 location 값을 Props로 전달했다.

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

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

App 컴포넌트는 name과 location 값을 Body 컴포넌트에 전달하고, Body 컴포넌트는 전달된 값을 화면에 렌더링했다.

 

 

구조 분해 할당으로 값 사용하기:

Props가 여러 개일 때, 구조 분해 할당을 사용하면 더 간편하게 사용할 수 있다.

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

이 방식은 구조 분해 할당을 사용해 코드가 더 간결해졌다.

 

스프레드 연산자로 값 전달하기:

Props로 전달할 값이 많을 때, 스프레드 연산자를 사용하면 더 간결하게 작성할 수 있다.

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

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

이 방법을 통해 객체의 프로퍼티를 Props 값으로 쉽게 전달할 수 있다.

 

기본값 설정하기:

Props가 전달되지 않을 경우를 대비해 기본값을 설정할 수 있다.

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

기본값 설정을 통해 Props가 누락되었을 때도 오류를 방지할 수 있다.

 

컴포넌트 전달하기:

Props로 문자열이나 숫자뿐만 아니라 컴포넌트도 전달할 수 있다.

// App.js
function ChildComp() {
  return <div>child component</div>;
}

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

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

이 방법을 통해 다른 컴포넌트를 Props로 전달하고, 해당 컴포넌트를 children 프로퍼티로 받아 렌더링할 수 있다.

 

이처럼 리액트에서는 Props를 통해 컴포넌트 간에 다양한 값을 전달하고 사용할 수 있다.

실습을 통해 Props의 활용 방법을 이해하고, 더 나은 리액트 앱을 개발할 수 있다.

 

 

4. 이벤트 처리하기

이벤트란?

이벤트란 웹 페이지에서 일어나는 사용자의 행위를 말한다. 버튼 클릭, 페이지 스크롤, 새로고침 등이 대표적인 예이다.

사용자가 버튼을 클릭하면 버튼 클릭 이벤트, 텍스트를 입력하면 텍스트 변경 이벤트가 발생한다고 표현한다.

이번 포스팅에서는 리액트에서 어떻게 이벤트를 처리하는지 알아보겠다.

 

이벤트 핸들링과 이벤트 핸들러

이벤트 핸들링은 이벤트가 발생하면 특정 코드가 동작하도록 만드는 작업이다.

예를 들어, 버튼을 클릭했을 때 경고 대화상자를 브라우저에 표시하는 동작이 이벤트 핸들링의 대표적인 예다.

다음은 HTML과 자바스크립트를 사용해 이벤트를 핸들링하는 예제다:

 

<script>
  function handleOnClick() {
    alert("button clicked!");
  }
</script>

<button onclick="handleOnClick()">Click Me!</button>

이 코드는 페이지의 버튼을 클릭하는 이벤트가 발생하면 handleOnClick 함수를 실행해 경고 대화상자를 표시한다.

이때 handleOnClick 함수를 '이벤트 핸들러'라고 한다.

 

리액트에서 이벤트 핸들링

리액트에서 이벤트를 핸들링하는 방법을 살펴보자. Body 컴포넌트에 버튼을 하나 만들고, 버튼을 클릭하는 이벤트가 발생하면 실행되는 이벤트 핸들러를 만들어 보겠다.

function Body() {
  function handleOnClick() {
    alert("버튼을 클릭하셨군요!");
  }

  return (
    <div className="body">
      <button onClick={handleOnClick}>클릭하세요</button>
    </div>
  );
}
export default Body;

 

  1. 함수 handleOnClick을 선언했다. 이 함수는 "버튼을 클릭하셨군요!"라는 메시지를 담은 대화상자를 띄운다.
  2. 버튼을 생성하고, 클릭 이벤트 핸들러로 handleOnClick을 설정했다.

 

저장한 다음, 페이지에서 버튼을 클릭하면 "버튼을 클릭하셨군요!"라는 메시지 대화상자가 나타난다.

리액트의 이벤트 핸들링은 HTML의 이벤트 핸들링과 유사하지만, 몇 가지 차이점이 있다.

HTML에서는 onclick으로 표기하지만, 리액트에서는 카멜 케이스 문법에 따라 onClick으로 표기한다.

또한, 이벤트 핸들러를 설정할 때는 함수 호출의 결괏값을 전달하는 것이 아니라 콜백 함수처럼 함수 그 자체를 전달한다.

 

// HTML, 자바스크립트를 사용할 때의 이벤트 핸들러 설정
<button onclick="handleOnClick()">Click Me!</button>

// 리액트를 사용할 때의 이벤트 핸들러 설정
<button onClick={handleOnClick}>클릭하세요</button>

 

HTML, 자바스크립트에서는 이벤트 핸들러를 설정할 때 함수를 호출하듯 소괄호를 붙여 주지만, 리액트에서는 소괄호 없이 함수 이름만 명시한다.

 

이벤트 객체 사용하기

리액트에서는 이벤트가 발생하면 이벤트 핸들러에게 이벤트 객체를 매개변수로 전달한다.

이벤트 객체에는 이벤트가 어떤 요소에서 어떻게 발생했는지에 관한 정보가 담겨 있다.

다음 예제에서는 Body 컴포넌트에 두 개의 버튼을 만들고, 이벤트가 발생하면 클릭한 버튼의 이름을 콘솔에 출력해 보겠다.

function Body() {
  function handleOnClick(e) {
    console.log(e.target.name);
  }

  return (
    <div className="body">
      <button name="A버튼" onClick={handleOnClick}>A 버튼</button>
      <button name="B버튼" onClick={handleOnClick}>B 버튼</button>
    </div>
  );
}
export default Body;
  1. handleOnClick 함수는 이벤트 객체 e를 매개변수로 받아, e.target.name 값을 콘솔에 출력한다.
  2. A 버튼을 만들고, name 속성을 "A버튼"으로 설정했다. 클릭 이벤트 핸들러로 handleOnClick을 설정했다.
  3. B 버튼을 만들고, name 속성을 "B버튼"으로 설정했다. 클릭 이벤트 핸들러로 handleOnClick을 설정했다.

 

이벤트 객체의 target 프로퍼티에는 이벤트가 발생한 요소가 저장된다.

따라서 A 버튼을 클릭하면 e.target에는 A 버튼이, B 버튼을 클릭하면 e.target에는 B 버튼이 저장된다.

저장하고 페이지에서 A 버튼과 B 버튼을 번갈아 클릭하면 클릭한 버튼의 이름이 콘솔에 출력된다.

 

이벤트 객체의 상세 정보 확인하기

이벤트 객체에는 많은 정보가 담겨 있다. 어떤 정보들이 있는지 확인하기 위해 handleOnClick 함수를 수정해 보자:

function Body() {
  function handleOnClick(e) {
    console.log(e);
    console.log(e.target.name);
  }

  return (
    <div className="body">
      <button name="A버튼" onClick={handleOnClick}>A 버튼</button>
      <button name="B버튼" onClick={handleOnClick}>B 버튼</button>
    </div>
  );
}
export default Body;

페이지에서 A 버튼을 클릭하면 콘솔에 출력된 이벤트 객체를 확인할 수 있다.

이벤트 객체에는 상당히 많은 프로퍼티가 저장되어 있으며, 일반적으로 실무에서는 필요한 몇 개의 값만 활용한다.

 

마무리

리액트에서 이벤트를 처리하는 방법을 알아보았다.

이벤트 핸들링은 HTML과 유사하지만, 몇 가지 차이점이 있다.

또한, 이벤트 객체를 활용해 이벤트가 발생한 요소와 관련된 다양한 정보를 얻을 수 있다.

이를 통해 사용자와의 상호작용을 더 효과적으로 처리할 수 있다.

이벤트 처리를 잘 이해하고 활용하면 더 나은 사용자 경험을 제공하는 리액트 애플리케이션을 만들 수 있다.

 

 

 


5. 컴포넌트와 상태

State란 무엇인가?

지금까지는 값이 변하지 않는 정적인 리액트 컴포넌트를 만들었다.

이제는 사용자의 행위나 시간의 흐름에 따라 값이 변하는 동적인 컴포넌트를 만들어 보자.

이를 위해 리액트의 핵심 기능 중 하나인 State를 이해해야 한다.

이번 포스팅에서는 State를 이용해 동적인 컴포넌트를 만드는 방법을 살펴보겠다.

 

State 이해하기

State는 상태라는 뜻이다. 상태는 어떤 사물의 형편이나 모양을 일컫는 말로, 일상 생활에서도 흔히 사용한다.

예를 들어 전구의 상태를 생각해보자. 스위치를 끄면 전구가 꺼져있는 상태(off), 스위치를 켜면 전구가 켜져있는 상태(on)가 된다.

 

전구의 상태 변화
- 전구의 상태는 소등과 점등으로 나눌 수 있다.
- 소등 상태일 때 스위치를 켜면 점등 상태로 변한다.
- 점등 상태일 때 스위치를 끄면 소등 상태로 변한다.

 

리액트 컴포넌트의 State도 전구의 상태 변화와 유사하다.

컴포넌트는 State 값에 따라 다른 결과를 렌더링한다.

 

State의 기본 사용법

리액트에서는 useState 함수를 사용해 State를 생성한다. 문법은 다음과 같다:

const [light, setLight] = useState('off');

여기서 light는 현재 상태의 값을 저장하는 변수(State 변수),

setLight는 상태를 업데이트하는 함수(set 함수)이다.

useState를 호출할 때 인수로 초깃값을 전달하면, 이 값이 State의 초깃값이 된다.

 

State 생성과 사용

Body 컴포넌트에서 숫자를 카운트할 수 있는 State 변수 count를 생성해보자.

import { useState } from "react";

function Body() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <h2>{count}</h2>
    </div>
  );
}

export default Body;

useState 함수는 count의 초깃값으로 0을 설정하고, count와 setCount를 반환한다.

이 상태에서 count는 0을 렌더링한다.

 

 

set 함수로 State 값 변경하기

이번에는 setCount 함수를 호출해 count 값을 1씩 늘려보겠다.

import { useState } from "react";

function Body() {
  const [count, setCount] = useState(0);

  const onIncrease = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={onIncrease}>+</button>
    </div>
  );
}

export default Body;

버튼의 이벤트 핸들러 onIncrease에서는 setCount를 호출해 count 값을 1 증가시킨다.

페이지에서 <+> 버튼을 클릭하면 숫자가 1씩 늘어난다.

 

컴포넌트의 업데이트

set 함수를 호출해 State 값을 변경하면, 컴포넌트는 변경값을 반영하기 위해 다시 렌더링된다.

이를 "컴포넌트의 업데이트"라고 한다. 다음 코드를 통해 확인해보자.

import { useState } from "react";

function Body() {
  console.log("Update!");

  const [count, setCount] = useState(0);

  const onIncrease = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <h2>{count}</h2>
      <button onClick={onIncrease}>+</button>
    </div>
  );
}

export default Body;

버튼을 클릭할 때마다 Body 컴포넌트가 다시 호출되고, 콘솔에 "Update!" 메시지가 출력된다.

 

State로 사용자 입력 관리하기

리액트에서는 다양한 입력 폼을 제공하며, State를 이용해 사용자의 입력을 효과적으로 처리할 수 있다.

예를 들어, input 태그로 텍스트를 입력받아보자.

import { useState } from "react";

function Body() {
  const handleOnChange = (e) => {
    console.log(e.target.value);
  };

  return (
    <div>
      <input onChange={handleOnChange} />
    </div>
  );
}

export default Body;

handleOnChange 함수는 사용자가 폼에 입력한 값을 콘솔에 출력한다.

 

State로 사용자 입력을 저장하기

입력된 텍스트를 State에 저장하려면 다음과 같이 수정한다.

import { useState } from "react";

function Body() {
  const [text, setText] = useState("");

  const handleOnChange = (e) => {
    setText(e.target.value);
  };

  return (
    <div>
      <input value={text} onChange={handleOnChange} />
      <div>{text}</div>
    </div>
  );
}

export default Body;

입력 폼에서 텍스트를 입력하면 onChange 이벤트가 발생해 handleOnChange가 호출되고, setText 함수로 text 값을 업데이트한다.

 

State와 입력 폼의 다양한 활용

리액트에서 State를 이용해 다양한 형식의 입력 폼을 관리할 수 있다.

예를 들어, 날짜 입력, 드롭다운 상자, 여러 줄의 텍스트 입력 등 다양한 입력 폼을 사용할 수 있다.

 

여러 개의 사용자 입력 관리하기

여러 개의 입력 폼을 관리할 때, 각각의 State를 개별적으로 설정할 수도 있지만, 객체 자료형을 이용해 하나의 State로 관리할 수도 있다.

import { useState } from "react";

function Body() {
  const [state, setState] = useState({
    name: "",
    gender: "",
    birth: "",
    bio: "",
  });

  const handleOnChange = (e) => {
    setState({
      ...state,
      [e.target.name]: e.target.value,
    });
  };

  return (
    <div>
      <input name="name" value={state.name} onChange={handleOnChange} placeholder="이름" />
      <select name="gender" value={state.gender} onChange={handleOnChange}>
        <option value=""></option>
        <option value="남성">남성</option>
        <option value="여성">여성</option>
      </select>
      <input name="birth" type="date" value={state.birth} onChange={handleOnChange} />
      <textarea name="bio" value={state.bio} onChange={handleOnChange} />
    </div>
  );
}

export default Body;

이처럼 객체 자료형을 이용하면 여러 입력을 하나의 State로 간단하게 관리할 수 있다.

 

Props와 State

동적으로 변하는 State 값도 Props로 전달할 수 있다.

Body 컴포넌트에 자식 컴포넌트를 만들고, Body의 State를 Props로 전달해보자.

import { useState } from "react";

function Viewer({ number }) {
  return <div>{number % 2 === 0 ? <h3>짝수</h3> : <h3>홀수</h3>}</div>;
}

function Body() {
  const [number, setNumber] = useState(0);

  const onIncrease = () => {
    setNumber(number + 1);
  };
  const onDecrease = () => {
    setNumber(number - 1);
  };

  return (
    <div>
      <h2>{number}</h2>
      <Viewer number={number} />
      <div>
        <button onClick={onDecrease}>-</button>
        <button onClick={onIncrease}>+</button>
      </div>
    </div>
  );
}

export default Body;

여기서 Viewer 컴포넌트는 Body 컴포넌트의 State인 number를 Props로 전달받아, 값에 따라 짝수 또는 홀수를 렌더링한다.

 

부모 컴포넌트의 State와 자식 컴포넌트

부모 컴포넌트의 State 값이 변하면, 해당 State를 Props로 받은 자식 컴포넌트도 리렌더링된다.

만약 부모가 자식에게 State를 Props로 전달하지 않는다면, 부모의 State가 변해도 자식 컴포넌트는 리렌더되지 않는다.

 

import { useState } from "react";

function Viewer() {
  console.log("viewer component update!");
  return <div>Viewer</div>;
}

function Body() {
  const [number, setNumber] = useState(0);

  const onIncrease = () => {
    setNumber(number + 1);
  };
  const onDecrease = () => {
    setNumber(number - 1);
  };

  return (
    <div>
      <h2>{number}</h2>
      <Viewer />
      <div>
        <button onClick={onDecrease}>-</button>
        <button onClick={onIncrease}>+</button>
      </div>
    </div>
  );
}

export default Body;

이 경우에도 부모 컴포넌트가 리렌더링되면 자식 컴포넌트도 함께 리렌더링된다. 리액트에서는 부모 컴포넌트가 리렌더링되면 자식도 함께 리렌더링된다. 이는 불필요한 리렌더링을 발생시켜 성능에 영향을 미칠 수 있으므로 주의가 필요하다.

(리액트에서 불필요한 리렌더링을 방지하기 위한 최적화 기법도 존재하는데, 이는 추후에 다루겠다.)

결론

이번 포스팅에서는 리액트의 State를 이용해 동적인 컴포넌트를 만드는 방법을 살펴보았다.

State를 사용하면 사용자 입력이나 이벤트에 따라 컴포넌트의 상태를 관리하고, 이를 기반으로 동적인 UI를 구현할 수 있다.

State와 Props를 적절히 활용하여 효율적인 리액트 애플리케이션을 개발할 수 있기를 바란다.

 

 


 

6. 리액트에서 Ref 사용하기

리액트에서 Ref를 사용하면 DOM 요소들을 직접 조작할 수 있다.

Ref는 Reference의 줄임말로 참조를 의미한다.

이번 포스팅에서는 이 기능을 이용해 DOM 요소를 제어하는 방법을 알아보겠다.

 

useRef 사용하기

리액트에서는 useRef라는 함수를 이용해 Ref 객체를 생성한다.

useRef를 사용하기 전에 Body 컴포넌트를 다음과 같이 수정한다.

import { useState } from "react";

function Body() {
  const [text, setText] = useState("");

  const handleOnChange = (e) => {
    setText(e.target.value);
  };

  const handleOnClick = () => {
    alert(text);
  };

  return (
    <div>
      <input value={text} onChange={handleOnChange} />
      <button onClick={handleOnClick}>작성 완료</button>
    </div>
  );
}
export default Body;

위 코드에서는 State 변수 text로 관리하는 텍스트 입력 폼과 버튼을 생성했다.

버튼을 클릭하면 handleOnClick 이벤트 핸들러가 실행되어 입력 폼에 작성한 텍스트를 메시지 대화상자에 표시한다.

이제, useRef를 이용해 <input> 태그의 입력 폼에 접근하는 Ref를 만들겠다.

 

import { useRef, useState } from "react";

function Body() {
  const [text, setText] = useState("");
  const textRef = useRef();

  const handleOnChange = (e) => {
    setText(e.target.value);
  };

  const handleOnClick = () => {
    alert(text);
  };

  return (
    <div>
      <input ref={textRef} value={text} onChange={handleOnChange} />
      <button onClick={handleOnClick}>작성 완료</button>
    </div>
  );
}
export default Body;
  1. useRef는 리액트가 제공하는 기능이므로 react 라이브러리에서 불러온다.
  2. useRef는 인수로 전달한 값을 초깃값으로 하는 Ref 객체를 생성하고, 이를 textRef 상수에 저장한다.
  3. <input> 태그에 ref={textRef}를 설정해 textRef가 DOM 입력 폼에 접근하도록 한다.

 

useRef로 입력 폼 초기화하기

이번에는 useRef를 이용해 텍스트 입력 폼을 초기화해보겠다.

import { useRef, useState } from "react";

function Body() {
  const [text, setText] = useState("");
  const textRef = useRef();

  const handleOnChange = (e) => {
    setText(e.target.value);
  };

  const handleOnClick = () => {
    alert(text);
    textRef.current.value = "";
  };

  return (
    <div>
      <input ref={textRef} value={text} onChange={handleOnChange} />
      <button onClick={handleOnClick}>작성 완료</button>
    </div>
  );
}
export default Body;

버튼을 클릭하면 handleOnClick 이벤트 핸들러가 실행되어, 대화상자에서 확인 버튼을 클릭하면 textRef.current.value를 공백 문자열로 초기화한다.

텍스트 입력 폼에서 '안녕 리액트'를 입력하고 <작성 완료> 버튼을 클릭하면, 입력한 문자열이 사라지고 빈 공백만 남게 된다.

 

useRef로 포커스하기

특정 폼에 포커스를 맞춰 사용자의 추가 입력을 유도할 수도 있다.

이번에는 텍스트 입력 폼에서 사용자가 다섯 글자 미만으로 입력하면 해당 요소에 포커스를 맞추도록 해보겠다.

 

import { useRef, useState } from "react";

function Body() {
  const [text, setText] = useState("");
  const textRef = useRef();

  const handleOnChange = (e) => {
    setText(e.target.value);
  };

  const handleOnClick = () => {
    if (text.length < 5) {
      textRef.current.focus();
    } else {
      alert(text);
      setText("");
    }
  };

  return (
    <div>
      <input ref={textRef} value={text} onChange={handleOnChange} />
      <button onClick={handleOnClick}>작성 완료</button>
    </div>
  );
}
export default Body;

입력한 텍스트가 다섯 글자보다 적다면 textRef.current.focus()로 입력 폼에 포커스를 맞추고, 그렇지 않다면 입력 값을 초기화한다.

 

리액트 훅(React Hook)

리액트 훅이란 함수로 만든 리액트 컴포넌트에서 클래스로 만든 리액트 컴포넌트의 기능을 이용할 수 있도록 도와주는 함수들이다. useState와 useRef 모두 리액트 훅에 속한다.

이들은 2018년도에 처음 발표되었으며, 함수로 만든 컴포넌트에서도 클래스로 만든 컴포넌트 기능을 사용할 수 있도록 해준다.

 

데이터 관련 : useState, useCallback, useMemo, useReducer

메서드 관련 : useRef, useImprerativeHandle

생명주기 관련 : useEffect, useLayoutEffec

데이터 공유 : useContext

 

1. 데이터 관련 훅

  • useState: 컴포넌트의 상태(state)를 관리합니다.
const [count, setCount] = useState(0);

 

위 코드에서 count는 상태 변수이고, setCount는 상태를 업데이트하는 함수입니다.

 

  • useCallback: 함수를 메모이제이션하여, 컴포넌트가 다시 렌더링될 때 함수가 재생성되지 않도록 합니다.
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

 

  • useMemo: 값을 메모이제이션하여, 컴포넌트가 다시 렌더링될 때 값이 재계산되지 않도록 합니다.
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);

 

 

  • useReducer: 복잡한 상태 관리를 위한 훅으로, 상태와 상태를 변경하는 방법을 정의합니다.
const [state, dispatch] = useReducer(reducer, initialState);

 

메서드 관련 훅

  • useRef: 컴포넌트의 특정 DOM 요소에 접근할 수 있도록 합니다.
const inputRef = useRef();

 

생명주기 관련 훅

  • useEffect: 컴포넌트가 렌더링된 후, 상태가 변경된 후에 실행될 코드를 정의합니다.
useEffect(() => {
  document.title = `You clicked ${count} times`;
}, [count]);

 

  • useLayoutEffect: useEffect와 비슷하지만, 화면이 업데이트되기 전에 실행됩니다.
useLayoutEffect(() => {
  // DOM 조작 코드
}, [dependencies]);

 

4. 데이터 공유 훅

  • useContext: 여러 컴포넌트에서 데이터를 쉽게 공유할 수 있도록 합니다.
const value = useContext(MyContext);

 

예시를 통한 설명

useState 예시

import React, { useState } from 'react';

function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>You clicked {count} times</p>
      <button onClick={() => setCount(count + 1)}>Click me</button>
    </div>
  );
}

위 코드에서, useState를 사용해 count라는 상태를 만들고, 버튼을 클릭할 때마다 count를 1씩 증가시킵니다.

 

useRef 예시

import React, { useRef } from 'react';

function TextInputWithFocusButton() {
  const inputEl = useRef(null);

  const onButtonClick = () => {
    inputEl.current.focus();
  };

  return (
    <div>
      <input ref={inputEl} type="text" />
      <button onClick={onButtonClick}>Focus the input</button>
    </div>
  );
}

 

위 코드에서, useRef를 사용해 input 요소에 접근하고, 버튼을 클릭할 때 input에 포커스를 맞춥니다.

리액트 훅은 함수형 컴포넌트에서 상태 관리, DOM 접근, 생명주기 관리 등을 쉽게 할 수 있도록 도와줍니다. 초등학생도 쉽게 이해할 수 있도록 훅의 용도를 간단히 설명하면 다음과 같습니다:

 

  • useState: "현재 상태를 저장하고, 그 상태를 변경할 수 있는 버튼을 만들 수 있어요."
  • useRef: "HTML 요소에 쉽게 접근해서, 특정 동작을 할 수 있어요."
  • useEffect: "컴포넌트가 화면에 보일 때, 또는 업데이트될 때 실행할 코드를 작성할 수 있어요."
  • useContext: "여러 컴포넌트에서 공통된 데이터를 쉽게 공유할 수 있어요."

이렇게 리액트 훅을 사용하면, 함수형 컴포넌트에서도 클래스형 컴포넌트와 같은 강력한 기능을 쉽게 구현할 수 있습니다.

반응형