React_Day.1

조현기·3일 전

React_Day.1

React 개발 환경 만들기(Node.js, Vite), 컴포넌트와 JSX, export default와 named export, Fragment, 이벤트 처리, useState 첫걸음.

JavaScript, Python, Git까지 기초를 마치고 React를 시작했다. React는 화면을 컴포넌트라는 조각으로 나눠서 만드는 JavaScript 라이브러리다. 첫날은 프로젝트를 만들고, 컴포넌트를 만들어 화면에 올리고, 버튼을 눌렀을 때 동작하게 하는 것까지 했다.


1. 개발 환경 만들기

1-1. 준비

  1. Node.js 설치: React 개발 도구들이 Node.js 위에서 돌아간다.
  2. 설치 확인: 터미널에서 node -v
$ node -v
v22.22.0            # 버전 번호가 나오면 설치 완료 (버전은 설치한 시점에 따라 다르다)
  1. VS Code에서 프로젝트 폴더를 연다. (Windows에서 PowerShell 실행 정책 때문에 npm 명령이 막히면 관리자 권한으로 PowerShell을 열어서 실행 정책을 조정해야 한다)

1-2. Vite로 프로젝트 생성

$ npm create vite@latest

npm create vite@latest는 Vite라는 도구로 React 프로젝트의 기본 뼈대를 만들어 준다. 실행하면 프로젝트 이름과 프레임워크(React), 언어(JavaScript)를 차례로 고르게 된다. 생성이 끝나면 안내대로 진행한다.

$ cd 프로젝트이름
$ npm install          # 필요한 라이브러리 설치
$ npm run dev          # 개발 서버 실행 → 브라우저에서 확인

필기에는 npm init도 같이 적혀 있다. npm init은 package.json만 만드는 명령어고, Vite로 만들 때는 npm create vite@latest가 package.json까지 알아서 만들어 주기 때문에 따로 할 필요는 없다.

생성된 프로젝트에서 이후 계속 만지게 될 파일은 src 폴더 안에 있다.

프로젝트/
├── index.html          # 화면의 뼈대 (<div id="root"></div> 가 들어 있다)
├── package.json
└── src/
    ├── main.jsx        # 시작점: App 컴포넌트를 root에 그린다
    ├── App.jsx         # 최상위 컴포넌트
    └── index.css

React 컴포넌트 파일의 확장자는 .jsx다. JavaScript 안에 HTML처럼 생긴 태그(JSX)를 쓸 수 있는 파일이라는 뜻이다.


2. 컴포넌트

2-1. 컴포넌트란

컴포넌트: HTML 태그를 반환하는 함수.

import React from 'react'

const App = () => {
  return (
    <div>
      <h1>header</h1>
    </div>
  )
}

export default App

return이 태그(JSX)를 돌려주는 함수이므로 App은 컴포넌트다. 이렇게 함수로 만든 컴포넌트를 함수형 컴포넌트라고 한다.

컴포넌트 이름은 맨 앞글자를 대문자로 쓴다. 이것이 중요한 이유는 React가 이름의 첫 글자로 HTML 태그인지 컴포넌트인지 구분하기 때문이다. 직접 확인해 보았다.

const header = () => <h1>lower</h1>      // 소문자로 시작

export default function App() {
  return <div><header /></div>
}
// 결과: <div><header></header></div>   ← 내가 만든 header 함수가 아니라 HTML의 <header> 태그가 되어 버린다

2-2. 컴포넌트를 쓰는 세 가지 선언 방식

// ① 함수 선언 + 위에서 바로 default export
export default function App() {
  return (
    <div>
      <h1>header</h1>
    </div>
  )
}
// ② 화살표 함수 + 아래에서 default export
const App = () => {
  return (
    <div>
      <h1>header</h1>
    </div>
  )
}

export default App
// ③ 화살표 함수 + 이름을 붙여서 export (named export)
export const App = () => {
  return (
    <div>
      <h1>header</h1>
    </div>
  )
}

①과 ②는 같은 컴포넌트다. export는 맨 위(선언과 함께)에 써도 되고, 맨 아래에 따로 써도 된다.

필기에 "컴포넌트 선언에 const를 적었을 때는 default를 같은 선언문에 적을 수 없다"고 되어 있다. 맞는 설명이다. export default const App = ...은 문법 에러(Unexpected token)가 난다. 그래서 const로 선언했다면 ②처럼 아래에서 export default App을 따로 쓴다. (export default function App() {}처럼 함수 선언은 한 줄에 쓸 수 있다)

