본문 바로가기

개발/React

[React] React를 위한 JavaScript (3)

JS 구성: 모듈, 조건 표현식, 비동기

 

Step 1. 모듈(Module)

JS에서 모듈이란 코드를 재사용 가능한 독립된 파일 단위로 쪼개어 관리하는 기술로 실제 프로젝트에서는 파일 하나에 코드를 다 작성하지 않고 파일을 잘게 나누어 작성한다.


React에서는 컴포넌트 하나당 파일 하나가 기본이다.

- 컴포넌트(Component): 화면을 구성하는 재사용 가능한 독립적인 UI 조각

 

나눈 파일들은 서로 값을 주고받아야 한다. 내보내는 쪽이 export, 받는 쪽이 import이다.

 

이제 2개의 js 실습 파일을 만든다.

cd ~/react-study/js
touch utils.js main.js

 

utils.js

// named export
export const add = (a, b) => a + b;
export const formatScore = (score) => `${score}점`;

// default export
const greet = (name) => `안녕, ${name}`;
export default greet;

 

main.js

import { add, formatScore } from "./utils.js";  // named export
import greet from "./utils.js";                 // default export

console.log(add(2, 3));
console.log(formatScore(85));
console.log(greet("지민"));

 

named export는 변수나 함수 앞에 export를 붙이고, 받을 때 중괄호로 감싼다. 이름이 정확히 일치해야 한다. 
한 파일에서 여러 개 내보낼 수 있다.

 

default export는 파일당 하나만 가능하고, 받을 때 중괄호가 없다. 이름은 자유롭게 지어도 된다.

import hello from "./utils.js" 라고 써도 똑같이 동작한다.

 

React에서는 컴포넌트를 default export로 내보내는 게 관례이다. 

import App from "./App.jsx" 같은 줄을 계속해서 보게 될 것이다.

 

실행하려면 Node에게 모듈 문법을 쓴다고 알려줘야 한다. 
파일 확장자를 .mjs로 바꾸거나, 같은 폴더에 package.json을 만들고 아래 내용을 넣으면 된다.

{ "type": "module" }

node main.js로 실행한다. 

Vite 프로젝트에서는 이미 설정돼 있어서 신경 쓸 필요가 없다.

 

Step 2.  조건 표현식(Conditional Expression)

조건 표현식이란 조건의 참과 거짓에 따라 각각 다른 결과값을 반환하여 하나의 값으로 만들어주는 구문을 말한다.

React에서 사용하는 JSX 안에서는 if 문을 쓸 수 없다. JSX 코드는 컴파일 과정에서 자바스크립트 함수 호출로 변환되는데 함수의 인자로는 '문(Statement)'이 아닌 '값'만 전달할 수 있기 때문이다.

JSX는 JavaScript에 XML/HTML 구조를 결합한 자바스크립트 확장 문법(JavaScript XML)을 뜻하며 JS 코드 안에서 HTML 태그와 비슷한 문법으로 UI 구조를 직관적으로 작성할 수 있어 React에서 컴포넌트의 생김새와 UI를 정의할 때 주로 사용된다.


그래서 조건을 값으로 만드는 문법을 쓴다.

삼항 연산자

조건 ? 참일때값 : 거짓일때값

const status = (score) => (score >= 60 ? "합격" : "불합격");

 

&& 연산자

왼쪽이 참이면 오른쪽을 내놓고, 거짓이면 왼쪽을 그대로 내놓는다.

console.log(true && "보임");   // 보임
console.log(false && "보임");  // false

 

예를 들어, "조건이 맞을 때만 화면에 보여주고, 아니면 아무것도 안 보여준다"에 사용한다. 

JSX에서 다음과 같이 쓰게 된다.

{ isLoggedIn && <p>환영합니다</p> }

 

여기 주의할 점이 하나 있다. 숫자 0을 조건으로 쓰면 안 된다.

console.log(0 && "항목 있음");  // 0

 

JS에서 0은 false 취급이라 왼쪽인 0이 그대로 나온다. 따라서 JS 확장 문법인 JSX에서 이 0이 화면에 그대로 찍히게 된다. 

