◎ Web/React

React 기본 개념과 흐름

reo91004 2024. 3. 10. 18:59
반응형

🖥️ 시작하며

React는 DOM을 이용해 동적인 사이트를 만들기 위해 사용한다. 자바스크립트를 쌩으로 사용하지 않고 리액트를 쓰는 궁극적인 이유는 HTML과 기능을 구현하는 자바스크립트가 분리되어있기 때문에 앱이 커지고 방대해지면 추후 코드해석이 어려울 수 있다. 이에 반해 리액트는 하나의 기능을 하나의 파일로 유지가 가능하다.

리액트의 기초를 배우면서 필수적으로 알아둬야 할 몇가지 사항들을 기록해두려고 한다.

React 작동 방식

우선 최상단의 index.html파일은 그리 다르지 않다. 여기서 <script type="module" src="/src/index.jsx"></script>를 통해 jsx 파일을 넣는 것이 핵심이다.

import ReactDOM from "react-dom/client";

import App from "./App.jsx";
import "./index.css";

const entryPoint = document.getElementById("root");
ReactDOM.createRoot(entryPoint).render(<App />);
  • ReactDOM은 리액트 앱을 DOM에 렌더링하기 위한 라이브러리.
  • entryPoint는 HTML 문서에서 id가 root인 엘리먼트이다.
  • entryPointReactDOM.createRoot(entryPoint)를 통해 리액트의 애플리케이션을 구동한다.
  • 뒤에 붙은 .render(<App />);은 실제로 애플리케이션이 존재하는 최상위 컴포넌트다. 즉 이 App이 실제 홈페이지를 구동하도록 하는 핵심 코드다.

 

그렇다면 App을 살펴보자.

 

React는 수많은 컴포넌트들로 이루어진다.

import { useState } from "react"; // UI 업데이트

import { CORE_CONCEPTS } from "./data";
import Header from "./components/Header/Header.jsx";
import CoreConcept from "./components/CoreConcept.jsx";
import TabButton from "./components/TabButton.jsx";
import { EXAMPLES } from "./data";

// 원래 JSX에서는 컴포넌트를 한번밖에 불러오지 않음! 그래서 UI를 업데이트 하려면 state를 써야 함.
function App() {
  const [selectedTopic, setSelectedTopic] = useState(null); // 최상위에서 호출해야 함
  // 실질적으로 첫 번째 요소를 관리함. 두 번째 요소는 함수임

  function handleSelect(selectedButton) {
    // selectedButton => 'components', 'jsx', 'props', 'state'
    setSelectedTopic(selectedButton);
  }

  let tabContent = <p>Please select a topic.</p>;

  if (selectedTopic) {
    tabContent = (
      <div id="tab-content">
        <h3>{EXAMPLES[selectedTopic].title}</h3>
        <p>{EXAMPLES[selectedTopic].description}</p>
        <pre>
          <code>{EXAMPLES[selectedTopic].code}</code>
        </pre>
      </div>
    );
  }

  return (
    <div>
      <Header />
      <main>
        <section id="core-concepts">
          <h2>Time to get started!</h2>
          <ul>
            {CORE_CONCEPTS.map((conceptItem) => (
              <CoreConcept key={conceptItem.title} {...conceptItem} />
            ))}
            {/* 
            // 위의 map과 같음
            <CoreConcept
              title={CORE_CONCEPTS[0].title}
              description={CORE_CONCEPTS[0].description}
              image={CORE_CONCEPTS[0].image}
            />
            <CoreConcept {...CORE_CONCEPTS[1]} />
            <CoreConcept {...CORE_CONCEPTS[2]} />
            <CoreConcept {...CORE_CONCEPTS[3]} /> */}
          </ul>
        </section>
        <section id="examples">
          <h2>Examples</h2>
          <menu>
            <TabButton isSelected={selectedTopic === "components"} onSelect={() => handleSelect("components")}>
              Components
            </TabButton>
            <TabButton isSelected={selectedTopic === "jsx"} onSelect={() => handleSelect("jsx")}>
              JSX
            </TabButton>
            <TabButton isSelected={selectedTopic === "props"} onSelect={() => handleSelect("props")}>
              Props
            </TabButton>
            <TabButton isSelected={selectedTopic === "state"} onSelect={() => handleSelect("state")}>
              State
            </TabButton>
          </menu>
          {tabContent}
        </section>
      </main>
    </div>
  );
}

export default App;

대충 보면, return문에 HTML코드가 들어가있는 이상한 모양을 취하고 있지만 이를 통해 하나의 파일에서 그 파일이 수행해야 하는 핵심 자바스크립트와 HTML을 모두 사용할 수 있다는 장점이 있다.

 

자바스크립트에서의 import

우선 import문에서 알고 넘어가야 할 몇 가지를 살펴보자.

자주 사용하는 구문부터.

import { 가져올_멤버 } from '가져올_파일'

가져올_멤버.메소드

// 즉 하나의 변수처럼 사용할 수 있다. 배열을 가져왔다면 인덱싱을 할 수 있던지, 그런 식.
import 가져올_멤버 from '가져올_파일'

가져올_멤버.메소드

// export할 때, default로 선언했다면 {}를 붙이지 않는다. 이는 보통 개체 하나만 선언되어있는 모듈에서 사용한다. 보통 컴포넌트들은 개체 하나만 들어있기 때문!

 