2-3. 자동 완성: rafce

VS Code에 React 스니펫 확장(ES7+ React/Redux/React-Native snippets)을 설치하면 짧은 약어로 컴포넌트 틀이 자동으로 만들어진다.

약어만들어지는 것
rafcearrow function component + export (화살표 함수, 아래에 export default)
rfcefunction component + export (function 선언, 아래에 export default)
rfcfunction component (export default function)

필기에 "rafce가 제일 중요"라고 적혀 있는 이유는 위 ②번 구조를 가장 자주 쓰기 때문이다. af는 arrow function, fc는 function component의 약자다.


3. 컴포넌트 나누기: import와 export

컴포넌트를 파일 하나에 다 쓰지 않고 파일별로 나눠서 가져다 쓴다.

// Header.jsx
import React from 'react'

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

export default Header
// App.jsx
import Header from "./Header"       // Header.jsx 를 불러온다
import React from 'react'

export default function App() {
  return (
    <div>
      <Header />                    {/* 컴포넌트는 HTML 태그처럼 쓴다 */}
      <h1>리액트</h1>
    </div>
  )
}
// 결과: <div><div><h1>header</h1></div><h1>리액트</h1></div>

<Header />처럼 컴포넌트를 태그 모양으로 쓰면 그 함수가 실행되어 반환한 태그가 그 자리에 들어간다. 같은 컴포넌트를 여러 번 쓸 수도 있다. (JSX 안의 주석은 {/* */}로 쓴다)

3-1. 시작점: main.jsx

// main.jsx : 이 모든 것의 뿌리
import { StrictMode } from 'react'
import { createRoot } from 'react-dom/client'
import './index.css'
import App from './App.jsx'

createRoot(document.getElementById('root')).render(
  <StrictMode>
    <App />
  </StrictMode>,
)
  • index.html에 있는 <div id="root">를 찾아서 그 안에 App 컴포넌트를 그린다.
  • StrictMode는 개발 중에 잠재적인 문제를 미리 알려 주는 검사 모드다. 개발 모드에서는 컴포넌트를 일부러 두 번 실행해서 부작용을 찾는다. 배포 빌드에서는 영향이 없다.

3-2. export default vs named export

// App.jsx - named export
export const App = () => { ... }

// main.jsx - named export 는 중괄호로 받는다
import { App } from './App.jsx'
구분export 방식import 방식
default exportexport default Appimport App from './App.jsx'
named exportexport const App = ...import { App } from './App.jsx'
  • default export: 파일 하나에 컴포넌트 하나가 대응될 때 쓴다. 가져올 때 이름은 마음대로 지을 수 있다.
  • named export: default 없이 내보내는 것. 한 파일에서 여러 개를 내보낼 때 쓴다. 가져올 때는 정확히 같은 이름을 중괄호로 받아야 한다.

방식을 맞추지 않으면 문제가 생긴다. export default App인 파일을 import { App } from ...으로 가져오면 App이 존재하지 않는 이름이 된다. 확인해 보면 이 모듈이 내보내는 이름은 ['default'] 하나뿐이고, { App }으로 꺼내면 undefined다. 필기에 "main.jsx의 import App을 { }로 감싸면 버그가 난다. default를 쓸 때는 조심하라"고 적은 부분이다. 브라우저에서는 보통 does not provide an export named 'App' 같은 에러로 화면이 뜨지 않는다.


4. JSX 규칙

4-1. 출력할 값은 중괄호로

const App = () => {
  const num = 0
  return (
    <h2>{num}</h2>      // 변수나 JavaScript 식은 { } 로 감싼다 → 0 출력
  )
}

4-2. 반드시 하나의 부모로 감싼다: Fragment

JSX는 최상위 요소가 하나여야 한다. 태그 두 개를 나란히 반환하면 에러가 난다.

export default function Two() {
  return (<h1>a</h1><h2>b</h2>)
}
// 에러: Adjacent JSX elements must be wrapped in an enclosing tag.

감싸는 방법은 세 가지다.

// ① <div> 로 감싼다 (실제 div 태그가 하나 더 생긴다)
<div>
  <h1>Hello</h1>
  <h2>Hi</h2>
</div>

// ② <Fragment> 로 감싼다 (화면에 태그를 만들지 않는다)
import React, { Fragment } from 'react'
<Fragment>
  <h1>Hello</h1>
  <h2>Hi</h2>
</Fragment>

// ③ 빈 태그 <> </> 로 감싼다 (Fragment의 줄임)
<>
  <h1>Hello</h1>
  <h2>Hi</h2>