items.length && <목록/> 와 같은 코드가 빈 목록일 때 화면에 0을 출력하는 유명한 버그이다. 
items.length > 0 && ... 와 같이 작성해야 한다.

 

?? 연산자

왼쪽이 null이나 undefined일 때만 오른쪽을 내놓는다.

const name = undefined ?? "이름 없음";	// 이름 없음
const count = 0 ?? 10;			// 0

0과 ""(빈 문자열)이 유효한 값으로서 사용될 때 ||을 쓰는 경우 기본값(오른쪽)으로 덮어씌워지는 부작용이 발생하므로, 오직 null과 undefined일 때만 기본값을 안전하게 할당하기 &&을 사용한다.

기본값(Default Value)은 변수에 값이 지정되지 않거나 함수에 값이 누락되었을 때 개발자가 의도한 값이 자동으로 할당되도록 지정한 값을 의미한다.

 

예를 들면, 수량이 0개이거나 점수가 0점인 상황에서 기본값을 적용하면 안 되는 경우이다.

function renderCartBadge(itemCount) {
  // || 사용 시: itemCount가 0이면 Falsy로 판단하여 1이 됨 (버그)
  // const count = itemCount || 1;

  // ?? 사용 시: 0은 null이나 undefined가 아니므로 0이 정상 유지됨
  const count = itemCount ?? 1;

  console.log(`장바구니 수량: ${count}`);
}

renderCartBadge(0);

 

Step 3. 비동기(Asynchronous)

비동기는 특정 작업이 완료될 때까지 기다리지 않고 바로 다음 작업을 실행하는 처리 방식이다.

 

서버에서 데이터를 가져오는 데는 시간이 걸린다. 0.5초일 수도 3초일 수도 있다. 
JavaScript는 서버에서 데이터를 가져오는 동안 멈춰서 기다리지 않는다. 요청만 보내고 다음 줄로 넘어간다.

그래서 결과를 바로 받을 수 없다. 대신 "나중에 결과가 올 자리" 를 받는다. 이 자리를 Promise라고 한다. 
택배 송장 같은 것이다. 물건은 아직 없지만 추적할 수 있는 무언가를 손에 쥐고 있는 것이다.

 

await는 "Promise가 결과를 받을 때까지 기다리고 있어라" 는 뜻이다. 
그리고 await를 쓰려면 함수 앞에 async를 붙여야 한다.

// 외부 서버에 비동기 HTTP 요청을 보내 사용자 정보를 가져온 뒤, 해당 사용자의 이름(name)을 콘솔에 출력하는 예제 코드

const getUser = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users/1");
  const user = await response.json();
  console.log(user.name);
};

getUser();

await가 두 번 나오는 게 이상해 보일 수 있지만 이유가 있다.

첫 번째 await fetch(...)는 서버가 응답할 때까지 기다린다. 여기서 response에 들어오는 것은 아직 데이터 전체가 아니라 "응답 객체"이다. "서버와 연결되어 응답이 시작되었다"는 응답 신호만 먼저 받은 것이다. 상태 코드 같은 정보가 들어 있다.

두 번째 await response.json()은 그 응답의 본문을 읽어서 JavaScript 객체로 바꾼다. 이것도 시간이 걸리는 작업이라 await가 필요하다.

 

좀 더 자세히 설명하자면

const response = await fetch("https://jsonplaceholder.typicode.com/users/1");

해당 단계에서는 외부 서버로 HTTP 요청을 전송하고, 서버로부터 HTTP 응답 헤더(상태 코드, 콘텐츠 타입 등)가 도착하는 즉시 fetch의 프로미스가 결과를 받았다고 판단하여 본문(Body) 데이터 전체가 아직 다 다운로드되지 않았더라도 Response 객체의 메타데이터 껍데기가 생성되었으므로 다음 줄로 넘어간다.

 

const user = await response.json();

해당 단계에서는 response.json() 메서드를 호출하는 순간, 네트워크를 통해 들어오고 있는 데이터 조각(chunk)들을 끝까지 읽어 들여 전체 본문 다운로드를 완료한 다음 본문을 내부적으로 JSON.parse()하여 자바스크립트 객체로 변환을 마친 뒤, 최종 객체를 반환하며 대기(await)가 풀린다.


