React 학습 1주차 — 설치부터 상호작용성까지
Vue.js를 주로 사용하다가 React를 처음 학습하면서 정리한 내용입니다. Vue와 비교되는 지점 위주로 기록했습니다.
설치
- Vue는 공식 문서만 따라가면 설치 방법이 심플한 편인데, React는 새 프로젝트를 시작할 때 선택지가 많습니다.
# Vue3
npm create vue@latest
# Vue2
npm install vue@^2 # Webpack으로 설치 시
vue create my-project # Vue CLI로 설치 시- React 공식 문서는 Next.js, Remix, Gatsby, 네이티브 앱까지 4가지 방향을 안내합니다.
npx create-next-app@latest # Next.js
npx create-remix # Remix
npx create-gatsby # Gatsby
npx create-expo-app # 네이티브 앱- 위의 선택지는 모두 프레임워크입니다. 순수한 React 프로젝트를 만들려면 아래와 같이 생성하면 됩니다.
npx create-react-app {프로젝트명}-
스터디에서는 CRA 대신 Vite를 사용해서 React를 설치하기로 했습니다.
-
React 공식 문서 외에 참고하기 좋은 튜토리얼도 찾았습니다. 2020년에 작성된 자료라 다소 오래됐지만, 공식 문서와 병행해서 보기에 좋습니다.
UI 표현하기
첫 번째 컴포넌트
- Vue는
<template>안에 HTML을 작성하지만, React는export default접두사 형태로 JavaScript 안에서 마크업을 내보냅니다. - React는 대문자로 시작하면 컴포넌트, 소문자로 시작하면 HTML 태그로 인식합니다. 따라서 컴포넌트 이름은 반드시 대문자로 시작해야 합니다.
- 마크업은 JSX 구문으로 작성합니다.
img와 같은 단일 태그도 닫는 슬래시가 필요합니다. Vue는 HTML5 형태로 작성해도 되지만, React는 좀 더 엄격합니다.
JSX란? JavaScript를 확장한 문법으로, JavaScript 안에서 HTML과 비슷하게 마크업을 작성할 수 있도록 해 줍니다.
- 컴포넌트 안에 또 다른 컴포넌트를 정의하는 것은 지양해야 합니다.
컴포넌트 import 및 export 하기
- Next.js처럼 파일 기반으로 라우팅하는 프레임워크는 페이지별로 root 컴포넌트가 다릅니다. React와 Vue 모두 기본 root 컴포넌트는
App.js입니다. - 컴포넌트를 import/export 하는 방식은 Vue의
<script>에서export default {}를 활용하는 방식과 크게 다르지 않습니다. - 파일 확장자 없이 import 할 수도 있지만, 빌드 단계에서 문제가 생기는 경우가 있습니다. 명확하게
.js까지 붙이는 편이 안전합니다. - export 방식에는 named export와 default export 두 가지가 있습니다.
export default는 개체가 하나임을 의미합니다. import 시 이름을 자유롭게 바꿔서 사용할 수 있습니다.- 복수의 개체를 내보낼 때는 named export를 주로 사용합니다.
JSX로 마크업 작성하기
- 여러 엘리먼트를 반환하려면 하나의 부모 태그로 감싸야 합니다.
<div>같은 HTML 태그로 감싸도 되고, 불필요한 마크업을 추가하고 싶지 않다면<></>(Fragment)로 감싸면 됩니다.key를 사용해야 하는 경우(예: 반복문)에는 React에서Fragment를 import해서 사용해야 합니다.- Vue2에서는 Fragment가 공식 지원이 아니라 서드파티 패키지를 쓰다가 렌더링 오류가 난 경험이 있었는데, React는 공식 지원이라 안심이 됩니다.
- 기존 HTML 방식의 속성명 습관은 버리고 camelCase로 작성해야 합니다.
class→className,background-image→backgroundImage- 단,
aria-*나data-*속성은 HTML과 동일하게 작성합니다.
중괄호가 있는 JSX 안에서 JavaScript 사용하기
React는 변수를 받을 때 중괄호 {}를 사용합니다. Vue의 :(v-bind)와 {{ }}에 대응합니다.
const test = '안녕안녕'
// Vue.js
<img src="" :alt="test" />
<h1>{{ test }}</h1>
// React
<img src="" alt={test} />
<h1>{test}</h1>React에서 중괄호를 2개({{ }}) 사용하면 객체를 의미합니다. Vue의 보간법({{ }})과 헷갈리지 않도록 주의해야 합니다.
컴포넌트에 props 전달하기
React에서 props를 자식 컴포넌트 내부에서 읽으려면 매개변수에 구조분해 할당 형태로 받습니다. Vue보다 훨씬 심플합니다.
// Vue
props: {
person: { type: String },
size: { type: Number },
}
// React
function Avatar({ person, size }) {}기본값 지정도 마찬가지로 매개변수 기본값 문법을 그대로 사용합니다.
// Vue
props: {
size: { type: Number, default: 100 },
}
// React
function Avatar({ person, size = 100 }) {}- props를 자식 요소에 전달할 때 spread 문법(
{...props})을 사용할 수도 있습니다. - 콘텐츠를 중첩해서 사용하고 싶다면
childrenprops를 활용합니다. Vue의<slot>에 해당합니다. 단, children은 하나이므로 named slot처럼 여러 개를 활용하는 패턴은 별도로 구현해야 합니다. - props는 읽기 전용으로 취급합니다. 상호작용이 필요하다면 state를 사용해야 합니다.
조건부 렌더링
- 조건부로 아무것도 렌더링하지 않을 때는
null을 return 합니다. - 삼항 연산자(
? :)와 논리 연산자(&&)를 활용하는 방식은 Vue와 동일합니다.
리스트 렌더링
- 리스트 렌더링 시 고유한
key를 사용해야 합니다. key에 index를 사용하면 미묘한 버그가 생길 수 있습니다. (Vue도 마찬가지입니다.)map()과filter()를 적극 활용합니다.
컴포넌트를 순수하게 유지하기
props,state,context는 읽기 전용으로 취급해야 합니다.- 렌더링은 언제든 발생할 수 있기 때문에, 컴포넌트는 서로의 렌더링 순서에 의존하지 않아야 합니다.
- 화면을 업데이트하려면 state를 설정해서 업데이트해야 합니다.
트리로서의 UI
- 렌더 트리: React 컴포넌트 간의 중첩 관계를 나타냅니다.
- 모듈 의존성 트리: React 앱의 모듈 의존성을 나타냅니다. 컴포넌트뿐만 아니라 일반 모듈도 포함됩니다.
- 실용적으로는 렌더 트리 개념만 잘 이해해도 충분합니다.
상호작용성 더하기
이벤트에 응답하기
- Vue에서는
$emit으로 이벤트를 핸들링했지만, React에서는 함수를 props로 전달합니다. 함수 활용이 훨씬 적극적입니다. - 화살표 함수를 사용하는 것을 권장합니다.
- 이벤트 핸들러에는 적절한 HTML 태그를 사용해야 합니다.
div태그에 클릭 이벤트를 붙이는 방식은 지양합니다. - React에서는
onScroll을 제외한 모든 이벤트가 전파됩니다.e.preventDefault()나e.stopPropagation()으로 필요 시 전파를 막아야 합니다.- Vue에서는
@click.stop.prevent로 간단하게 처리했던 부분을 직접 코드로 작성해야 해서 다소 번거롭습니다.
State: 컴포넌트의 기억 저장소
React에서 컴포넌트별 메모리를 state라고 부릅니다. Vue의 data와 유사한 개념입니다.
import { useState } from 'react';
const [index, setIndex] = useState(0); // 네이밍은 되도록 동일하게 유지use로 시작하는 함수들을 Hook이라고 부릅니다.- state 변수명과 setter 함수명은 동일한 패턴(
[value, setValue])으로 유지하는 것을 권장합니다. - state는 여러 개 선언할 수 있습니다.
렌더링 그리고 커밋
React의 렌더링 흐름은 트리거 → 렌더링 → 커밋 순서입니다.
![]()
참고: React Lifecycle Methods diagram
- 렌더링 트리거:
createRoot로 DOM 노드와 함께 렌더링 가능한 위치를 파악합니다. Vue의v.$mount('#app')에 해당합니다. - 초기 렌더링: React가
document.createElement와 동일하게 DOM 노드를 생성합니다. - state 업데이트 시: 렌더링을 트리거한 컴포넌트를 호출합니다.
- 커밋: 렌더링 결과를 비교해 차이가 있는 경우에만 DOM 노드를 변경합니다.
- 브라우저 렌더링: React가 DOM을 업데이트한 뒤 브라우저가 화면을 다시 그립니다.
렌더링 결과가 동일하면 React는 DOM을 건드리지 않습니다.
스냅샷으로서의 state
- React에서 props, 이벤트 핸들러, 로컬 변수는 렌더링 당시의 state를 사용합니다.
setState를 호출해도 state가 즉시 업데이트되지 않고 비동기로 처리됩니다.- React는 state를 업데이트하기 전에 이벤트 핸들러의 모든 코드가 실행될 때까지 기다립니다. (batching)
- Vue에서 스냅샷 개념이 없었기 때문에, Vue → React 전환 시 예상치 못한 케이스가 발생할 수 있는 부분입니다.
State 업데이트 큐
- React는 이벤트 핸들러의 모든 코드가 실행될 때까지 state 업데이트를 기다립니다. 이를 batching이라고 합니다.
- 하나의 이벤트 핸들러에서
setState를 여러 번 호출해도, 모든 호출이 완료된 뒤 리렌더링이 1회 발생합니다. 불필요한 리렌더링을 줄이는 효과가 있습니다. - 다음 렌더링 전에 여러 번 업데이트해야 한다면 화살표 함수(함수형 업데이트)를 활용합니다.
객체 state 업데이트하기
객체 state를 직접 변경(mutation)하면 리렌더링이 발생하지 않습니다. 항상 새로운 객체를 생성해서 전달해야 합니다.
// 직접 할당 금지 (Vue도 마찬가지)
position.x = e.clientX;
position.y = e.clientY;
// 올바른 방법: spread 문법으로 새 객체 생성
setPosition({ ...position, x: e.clientX, y: e.clientY });- 반복적인 복사 코드를 줄이고 싶다면 Immer 라이브러리를 사용할 수 있습니다. 다만 일반 JavaScript로 작성한 것이 성능 면에서 더 좋으며, Proxy를 사용하기 때문에 구형 브라우저나 React Native 환경에서는 주의가 필요합니다.
- 객체 업데이트 시 항상 깊은 복사/얕은 복사를 염두에 두어야 합니다.
배열 state 업데이트하기
배열도 객체와 동일하게 직접 변경하지 않고, 새로운 배열을 만들어서 업데이트해야 합니다.
![]()
Vue에서 선호하던 배열 변경 메서드(push, splice, $set 등)가 React에서는 비선호 방식에 해당하므로, 전환 시 특히 주의가 필요한 부분입니다.