</>

<Fragment>와 빈 태그 <> </>는 같은 것이다. 결과 HTML은 <h1>Hello</h1><h2>Hi</h2>로 불필요한 div 없이 나온다. 필기의 정리대로 컴포넌트를 만들 때는 바깥을 감싸는 빈 <>나 <div>를 항상 두는 습관을 들이면 된다.


5. 이벤트 처리

import React, { Fragment } from 'react'

function App() {

  // 동작 함수는 return 바깥에 둔다
  const onClickButton = () => {
    alert('버튼 클릭됨.')
  }

  return (
    <Fragment>
      <h1>Hello</h1>
      <h2>Hi</h2>
      <button onClick={onClickButton}>버튼</button>
    </Fragment>
  )
}

export default App
  • 동작을 담당하는 함수는 return 바깥에 만든다. return 안은 화면에 그릴 태그(JSX)를 쓰는 자리라서 함수 선언을 넣기에 맞지 않는다. (필기에는 "해시태그"라고 적혀 있는데, HTML 태그/JSX를 말한다)
  • 이벤트는 onClick처럼 camelCase로 쓴다. JS Day.6에서 본 onclick(소문자)과 다르다.
  • 값으로는 함수 자체를 { }로 넘긴다.

여기서 흔한 실수가 있다.

<button onClick={onClickButton}>버튼</button>     // ✅ 클릭했을 때 실행된다
<button onClick={onClickButton()}>버튼</button>   // ❌ 화면을 그리는 순간 바로 실행된다

onClickButton()처럼 괄호를 붙이면 그 자리에서 호출한 결과를 넘기는 것이다. 확인해 보면 onClick={on}은 렌더링 후 호출 횟수가 0, onClick={on()}은 렌더링만 했는데 1번 호출됐다. 클릭할 때 실행하려면 함수 이름만 넘긴다. JS Day.6, Day.10에서 콜백을 넘길 때 fun2()가 아니라 fun2라고 했던 것과 같은 원리다. 인자를 넘겨야 할 때는 onClick={() => handle(1)}처럼 화살표 함수로 감싼다.


6. useState: 값이 바뀌면 화면이 바뀐다

import React, { useState } from 'react'

function App() {

  const [num, setNum] = useState(0)    // num 의 초기값은 0

  return (
    <div>
      <h1>{num}</h1>     {/* 0 출력 */}
    </div>
  )
}

export default App

useState는 화면에 보여주는 값이 바뀔 때, React가 자동으로 화면을 다시 그리게(리렌더링) 하려고 쓴다. 이런 use로 시작하는 기능들을 Hook이라고 한다.

  • useState(0)은 값과 값을 바꾸는 함수 두 개를 담은 배열을 돌려준다. 그것을 const [num, setNum] = ...처럼 구조 분해로 받았다. (JS Day.7의 구조 분해 할당)
  • num은 현재 값, setNum은 값을 바꾸는 함수다. useState(0)의 0이 초기값이다.
  • num이 바뀔 때마다 이 컴포넌트가 다시 실행되어 화면을 새로 그린다.
  • Hook은 컴포넌트 함수 안쪽 최상단에서 호출한다. if나 반복문 안에서 쓰면 안 된다.

일반 변수(let num = 0)를 바꿔서는 화면이 바뀌지 않는다. React는 useState로 만든 값이 바뀔 때만 다시 그리기 때문이다. 이날은 초기값 0을 출력하는 데까지만 했다. 값을 실제로 바꾸는 setNum 사용은 다음 노트에서 이어진다.


마무리

  • 환경: Node.js 설치 → npm create vite@latest → npm install → npm run dev.
  • 컴포넌트는 JSX를 반환하는 함수이고, 이름은 대문자로 시작해야 HTML 태그와 구분된다. 파일 확장자는 .jsx.
  • export: 파일 하나에 컴포넌트 하나면 export default / import 이름 from, 여러 개면 named export / import { 이름 } from. 방식을 맞추지 않으면 못 가져온다. export default const는 문법 에러라서 const는 아래에서 따로 export default 한다.
  • JSX는 변수를 { }로 출력하고, 최상위 요소는 하나여야 한다(<div>, <Fragment>, <>).
  • 이벤트는 onClick={함수}로 연결한다. 괄호를 붙이면 렌더링 때 바로 실행된다.
  • useState로 만든 값이 바뀔 때만 화면이 다시 그려진다.

Tags: React Vite 컴포넌트 JSX useState Hook Fragment export 개발자 학습기록

profile
난 뭘까

0개의 댓글