fetch에는 주의할 점이 하나 있다. 404나 500 응답이 와도 오류로 취급하지 않는다. 네트워크 자체가 끊긴 경우에만 실패한다. 그래서 직접 확인해야 한다.

 

const getUser = async (id) => {
  try {
    const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);

    if (!response.ok) {
      throw new Error(`요청 실패: ${response.status}`);
    }

    const user = await response.json();
    return user;
  } catch (error) {
    console.log("에러:", error.message);
    return null;
  }
};

response.ok는 상태 코드가 200번대면 true이다. throw는 "여기서 문제가 생겼다"고 알리는 것이고, try/catch는 그걸 받아낸다. try 블록 안에서 문제가 생기면 즉시 catch로 점프한다.

 

이 구조 — 요청, 상태 확인, 파싱, 에러 처리 — 가 React 데이터 페칭의 뼈대가 된다.

데이터 페칭(Data Fetching)은 서버에서 데이터를 불러와 화면에 렌더링하는 작업이다.

 

생각해볼 부분

다음 코드는 무엇을 출력할지 생각해 보자.

const getUsers = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");
  return await response.json();
};

const users = getUsers();
console.log(users);

async 함수인 getUsers 함수를 await 없이 호출했다. users에 무엇이 들어 있을지 생각해 보자.

 

const users = getUsers(); 에서 async 함수를 호출하면 async 함수는 첫 번째 await를 만나는 순간 실행을 일시 중단하고 대기(pending) 상태의 Promise를 먼저 반환한다. (비유하자면, 택배를 부치기 전에 송장 번호를 먼저 받아놓는다.)

이때, 객체가 반환되면서 함수 앞에 await가 없기 때문에 즉시 users 변수에 할당되고 그다음 줄 console.log에서 출력된다.

따라서 다음과 같이 출력된다.

Promise { <pending> }

 

즉, "비동기 데이터는 껍데기로 오고, 벗기려면 await가 필요하다"고 개념을 잡고 간다.

 

연습

다음 연습용 공개 API를 사용한다.

https://jsonplaceholder.typicode.com/users

이 주소는 사용자 10명의 배열을 돌려준다. 각 사용자는 id, name, email, company 등을 가지고 있다.

  1. 사용자 목록을 가져와서 그대로 console.log로 출력하시오. 어떤 모양인지 눈으로 먼저 확인하는 게 목적이다.
  2. 가져온 목록에서 이름만 뽑아 배열로 출력하시오.
  3. 함수 getUserById(id) 를 만드세요. https://jsonplaceholder.typicode.com/users/{id} 를 요청하고, 성공하면 사용자 객체를, 실패하면 null을 반환한다. try/catch와 response.ok 확인을 포함하시오.
  4. 3번 문제의 함수를 getUserById(999) 로 호출해보시오. 없는 사용자이다. 에러 메시지가 잘 찍히고 null이 반환되는지 확인하시오.

 

답안

const getUsers = async () => {
  const response = await fetch("https://jsonplaceholder.typicode.com/users");
  if (!response.ok) throw new Error(`요청 실패: ${response.status}`);
  return await response.json();
};

const getUserById = async (id) => {
  try {
    const response = await fetch(
      `https://jsonplaceholder.typicode.com/users/${id}`,
    );
    if (!response.ok) throw new Error(`요청 실패: ${response.status}`);
    return await response.json();
  } catch (error) {
    console.log("에러:", error.message);
    return null;
  }
};

// await는 원칙적으로 async 함수 안에서만 쓸 수 있기 때문에 다음과 같이 main 함수를 정의해서 사용한다.
const main = async () => {
  const users = await getUsers();
  console.log(users);

  const userNames = users.map((user) => user.name);
  console.log(userNames);

  console.log(await getUserById(1));
  console.log(await getUserById(999));
};

main();

 

'개발 > React' 카테고리의 다른 글

[React] Component와 Props  (0) 2026.09.14
[React] React 개요  (0) 2026.09.14
[React] React를 위한 JavaScript (2)  (0) 2026.09.07
[React] React를 위한 JavaScript (1)  (0) 2026.09.07
[React] macOS에서 VS Code를 통한 React 환경 세팅  (0) 2026.09.04