컴포넌트 재사용

React에서 가장 큰 장점은 컴포넌트라는 기능의 단위를 파편화해 이를 재사용할 수 있다는 점이다. 이는 아래에 대한 예시다.

// '속성'으로 컴포넌트 재사용
// function CoreConcept(props) {
//   return (
//     <li>
//       <img src={props.image} alt={props.title} />
//       <h3>{props.title}</h3>
//       <p>{props.description}</p>
//     </li>
//   );
// }

// 위보다 더 좋은 방법
export default function CoreConcept({ image, title, description }) {
  return (
    <li>
      <img src={image} alt={title} />
      <h3>{title}</h3>
      <p>{description}</p>
    </li>
  );

CoreConcept 컴포넌트를, App에서 재사용할 수 있다.

<ul>
            {CORE_CONCEPTS.map((conceptItem) => (
              <CoreConcept key={conceptItem.title} {...conceptItem} />
            ))}
            {/* 
            // 위의 map과 같음
            <CoreConcept
              title={CORE_CONCEPTS[0].title}
              description={CORE_CONCEPTS[0].description}
              image={CORE_CONCEPTS[0].image}
            />
            <CoreConcept {...CORE_CONCEPTS[1]} />
            <CoreConcept {...CORE_CONCEPTS[2]} />
            <CoreConcept {...CORE_CONCEPTS[3]} /> */}
          </ul>

 

...에 대한 부가설명!

자바스크립트에서는 특이하게 전개 연산자라고 ...연산자를 사용할 수 있다. 이는 배열을 펼치는 작업을 수행한다. 위의 코드를 다시 해석해보자.

우선 자바스크립트에서는 map을 통해 현존하는 배열에 기반해 새로운 배열을 제공할 수 있다. () =>구문을 통해 CORE_CONCEPTS의 요소들을 받아 이를 전개 연산자를 통해 출력한다.

keyprops는 동적으로 생성된 엘리먼트들을 식별하기 위해 사용된다.

 

컴포넌트에서의 children props

컴포넌트에서는 무조건 생성되고, 겉으론 보이지 않는 children이라는 props가 있다. 여기서 children<> </>사이에 들어가는 내용 그 자체다.

// children은 항상 받는 props. 객체 태그 사이에 넣는 내용이 그것임

export default function TabButton({ children, onSelect, isSelected }) {
  return (
    <li>
      {/* className을 active로 바꾸면 css가 적용됨 */}
      <button className={isSelected ? "active" : undefined} onClick={onSelect}>
        {children}
      </button>
    </li>
  );
}

 

리액트에서 UI를 업데이트하는 법

리액트에서 UI를 업데이트하려면 자바스크립트에서 이벤트 리스너를 사용하는 것과 달리, 여기선 StateHooks를 이용해야 한다.

 const [selectedTopic, setSelectedTopic] = useState(null); // 최상위에서 호출해야 함
  // 실질적으로 첫 번째 요소를 관리함. 두 번째 요소는 함수임

  function handleSelect(selectedButton) {
    // selectedButton => 'components', 'jsx', 'props', 'state'
    setSelectedTopic(selectedButton);
  }

  let tabContent = <p>Please select a topic.</p>;

  if (selectedTopic) {
    tabContent = (
      <div id="tab-content">
        <h3>{EXAMPLES[selectedTopic].title}</h3>
        <p>{EXAMPLES[selectedTopic].description}</p>
        <pre>
          <code>{EXAMPLES[selectedTopic].code}</code>
        </pre>
      </div>
    );
  }
<menu>
<TabButton isSelected={selectedTopic === "components"} onSelect={() => handleSelect("components")}>
  Components
</TabButton>
<TabButton isSelected={selectedTopic === "jsx"} onSelect={() => handleSelect("jsx")}>
  JSX
</TabButton>
<TabButton isSelected={selectedTopic === "props"} onSelect={() => handleSelect("props")}>
  Props
</TabButton>
<TabButton isSelected={selectedTopic === "state"} onSelect={() => handleSelect("state")}>
  State
</TabButton>
</menu>
{tabContent}

흐름을 보자.

  1. 최상단에 const [selectedTopic, setSelectedTopic] = useState(null);을 통해 데이터 기반 State를 가져올 것을 명시한다. 이때 null을 넣은 이유는 초기에 아무 값도 없다고 알려주기 위해서다.
  2. 우선 let tabContent를 선언해 기본 콘텐츠를 설정한다.
  3. TabButton에서, onSelect={() => handleSelect("props")}를 통해 handleSelect를 호출함과 동시에 인자를 전달한다.
  4. 여기서 () => handleSelect("state")}와 그냥 handleSelect를 인자로 주는 것에 대한 차이는 말 그대로 handleSelect에 인자를 주기 위해서에 대한 차이밖에 없다.
  5. handleSelect('props')가 호출되며 setSelectedTopic이 호출되고, 이는 selectTopic변수의 값을 변화시킨다. 이 경우엔 props다.
  6. 이어서 tabContent의 내용이 업데이트 된다.

두서없이 공부하며 내가 헷갈렸던 흐름에 대해 조금 정리해보았다.

반응형