
웹 화면을 만드는 방식은 크게 서버 중심 방식과 브라우저 중심 방식으로 나눌 수 있다.
기존Spring MVC는 서버가 화면을 완성해서 브라우저에 보내는 방식이다.
반면React + REST구조는 서버가 데이터만 보내고, 브라우저의React가 그 데이터를 이용해서 화면을 만든다.
이 차이를 먼저 이해해야React가 왜 필요한지, 그리고Spring Boot와 어떤 방식으로 연결되는지 자연스럽게 이해할 수 있다.
React를 배우는 이유는 단순히 새로운 화면 문법을 배우기 위해서가 아니다.
서버가 화면까지 모두 만들던 방식에서 벗어나, 화면과 사용자 상호작용을 브라우저에서 직접 관리하는 방식으로 개발 흐름이 바뀌기 때문이다.
1. 복습: Spring MVC 중심 개발과 React + REST 개발 비교
1-1. 기존 Spring MVC 화면 개발 흐름
Spring MVC중심 개발에서는 브라우저가 서버에 화면을 요청한다.
서버의Spring Controller는 요청을 받고, 화면에 필요한 데이터를Model에 담는다.
그 다음View Template이Model데이터를 사용해서 완성된HTML을 만든다.
마지막으로 서버는 완성된HTML을 브라우저에 응답한다.
이 흐름을 단계로 나누면 다음과 같다.
- 브라우저가 특정 주소로 화면을 요청한다.
- 서버의
Controller가 요청을 받는다.- 서버는 화면에 필요한 데이터를 준비한다.
- 준비한 데이터를
Model에 담는다.View Template이 데이터를 넣어 완성된HTML을 만든다.- 서버는 완성된
HTML을 브라우저에 보낸다.
이 방식에서는 화면을 만드는 주체가 서버이다. 브라우저는 서버가 만들어 준 `HTML`을 받아서 보여주는 역할에 가깝다. 그래서 화면이 바뀌려면 보통 다시 서버에 요청하고, 서버가 새로운 `HTML`을 만들어 보내야 한다.
Spring MVC방식에서는 브라우저 요청을 서버가 받고, 서버가Model과View Template을 이용해 완성된HTML을 만들어 응답한다.
예를 들어 게시글 목록 화면을 생각하면 이해하기 쉽다.
Spring MVC에서는 사용자가 게시글 목록 주소로 이동하면 서버가 게시글 데이터를 조회한다.
그 다음 서버가 게시글 데이터를 화면 템플릿에 끼워 넣고, 완성된 게시글 목록HTML을 브라우저에 보낸다.
브라우저는 그 결과를 그대로 보여준다.
즉,Spring MVC방식에서 브라우저는 주로 결과 화면을 보여주는 역할을 한다.
화면을 어떻게 만들지는 서버가 결정한다.
1-2. React + Spring Boot REST 화면 개발 흐름
React + Spring Boot REST구조에서는 화면을 만드는 위치가 달라진다.
브라우저 안에서React App이 실행되고, 이React App이 화면을 구성한다.
사용자가 화면에서 버튼을 누르거나 데이터를 요청하면fetch또는axios로 서버의API를 호출한다.
여기서API는 프론트엔드가 서버에 데이터를 요청하기 위해 사용하는 연결 통로이다.
기존Spring MVC에서는 서버가 화면까지 만들어 응답했다.
하지만React + REST구조에서는 서버가 화면 자체를 만들지 않는다.
서버는 화면에 필요한 데이터를JSON형태로 응답한다.
JSON은 데이터를 주고받기 위한 가벼운 형식이다.
완성된 화면이 아니라 값만 담긴 데이터라고 보면 된다.
브라우저의React는 서버에서 받은JSON데이터를state에 반영한다.
state는 화면에 영향을 주는 데이터 저장 공간이다.
state가 바뀌면React는 그 값을 기준으로 화면을 다시 계산해서 보여준다.
이 흐름을 단계로 나누면 다음과 같다.
- 브라우저에서
React App이 실행된다.- 사용자가 버튼 클릭, 검색, 페이지 이동 같은 동작을 한다.
React가fetch또는axios로 서버API를 호출한다.Spring Boot REST Controller가 요청을 받는다.- 서버는 필요한 데이터를 조회하거나 처리한다.
- 서버는 완성된
HTML이 아니라JSON데이터를 응답한다.React는 받은 데이터를state에 저장한다.state변경에 따라 화면이 다시 렌더링된다.이 방식에서는 화면을 만드는 주체가 브라우저의
React이다.
Spring Boot는 화면을 직접 만들기보다 데이터 제공, 인증 처리, 저장 처리 같은 백엔드 역할에 집중한다.
예를 들어 게시글 목록 화면을 다시 생각하면,Spring Boot는 게시글 목록 데이터를JSON으로 보내준다.
그러면React가 그 데이터를 받아 게시글 카드나 게시글 목록 형태로 화면에 출력한다.
서버는 데이터를 주고, 브라우저는 화면을 만든다.
정리하면React + Spring Boot REST방식은 서버가 완성된HTML을 보내는 구조가 아니다.
서버는 필요한 데이터를JSON으로 보내고, 브라우저의React가 그 데이터를 이용해 화면을 구성하는 구조이다.
1-3. CSR과 SSR 비교
CSR은Client-Side Rendering의 줄임말이다.
클라이언트, 즉 브라우저에서 화면을 렌더링하는 방식이다.
렌더링은 데이터를 화면에 보이는 구조로 그려내는 작업을 의미한다.
CSR에서는 처음에 브라우저가JavaScript파일을 내려받는다.
그 다음 내려받은JavaScript가 실행되면서 화면을 만든다.
그래서 첫 화면 속도는 상대적으로 느릴 수 있다.
화면을 보이기 전에JavaScript를 내려받고 실행하는 시간이 필요하기 때문이다.
하지만 한 번 앱이 실행된 뒤에는 화면 전환이 빠르다.
새 페이지를 매번 서버에서 다시 받아오는 방식이 아니라, 브라우저 안에서 필요한 부분만 바꾸기 때문이다.
React,Vue,Angular가 대표적인CSR도구이다.
SSR은Server-Side Rendering의 줄임말이다.
서버에서 완성된HTML을 만들어 브라우저로 보내는 방식이다.
브라우저는 이미 완성된HTML을 받기 때문에 첫 화면을 빠르게 표시할 수 있다.
또한 검색 엔진이 완성된HTML을 바로 읽을 수 있어SEO에 유리하다.
SEO는 검색 엔진이 페이지 내용을 잘 읽고 검색 결과에 반영하도록 만드는 것을 의미한다.
다만SSR은 페이지를 전환할 때마다 서버 요청이 자주 발생할 수 있다.
서버가 매 요청마다HTML을 만들어야 하므로 서버 부하가 높아질 수 있다.
CSR은 브라우저에서 화면을 만들고,SSR은 서버에서 완성된HTML을 만들어 보낸다.
두 방식을 비교하면 다음과 같다.
CSR은 브라우저가 화면을 만든다.SSR은 서버가 화면을 만든다.CSR은 처음 실행 준비 시간이 필요할 수 있다.SSR은 완성된HTML을 받기 때문에 첫 화면 표시가 빠를 수 있다.CSR은 앱 실행 후 화면 전환이 빠르다.SSR은 페이지 전환마다 서버 작업이 많아질 수 있다.
React를 배울 때는 우선CSR흐름을 이해하는 것이 중요하다.
브라우저 안에서React가 실행되고, 필요한 데이터를 서버에서 받아 화면을 바꾸는 방식이React + REST구조의 기본 흐름이기 때문이다.
1-4. Spring MVC 방식과 React 방식의 핵심 차이 정리
Spring MVC방식과React + REST방식의 가장 큰 차이는 화면을 어디에서 만드느냐이다.
Spring MVC는 서버가HTML을 만들고,React + REST는 브라우저의React가 화면을 만든다.
서버 응답의 형태도 다르다.
Spring MVC는 보통 완성된HTML페이지를 응답한다.
반면React + REST에서는 서버가JSON데이터를 응답하고, 브라우저가 그 데이터를 화면에 반영한다.
역할도 달라진다.
Spring MVC중심 개발에서는 서버가 화면 흐름과 데이터 흐름을 함께 담당한다.
React + REST중심 개발에서는Spring Boot가 데이터와 인증을 담당하고,React가 화면과 사용자 상호작용을 담당한다.
핵심 차이를 정리하면 다음과 같다.
Spring MVC는 서버 중심 화면 개발 방식이다.React + REST는 브라우저 중심 화면 개발 방식이다.Spring MVC의 서버 응답은 주로 완성된HTML이다.React + REST의 서버 응답은 주로JSON데이터이다.Spring MVC에서는 서버가 화면 생성까지 담당한다.React + REST에서는React가 화면 생성과 사용자 상호작용을 담당한다.Spring Boot는 데이터 처리, 저장, 인증,API제공 역할에 집중한다.
초보자가 가장 헷갈리기 쉬운 부분은 `Spring Boot`가 사라지는 것이 아니라는 점이다. `React`를 사용한다고 해서 백엔드가 필요 없어지는 것은 아니다. 오히려 역할이 더 분명해진다. `Spring Boot`는 데이터를 제공하고, `React`는 그 데이터를 화면에 보여준다.
정리하면 `React + REST` 구조는 서버가 화면을 완성해서 보내는 방식이 아니라, 서버는 데이터를 보내고 브라우저가 화면을 조립하는 방식이다. 이 흐름을 이해하면 뒤에서 배우는 `SPA`, `JSX`, `props`, `state` 개념도 훨씬 쉽게 연결된다.
React를 이해하려면 먼저 기존Spring MVC방식과 무엇이 달라졌는지 알아야 한다.
기존에는 서버가 화면을 만들고 브라우저는 그 결과를 보여주는 구조였다.
하지만React + REST구조에서는 브라우저의React가 화면을 만들고, 서버는 화면에 필요한 데이터를 제공한다.
즉, 핵심은 누가HTML을 만들고, 데이터는 어디에서 오는가를 구분하는 것이다.
이 구분이 잡히면 뒤에서 배우는JSX,component,props,state도 훨씬 쉽게 연결된다.
2. Spring MVC와 React
2-1. Spring MVC에서 화면을 만드는 방식
Spring MVC에서는 서버가 화면을 만드는 일을 중심으로 동작한다.
브라우저가 어떤 화면을 요청하면 서버의Controller가 요청을 받는다.
그 다음 화면에 필요한 데이터를 준비하고, 그 데이터를Model에 담는다.
마지막으로View Template이Model데이터를 사용해서 완성된HTML을 만든다.
이 흐름에서 중요한 점은 브라우저가 직접 화면을 조립하지 않는다는 것이다.
브라우저는 서버가 완성해서 보내준HTML을 받아서 보여준다.
그래서Spring MVC중심 개발에서는 서버가 화면 흐름과 데이터 흐름을 함께 담당한다.
Spring MVC화면 생성 흐름을 단계로 나누면 다음과 같다.
- 브라우저가 서버에 화면을 요청한다.
Controller가 요청을 처리한다.- 서버가 화면에 필요한 데이터를 준비한다.
- 준비한 데이터를
Model에 담는다.View Template이 데이터를 사용해서HTML을 만든다.- 브라우저는 완성된
HTML을 받아 화면에 표시한다.예를 들어 게시글 목록 화면을 생각하면 된다.
사용자가 게시글 목록 주소로 이동하면 서버가 게시글 데이터를 조회한다.
그리고 서버가 게시글 목록HTML을 완성해서 브라우저에 보낸다.
브라우저는 그 결과 화면을 그대로 보여준다.
2-2. React와 REST 기반 구조에서 화면을 만드는 방식
React + REST구조에서는 화면을 만드는 위치가 서버에서 브라우저로 이동한다.
브라우저 안에서React App이 실행되고,React가 화면을 구성한다.
서버는 완성된HTML을 보내는 대신 화면에 필요한 데이터를JSON으로 응답한다.
여기서JSON은 데이터를 주고받기 위한 형식이다.
완성된 화면이 아니라 값만 담긴 데이터라고 이해하면 된다.
예를 들어 서버가 게시글 목록을 보낼 때, 게시글 제목과 내용이 담긴JSON데이터를 보낸다.
그러면React가 그 데이터를 받아 게시글 카드나 목록 화면으로 바꿔서 보여준다.
React + REST화면 생성 흐름을 단계로 나누면 다음과 같다.
- 브라우저에서
React App이 실행된다.- 사용자가 버튼 클릭, 검색, 페이지 이동 같은 동작을 한다.
React가fetch또는axios로 서버API를 호출한다.Spring Boot REST API가 요청을 처리한다.- 서버는 완성된
HTML이 아니라JSON데이터를 응답한다.React는 받은JSON데이터를state에 저장한다.state변경에 따라React Component가 화면을 다시 렌더링한다.
state는 화면에 영향을 주는 데이터 저장 공간이다.
React는state값이 바뀌면 화면도 다시 계산한다.
그래서 사용자가 버튼을 누르거나 서버에서 새 데이터를 받아오면, 화면 전체를 새로고침하지 않아도 필요한 부분만 바뀔 수 있다.
Spring MVC는 서버가HTML을 만들고,React + REST는 서버가JSON데이터를 보내면 브라우저의React가 화면을 만든다.
2-3. 서버 역할과 프론트엔드 역할의 차이
Spring MVC중심 개발과React + REST중심 개발의 차이는 단순히 화면 작성 문법이 달라지는 정도가 아니다.
서버와 프론트엔드가 맡는 역할 자체가 달라진다.
Spring MVC에서는 백엔드가 화면 생성까지 담당한다.
화면에 보여줄 데이터 조회, 화면 이동,HTML생성까지 서버 쪽 흐름에 많이 묶여 있다.
반면React + REST에서는 백엔드가 데이터와 인증을 담당하고, 프론트엔드가 화면과 사용자 상호작용을 담당한다.
비교하면 다음과 같다.
Spring MVC는 서버에서View Template으로HTML을 생성한다.React + REST는 브라우저에서React Component가 화면을 생성한다.Spring MVC의 서버 응답은 주로HTML페이지이다.React + REST의 서버 응답은 주로JSON데이터이다.Spring MVC의 화면 전환은 새 페이지 요청 또는 서버 렌더링 중심이다.React + REST의 화면 전환은React Router를 통한 클라이언트 라우팅 중심이다.Spring MVC는 폼 제출 후 서버 응답으로 화면을 갱신한다.React + REST는state변경으로 화면을 즉시 갱신한다.
React Router는React에서 화면 주소 이동을 처리하는 도구이다.
기존 방식처럼 서버에 매번 새HTML을 요청하는 것이 아니라, 브라우저 안에서 보여줄 컴포넌트를 바꾸는 방식으로 화면 전환을 처리한다.
Spring MVC중심 개발은 서버가 화면과 데이터 흐름을 모두 담당하고,React + REST중심 개발은Spring Boot가 데이터와 인증을 맡고React가 화면과 사용자 상호작용을 맡는다.
2-4. React 학습의 핵심 사고방식
React학습의 핵심은 화면은 컴포넌트로 나누고, 화면에 필요한 데이터는 상태로 관리한다는 것이다.
여기서component는 화면을 이루는 독립적인 조각이다.
버튼, 검색창, 게시글 카드, 목록, 모달 같은 화면 요소를 각각 하나의 조각으로 나누어 만들 수 있다.
Spring MVC에 익숙하면 처음에는 서버가 화면을 완성하지 않는다는 점이 낯설 수 있다.
하지만React에서는 화면을 작은 컴포넌트로 나누고, 각 컴포넌트가 필요한 데이터를 받아 화면을 구성한다.
데이터가 바뀌면React는 바뀐 데이터에 맞게 화면을 다시 그린다.
예를 들어 게시글 목록 화면을React방식으로 생각하면 다음과 같다.
PostList컴포넌트는 게시글 목록 전체를 담당한다.PostCard컴포넌트는 게시글 하나를 담당한다.- 서버에서 받은 게시글 데이터는
state에 저장된다.state에 저장된 게시글 수만큼PostCard가 반복해서 화면에 표시된다.이 흐름을 알면
React가 왜component,props,state를 중요하게 다루는지 이해할 수 있다.
component는 화면 조각이고,props는 컴포넌트에 전달되는 값이며,state는 화면이 바뀌는 기준이 되는 데이터이다.
정리하면React는 서버가 만들어 준 완성 화면을 보여주는 방식이 아니다.
브라우저에서 실행되는React가 데이터를 받아 화면을 직접 구성하는 방식이다.
3. SPA 개념 이해하기
3-1. SPA의 의미
SPA는Single Page Application의 줄임말이다.
뜻 그대로 하나의HTML페이지를 기반으로 동작하는 웹 애플리케이션이다.
처음에 기본HTML,JavaScript,CSS를 내려받고 나면, 이후 화면 변화는 브라우저 안에서 처리한다.
SPA라고 해서 화면이 하나만 있다는 뜻은 아니다.
사용자는 여러 화면을 보는 것처럼 느낀다.
다만 서버에서 매번 새로운HTML페이지를 받아오는 것이 아니라, 하나의 페이지 안에서 필요한 컴포넌트를 바꿔가며 화면을 갱신한다.
초보자 기준으로 쉽게 말하면SPA는 다음과 같다.
- 처음에는 앱을 실행하기 위한 기본 파일을 받는다.
- 이후에는 화면 전체를 새로 받지 않는다.
- 필요한 데이터만 서버에서 가져온다.
- 브라우저 안의
React가 화면을 다시 구성한다.- 사용자는 페이지가 빠르게 바뀌는 것처럼 느낀다.
SPA의 핵심은 서버에서 완성된 화면을 계속 받는 것이 아니라, 필요한 데이터만 받아 브라우저에서 화면을 바꾸는 것이다.
3-2. SPA에서 화면이 바뀌는 방식
SPA에서는 사용자가 버튼을 클릭하거나 주소를 이동해도 서버가 매번 완성된HTML을 새로 만들어 보내지 않는다.
브라우저 안에서 실행 중인React가 현재 주소나 사용자 동작에 맞는 컴포넌트를 렌더링한다.
렌더링은 화면에 보일 구조를 그려내는 작업이다.
React는 현재 데이터와 상태를 기준으로 어떤 화면을 보여줄지 계산한다.
그 결과 필요한 부분만 화면에 반영된다.
예를 들어 사용자가 게시글 목록에서 상세보기 버튼을 클릭했다고 생각하면 된다.
기존 서버 중심 방식에서는 상세 페이지HTML을 서버에서 다시 받아오는 흐름이 많다.
하지만SPA에서는React가 상세 화면 컴포넌트를 보여주고, 필요한 게시글 상세 데이터만API로 요청할 수 있다.
SPA화면 변화 흐름은 다음과 같다.
- 사용자가 버튼 클릭이나 주소 이동을 한다.
React가 어떤 화면을 보여줄지 결정한다.- 데이터가 필요하면 서버
API를 호출한다.- 서버는
JSON데이터를 응답한다.React는 받은 데이터를state에 저장한다.- 변경된
state를 기준으로 화면을 다시 렌더링한다.이 방식 덕분에 화면 전체가 매번 새로고침되는 느낌이 줄어든다.
사용자는 더 자연스럽고 빠른 화면 전환을 경험할 수 있다.
3-3. React와 Spring Boot REST API가 연결되는 흐름
React와Spring Boot REST API는 화면과 데이터를 나누어 담당한다.
React는 브라우저에서 화면을 만들고,Spring Boot는 서버에서 데이터를 제공한다.
둘 사이의 연결은API호출로 이루어진다.
React는fetch또는axios를 사용해서 서버에 요청을 보낸다.
fetch와axios는 브라우저에서 서버API를 호출할 때 사용하는 도구이다.
서버는 요청을 처리한 뒤JSON데이터를 응답한다.
React는 응답받은 데이터를state에 저장하고 화면에 반영한다.
연결 흐름을 정리하면 다음과 같다.
React화면에서 데이터가 필요해진다.React가fetch또는axios로Spring Boot REST API를 호출한다.Spring Boot는 요청을 받아 필요한 데이터를 조회하거나 처리한다.Spring Boot는 결과를JSON으로 응답한다.React는JSON데이터를 받아state에 저장한다.state가 바뀌면서 화면이 다시 렌더링된다.예를 들어 로그인 화면에서는
React가 사용자가 입력한 아이디와 비밀번호를 서버로 보낸다.
Spring Boot는 로그인 정보를 확인하고 성공 또는 실패 결과를JSON으로 응답한다.
그 결과에 따라React는 로그인 성공 화면으로 이동하거나 에러 메시지를 보여줄 수 있다.
SPA는 하나의 페이지 안에서 필요한 데이터만API로 받아오고, 브라우저의React가 화면을 동적으로 갱신하는 구조이다.
3-4. Spring MVC와 SPA 방식 비교
Spring MVC와SPA방식은 화면을 바꾸는 기준이 다르다.
Spring MVC는 서버가 새 화면을 만들어 보내는 흐름이 중심이다.
SPA는 브라우저 안에서 화면을 바꾸고, 필요한 데이터만 서버에서 가져오는 흐름이 중심이다.
차이를 다시 정리하면 다음과 같다.
Spring MVC는 화면 요청이 들어오면 서버가HTML을 만들어 응답한다.SPA는 처음 받은 페이지 안에서React가 화면을 바꾼다.Spring MVC는 화면 전환 시 서버 렌더링 흐름이 자주 발생한다.SPA는 화면 전환 시 필요한 데이터만API로 요청할 수 있다.Spring MVC는 서버가 화면 구성까지 담당한다.SPA는 브라우저가 화면 구성을 담당하고 서버는 데이터를 제공한다.둘 중 하나가 무조건 더 좋다는 뜻은 아니다.
중요한 것은 어떤 역할을 어디에서 처리하는지 이해하는 것이다.
이번 흐름에서는Spring Boot가 데이터를 제공하고,React가 화면을 구성하는 구조를 기준으로 학습한다.
정리하면SPA는 하나의HTML페이지 안에서React가 필요한 화면을 바꾸고, 데이터가 필요할 때만Spring Boot REST API를 호출하는 방식이다.
이 개념을 이해하면 다음에 나오는React개요와 컴포넌트 기반 화면 구성 방식도 자연스럽게 이어진다.
4. React 개요
4-1. React의 기본 의미
React는 사용자 인터페이스를 만들기 위한JavaScript라이브러리이다.
사용자 인터페이스는 사용자가 화면에서 직접 보고 조작하는 부분을 의미한다.
예를 들면 버튼, 입력창, 검색창, 게시글 카드, 목록, 모달, 대시보드 같은 화면 요소가 모두 사용자 인터페이스에 해당한다.
React는 이런 화면 요소를 하나의 큰 덩어리로 만들지 않는다.
화면을 작은 조각으로 나눈 뒤, 그 조각들을 조립해서 하나의 화면을 만든다.
여기서 화면을 이루는 작은 조각을component라고 한다.
초보자 기준으로 쉽게 생각하면React의 화면 구성은 블록 조립과 비슷하다.
게시글 목록 화면을 만든다고 하면 전체 화면을 한 번에 만들지 않고, 다음처럼 나누어 생각한다.
- 제목 영역
- 검색창
- 게시글 목록 영역
- 게시글 카드 하나
- 페이지 이동 버튼
이렇게 화면을 나누면 같은 조각을 여러 곳에서 다시 사용할 수 있다.
예를 들어 게시글 카드 모양이 같다면, 게시글이 1개든 10개든 같은PostCard컴포넌트를 반복해서 사용할 수 있다.
4-2. 컴포넌트로 화면을 나누는 이유
component는 화면을 구성하는 독립적인 조각이다.
독립적인 조각이라는 말은, 하나의 컴포넌트가 자기 역할을 가지고 따로 분리될 수 있다는 뜻이다.
예를 들어 쇼핑몰 화면을 생각하면 다음처럼 나눌 수 있다.
Header컴포넌트는 상단 메뉴를 담당한다.SearchBox컴포넌트는 검색 입력창을 담당한다.ProductList컴포넌트는 상품 목록 전체를 담당한다.ProductCard컴포넌트는 상품 하나를 담당한다.CartButton컴포넌트는 장바구니 버튼을 담당한다.이렇게 나누면 코드가 훨씬 관리하기 쉬워진다.
문제가 생겼을 때 전체 화면을 뒤지는 것이 아니라, 문제가 생긴 컴포넌트만 확인하면 된다.
또한 같은 모양이 반복되는 화면도 쉽게 만들 수 있다.
게시글 카드, 상품 카드, 댓글 항목처럼 같은 구조가 반복되는 화면은 컴포넌트로 만들면 재사용하기 좋다.
React에서 화면을 컴포넌트로 나누는 이유는 재사용과 유지보수를 쉽게 하기 위해서이다.
화면이 커질수록 이 방식이 중요해진다.
4-3. 함수 기반 컴포넌트 예시
React에서는 함수를 사용해서 컴포넌트를 만들 수 있다.
함수는 어떤 작업을 묶어 놓은 코드이고,React컴포넌트 함수는 화면에 보여줄JSX를 반환한다.
JSX는JavaScript안에서HTML처럼 생긴 화면 구조를 작성할 수 있게 해주는 문법이다.
간단한 컴포넌트 예시는 다음과 같다.// App.jsx function MyButton() { // 버튼 화면을 반환한다. return <button>클릭</button>; } function App() { // MyButton 컴포넌트를 화면에 사용한다. return ( <div> <h1>안녕하세요</h1> <MyButton /> </div> ); } export default App;위 코드에서
MyButton은 버튼 하나를 담당하는 컴포넌트이다.
App은 전체 화면에 가까운 컴포넌트이고, 그 안에서MyButton을 태그처럼 사용하고 있다.
여기서 중요한 점은MyButton()처럼 함수를 직접 호출하지 않는다는 것이다.
React컴포넌트는<MyButton />처럼 태그 형태로 사용한다.
이 태그를 보고React가 해당 컴포넌트 함수를 실행해서 화면을 만든다.
React에서는 컴포넌트를 함수로 구현하고, 구현한 컴포넌트는 다른 컴포넌트 안에서 태그처럼 사용한다.
4-4. props를 받는 컴포넌트 예시
컴포넌트는 항상 똑같은 화면만 보여주는 것이 아니다.
부모 컴포넌트가 값을 전달하면, 자식 컴포넌트는 그 값을 받아 화면에 표시할 수 있다.
이때 전달되는 값을props라고 한다.
예시는 다음과 같다.// EduApp1.jsx function Welcome({ name }) { // props에서 name 값을 꺼내 화면에 출력한다. return <h1>안녕? {name}</h1>; } export default function EduApp1() { // Welcome 컴포넌트에 name 값을 전달한다. return <Welcome name="React" />; }// 출력결과 // 안녕? React
Welcome컴포넌트는{ name }을 통해 전달받은 값을 꺼낸다.
그리고<h1>안녕? {name}</h1>에서{name}부분에 실제 값인React를 넣어 화면에 출력한다.
여기서{name}은 단순한 글자가 아니다.
JSX안에서JavaScript값을 화면에 넣기 위해 사용하는 표현식이다.
따라서name값이 바뀌면 화면에 출력되는 글자도 바뀐다.
4-5. React의 점진적 도입 방식과 발전 방향
React는 처음부터 큰 프로젝트 전체에만 사용하는 도구가 아니다.
필요하다면 기존HTML페이지의 일부 영역에만React를 붙일 수도 있다.
반대로 전체 웹 애플리케이션을React기반으로 만들 수도 있다.
이런 특징을 점진적 도입이라고 한다.
점진적 도입은 한 번에 모든 것을 바꾸지 않고, 필요한 부분부터 조금씩 적용할 수 있다는 뜻이다.
또한 최신React는 단순히 브라우저에서 화면만 그리는 방향에 머무르지 않는다.
서버에서 처리하면 좋은 부분은 서버에서 처리하고, 사용자가 직접 조작하는 부분은 클라이언트에서 처리하는 방향으로 발전하고 있다.
정리하면React는 화면을 컴포넌트 단위로 나누어 만들고, 데이터 변화에 따라 화면을 효율적으로 다시 렌더링하기 위한 도구이다.
이제부터 배우는JSX,props,state는 모두 이 컴포넌트 중심 구조를 이해하기 위한 핵심 개념이다.
React프로젝트를 만들려면 먼저 프로젝트 생성 도구와 기본 폴더 구조를 이해해야 한다.
React코드는 브라우저에서 실행되지만, 개발할 때는 여러 파일과 설정 파일이 함께 필요하다.
그래서 처음에는 어떤 파일이 시작점이고, 어떤 파일을 수정해야 화면이 바뀌는지 구분하는 것이 중요하다.
이번 구간에서는Vite로React프로젝트를 만들고, 생성된 프로젝트 안에서index.html,main.jsx,App.jsx,package.json,vite.config.js가 각각 어떤 역할을 하는지 정리한다.
이 흐름을 이해하면 뒤에서 컴포넌트를 만들 때 코드가 어디에서 시작되고 어디로 연결되는지 헷갈리지 않는다.
5. Vite로 React 프로젝트 생성하기
5-1. 프로젝트 생성 명령어
React프로젝트는Vite를 사용해서 만들 수 있다.
Vite는 프론트엔드 프로젝트를 빠르게 생성하고 실행할 수 있게 도와주는 개발 도구이다.
쉽게 말하면React프로젝트에 필요한 기본 파일과 실행 환경을 자동으로 준비해 주는 도구이다.
프로젝트 생성 명령어는 다음과 같다.// terminal npm create vite@latest my-react-app -- --template react이 명령어는
my-react-app이라는 이름의React프로젝트를 생성한다.
npm은Node.js환경에서 패키지를 설치하거나 실행할 때 사용하는 도구이다.
여기서는Vite프로젝트 생성기를 실행하는 역할을 한다.
명령어를 나누어 보면 다음과 같다.
npm create vite@latest는 최신Vite프로젝트 생성기를 실행한다.my-react-app은 생성할 프로젝트 폴더 이름이다.--는npm명령 옵션과Vite생성기 옵션을 구분한다.--template react는React템플릿으로 프로젝트를 만들겠다는 뜻이다.여기서 템플릿은 미리 준비된 기본 프로젝트 구조를 의미한다.
따라서--template react를 붙이면 처음부터React개발에 맞는 파일 구조가 만들어진다.
5-2. --template react 옵션의 의미
--template react옵션은Vite에게React용 프로젝트 구조로 생성하라고 알려주는 옵션이다.
이 옵션을 붙이지 않으면 프로젝트 생성 중에 어떤 프레임워크를 사용할지 직접 선택해야 한다.
예를 들어Vanilla,Vue,React,Svelte같은 선택지가 나올 수 있다.
처음React를 학습할 때는 선택 과정을 줄이는 것이 좋다.
우리가 만들 프로젝트는React프로젝트이므로 처음부터--template react를 붙이면 된다.
그러면 중간에 프레임워크를 고르는 과정 없이 바로React기본 구조로 생성된다.
정리하면 다음과 같다.
- 옵션을 생략하면 프레임워크를 선택하는 과정이 나온다.
--template react를 붙이면React프로젝트로 바로 생성된다.- 실습에서는
React를 학습하는 것이 목적이므로--template react를 사용하는 것이 적절하다.
--template react는 단순히 이름만 붙이는 옵션이 아니다.
처음 생성되는 파일 구조와 실행 환경을React기준으로 맞춰 주는 옵션이다.
5-3. 옵션을 지정하지 않았을 때의 선택 과정
--template react를 지정하지 않으면Vite는 프로젝트 생성 중에 사용할 프레임워크를 물어본다.
이때 사용자가React를 선택하고, 다시JavaScript또는TypeScript같은 세부 선택지를 고르게 된다.
이 과정 자체가 잘못된 것은 아니다.
다만 처음 학습하는 단계에서는 어떤 선택지를 골라야 하는지 헷갈릴 수 있다.
그래서 실습에서는 명령어에--template react를 붙여 바로React프로젝트를 만드는 흐름으로 진행하는 것이 좋다.
두 방식을 비교하면 다음과 같다.
npm create vite@latest my-react-app은 중간 선택 과정이 나온다.npm create vite@latest my-react-app -- --template react는React템플릿으로 바로 생성된다.즉, 프로젝트 생성 방식은 둘 다 가능하지만, 학습 흐름에서는
--template react를 붙인 명령이 더 명확하다.
5-4. 프로젝트 생성 후 개발 서버 실행
프로젝트가 생성되면 해당 프로젝트 폴더로 이동해서 개발 서버를 실행한다.
개발 서버는 작성한React화면을 브라우저에서 바로 확인할 수 있게 해주는 서버이다.
실행 흐름은 다음과 같다.// terminal cd my-react-app npm run dev
cd my-react-app은 생성된 프로젝트 폴더로 이동하는 명령어이다.
npm run dev는 개발 서버를 실행하는 명령어이다.
개발 서버가 실행되면 브라우저에서localhost주소로 접속해 기본 화면을 확인할 수 있다.
여기서localhost는 현재 내 컴퓨터를 의미한다.
즉, 외부 서버에 배포한 상태가 아니라 내 컴퓨터 안에서 개발용으로 실행하는 상태이다.
주의할 점은npm run dev를 실행하는 위치이다.
이 명령어는 반드시package.json파일이 있는 프로젝트 폴더 안에서 실행해야 한다.
package.json에는dev실행 명령이 정의되어 있기 때문이다.
Vite개발 서버를 실행하면 브라우저에서localhost주소로React기본 화면을 확인할 수 있다.
정리하면React프로젝트 실행 흐름은 다음과 같다.
Vite로 프로젝트를 생성한다.- 생성된 프로젝트 폴더로 이동한다.
npm run dev를 실행한다.- 브라우저에서
localhost주소로 접속한다.React기본 화면이 표시되는지 확인한다.이 흐름까지 확인되면
React프로젝트 생성과 실행이 정상적으로 끝난 것이다.
6. React 프로젝트 주요 파일 구조
6-1. index.html의 역할
index.html은 브라우저가 처음 읽는HTML파일이다.
React프로젝트도 결국 브라우저에서 실행되기 때문에 시작점이 되는HTML문서가 필요하다.
index.html안에는React화면이 들어갈 자리인root요소가 있다.
처음에는 비어 있는div이지만, 나중에main.jsx가 이 위치를 찾아서React앱을 연결한다.
핵심 구조는 다음과 같다.// index.html <body> <!-- React 앱이 들어갈 자리이다. --> <div id="root"></div> <!-- React 앱의 시작 파일을 불러온다. --> <script type="module" src="/src/main.jsx"></script> </body>
<div id="root"></div>는React화면이 들어갈 자리이다.
<script type="module" src="/src/main.jsx"></script>는React앱의 시작 파일인main.jsx를 불러온다.
따라서index.html은 직접 화면을 모두 만드는 파일이 아니다.
브라우저가 처음 읽는 문서이면서,React앱이 들어갈 위치와 시작 파일을 연결하는 역할을 한다.
6-2. src/main.jsx의 역할
src/main.jsx는React앱의 시작점이다.
시작점이라는 말은React코드가 실제로 실행되기 시작하는 파일이라는 뜻이다.
main.jsx는index.html안에 있는root요소를 찾는다.
그 다음createRoot()와render()를 사용해서App컴포넌트를 화면에 표시한다.
흐름을 나누면 다음과 같다.
index.html이main.jsx를 불러온다.main.jsx가root요소를 찾는다.createRoot()가React앱을 연결할 준비를 한다.render()가App컴포넌트를 화면에 표시한다.
main.jsx는 화면을 직접 많이 작성하는 파일이라기보다,React앱을 브라우저의root위치에 연결하는 파일이다.
6-3. src/App.jsx의 역할
src/App.jsx는 처음 렌더링되는 대표 컴포넌트이다.
대표 컴포넌트라는 말은 화면의 중심이 되는 최상위 컴포넌트에 가깝다는 뜻이다.
보통App.jsx안에서 다른 컴포넌트들을 불러와 조립한다.
예를 들어 화면이 상단 메뉴, 목록, 하단 영역으로 나뉜다면App.jsx에서Header,PostList,Footer같은 컴포넌트를 배치할 수 있다.
쉽게 말하면main.jsx는React앱을 시작시키는 파일이다.
반면App.jsx는 실제 화면 구조의 중심이 되는 파일이다.
6-4. package.json의 역할
package.json은 프로젝트의 실행 스크립트와 의존성 정보를 담는 파일이다.
실행 스크립트는npm run dev처럼 프로젝트를 실행할 때 사용하는 명령을 의미한다.
의존성은 프로젝트가 실행되기 위해 필요한 라이브러리나 도구를 의미한다.
예를 들어React,Vite같은 도구들은 프로젝트에 필요한 의존성이다.
이 정보가package.json에 기록되어 있어야 프로젝트를 설치하고 실행할 수 있다.
초보자가 먼저 기억할 부분은 다음과 같다.
package.json이 있는 폴더가 프로젝트의 중심 폴더이다.npm run dev는package.json안의 실행 스크립트를 실행한다.- 필요한 라이브러리 정보도
package.json에 기록된다.그래서
npm run dev를 실행할 때는 반드시package.json이 있는 위치인지 확인해야 한다.
6-5. vite.config.js의 역할
vite.config.js는Vite빌드 도구 설정 파일이다.
빌드 도구는 개발자가 작성한 코드를 브라우저가 실행할 수 있는 형태로 준비해 주는 도구이다.
처음React를 배울 때는vite.config.js를 자주 수정하지 않아도 된다.
다만 이 파일이Vite설정을 담당한다는 점은 알고 있어야 한다.
프로젝트가 커지면 개발 서버 설정, 경로 설정, 플러그인 설정 등을 이 파일에서 조정할 수 있다.
지금 단계에서는vite.config.js를 외우기보다Vite관련 설정 파일이라고 이해하면 충분하다.
6-6. src 폴더와 컴포넌트 코드 위치
src폴더는 실제React코드를 작성하는 핵심 폴더이다.
src는source의 줄임말로, 프로젝트의 소스 코드가 들어가는 위치라고 이해하면 된다.
보통main.jsx,App.jsx, 스타일 파일, 컴포넌트 파일들이src폴더 안에 들어간다.
앞으로 컴포넌트를 나누어 만들게 되면 대부분의 화면 관련 코드는src폴더 안에서 관리하게 된다.
React프로젝트는index.html에서 시작하고,src폴더 안의main.jsx와App.jsx를 통해 화면이 연결된다.
주요 파일 역할을 정리하면 다음과 같다.
index.html은 브라우저가 처음 읽는HTML파일이다.src/main.jsx는React앱의 시작점이다.src/App.jsx는 처음 렌더링되는 대표 컴포넌트이다.package.json은 실행 스크립트와 의존성 정보를 담는다.vite.config.js는Vite빌드 도구 설정 파일이다.
각 파일의 역할을 구분하면
React앱이 어디서 시작되고 어떤 파일을 수정해야 화면이 바뀌는지 파악하기 쉬워진다.
7. main.jsx: React 앱의 시작점
7-1. main.jsx 코드 구조
main.jsx는React앱을 실제 화면에 연결하는 파일이다.
기본 코드는 다음과 같은 구조를 가진다.// 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> );이 코드에서
import는 다른 파일이나 라이브러리에서 필요한 기능을 가져오는 문법이다.
StrictMode는 개발 중 문제를 더 잘 찾도록 도와주는 검사 도구이다.
createRoot는React앱을 특정HTML요소에 연결하기 위해 사용하는 함수이다.
App은 화면에 표시할 대표 컴포넌트이다.
즉,main.jsx는 필요한 기능을 가져오고,root요소를 찾은 뒤, 그 안에App컴포넌트를 렌더링한다.
7-2. document.getElementById("root")의 역할
document.getElementById("root")는index.html안에 있는id가root인 요소를 찾는다.
document는 현재 브라우저가 읽고 있는HTML문서를 의미한다.
getElementById()는 특정id값을 가진 요소를 찾는 메서드이다.
index.html에는 다음 요소가 있다.// index.html <div id="root"></div>
main.jsx는 이 요소를 찾아서 그 안에React앱을 넣는다.
처음에는 비어 있는div이지만,React가 실행되면 이 안에App컴포넌트 화면이 만들어진다.
정리하면root는React화면이 들어갈 자리이다.
document.getElementById("root")는 그 자리를 찾아오는 코드이다.
7-3. createRoot()와 render()의 역할
createRoot()는React앱을 특정HTML요소에 렌더링할 준비를 한다.
여기서는document.getElementById("root")로 찾은 요소가 대상이다.
render()는 실제로 컴포넌트를 화면에 표시한다.
즉,createRoot()가 준비 단계라면render()는 화면 출력 단계이다.
흐름을 나누면 다음과 같다.
document.getElementById("root")가React화면이 들어갈 위치를 찾는다.createRoot()가 그 위치를React앱의 렌더링 대상으로 만든다.render()가App컴포넌트를 화면에 표시한다.이 세 단계가 연결되어야 브라우저에
React화면이 나타난다.
7-4. App 컴포넌트를 화면에 표시하는 흐름
App컴포넌트는main.jsx에서 불러와 화면에 표시된다.
코드에서import App from './App.jsx'는App.jsx파일에서 기본으로 내보낸 컴포넌트를 가져온다는 뜻이다.
가져온App컴포넌트는<App />형태로 사용된다.
React에서는 컴포넌트를 함수처럼 직접 호출하지 않고 태그처럼 사용한다.
그래서<App />은App컴포넌트를 화면에 표시하라는 의미이다.
main.jsx는root요소를 찾아App컴포넌트를 화면에 렌더링하는React앱의 시작점이다.
예를 들어App.jsx에서 화면 문구를 수정하면 브라우저에 보이는 화면도 바뀐다.
하지만 그App.jsx가 실제 화면에 보이려면main.jsx에서<App />으로 렌더링되어야 한다.
7-5. StrictMode의 의미와 개발 중 검사 역할
StrictMode는 개발 중 문제를 미리 찾기 위해 사용하는 도구이다.
이름 그대로 코드를 더 엄격하게 검사하는 역할을 한다.
StrictMode는 실제 배포 화면의 기능을 바꾸는 도구가 아니다.
개발 모드에서 잠재적인 문제를 찾도록 도와주는 안전 장치에 가깝다.
대표적인 역할은 다음과 같다.
- 컴포넌트를 개발 중 한 번 더 렌더링해 의도치 않은 문제를 찾는다.
- 사라질 예정인 기능 사용을 경고한다.
- 의도하지 않은 부작용이 있는지 확인하는 데 도움을 준다.
초보자 입장에서는
StrictMode때문에 개발 중 코드가 한 번 더 실행되는 것처럼 보일 수 있다.
이것은 오류가 아니라 문제를 미리 찾기 위한 개발 모드의 검사 동작이다.
정리하면StrictMode는React앱을 더 안전하게 개발하도록 도와주는 검사 도구이다.
8. 맛보기 주요 소스 연결 흐름
8-1. index.html에서 main.jsx를 불러오는 흐름
React앱은index.html에서 시작된다.
브라우저가 처음 읽는 파일이index.html이기 때문이다.
하지만index.html안에 화면 전체 코드가 들어 있지는 않다.
대신index.html은root요소와main.jsx연결 코드를 가지고 있다.
핵심 코드는 다음과 같다.// index.html <div id="root"></div> <script type="module" src="/src/main.jsx"></script>
<div id="root"></div>는React화면이 들어갈 자리이다.
<script type="module" src="/src/main.jsx"></script>는main.jsx를 불러오는 코드이다.
즉,index.html은 빈 화면 자리인root를 준비하고, 실제React실행은main.jsx에게 넘긴다.
8-2. main.jsx에서 EduApp1.jsx를 불러오는 흐름
main.jsx는React앱을 시작하면서 화면에 표시할 컴포넌트를 불러온다.
예시에서는EduApp1.jsx파일에서EduApp1컴포넌트를 가져온다.
핵심 코드는 다음과 같다.// main.jsx import EduApp1 from './EduApp1.jsx'; createRoot(document.getElementById('root')).render( <StrictMode> <EduApp1 /> </StrictMode> );
import EduApp1 from './EduApp1.jsx'는EduApp1.jsx파일에서 기본으로 내보낸 컴포넌트를 가져온다는 뜻이다.
그리고<EduApp1 />은 가져온 컴포넌트를 화면에 렌더링하겠다는 뜻이다.
정리하면main.jsx는index.html의root에 어떤 컴포넌트를 보여줄지 결정하는 파일이다.
8-3. EduApp1.jsx에서 컴포넌트를 만들고 사용하는 흐름
EduApp1.jsx에서는 실제 화면에 보여줄 컴포넌트를 만든다.
예시에서는Welcome컴포넌트를 만들고,EduApp1컴포넌트 안에서<Welcome name="React" />형태로 사용한다.
코드는 다음과 같은 흐름이다.// EduApp1.jsx function Welcome({ name }) { // props에서 name 값을 꺼내 화면에 출력한다. return <h1>안녕? {name}</h1>; } export default function EduApp1() { // Welcome 컴포넌트에 name 값을 전달한다. return <Welcome name="React" />; }
Welcome({ name })에서{ name }은props객체에서name값을 꺼내는 문법이다.
props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 값이다.
여기서는EduApp1이Welcome에게name="React"값을 전달한다.
그 결과Welcome은name값으로React를 받아<h1>안녕? React</h1>화면을 만든다.
8-4. root 요소에 최종 화면이 렌더링되는 흐름
이제 전체 연결 흐름을 한 번에 보면 다음과 같다.
- 브라우저가
index.html을 읽는다.index.html안에는root요소가 있다.index.html이/src/main.jsx를 불러온다.main.jsx가EduApp1.jsx를 불러온다.main.jsx가root요소에<EduApp1 />을 렌더링한다.EduApp1안에서<Welcome name="React" />가 실행된다.Welcome이안녕? React라는 화면을 만든다.- 최종적으로 브라우저 화면에
안녕? React가 표시된다.이 흐름을 이해하면
React앱이 단순히 한 파일에서 바로 실행되는 것이 아니라 여러 파일이 연결되어 화면을 만든다는 점을 알 수 있다.
React앱은index.html의root에서 시작해main.jsx를 거쳐 컴포넌트 화면으로 연결된다.
정리하면index.html은 브라우저의 시작 문서이고,main.jsx는React앱의 시작점이다.
그리고EduApp1.jsx같은 컴포넌트 파일은 실제 화면 내용을 만든다.
따라서React프로젝트를 볼 때는index.html → main.jsx → 컴포넌트 파일 → 브라우저 화면흐름으로 이해하면 된다.
React를 배우면서 가장 먼저 자주 만나게 되는 문법이JSX이다.
처음 보면HTML처럼 생겨서 마크업 문법처럼 보이지만, 실제로는JavaScript안에서 사용하는 특별한 문법이다.
그래서JSX를 이해할 때는 “겉모양은HTML처럼 보이지만, 실제로는JavaScript안에서 화면을 만들기 위한 문법”이라고 잡고 가면 된다.
이 구간에서는JSX가 무엇인지부터 시작해서, 기본 규칙, 속성 문법, 이벤트 처리, 조건부 렌더링, 리스트 렌더링, 자주 나는 오류까지 한 번에 정리한다.
9. JSX의 의미와 사용하는 이유
9-1. JSX의 기본 의미
JSX는JavaScript XML의 줄임말이다.
이름만 보면 어려워 보이지만, 초보자 기준으로는JavaScript안에서HTML처럼 화면 구조를 작성할 수 있게 해주는 문법이라고 이해하면 된다.
예를 들어 화면에 제목 하나를 보여주고 싶다면, 일반JavaScript만으로는 객체를 만들고 함수로 요소를 생성하는 흐름이 길어질 수 있다.
하지만JSX를 사용하면<h1>Hello React</h1>처럼 눈에 바로 읽히는 형태로 화면 구조를 작성할 수 있다.
즉,JSX는 화면 모양을 더 직관적으로 적기 위해 사용한다.
코드를 읽는 사람도 “이 컴포넌트는 이런 화면을 보여주는구나”를 빠르게 이해할 수 있다.
9-2. JSX가 HTML처럼 보이지만 JavaScript인 이유
JSX는 모양은HTML태그와 비슷하다.
하지만 브라우저가 그대로 읽는 순수HTML은 아니다.
React는JSX코드를 받아서 내부적으로JavaScript코드로 변환한 뒤 실행한다.
그래서JSX안에서는JavaScript변수, 함수, 연산식을 함께 사용할 수 있다.
반대로HTML에서는 그냥 태그와 문자열만 적는 것이 기본이다.
이 차이 때문에JSX는HTML처럼 보이지만 실제로는JavaScript에 더 가깝다.
예를 들어 다음 코드를 보면HTML처럼 보이지만{title}이라는JavaScript값을 함께 사용하고 있다.// JsxLooksLikeHtmlExample.jsx export default function JsxLooksLikeHtmlExample() { const title = 'Hello React'; return <h1>{title}</h1>; }위 코드는 화면 구조는 태그처럼 보이지만, 실제로는
JavaScript변수title을 사용하고 있다.
이 점이HTML과JSX의 가장 큰 차이이다.
9-3. 화면 구조와 렌더링 로직을 함께 두는 이유
기존에는 화면 구조를 만드는 코드와 동작을 처리하는 코드를 나누어 생각하는 경우가 많았다.
하지만React는 화면도 결국 데이터에 따라 바뀌는 결과라고 본다.
그래서 화면 구조와 그 화면을 바꾸는 로직을 한 컴포넌트 안에 같이 두는 방식이 자연스럽다.
예를 들어 로그인 여부에 따라 다른 문구를 보여주고 싶다면, 화면 태그와 조건 로직이 서로 가까이 있는 것이 오히려 이해하기 쉽다.
이렇게 하면 “어떤 조건일 때 어떤 화면이 나오는지”를 한 파일 안에서 바로 확인할 수 있다.
쉽게 말하면JSX는 화면과 로직을 억지로 분리하기보다, 함께 봤을 때 더 이해가 쉬운 구조로 정리해 주는 문법이다.
9-4. JSX는 값처럼 다룰 수 있는 표현식이라는 점
JSX는 단순히 눈에 보이는 태그 문법이 아니다.
JSX자체도 하나의 값처럼 다룰 수 있다.
즉, 변수에 저장할 수도 있고, 다른 컴포넌트 안에 넣을 수도 있고, 배열 안에 담을 수도 있다.
다음 예시를 보면 이 점이 더 잘 보인다.// JsxExpressionValueExample.jsx export default function JsxExpressionValueExample() { const title = <h1>Hello React</h1>; const items = [ <li key="1">사과</li>, <li key="2">바나나</li>, <li key="3">포도</li>, ]; return ( <div> {title} <ul>{items}</ul> </div> ); }위 코드에서
title에는JSX가 들어가고,items배열 안에도JSX가 들어간다.
즉,JSX는 화면을 만드는 데 쓰이지만 동시에 값처럼 다룰 수도 있다.
React의 컴포넌트는 화면을 구성하기 위해JSX를 리턴한다.
정리하면JSX는React컴포넌트가 화면을 설명하는 방법이며,HTML처럼 보이지만 실제로는JavaScript안에서 값처럼 다룰 수 있는 문법이다.
10. JSX의 기본 형태
10-1. 함수형 컴포넌트가 JSX를 반환하는 구조
React에서 함수형 컴포넌트는 보통JSX를return한다.
즉, 함수가 최종적으로 “이 화면을 보여 주세요”라고JSX를 돌려주는 구조이다.
가장 기본 형태는 다음과 같다.// JsxBasicReturnExample.jsx export default function JsxBasicReturnExample() { // 화면에 보여줄 JSX를 반환한다. return <h1>Hello React</h1>; }// 출력결과 // Hello React이 코드는
JsxBasicReturnExample컴포넌트가<h1>Hello React</h1>라는 화면을 반환한다는 뜻이다.
return뒤에 있는JSX가 실제 브라우저 화면에 렌더링된다.
10-2. 컴포넌트를 다른 컴포넌트에서 태그처럼 사용하는 방식
컴포넌트는 한 번 만들고 끝나는 것이 아니다.
다른 컴포넌트 안에서 태그처럼 다시 사용할 수 있다.
예를 들어 버튼 역할을 하는 컴포넌트를 만들어 두면, 다른 컴포넌트 안에서<MyButton />처럼 사용할 수 있다.// ComponentTagUsageExample.jsx function MyButton() { // 버튼 하나를 담당하는 컴포넌트이다. return <button>클릭</button>; } export default function ComponentTagUsageExample() { // MyButton 컴포넌트를 화면에 포함한다. return ( <div> <h1>안녕하세요</h1> <MyButton /> </div> ); }
MyButton함수는 버튼 화면을 반환한다.
ComponentTagUsageExample안에서<MyButton />을 사용하면MyButton컴포넌트가 실행되고, 버튼이 화면에 표시된다.
React컴포넌트는 함수 이름을 직접 호출하는 것이 아니라 태그처럼 사용한다.
즉,MyButton()이 아니라<MyButton />으로 작성해야 한다.
10-3. 컴포넌트 이름이 대문자로 시작해야 하는 이유
React는 태그 이름이 소문자로 시작하면 기본HTML태그로 인식한다.
반대로 대문자로 시작하면 사용자가 직접 만든React컴포넌트로 인식한다.
예를 들어<div>는 기본HTML태그이다.
반면<MyButton />은 개발자가 직접 만든 컴포넌트이다.
그래서 컴포넌트 이름은PascalCase, 즉 첫 글자를 대문자로 시작하는 방식으로 작성해야 한다.
잘못된 예시는 다음과 같다.// WrongComponentNameExample.jsx function myButton() { // 컴포넌트 이름이 소문자로 시작한다. return <button>클릭</button>; } export default function App() { // 소문자로 시작하면 React 컴포넌트로 실행되지 않는다. return <myButton />; }위 코드에서는
myButton함수가 있어도<myButton />을 사용자 정의 컴포넌트로 실행하지 않는다.
React는 소문자로 시작하는 태그를 기본HTML태그처럼 처리하려고 하기 때문이다.
올바른 예시는 다음과 같다.// CorrectComponentNameExample.jsx function MyButton() { // 컴포넌트 이름은 대문자로 시작한다. return <button>클릭</button>; } export default function App() { // 사용자 정의 컴포넌트로 인식된다. return <MyButton />; }컴포넌트 이름은 대문자로 시작해야
React가 사용자 정의 컴포넌트로 인식한다.
11. JSX는 반드시 하나의 부모 요소로 감싸야 한다
11-1. 최상위 요소가 여러 개이면 오류가 나는 이유
컴포넌트가
return할 때는 최상위에서 하나의 부모 요소만 반환해야 한다.
즉, 맨 바깥 구조가 하나로 감싸져 있어야 한다.
다음 코드는 오류가 난다.// MultipleRootErrorExample.jsx export default function MultipleRootErrorExample() { return ( <h1>제목</h1> <p>본문</p> ); }// 오류 예시 // Adjacent JSX elements must be wrapped in an enclosing tag이 코드가 문제가 되는 이유는
<h1>과<p>가 같은 레벨에서 나란히 놓여 있기 때문이다.
React는 한 번의return에서 하나의 루트 구조를 기대한다.
여기서 루트 구조는 가장 바깥을 감싸는 하나의 부모 요소를 의미한다.
따라서 여러 요소를 반환하려면div또는Fragment로 감싸야 한다.
11-2. div로 감싸는 방식
가장 쉬운 해결 방법은
div로 감싸는 것이다.// DivWrapperExample.jsx export default function DivWrapperExample() { return ( <div> <h1>제목 1</h1> <p>본문 1</p> </div> ); }이렇게 하면 최상위 부모 요소가
<div>하나가 되므로 문제가 없다.
다만 실제 브라우저의DOM구조에도<div>가 하나 생긴다.
11-3. Fragment로 감싸는 방식
화면 구조상 불필요한
div를 더 만들고 싶지 않을 때는Fragment를 사용할 수 있다.
Fragment는 여러 요소를 하나로 묶어 주지만 실제HTML요소로는 추가되지 않는다.// FragmentWrapperExample.jsx export default function FragmentWrapperExample() { return ( <> <h1>제목 2</h1> <p>본문 2</p> </> ); }
<>...</>는 짧은Fragment문법이다.
화면에는<h1>과<p>가 보이지만, 감싸는 별도의div는 추가되지 않는다.
정리하면div는 실제 태그를 하나 추가하고,Fragment는 실제 태그 없이 문법적으로만 여러 요소를 묶는다.
11-4. div와 Fragment의 차이
div는 실제HTML요소이다.
그래서 브라우저의DOM구조 안에<div>가 하나 더 생긴다.
반면Fragment는 단지 묶는 역할만 한다.
화면 구조를 정리할 때는 필요하지만, 실제DOM에는 별도 태그가 생기지 않는다.
Fragment를 사용하면 불필요한div없이 여러 요소를 하나로 묶을 수 있다.
즉, 레이아웃을 위한 실제 박스가 필요하면div를 쓰고, 단순히 문법상 하나로 묶기만 하면 될 때는Fragment를 쓰면 된다.
12. JSX에서 JavaScript 표현식 사용하기
12-1. 중괄호의 역할
JSX안에서JavaScript값을 사용하려면 중괄호{}를 쓴다.
중괄호는 “여기부터는JavaScript값을 넣겠다”는 표시라고 생각하면 된다.
예를 들어 변수 값을 화면에 넣는 코드는 다음과 같다.// JsxBraceExample.jsx export default function JsxBraceExample() { const name = '언덕이'; const age = 25; return ( <div> <h1>{name}</h1> <p>{age}</p> </div> ); }
{name}과{age}는 각각 변수 값을 화면에 넣는다.
12-2. 문자열 속성과 JavaScript 값 전달의 차이
속성에 값을 줄 때 문자열은 따옴표로 적을 수 있다.
하지만JavaScript값이나 표현식을 넣고 싶을 때는 중괄호를 사용해야 한다.// JsxAttributeValueExample.jsx export default function JsxAttributeValueExample() { const imageWidth = 200; const imageAlt = '프로필 이미지'; return ( <img src="/profile.png" alt={imageAlt} width={imageWidth} /> ); }
src="/profile.png"는 문자열이다.
반면alt={imageAlt},width={imageWidth}는 변수 값을 넣은 것이다.
12-3. 변수, 숫자, 객체, 배열, 함수 전달 예시
중괄호 안에는 다양한
JavaScript값을 넣을 수 있다.
다만 모든 값을 화면에 그대로 출력할 수 있는 것은 아니다.
문자열, 숫자, 계산식, 함수 호출 결과처럼 화면에 표시 가능한 값은 바로 출력할 수 있다.
반면 객체는 화면에 그대로 출력하기보다, 객체 안의 특정 속성을 꺼내서 출력하거나 다른 컴포넌트에props로 전달하는 방식으로 사용한다.
대표적인 사용 방식은 다음과 같다.
- 문자열 변수는 화면에 바로 출력할 수 있다.
- 숫자는 화면에 바로 출력할 수 있다.
- 계산식은 계산 결과가 화면에 출력된다.
- 배열은 값의 형태에 따라 출력할 수 있다.
- 함수 호출 결과는 반환값이 화면에 출력된다.
- 객체는 객체 자체가 아니라 속성을 꺼내서 출력하거나
props로 전달한다.예를 들어 다음과 같이 사용할 수 있다.
// JsxVariousValuesExample.jsx export default function JsxVariousValuesExample() { const name = 'React'; const count = 3; const numbers = [10, 20, 30]; const user = { nickname: '언덕이', age: 25, }; function double(value) { // 전달받은 숫자를 2배로 만든다. return value * 2; } return ( <div> <h1>{name}</h1> <p>{count}</p> <p>{1 + 2}</p> <p>{numbers[0]}</p> <p>{double(5)}</p> <p>{user.nickname}</p> <p>{user.age}</p> </div> ); }// 출력결과 // React // 3 // 3 // 10 // 10 // 언덕이 // 25
numbers[0]은 배열의 첫 번째 값을 꺼내는 코드이다.
numbers배열의 첫 번째 값은10이므로 화면에10이 출력된다.
double(5)는5를 함수에 전달하고, 함수가5 * 2결과인10을 반환한다.
그래서 화면에는10이 출력된다.
user는 객체이다.
객체 전체를{user}처럼 바로 출력하는 것이 아니라,{user.nickname},{user.age}처럼 객체 안의 필요한 값만 꺼내서 출력한다.
JSX중괄호 안에는JavaScript표현식을 넣을 수 있지만, 화면에 직접 표시할 수 있는 값인지도 함께 확인해야 한다.
12-4. 표현식과 문장의 차이
여기서 중요한 개념이 표현식과 문장의 차이이다.
표현식은 결과값이 나오는 코드이다.
반면 문장은 동작을 수행하지만 그 자체가 값이 되지는 않는다.
예를 들어1 + 2,name,isLogin ? '로그인' : '로그아웃'은 표현식이다.
하지만if (...) {},for (...) {},while (...) {}는 문장이다.
JSX의{}안에는 값을 만들어내는 표현식이 들어가며,if문이나for문은 직접 넣을 수 없다.
12-5. if문과 for문을 JSX 중괄호 안에 직접 넣을 수 없는 이유
JSX의{}안에는 결과값이 들어가야 한다.
그래서if문과for문처럼 값이 아니라 흐름 제어를 위한 문장은 직접 넣을 수 없다.
다음 코드는 잘못된 예시이다.// WrongJsxStatementExample.jsx export default function WrongJsxStatementExample() { const isLogin = true; return ( <div> {if (isLogin) { '로그인' }} </div> ); }위 코드는
if문을 중괄호 안에 직접 넣었기 때문에 문법 오류가 난다.
if문은 조건에 따라 코드를 실행하는 문장이다.
문장은 그 자체가 화면에 넣을 결과값이 되지 않는다.
올바른 예시는 다음과 같다.// CorrectJsxConditionalExample.jsx export default function CorrectJsxConditionalExample() { const isLogin = true; return ( <div> {isLogin ? '로그인' : '로그아웃'} </div> ); }// 출력결과 // 로그인삼항 연산자는
조건 ? 참일 때 값 : 거짓일 때 값형태이다.
결과값을 만들어내는 표현식이기 때문에JSX중괄호 안에 넣을 수 있다.
즉,if문은 값이 아니라 실행 흐름을 제어하는 문장이다.
삼항 연산자는 결과값을 만들어내는 표현식이다.
이 차이 때문에JSX안에서는 삼항 연산자를 사용할 수 있고,if문은 직접 사용할 수 없다.
JSX중괄호 안에는 값을 만드는 표현식만 들어간다.
13. JSX 속성 문법
13-1. HTML과 JSX 속성 이름이 다른 이유
JSX는 모양은HTML과 비슷하지만 실제로는JavaScript안에서 동작한다.
그래서 속성 이름도JavaScript규칙과 충돌하지 않도록 바뀐 경우가 있다.
예를 들어class,for는 원래HTML에서는 자연스럽게 쓰는 속성이지만,JavaScript에서는 이미 다른 의미를 가진 단어이다.
그래서JSX에서는 다른 이름을 사용한다.
13-2. class 대신 className을 쓰는 이유
HTML에서는class속성을 사용한다.
하지만JavaScript에는class키워드가 있기 때문에JSX에서는className을 사용한다.// ClassNameExample.jsx export default function ClassNameExample() { return <div className="box">박스</div>; }
class="box"라고 쓰면JSX문법 기준으로 올바르지 않다.
13-3. for 대신 htmlFor를 쓰는 이유
label태그의for속성도JSX에서는htmlFor로 작성한다.// HtmlForExample.jsx export default function HtmlForExample() { return ( <div> <label htmlFor="userId">아이디</label> <input id="userId" /> </div> ); }이렇게 하면
label과input이 연결된다.
13-4. 이벤트 속성을 camelCase로 쓰는 이유
이벤트 속성은
onClick,onChange,onSubmit처럼camelCase로 작성한다.
camelCase는 첫 단어는 소문자, 다음 단어부터 첫 글자를 대문자로 쓰는 방식이다.
예를 들어onclick이 아니라onClick으로 써야 한다.
이것은JSX가JavaScript스타일 규칙을 따르기 때문이다.
13-5. style 속성을 객체로 작성하는 이유
HTML에서는style="color:red;"처럼 문자열로 쓰는 경우가 많다.
하지만JSX에서는style속성에 객체를 전달한다.// JsxStyleObjectExample.jsx export default function JsxStyleObjectExample() { return ( <p style={{ color: 'blue', fontSize: '20px' }}> 인라인 스타일 예시 </p> ); }여기서 바깥 중괄호는
JavaScript값을 넣는다는 뜻이고, 안쪽 중괄호는 객체 자체를 만드는 문법이다.
JSX는HTML처럼 보이지만 실제로는JavaScript이므로 속성 이름과 작성 방식이 다르다.
속성 문법은 외워야 할 부분도 조금 있지만, 이유를 같이 이해하면 훨씬 덜 헷갈린다.
14. JSX 이벤트 처리
14-1. 이벤트 이름은 camelCase로 작성한다
React의 이벤트 이름은camelCase로 작성한다.
그래서 클릭 이벤트는onclick이 아니라onClick이다.
입력 변경 이벤트는onchange가 아니라onChange이다.
이 규칙은JSX가JavaScript스타일을 따르기 때문에 생긴다.
처음에는 사소해 보여도 틀리기 쉬우니 조심해야 한다.
14-2. 이벤트에는 함수를 전달한다
이벤트 속성에는 “이 이벤트가 발생했을 때 실행할 함수”를 전달한다.
즉, 클릭했을 때 실행할 함수를 미리 준비해 두고 그 함수를 연결하는 구조이다.// EventHandlerBasicExample.jsx export default function EventHandlerBasicExample() { function handleClick() { alert('버튼을 클릭했습니다.'); } return <button onClick={handleClick}>클릭</button>; }위 코드에서 버튼을 클릭했을 때
handleClick함수가 실행된다.
onClick처럼 이벤트 속성에는 함수를 전달하고, 이벤트 핸들러 이름은 보통camelCase로 작성한다.
14-3. onClick={handleClick}과 onClick={handleClick()}의 차이
이 부분은 초보자가 가장 많이 헷갈리는 부분이다.
onClick={handleClick}은 클릭했을 때 실행하라는 뜻이다.
반면onClick={handleClick()}은 렌더링되는 순간 함수를 바로 실행하는 뜻이다.
즉, 대부분의 클릭 이벤트에서는()를 붙이지 않고 함수 참조만 전달해야 한다.
함수 참조는 “지금 실행하지 말고, 나중에 이벤트가 발생하면 실행할 함수”를 넘기는 것이다.
onClick={handleClick}은 클릭했을 때 실행하라는 뜻이고,onClick={handleClick()}은 렌더링 중 즉시 실행한다는 뜻이다.
다음 예시로 보면 더 분명하다.// EventHandlerDifferenceExample.jsx export default function EventHandlerDifferenceExample() { function handleClick() { // 버튼 클릭 시 실행할 함수이다. alert('클릭'); } return ( <div> <button onClick={handleClick}>정상 예시</button> <button onClick={handleClick()}>잘못된 예시</button> </div> ); }두 번째 버튼은 화면이 그려지는 순간
handleClick()이 바로 실행된다.
그래서 사용자가 버튼을 클릭하기 전에alert가 먼저 뜰 수 있다.
이것은 대부분 의도한 동작이 아니다.
차이를 정리하면 다음과 같다.
onClick={handleClick}은 함수를 예약한다.onClick={handleClick()}은 함수를 즉시 실행한다.- 클릭 이벤트에서는 보통 함수 이름만 전달한다.
- 인자가 필요할 때는 화살표 함수로 감싸서 전달한다.
인자가 필요한 경우에는 다음처럼 작성한다.
// EventHandlerArgumentCorrectExample.jsx export default function EventHandlerArgumentCorrectExample() { function handleClick(id) { // 전달받은 id를 사용한다. alert(`${id}번 클릭`); } return ( <button onClick={() => handleClick(3)}> 클릭 </button> ); }// 버튼 클릭 결과 // 3번 클릭
onClick={() => handleClick(3)}은 클릭하기 전까지handleClick(3)을 실행하지 않는다.
버튼을 클릭했을 때 화살표 함수가 실행되고, 그 안에서handleClick(3)이 실행된다.
14-4. 인자가 필요한 이벤트 핸들러 작성 방식
만약 이벤트 핸들러에 인자를 전달해야 한다면, 바로
handleClick(id)처럼 쓰지 않고 화살표 함수를 한 번 감싸 주는 방식이 많이 쓰인다.// EventHandlerArgumentExample.jsx export default function EventHandlerArgumentExample() { function handleDelete(id) { alert(`${id}번 삭제`); } return ( <button onClick={() => handleDelete(3)}> 삭제 </button> ); }이 코드는 버튼을 클릭했을 때만
handleDelete(3)이 실행된다.
즉, 인자가 필요할 때는() => 함수(인자)형태를 자주 사용한다.
15. JSX 인라인 스타일 작성
15-1. style 속성은 문자열이 아니라 객체로 작성한다
JSX의style속성은 문자열이 아니라 객체를 사용한다.
그래서style="color:red"처럼 쓰는 것이 아니라style={{ color: 'red' }}처럼 쓴다.
이유는JSX가JavaScript안에서 동작하기 때문이다.
문자열 한 덩어리보다 객체 형태가 더 다루기 쉽고,JavaScript값과 연결하기도 쉽다.
15-2. 바깥 중괄호와 안쪽 중괄호의 차이
style={{ color: 'blue' }}에서 중괄호가 두 번 보인다.
이것도 처음에는 헷갈리기 쉽다.
- 바깥 중괄호는
JSX안에서JavaScript값을 넣겠다는 뜻이다.- 안쪽 중괄호는
JavaScript객체를 만드는 문법이다.즉, 바깥은 “값을 넣기 위한 문법”, 안쪽은 “객체 자체를 만드는 문법”이라고 이해하면 된다.
15-3. CSS 속성명을 camelCase로 작성하는 이유
style객체 안에서는font-size처럼 하이픈이 있는 이름을 그대로 쓰지 않는다.
fontSize,backgroundColor처럼camelCase로 작성한다.// InlineStyleExample.jsx export default function InlineStyleExample() { return ( <div style={{ color: 'purple', fontSize: '24px' }}> 스타일 적용 예시 </div> ); }// 화면 결과 // 스타일 적용 예시가 purple 색상, 24px 크기로 표시된다.
style={{ color: 'purple', fontSize: '24px' }}에서 바깥 중괄호는JSX안에JavaScript값을 넣겠다는 뜻이다.
안쪽 중괄호는JavaScript객체를 만드는 문법이다.
구조를 나누어 보면 다음과 같다.
style={...}는style속성에JavaScript값을 전달한다는 뜻이다.{ color: 'purple', fontSize: '24px' }는 스타일 정보를 담은 객체이다.fontSize처럼 작성하는 이유는JavaScript객체의 속성 이름으로 사용하기 때문이다.즉,
JSX에서 인라인 스타일은 문자열이 아니라 객체로 작성한다.
15-4. 실제 프로젝트에서 CSS 파일을 더 많이 사용하는 이유
인라인 스타일은 간단한 예시를 보여줄 때는 편하다.
하지만 실제 프로젝트에서는 스타일이 많아지고 재사용도 필요해진다.
그래서 대부분은CSS파일이나CSS Module,styled-components같은 방법을 더 많이 사용한다.
즉, 인라인 스타일은 구조 이해용으로는 좋지만, 실무에서는 모든 스타일을 이것만으로 처리하지는 않는다는 점도 함께 기억하면 좋다.
16. JSX 조건부 렌더링
16-1. 조건부 렌더링의 의미
조건부 렌더링은 말 그대로 조건에 따라 다른 화면을 보여주는 것이다.
예를 들어 로그인했을 때만 환영 문구를 보여주거나, 데이터가 없을 때 “데이터가 없습니다”를 보여주는 것이 모두 조건부 렌더링이다.
JSX에서는 조건에 따라 화면을 바꾸는 일이 아주 자주 나온다.
그래서 몇 가지 대표적인 패턴을 익혀 두는 것이 중요하다.
16-2. if문으로 먼저 반환하는 방식
가장 쉬운 방식은
return전에if문으로 분기하는 것이다.// ConditionalIfReturnExample.jsx export default function ConditionalIfReturnExample({ isLogin }) { if (isLogin) { // 로그인 상태이면 이 화면을 먼저 반환한다. return <h1>환영합니다.</h1>; } // 로그인 상태가 아니면 이 화면을 반환한다. return <h1>로그인이 필요합니다.</h1>; }// isLogin이 true일 때 출력결과 // 환영합니다. // isLogin이 false일 때 출력결과 // 로그인이 필요합니다.
if + return방식은 조건에 따라 화면 전체를 빠르게 나누고 싶을 때 사용하기 좋다.
조건이 복잡하거나 먼저 처리해야 하는 상태가 있을 때 읽기 쉽다.
16-3. 삼항 연산자로 둘 중 하나를 보여주는 방식
조건에 따라 둘 중 하나를 보여줄 때는 삼항 연산자를 자주 쓴다.
// ConditionalTernaryExample.jsx export default function ConditionalTernaryExample({ isLogin }) { return ( <div> {isLogin ? <h1>환영합니다.</h1> : <h1>로그인이 필요합니다.</h1>} </div> ); }// isLogin이 true일 때 출력결과 // 환영합니다. // isLogin이 false일 때 출력결과 // 로그인이 필요합니다.삼항 연산자는 둘 중 하나를 반드시 보여줘야 할 때 사용하기 좋다.
예를 들어 로그인 상태라면 환영 문구를 보여주고, 아니라면 로그인 안내 문구를 보여줄 수 있다.
16-4. && 연산자로 조건이 참일 때만 보여주는 방식
조건이 참일 때만 특정 요소를 보여주고, 거짓일 때는 아무것도 안 보여주고 싶다면
&&를 쓴다.// ConditionalAndExample.jsx export default function ConditionalAndExample({ isAdmin }) { return ( <div> <h1>메인 화면</h1> {isAdmin && <button>관리자 메뉴</button>} </div> ); }// isAdmin이 true일 때 출력결과 // 메인 화면 // 관리자 메뉴 // isAdmin이 false일 때 출력결과 // 메인 화면
isAdmin이true이면&&뒤의<button>관리자 메뉴</button>가 화면에 표시된다.
isAdmin이false이면&&뒤의 요소는 렌더링되지 않는다.
즉,&&는 “조건이 참일 때만 보여주기”에 적합하다.
거짓일 때 다른 문구를 보여줘야 한다면 삼항 연산자를 사용하는 것이 더 알맞다.
17. JSX 리스트 렌더링과 key
17-1. 배열 데이터를 화면에 반복 출력하는 방식
배열 데이터를 화면에 반복해서 보여줄 때는
map()을 많이 사용한다.
map()은 배열의 각 요소를 하나씩 꺼내 새로운 결과를 만드는 메서드이다.
React에서는 이 결과를JSX로 만들어 화면에 렌더링한다.// ListRenderingExample.jsx export default function ListRenderingExample() { const fruits = ['사과', '바나나', '포도']; return ( <ul> {fruits.map((fruit, index) => ( <li key={index}>{fruit}</li> ))} </ul> ); }// 출력결과 // 사과 // 바나나 // 포도이 코드는 배열의 각 과일 이름을
<li>로 바꿔 화면에 출력한다.
fruits배열에 값이 3개 있으므로<li>도 3개 만들어진다.
map()안의fruit는 배열에서 하나씩 꺼낸 값이다.
첫 번째 반복에서는fruit가사과이고, 두 번째 반복에서는바나나이고, 세 번째 반복에서는포도이다.
여기서는 과일 데이터에 고유한id가 없기 때문에 단순 예시로index를key에 사용했다.
하지만 실제 데이터에 고유한id가 있다면index보다id를key로 사용하는 것이 더 안전하다.
17-2. map을 사용하는 이유
반복되는 요소를 하나하나 직접 쓰면 데이터가 바뀔 때마다 코드를 계속 수정해야 한다.
하지만map()을 쓰면 배열 데이터만 바뀌어도 화면이 자동으로 같이 바뀐다.
즉,map()은 반복되는 화면을 데이터 중심으로 그리기 위해 쓰는 도구이다.
React에서는 이 방식이 아주 중요하다.
17-3. key가 필요한 이유
리스트 렌더링에서는 각 항목에
key를 넣어야 한다.
key는 각 요소를 구분하기 위한 고유한 식별자이다.
React는 화면이 바뀔 때 어떤 항목이 추가되었는지, 삭제되었는지, 순서가 바뀌었는지를 알아야 한다.
이때key가 있어야 각 항목을 제대로 구분할 수 있다.// ListKeyExample.jsx export default function ListKeyExample() { const fruits = [ { id: 101, name: '사과' }, { id: 102, name: '바나나' }, { id: 103, name: '포도' }, ]; return ( <ul> {fruits.map((fruit) => ( <li key={fruit.id}>{fruit.name}</li> ))} </ul> ); }// 출력결과 // 사과 // 바나나 // 포도출력결과에는
101,102,103이 보이지 않는다.
key는 화면에 보여주는 값이 아니라React가 내부적으로 항목을 구분하기 위해 사용하는 값이기 때문이다.
key는 사용자에게 보여주는 값이 아니라 반복 항목을 안정적으로 구분하기 위한 값이다.
17-4. index보다 고유 id를 key로 쓰는 이유
index를key로 쓰는 예시도 많다.
간단한 연습에서는 가능하지만, 항목 추가나 삭제, 순서 변경이 있는 리스트에서는 문제가 생길 수 있다.
왜냐하면index는 위치일 뿐, 항목 자체를 나타내는 고유 값이 아니기 때문이다.
순서가 바뀌면 같은 항목인데도 다른key처럼 보일 수 있다.
그래서 가능하면 데이터 자체가 가진 고유한id를key로 사용하는 것이 더 안전하다.
18. 컴포넌트 태그 문법과 렌더링 가능한 값
18-1. 소문자로 시작하면 HTML 태그로 인식한다
JSX에서 태그 이름이 소문자로 시작하면React는 기본HTML태그로 인식한다.
예를 들어<div>,<h1>,<button>은 모두 기본 태그이다.
즉, 소문자 시작은 브라우저가 이미 알고 있는 태그라고 보면 된다.
18-2. 대문자로 시작하면 React 컴포넌트로 인식한다
반대로 태그 이름이 대문자로 시작하면
React는 개발자가 만든 컴포넌트라고 인식한다.
예를 들어<MyCard />,<Header />,<LoginForm />은 모두 사용자 정의 컴포넌트이다.
이 규칙 덕분에React는 어떤 것이 기본 태그이고 어떤 것이 컴포넌트인지 구분할 수 있다.
18-3. JSX에서 렌더링 가능한 값
JSX안에서는 화면에 보여줄 수 있는 값들을 넣을 수 있다.
대표적으로 다음 값들이 렌더링 가능하다.
- 문자열
- 숫자
JSX- 배열 안에 들어 있는
JSX예를 들면 다음과 같다.
// RenderableValuesExample.jsx export default function RenderableValuesExample() { const title = '안녕하세요'; const count = 3; const items = [<li key="1">하나</li>, <li key="2">둘</li>]; return ( <div> <h1>{title}</h1> <p>{count}</p> <ul>{items}</ul> </div> ); }이 값들은 모두 화면에 반영된다.
18-4. true, false, null, undefined가 화면에 표시되지 않는 이유
true,false,null,undefined는JSX안에 넣어도 화면에 그대로 글자로 보이지 않는다.
이 값들은 “아무것도 렌더링하지 않음”에 가깝게 처리된다.
다음 예시를 보면 차이가 더 잘 보인다.// RenderableAndInvisibleValuesExample.jsx export default function RenderableAndInvisibleValuesExample() { return ( <div> <p>{'문자열'}</p> <p>{123}</p> <p>{0}</p> <p>{true}</p> <p>{false}</p> <p>{null}</p> <p>{undefined}</p> </div> ); }// 출력결과 // 문자열 // 123 // 0문자열과 숫자는 화면에 표시된다.
0도 숫자이기 때문에 화면에 표시된다.
하지만true,false,null,undefined는 화면에 글자로 표시되지 않는다.
이 특징 때문에{isShow && <p>보임</p>}같은 조건부 렌더링이 가능하다.
isShow가false이면false가 결과가 되지만,React는false를 화면에 표시하지 않는다.
그래서 아무것도 안 보이는 것처럼 동작한다.
19. JSX 주석과 자주 발생하는 오류
19-1. JSX 영역 안에서 주석을 쓰는 방식
JSX영역 안에서는 일반JavaScript한 줄 주석//를 그대로 쓸 수 없다.
JSX태그 사이에서는{/* ... */}형태를 사용해야 한다.// JsxCommentInsideExample.jsx export default function JsxCommentInsideExample() { return ( <div> {/* 제목 영역 */} <h1>안녕하세요</h1> </div> ); }즉,
JSX안에서는 중괄호와/* */를 함께 사용한다.
19-2. 일반 JavaScript 영역과 JSX 영역의 차이
컴포넌트 함수 안이라도
return바깥의 일반JavaScript영역에서는//주석을 사용할 수 있다.// JsxCommentOutsideExample.jsx export default function JsxCommentOutsideExample() { // 이 부분은 일반 JavaScript 영역이다. const title = '안녕하세요'; return ( <div> {/* 이 부분은 JSX 영역이다. */} <h1>{title}</h1> </div> ); }즉, 같은 파일 안에서도 지금 위치가 일반
JavaScript영역인지,JSX영역인지에 따라 주석 문법이 달라진다.
19-3. 자주 발생하는 JSX 오류 정리
JSX를 처음 배울 때 자주 만나는 오류들은 꽤 정해져 있다.
대표적으로는 다음과 같다.
- 최상위 요소를 하나로 감싸지 않음
class를 그대로 사용함- 태그를 닫지 않음
onClick={fn()}처럼 즉시 실행해 버림- 리스트 렌더링에서
key를 빼먹음
JSX에서 자주 나는 오류는 대부분 기본 규칙을 놓쳤을 때 발생한다.
이 오류들은 하나하나 외우기보다, 지금까지 정리한 기본 규칙을 함께 기억하면 자연스럽게 줄어든다.
19-4. JSX 기본 규칙 최종 정리
마지막으로
JSX에서 자주 쓰는 기본 규칙을 한 번에 다시 정리하면 다음과 같다.
- 반환값은 하나의 부모 요소로 감싼다.
- 여러 요소만 묶고 싶다면
Fragment를 사용할 수 있다.JSX안에서는 중괄호{}로JavaScript표현식을 넣는다.if문,for문은 중괄호 안에 직접 넣지 않는다.- 속성은
className,htmlFor,onClick처럼JSX문법에 맞게 작성한다.style은 객체로 작성한다.- 이벤트에는 함수 참조를 전달한다.
- 리스트 렌더링에서는
key가 필요하다.JSX안의 주석은{/* ... */}형식을 사용한다.
JSX의 기본 규칙은 하나의 부모 요소,Fragment, 태그 닫기, 대소문자 구분처럼 가장 기본 문법부터 시작한다.
중괄호 안에는
JavaScript표현식을 넣을 수 있고, 삼항 연산자,&&, 리스트 렌더링처럼 결과값을 만드는 방식으로 화면을 제어한다.
className, 인라인 스타일 객체, 동적 클래스처럼 스타일과 클래스 문법도JSX방식에 맞춰 써야 한다.
이벤트 처리에서는
camelCase, 함수 참조 전달, 이벤트 객체 사용, 기본 동작 막기 같은 규칙이 중요하다.
key,ref,htmlFor,defaultValue,dangerouslySetInnerHTML처럼 자주 등장하는 특수 속성도 함께 기억해 두면 좋다.
특히dangerouslySetInnerHTML은HTML문자열을 직접 삽입하는 속성이므로, 보안 문제가 생길 수 있어 초보 단계에서는 함부로 사용하지 않는 것이 좋다.
주석은
JSX안과 밖의 문법이 다르므로, 어느 영역인지 구분해서 작성해야 한다.
정리하면JSX는React에서 화면을 만드는 핵심 문법이며, 하나의 부모 요소, 중괄호 표현식,camelCase속성, 이벤트 함수 전달,key규칙만 제대로 잡아도 대부분의 기본 문법을 이해할 수 있다.
이 흐름이 잡히면 다음에 배우는props,state, 이벤트 처리, 조건부 렌더링, 리스트 렌더링도 훨씬 쉽게 이어진다.
React화면은 하나의 큰 코드 덩어리로 만드는 것이 아니라, 여러 개의 작은 화면 조각을 조립해서 만든다.
이 작은 화면 조각을component라고 한다.
component를 나누면 화면을 더 쉽게 관리할 수 있고, 같은 화면 조각을 여러 곳에서 다시 사용할 수 있다.
그리고 컴포넌트끼리 데이터를 주고받을 때는props를 사용한다.
props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 값이다.
이 흐름을 이해해야 나중에state, 이벤트 처리, 부모와 자식 컴포넌트의 데이터 전달 방향까지 자연스럽게 이어진다.
20. 컴포넌트의 의미
20-1. 컴포넌트는 화면을 구성하는 독립적인 조각이다
component는 화면을 구성하는 독립적인 조각이다.
여기서 독립적이라는 말은 각 조각이 자기 역할을 따로 가진다는 뜻이다.
예를 들어 하나의 게시글 목록 화면을 만든다고 생각해 보자.
이 화면을 한 파일에 전부 몰아서 만들 수도 있지만, 그렇게 하면 코드가 길어지고 관리가 어려워진다.
React에서는 화면을 작은 컴포넌트로 나누어 생각한다.
게시글 목록 화면은 다음처럼 나눌 수 있다.
Header는 상단 영역을 담당한다.LoginStatus는 로그인 상태 표시를 담당한다.PostList는 게시글 목록 전체를 담당한다.PostCard는 게시글 하나를 담당한다.Counter는 상태 연습 영역을 담당한다.이렇게 나누면 각 컴포넌트가 어떤 역할을 하는지 바로 보인다.
화면 전체를 한 번에 이해하는 것이 아니라, 작은 조각별로 나누어 이해할 수 있다.
React화면은 여러 컴포넌트를 트리 구조로 조립해서 만든다.
20-2. 화면을 Header, PostList, PostCard처럼 나누는 이유
화면을 컴포넌트로 나누는 이유는 역할을 분리하기 위해서이다.
역할이 분리되면 코드를 읽을 때 “이 부분은 무엇을 담당하는가”를 쉽게 파악할 수 있다.
예를 들어PostList안에 게시글 목록 전체 코드와 게시글 하나의 모양까지 모두 들어 있으면 코드가 복잡해진다.
하지만PostList는 목록 전체만 담당하고, 게시글 하나의 모양은PostCard가 담당하게 만들면 구조가 훨씬 깔끔해진다.
이 흐름을 게시글 화면에 적용하면 다음과 같다.
PostList는 게시글 데이터를 반복해서 보여주는 역할을 한다.PostCard는 게시글 하나의 제목, 내용, 작성자 같은 정보를 보여주는 역할을 한다.- 게시글이 여러 개라면
PostCard를 여러 번 사용하면 된다.즉,
PostList는 전체 목록의 틀이고,PostCard는 반복되는 게시글 한 칸이다.
이렇게 나누면 같은 모양이 반복되는 화면을 만들기 쉽다.
20-3. 컴포넌트로 나누면 재사용과 유지보수가 쉬워지는 이유
컴포넌트로 나누면 같은 화면 조각을 여러 곳에서 다시 사용할 수 있다.
이것을 재사용이라고 한다.
예를 들어 게시글 카드 디자인을PostCard컴포넌트로 만들어 두면, 메인 화면에서도 쓰고 마이페이지에서도 쓸 수 있다.
같은 코드를 여러 번 복사해서 붙이는 것이 아니라, 같은 컴포넌트를 가져와 사용하면 된다.
유지보수도 쉬워진다.
유지보수는 코드를 수정하거나 고치는 작업이다.
게시글 카드 디자인을 바꾸고 싶을 때, 여러 파일을 전부 수정할 필요 없이PostCard컴포넌트만 수정하면 된다.
정리하면 컴포넌트를 나누는 이유는 다음과 같다.
- 화면을 역할별로 나눌 수 있다.
- 같은 화면 조각을 재사용할 수 있다.
- 코드가 길어지는 것을 막을 수 있다.
- 문제가 생겼을 때 수정할 위치를 찾기 쉽다.
- 화면 구조를 트리 형태로 이해할 수 있다.
React에서 컴포넌트는 화면을 작게 나누어 조립하기 위한 기본 단위이다.
이 개념이 잡혀야 뒤에서 배우는props와state도 자연스럽게 이해된다.
21. 함수형 컴포넌트
21-1. 함수형 컴포넌트의 기본 형태
함수형 컴포넌트는 함수로 만든
React컴포넌트이다.
함수는 어떤 작업을 묶어 놓은 코드이고, 함수형 컴포넌트는 화면에 보여줄JSX를 반환한다.
기본 형태는 다음과 같다.// WelcomeComponentExample.jsx function Welcome() { // 화면에 보여줄 JSX를 반환한다. return <h1>Hello, React</h1>; } export default Welcome;이 코드에서
Welcome은 컴포넌트 이름이다.
return <h1>Hello, React</h1>은 이 컴포넌트가 화면에 보여줄 내용을 의미한다.
즉, 함수형 컴포넌트는 화면을 반환하는 함수라고 이해하면 된다.
21-2. 컴포넌트 이름은 대문자로 시작한다
React컴포넌트 이름은 대문자로 시작해야 한다.
React는 태그 이름이 소문자로 시작하면 기본HTML태그로 인식하고, 대문자로 시작하면 사용자가 만든 컴포넌트로 인식한다.
예를 들어<div>는 기본HTML태그이다.
반면<Welcome />은 사용자가 만든React컴포넌트이다.
따라서 컴포넌트 이름은 다음처럼 작성한다.
WelcomePostCardLoginStatusMyButton이런 이름 작성 방식을
PascalCase라고 한다.
PascalCase는 단어의 첫 글자를 대문자로 쓰는 방식이다.
21-3. JSX를 반환하는 함수가 화면 조각이 되는 흐름
함수형 컴포넌트는 단순한 함수처럼 보이지만,
React에서는 이 함수가 화면 조각이 된다.
그 이유는 함수가JSX를 반환하기 때문이다.
예를 들어 다음처럼 사용할 수 있다.// AppComponentUsageExample.jsx function Welcome() { // 제목 화면 조각을 반환한다. return <h1>Hello, React</h1>; } export default function App() { // Welcome 컴포넌트를 태그처럼 사용한다. return ( <div> <Welcome /> </div> ); }
Welcome함수는<h1>Hello, React</h1>를 반환한다.
App컴포넌트는<Welcome />을 사용해서Welcome컴포넌트를 화면에 포함한다.
중요한 점은Welcome()처럼 함수를 직접 호출하지 않는다는 것이다.
React컴포넌트는<Welcome />처럼 태그 형태로 사용한다.
이렇게 작성하면React가 컴포넌트를 실행하고 반환된JSX를 화면에 그린다.
22. props는 부모가 자식에게 전달하는 값
22-1. props의 의미
props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 값이다.
properties의 줄임말이며, 컴포넌트 태그의 속성처럼 작성해서 값을 넘긴다.
쉽게 말하면props는 컴포넌트 사이에서 데이터를 전달하는 통로이다.
부모가 자식에게 “이 값을 가지고 화면을 만들어라”라고 넘겨주는 방식이다.
예를 들어Welcome컴포넌트에게 이름을 전달하고 싶다면 다음처럼 작성한다.// PropsBasicExample.jsx function Welcome({ name }) { // 전달받은 name 값을 화면에 출력한다. return <h1>안녕? {name}</h1>; } export default function App() { // Welcome 컴포넌트에 name props를 전달한다. return <Welcome name="React" />; }
App은 부모 컴포넌트이고,Welcome은 자식 컴포넌트이다.
name="React"는 부모가 자식에게 전달한props이다.
자식 컴포넌트는{ name }으로 값을 받아 화면에 출력한다.
props는 부모 컴포넌트가 자식 컴포넌트에게 값을 전달하는 통로이다.
22-2. 부모 컴포넌트와 자식 컴포넌트 관계
부모 컴포넌트는 다른 컴포넌트를 사용하는 컴포넌트이다.
자식 컴포넌트는 부모 컴포넌트 안에서 사용되는 컴포넌트이다.
위 예시에서는App이Welcome을 사용하고 있다.
따라서App이 부모이고,Welcome이 자식이다.
이 관계에서 데이터는 보통 부모에서 자식으로 내려간다.
즉, 부모가 값을 정하고 자식은 그 값을 받아 화면에 사용한다.
정리하면 다음과 같다.
- 부모 컴포넌트는 자식 컴포넌트를 사용한다.
- 부모 컴포넌트는 자식에게
props를 전달할 수 있다.- 자식 컴포넌트는 전달받은
props를 사용해 화면을 만든다.- 기본 흐름은 부모에서 자식 방향이다.
이 흐름을 알아야 나중에
state를 어디에 두고, 값을 어떻게 내려보낼지 이해할 수 있다.
22-3. JSX 속성으로 props를 전달하는 방식
props는JSX태그의 속성처럼 작성한다.
기본HTML에서<input type="text">처럼 속성을 쓰는 것과 비슷하게 보이지만, 여기서는 컴포넌트에 값을 전달하는 역할을 한다.
예시는 다음과 같다.// PropsAttributeExample.jsx function Welcome({ name, color }) { // 전달받은 color 값을 style에 사용한다. return <h1 style={{ color }}>안녕, {name}!</h1>; } export default function App() { // name과 color props를 전달한다. return <Welcome name="철수" color="blue" />; }
<Welcome name="철수" color="blue" />에서name과color가props이다.
Welcome컴포넌트는{ name, color }로 값을 받아 화면에 사용한다.
부모 컴포넌트가
name과color값을 전달하면, 자식 컴포넌트는 전달받은 값을 화면과 스타일에 사용할 수 있다.
22-4. 문자열, 숫자, 객체, 배열, 함수 props 전달 방식
props로는 문자열만 전달할 수 있는 것이 아니다.
숫자, 객체, 배열, 함수도 전달할 수 있다.
다만 문자열이 아닌 값은 중괄호{}를 사용해서 전달해야 한다.
중괄호는JSX안에서JavaScript값을 넣겠다는 뜻이다.
대표적인 전달 방식은 다음과 같다.// VariousPropsExample.jsx function CharacterCard({ name, age, friends, character, onClick }) { // 전달받은 props를 화면에 사용한다. return ( <div> <h2>{name}</h2> <p>나이: {age}</p> <p>친구 수: {friends.length}</p> <p>역할: {character.role}</p> <button onClick={onClick}>확인</button> </div> ); } export default function App() { const friends = ['또치', '도우너', '마이콜']; const character = { name: '둘리', age: 10, role: '아기공룡' }; return ( <CharacterCard name="둘리" age={10} friends={friends} character={character} onClick={() => alert('클릭됨')} /> ); }이 예시에서
name="둘리"는 문자열 전달이다.
age={10}은 숫자 전달이다.
friends={friends}는 배열 전달이다.
character={character}는 객체 전달이다.
onClick={() => alert('클릭됨')}은 함수 전달이다.
즉,props는 컴포넌트에 다양한 종류의 데이터를 전달할 수 있다.
22-5. 문자열 외에는 중괄호를 사용해야 하는 이유
JSX에서 따옴표로 전달한 값은 문자열로 전달된다.
반면 중괄호로 전달한 값은JavaScript값으로 전달된다.
예를 들어 다음 두 코드는 겉으로 비슷해 보이지만 의미가 다르다.// PropsStringAndNumberExample.jsx function CharacterCard({ age }) { // age 값과 자료형을 확인한다. return <p>{typeof age}: {age}</p>; } export default function App() { return ( <div> <CharacterCard age={10} /> <CharacterCard age="10" /> </div> ); }
age={10}은 숫자10을 전달한다.
age="10"은 문자열"10"을 전달한다.
화면에는 둘 다10처럼 보일 수 있지만, 실제 데이터 타입은 다르다.
숫자로 계산해야 하는 값이라면 반드시 중괄호로 전달해야 한다.
정리하면 다음과 같다.
- 문자열은 따옴표로 전달할 수 있다.
- 숫자는 중괄호로 전달한다.
- 객체는 중괄호로 전달한다.
- 배열은 중괄호로 전달한다.
- 함수도 중괄호로 전달한다.
이 차이를 모르고 작성하면 화면에는 비슷해 보여도 계산이나 조건 처리에서 예상과 다른 결과가 나올 수 있다.
23. props 객체와 구조 분해 할당
23-1. props 객체로 받는 방식
부모가 자식 컴포넌트에 전달한 값들은 자식 컴포넌트에서
props객체로 받을 수 있다.
객체는 여러 값을 이름표와 함께 묶어 놓은 데이터이다.
예를 들어 부모가name값을 전달하면, 자식 컴포넌트의props.name으로 접근할 수 있다.// PropsObjectExample.jsx function UserCard(props) { // props 객체에서 name 값을 꺼내 사용한다. return <p>{props.name}</p>; } export default function App() { // UserCard 컴포넌트에 name 값을 전달한다. return <UserCard name="언덕이" />; }여기서
props는{ name: '언덕이' }와 비슷한 객체라고 생각하면 된다.
23-2. 구조 분해 할당으로 필요한 값만 받는 방식
props.name처럼 매번 객체 이름을 붙여도 되지만, 값이 많아지면 코드가 길어진다.
그래서React에서는 구조 분해 할당을 자주 사용한다.
구조 분해 할당은 객체에서 필요한 값만 바로 꺼내 변수처럼 사용하는 문법이다.// PropsDestructuringExample.jsx function UserCard({ name }) { // 구조 분해 할당으로 name 값을 바로 받는다. return <p>{name}</p>; } export default function App() { // UserCard 컴포넌트에 name 값을 전달한다. return <UserCard name="언덕이" />; }이렇게 작성하면
props.name대신name을 바로 사용할 수 있다.
23-3. 두 방식이 같은 의미인 이유
아래 두 코드는 같은 의미이다.
// PropsObjectAndDestructuringCompare.jsx function UserCard1(props) { // props 객체에서 name 속성을 꺼내 사용한다. return <p>{props.name}</p>; } function UserCard2({ name }) { // props 객체에서 name 속성만 바로 꺼내 사용한다. return <p>{name}</p>; }
UserCard1은props객체 전체를 받은 뒤props.name으로 접근한다.
UserCard2는 처음부터props객체 안의name만 꺼내서 받는다.
즉, 두 방식 모두 부모에게 전달받은name값을 사용하는 코드이다.
차이는 값을 꺼내는 모양뿐이다.
23-4. JavaScript 기반 React에서 구조 분해 할당을 자주 쓰는 이유
JavaScript기반React에서는 구조 분해 할당을 자주 사용한다.
그 이유는 코드가 짧아지고, 컴포넌트가 어떤props를 사용하는지 함수 선언부에서 바로 보이기 때문이다.
예를 들어function Profile({ name, age, email })이라고 쓰면 이 컴포넌트가name,age,
반면function Profile(props)라고만 쓰면 내부 코드를 읽어 봐야 어떤 값을 쓰는지 알 수 있다.
정리하면 구조 분해 할당을 쓰는 이유는 다음과 같다.
- 코드가 짧아진다.
- 어떤
props를 받는지 바로 보인다.props.name처럼 반복해서 쓰지 않아도 된다.- 여러 값을 받을 때 가독성이 좋아진다.
다만 구조 분해 할당을 쓰려면 부모가 전달한 이름과 자식이 받는 이름이 같아야 한다.
부모가name으로 전달했으면 자식도{ name }으로 받아야 한다.
24. props는 읽기 전용이다
24-1. 자식 컴포넌트가 props를 직접 수정할 수 없는 이유
props는 부모 컴포넌트가 자식 컴포넌트에게 내려주는 값이다.
자식 컴포넌트는 이 값을 받아서 사용할 수 있지만, 직접 수정해서는 안 된다.
이유는 데이터의 흐름을 예측 가능하게 만들기 위해서이다.
부모가 내려준 값을 자식이 마음대로 바꾸면, 어느 컴포넌트에서 데이터가 바뀌었는지 추적하기 어려워진다.
그래서React에서는props를 읽기 전용으로 다룬다.
읽기 전용은 값을 읽을 수는 있지만 직접 바꿀 수는 없다는 뜻이다.
잘못된 흐름은 다음과 같다.// WrongPropsModifyExample.jsx function Welcome(props) { // 부모가 내려준 props 객체를 직접 바꾸려고 하면 안 된다. props.name = '수정된 이름'; return <h1>{props.name}</h1>; }이런 코드는 자식이 부모가 내려준 값을 직접 고치려는 흐름이므로 피해야 한다.
전달받은 값을 다른 형태로 보여주고 싶다면 새 변수로 가공하고, 실제 데이터 변경은state로 처리해야 한다.
24-2. 데이터 변경이 필요할 때 부모 state를 바꾸는 방식
자식 컴포넌트에서 어떤 값을 바꾸고 싶다면, 자식이
props를 직접 수정하는 것이 아니다.
보통 부모 컴포넌트가state를 가지고 있고, 자식에게 변경 요청 함수를props로 내려준다.
쉽게 말하면 자식은 “값 바꿔줘”라고 요청하고, 실제 값 변경은 부모가 한다.
이렇게 하면 데이터가 어디에서 바뀌는지 추적하기 쉽다.
흐름은 다음과 같다.
- 부모 컴포넌트가
state를 가진다.- 부모가 자식에게 현재 값을
props로 내려준다.- 부모가 자식에게 값을 바꾸는 함수도
props로 내려준다.- 자식은 그 함수를 호출해 변경을 요청한다.
- 실제
state변경은 부모에서 일어난다.이 흐름은 나중에
state와 이벤트 처리를 배울 때 더 자세히 다룬다.
지금은props를 자식이 직접 고치지 않는다는 점을 먼저 기억하면 된다.
24-3. props와 state의 기본 차이
props와state는 둘 다 화면에 영향을 줄 수 있는 데이터이다.
하지만 관리 주체와 변경 가능 여부가 다르다.
props는 부모가 자식에게 내려주는 값이다.
자식 입장에서는 읽기 전용이다.
반면state는 컴포넌트가 직접 관리하는 값이다.
state는 변경 함수로 바꿀 수 있고, 값이 바뀌면 화면도 다시 렌더링된다.
props는 부모에서 자식으로 내려가는 데이터이고,state는 컴포넌트가 직접 관리하는 변경 가능한 데이터이다.
차이를 정리하면 다음과 같다.
props는 부모 컴포넌트가 내려주는 값이다.props는 자식 컴포넌트에서 읽기 전용이다.state는 현재 컴포넌트가 직접 관리하는 값이다.state는 변경 함수를 통해 바꿀 수 있다.props는 데이터 전달용이고,state는 컴포넌트 내부 데이터 관리용이다.
props와state의 가장 큰 차이는 누가 관리하고 누가 바꿀 수 있느냐이다.
props는 부모가 관리하고,state는 현재 컴포넌트가 관리한다.
25. children props
25-1. children props의 의미
children은 컴포넌트 태그 사이에 작성한 내용을 전달받는 특별한props이다.
일반props는 태그의 속성처럼 전달하지만,children은 태그의 시작과 끝 사이에 들어간 내용이 전달된다.
예를 들어 다음 구조를 보자.// ChildrenBasicExample.jsx function Panel({ children }) { // 태그 사이에 들어온 내용을 그대로 출력한다. return <section className="panel">{children}</section>; } export default function App() { return ( <Panel> <h2>공지사항</h2> <p>React 수업을 시작한다.</p> </Panel> ); }
<Panel>과</Panel>사이에 있는<h2>와<p>가children으로 전달된다.
Panel컴포넌트는{children}위치에 그 내용을 그대로 보여준다.
컴포넌트 태그 사이에 작성한 내용은
children이라는 특별한props로 전달되고, 자식 컴포넌트는{children}위치에 그 내용을 출력한다.
25-2. 컴포넌트 태그 사이의 내용이 전달되는 방식
children은 일반 속성처럼 직접 이름을 적어서 전달하지 않아도 된다.
컴포넌트 태그 사이에 내용을 넣으면 자동으로children에 담긴다.
예를 들어 다음 두 부분이 연결된다.
- 부모는
<Panel>내용</Panel>처럼 태그 사이에 내용을 넣는다.- 자식은
function Panel({ children })처럼children을 받는다.- 자식은
{children}위치에 전달받은 내용을 출력한다.이 방식은 컴포넌트 안에 어떤 내용이 들어올지 미리 고정하지 않아도 될 때 유용하다.
부모가 태그 사이에 넣는 내용에 따라Panel안의 내용이 달라진다.
25-3. 버튼, 카드, 레이아웃 컴포넌트에서 children을 사용하는 이유
children은 버튼, 카드, 레이아웃 컴포넌트를 만들 때 자주 사용한다.
이런 컴포넌트는 겉모양은 같지만 안에 들어가는 내용이 매번 달라질 수 있기 때문이다.
예를 들어 카드 컴포넌트를 생각해 보자.
카드의 테두리와 여백은 같게 유지하고, 카드 안의 제목과 본문은 상황에 따라 다르게 넣고 싶을 수 있다.
이럴 때children을 사용하면 좋다.
예시는 다음과 같다.// CardChildrenExample.jsx function Card({ children }) { // 카드 공통 틀을 만들고 내부 내용은 children으로 받는다. return <div className="card">{children}</div>; } export default function App() { return ( <Card> <h2>회원 정보</h2> <p>이름: 언덕이</p> </Card> ); }이 구조에서는
Card가 카드의 공통 틀을 담당한다.
안에 들어갈 실제 내용은 부모가 태그 사이에 작성한다.
정리하면children을 사용하는 이유는 다음과 같다.
- 공통 레이아웃을 재사용할 수 있다.
- 내부 내용은 부모가 자유롭게 넣을 수 있다.
- 버튼, 카드, 모달, 패널 같은 컴포넌트를 만들기 좋다.
- 컴포넌트를 더 유연하게 사용할 수 있다.
props는 문자열, 숫자, 불리언, 객체, 함수,children처럼 다양한 형태로 전달할 수 있다.
정리하면props는 부모가 자식에게 값을 전달하는 방법이고,children은 컴포넌트 태그 사이의 내용을 전달하는 특별한props이다.
컴포넌트를 나누고props로 값을 전달하는 흐름을 이해하면,React화면을 작은 조각으로 나누어 조립하는 방식이 훨씬 자연스럽게 보인다.
props는 부모 컴포넌트가 자식 컴포넌트에게 내려주는 값이다.
반면state는 컴포넌트 내부에서 직접 관리하는 변경 가능한 데이터이다.
React화면은 데이터에 따라 바뀐다.
그래서 버튼을 클릭했을 때 숫자가 증가하거나, 입력창에 글자를 입력했을 때 화면 값이 바뀌려면state를 사용해야 한다.
state는 화면을 다시 그리게 만드는 기준 데이터이다.
state가 바뀌면React는 컴포넌트를 다시 실행하고, 바뀐 값을 기준으로 화면을 다시 계산한다.
26. State와 화면 리렌더링
26-1. State의 의미
state는 컴포넌트 내부에서 가지고 있는 변경 가능한 데이터 공간이다.
여기서 상태는 현재 화면이 어떤 값을 기준으로 보여지고 있는지를 의미한다.
예를 들어 카운터 화면을 생각해 보자.
처음 숫자가0으로 보이고, 버튼을 누를 때마다1,2,3으로 바뀐다.
이때 현재 숫자 값이 바로state가 될 수 있다.
state가 필요한 상황은 다음과 같다.
- 버튼 클릭으로 숫자가 바뀌어야 할 때
- 입력창에 입력한 값을 화면에서 관리해야 할 때
- 서버에서 받아온 게시글 목록을 화면에 보여줘야 할 때
- 로그인 여부에 따라 화면을 다르게 보여줘야 할 때
- 체크박스 선택 여부를 화면에 반영해야 할 때
이런 값들은 화면에 영향을 준다.
그래서 일반 변수보다state로 관리하는 것이 적절하다.
26-2. State가 바뀌면 화면이 다시 렌더링되는 이유
React는state값이 바뀌면 화면을 다시 렌더링한다.
렌더링은 화면에 보일 구조를 다시 계산해서 그리는 작업이다.
중요한 점은 개발자가 직접DOM을 찾아서 글자를 바꾸는 방식이 아니라는 것이다.
React에서는state를 바꾸면,React가 바뀐 값을 기준으로 화면을 다시 계산한다.
흐름은 다음과 같다.
- 사용자가 버튼을 클릭하거나 입력창에 값을 입력한다.
- 이벤트 핸들러가 실행된다.
setState역할을 하는 상태 변경 함수가 호출된다.state변경이 요청된다.React가 컴포넌트를 다시 실행한다.- 바뀐
state값을 기준으로 새 화면을 계산한다.- 브라우저 화면에서 필요한 부분이 갱신된다.
React에서는DOM을 직접 바꾸기보다state를 바꾸고,React가 변경된state를 기준으로 화면을 다시 그리게 한다.
26-3. 일반 변수와 State의 차이
일반 변수도 값을 저장할 수 있다.
하지만 일반 변수를 바꾼다고 해서React가 화면을 다시 렌더링하지는 않는다.
반면state는React가 감시하는 값이다.
정확히는state변경 함수를 통해 변경을 요청하면,React가 그 변경을 인식하고 화면을 다시 그린다.
차이를 정리하면 다음과 같다.
- 일반 변수는 값을 저장할 수 있지만 화면 갱신을 자동으로 일으키지 않는다.
state는 값이 바뀌면 화면 렌더링 흐름과 연결된다.- 화면에 보여지는 값이 바뀌어야 한다면
state로 관리하는 것이 적절하다.state는 직접 대입으로 바꾸지 않고 변경 함수를 사용한다.예를 들어
count = count + 1처럼 일반 변수만 바꾸면React가 화면을 다시 그려야 한다고 알기 어렵다.
하지만setCount(count + 1)처럼 상태 변경 함수를 호출하면React가 변경을 인식한다.
화면에 영향을 주는 변경 가능한 값은 일반 변수보다state로 관리해야 한다.
27. useState() 기본 문법
27-1. useState()의 의미
useState()는 컴포넌트 안에state저장 공간을 만드는Hook이다.
Hook은 함수형 컴포넌트에서React의 주요 기능을 사용할 수 있게 해주는 특별한 함수이다.
useState()를 사용하면 현재 상태 값과 그 값을 바꾸는 함수를 함께 받을 수 있다.
보통 배열 구조 분해 할당으로 다음처럼 작성한다.// UseStateBasicSyntaxExample.jsx import { useState } from 'react'; export default function UseStateBasicSyntaxExample() { // count는 현재 상태 값이고, setCount는 count를 바꾸는 함수이다. const [count, setCount] = useState(0); return ( <section> <p>현재 값: {count}</p> <button onClick={() => setCount(count + 1)}>증가</button> </section> ); }
useState(0)에서0은 초기값이다.
초기값은 화면이 처음 렌더링될 때state에 들어가는 값이다.
count는 현재 상태 값을 읽을 때 사용하는 상수이다.
setCount는count값을 바꾸도록React에게 요청하는 함수이다.
27-2. 현재 값과 값을 바꾸는 함수
useState()는 배열을 반환한다.
그 배열에는 현재 값과 값을 바꾸는 함수가 들어 있다.
그래서 실제 코드에서는 보통 다음처럼 작성한다.// UseStateReturnExample.jsx import { useState } from 'react'; export default function UseStateReturnExample() { // count는 현재 값이고, setCount는 count를 바꾸는 함수이다. const [count, setCount] = useState(0); return ( <section> <p>현재 값: {count}</p> <button onClick={() => setCount(count + 1)}>증가</button> </section> ); }
useState(0)에서0은 초기값이다.
count는 현재 상태 값을 읽을 때 사용한다.
setCount는 상태 값을 바꾸도록React에게 요청할 때 사용한다.
실제 코드에서는 보통 값 이름과 함수 이름을 맞춰서 작성한다.
예를 들어count라는 값을 만들면 변경 함수 이름은setCount로 작성한다.
예시는 다음과 같다.
const [count, setCount] = useState(0)const [name, setName] = useState('')const [posts, setPosts] = useState([])const [user, setUser] = useState({})이름이 완전히 정해진 문법은 아니다.
하지만값 이름과set값이름형태로 맞추면 코드 의미가 바로 보인다.// 출력결과 // 현재 값: 0
27-3. count와 setCount의 역할
count와setCount는 역할이 다르다.
count는 현재 상태 값을 읽는 데 사용한다.
setCount는 상태 값을 바꾸도록 요청하는 데 사용한다.
예를 들어 다음 코드를 보자.// CounterExample.jsx import { useState } from 'react'; export default function CounterExample() { // count의 초기값을 0으로 설정한다. const [count, setCount] = useState(0); return ( <section> <p>현재 값: {count}</p> <button onClick={() => setCount(count + 1)}>증가</button> </section> ); }처음 화면에는
현재 값: 0이 표시된다.
버튼을 클릭하면setCount(count + 1)이 실행된다.
그러면count값이1로 바뀌고,React가 화면을 다시 렌더링한다.
다시 버튼을 누르면 현재count값에1을 더해 다음 값으로 바뀐다.
이렇게state를 사용하면 버튼 클릭에 따라 화면이 자동으로 갱신된다.
useState()는 현재 상태 값과 상태 값을 변경하는 함수를 함께 반환하며, 상태 변경 함수가 호출되면 화면이 다시 렌더링된다.
27-4. setCount(count + 1)이 화면을 바꾸는 흐름
setCount(count + 1)은 단순히 변수 값을 바꾸는 코드가 아니다.
React에게 “상태 값을 새 값으로 바꿔 달라”고 요청하는 코드이다.
흐름은 다음과 같다.
- 사용자가 증가 버튼을 클릭한다.
onClick에 연결된 화살표 함수가 실행된다.setCount(count + 1)이 호출된다.React가count값을 새 값으로 변경한다.- 컴포넌트가 다시 실행된다.
- 새
count값이{count}위치에 출력된다.이 흐름 때문에 개발자가 직접 화면의 글자를 찾아 바꾸지 않아도 된다.
state를 바꾸면React가 알아서 화면을 다시 계산한다.
useState()의 핵심은 값을 저장하는 것에서 끝나지 않고, 값 변경과 화면 갱신을 연결한다는 점이다.
28. 배열과 객체 state 변경 방식
28-1. 배열이나 객체 state를 직접 수정하면 안 되는 이유
state에 저장된 값이 배열이나 객체일 때는 기존 배열이나 객체를 직접 수정하지 않는 것이 중요하다.
React는 상태가 바뀌었는지 판단할 때 새 값이 전달되었는지를 기준으로 삼는다.
그래서 기존 배열이나 객체를 직접 고쳐 쓰면 변경을 제대로 감지하기 어렵다.
이 단계에서는 기존 값을 직접 고치지 말고 새로운 배열이나 새로운 객체를 만들어 전달한다고 이해하면 된다.
잘못된 예시는 다음과 같다.// WrongArrayStateUpdateExample.jsx import { useState } from 'react'; export default function WrongArrayStateUpdateExample() { const [posts, setPosts] = useState([]); function addPost() { // 기존 배열을 직접 수정하는 방식이라 피해야 한다. posts.push({ id: 1, title: '새 글' }); setPosts(posts); } return <button onClick={addPost}>추가</button>; }
posts.push()는 기존 배열 자체를 직접 바꾼다.
이런 방식은React에서 상태 변경 흐름을 불안정하게 만들 수 있다.
28-2. 스프레드 연산자로 새 배열 만들기
배열
state를 변경할 때는 기존 배열을 직접 수정하지 않고 새 배열을 만들어 전달한다.
이때 자주 사용하는 문법이 스프레드 연산자이다.
스프레드 연산자...는 배열이나 객체 안의 값을 펼쳐서 복사할 때 사용한다.
예시는 다음과 같다.// ArrayStateUpdateExample.jsx import { useState } from 'react'; export default function ArrayStateUpdateExample() { const [posts, setPosts] = useState([]); function addPost() { const newPost = { id: 1, title: '새 글' }; // 새 글과 기존 글 목록을 합쳐 새 배열을 만든다. setPosts([newPost, ...posts]); } return <button onClick={addPost}>게시글 추가</button>; }
[newPost, ...posts]는 새 배열이다.
기존posts배열을 직접 고치는 것이 아니라, 새 게시글과 기존 게시글을 합쳐 새로운 배열을 만든다.
이렇게 만든 새 배열을setPosts()에 전달하면React가 상태 변경으로 인식하기 쉽다.
28-3. 스프레드 연산자로 새 객체 만들기
객체
state도 마찬가지이다.
기존 객체의 속성을 직접 바꾸기보다 새 객체를 만들어 전달한다.
예시는 다음과 같다.// ObjectStateUpdateExample.jsx import { useState } from 'react'; export default function ObjectStateUpdateExample() { const [user, setUser] = useState({ name: '둘리', age: 10 }); function addAddress() { // 기존 user 값을 복사하고 addr 속성을 추가한 새 객체를 만든다. setUser({ ...user, addr: '쌍문동' }); } return ( <section> <p>이름: {user.name}</p> <p>나이: {user.age}</p> <p>주소: {user.addr}</p> <button onClick={addAddress}>주소 추가</button> </section> ); }
{ ...user, addr: '쌍문동' }은 기존user객체를 복사한 뒤addr속성을 추가한 새 객체이다.
기존 객체를 직접 수정하지 않고 새 객체를 만들어setUser()에 전달한다.
28-4. 새로운 복사본을 전달해야 React가 변경을 인식하는 흐름
배열과 객체
state를 다룰 때 핵심은 새 복사본이다.
기존 값을 직접 고쳐 쓰면 변경 흐름이 불분명해진다.
반대로 새 배열이나 새 객체를 만들어 전달하면React가 상태 변경을 더 명확하게 인식할 수 있다.
정리하면 다음과 같다.
- 배열에 값을 추가할 때는 새 배열을 만들어
setState에 전달한다.- 객체 속성을 변경할 때는 새 객체를 만들어
setState에 전달한다.- 기존 배열이나 객체를 직접 수정하지 않는다.
- 스프레드 연산자
...를 사용하면 기존 값을 복사하기 쉽다.
React에게 변화를 알리고 싶다면 기존 것을 고쳐 쓰지 말고 새로운 복사본을 전달해야 한다.
이 원칙은 배열과 객체state를 다룰 때 아주 중요하다.
29. 이벤트 처리와 입력값 관리
29-1. 이벤트 처리의 기본 흐름
이벤트는 사용자가 화면에서 일으키는 동작이다.
예를 들어 클릭, 입력, 제출 같은 동작이 이벤트이다.
React에서는 이벤트 속성을camelCase로 작성하고, 실행할 함수를 전달한다.
클릭 이벤트는onClick, 입력 변경 이벤트는onChange, 폼 제출 이벤트는onSubmit으로 작성한다.
기본 예시는 다음과 같다.// LoginButtonEventExample.jsx export default function LoginButtonEventExample() { function handleLogin() { // 버튼 클릭 시 실행된다. alert('로그인 버튼 클릭'); } return <button onClick={handleLogin}>로그인</button>; }
onClick={handleLogin}은 버튼을 클릭했을 때handleLogin함수를 실행하라는 뜻이다.
여기서handleLogin()처럼 괄호를 붙이면 렌더링 순간 바로 실행될 수 있으므로 주의해야 한다.
29-2. onChange로 입력값을 읽는 방식
입력 상자의 값이 바뀔 때는
onChange이벤트를 사용한다.
사용자가 글자를 입력할 때마다onChange가 실행되고, 이벤트 객체를 통해 현재 입력값을 읽을 수 있다.
입력값은event.target.value로 꺼낸다.
여기서event는 이벤트 정보를 담은 객체이고,target은 이벤트가 발생한 요소이다.
입력창에서 이벤트가 발생했으므로event.target.value는 입력창의 현재 값을 의미한다.
예시는 다음과 같다.// InputChangeExample.jsx export default function InputChangeExample() { function handleChange(event) { // 입력창의 현재 값을 확인한다. console.log(event.target.value); } return <input onChange={handleChange} placeholder="검색어를 입력하세요" />; }이 코드는 사용자가 입력할 때마다 현재 입력값을 콘솔에 출력한다.
하지만 이 값은 아직React state로 관리되는 값은 아니다.
29-3. Controlled Input의 의미
Controlled Input은 입력값을React state가 관리하는 입력 요소를 의미한다.
즉, 입력창의 현재 값이state에 저장되고, 화면의 입력창 값도 그state를 기준으로 표시된다.
예시는 다음과 같다.// ControlledInputExample.jsx import { useState } from 'react'; export default function ControlledInputExample() { // keyword 상태에 입력값을 저장한다. const [keyword, setKeyword] = useState(''); return ( <input value={keyword} onChange={(event) => setKeyword(event.target.value)} placeholder="검색어를 입력하세요" /> ); }
value={keyword}는 입력창에 표시할 값을keyword상태로 정한다.
onChange={(event) => setKeyword(event.target.value)}는 사용자가 입력할 때마다 새 입력값을keyword상태에 저장한다.
즉, 사용자가 입력하면 입력값이state에 저장되고, 그state값이 다시 입력창에 표시된다.
29-4. 입력값을 state에 저장하는 이유
입력값을
state에 저장하면 화면과 데이터 흐름을React가 관리할 수 있다.
사용자가 입력한 값을 검색 요청에 사용하거나, 로그인 요청에 사용하거나, 유효성 검사에 사용할 수 있다.
입력값을state로 관리하면 다음 작업이 쉬워진다.
- 현재 입력값을 화면에 표시할 수 있다.
- 입력값이 비었는지 검사할 수 있다.
- 버튼 클릭 시 입력값을 서버로 보낼 수 있다.
- 입력값에 따라 다른 안내 문구를 보여줄 수 있다.
- 입력값을 초기화할 수 있다.
입력값이 화면 동작이나 서버 요청에 사용된다면
state로 관리하는 것이 자연스럽다.
이 방식이React에서 입력 폼을 다루는 기본 흐름이다.
30. form submit 처리
30-1. form 태그의 기본 동작
HTML의form태그는 기본적으로 제출될 때 페이지를 새로고침하려고 한다.
기존 서버 중심 방식에서는form제출 후 서버가 새 화면을 응답하는 흐름이 자연스러웠다.
하지만React SPA에서는 화면 전체를 새로고침하지 않고 처리하는 경우가 많다.
입력값을state로 가지고 있다가submit시 서버API를 호출하고, 결과에 따라 화면만 바꾸는 흐름을 사용한다.
그래서React에서form제출을 처리할 때는 기본 새로고침 동작을 막아야 한다.
30-2. event.preventDefault()를 사용하는 이유
event.preventDefault()는 브라우저의 기본 동작을 막는 메서드이다.
form제출에서는 페이지 새로고침을 막기 위해 사용한다.
예시는 다음과 같다.// FormSubmitExample.jsx import { useState } from 'react'; export default function FormSubmitExample() { // title 상태에 입력값을 저장한다. const [title, setTitle] = useState(''); function handleSubmit(event) { // form의 기본 새로고침 동작을 막는다. event.preventDefault(); // 현재 title 값을 사용한다. alert(title); } return ( <form onSubmit={handleSubmit}> <input value={title} onChange={(event) => setTitle(event.target.value)} placeholder="제목을 입력하세요" /> <button type="submit">저장</button> </form> ); }
onSubmit={handleSubmit}은form이 제출될 때handleSubmit함수를 실행한다는 뜻이다.
handleSubmit안에서event.preventDefault()를 호출하면 페이지 새로고침이 막힌다.
30-3. submit 이벤트에서 state 값을 사용하는 흐름
form제출 흐름은 입력값 관리와 함께 이해해야 한다.
입력할 때마다onChange가 실행되고, 입력값이state에 저장된다.
그 다음 제출 버튼을 누르면onSubmit이 실행되고, 현재state값을 사용할 수 있다.
흐름은 다음과 같다.
- 사용자가 입력창에 값을 입력한다.
onChange가 실행된다.setTitle(event.target.value)로 입력값을state에 저장한다.- 사용자가 저장 버튼을 누른다.
onSubmit이 실행된다.event.preventDefault()로 새로고침을 막는다.- 현재
title값을 사용해 알림을 띄우거나 서버로 요청한다.이 흐름을 사용하면 페이지 전체를 새로고침하지 않고도 입력값을 처리할 수 있다.
30-4. React SPA에서 새로고침 없이 처리하는 흐름
React SPA에서는 화면 전환이나 데이터 처리를 브라우저 안에서 처리하는 경우가 많다.
그래서form을 제출할 때도 기존 서버 중심 방식처럼 새 페이지를 요청하지 않는다.
대신 입력값을state로 관리하고,submit시 필요한 데이터를 서버API로 보낸다.
서버는 결과를JSON으로 응답하고,React는 그 응답을 바탕으로 화면 상태를 바꾼다.
정리하면 다음과 같다.
- 입력값은
state로 관리한다.onChange로 입력값을 계속 갱신한다.onSubmit에서 제출 흐름을 처리한다.event.preventDefault()로 기본 새로고침을 막는다.- 필요한 경우
fetch또는axios로 서버API를 호출한다.- 서버 응답 결과에 따라 화면을 다시 렌더링한다.
정리하면
React에서form처리는 입력값을state로 관리하고, 제출 시 기본 새로고침을 막은 뒤, 필요한 로직을 직접 실행하는 흐름이다.
이 개념을 이해하면 로그인 폼, 검색 폼, 게시글 작성 폼 같은 화면을 만들 때React방식의 입력 처리 흐름을 잡을 수 있다.
React화면은 항상 같은 모습만 보여주는 것이 아니다.
로그인 여부, 관리자 여부, 데이터 로딩 상태, 서버 응답 결과에 따라 화면이 달라질 수 있다.
이처럼 조건에 따라 다른 화면을 보여주는 방식을 조건부 렌더링이라고 한다.
또한 게시글 목록, 댓글 목록, 상품 목록처럼 같은 구조가 반복되는 화면도 자주 만든다.
이때 배열 데이터를 반복해서 화면으로 바꾸는 방식을 리스트 렌더링이라고 한다.
이번 구간에서는 조건부 렌더링, 리스트 렌더링, 부모와 자식 컴포넌트의 데이터 전달 방향, 그리고 기존Spring MVC에서React + REST구조로 넘어갈 때 바뀌는 흐름까지 정리한다.
31. 조건부 렌더링 패턴 정리
31-1. 삼항 연산자를 사용하는 경우
조건부 렌더링은 조건에 따라 화면을 다르게 보여주는 방식이다.
그중 삼항 연산자는 둘 중 하나를 선택해서 보여줄 때 사용한다.
삼항 연산자는조건 ? 참일 때 값 : 거짓일 때 값형태로 작성한다.
JSX중괄호 안에는 값을 만들어내는 표현식이 들어갈 수 있으므로, 삼항 연산자를 넣을 수 있다.
예를 들어 로그인 상태에 따라 다른 문구를 보여주고 싶다면 다음처럼 작성할 수 있다.// ConditionalTernaryPatternExample.jsx export default function ConditionalTernaryPatternExample() { const isLogin = true; return ( <div> <h1>{isLogin ? '로그인' : '로그아웃'}</h1> </div> ); }위 코드에서
isLogin이true이면로그인이 화면에 보인다.
isLogin이false이면로그아웃이 화면에 보인다.
삼항 연산자는 둘 중 하나를 반드시 보여줘야 할 때 사용하기 좋다.
예를 들면 로그인 상태에 따라로그인버튼과로그아웃버튼 중 하나만 보여주는 경우가 있다.
31-2. && 연산자를 사용하는 경우
&&연산자는 조건이 참일 때만 특정 요소를 보여주고 싶을 때 사용한다.
거짓일 때 따로 보여줄 내용이 없다면 삼항 연산자보다&&가 더 간단하다.
예를 들어 관리자일 때만 관리자 메뉴를 보여주고 싶다면 다음처럼 작성할 수 있다.// ConditionalAndPatternExample.jsx export default function ConditionalAndPatternExample() { const isAdmin = true; return ( <div> <h1>메인 화면</h1> {isAdmin && <button>관리자 메뉴</button>} </div> ); }
isAdmin이true이면<button>관리자 메뉴</button>가 렌더링된다.
isAdmin이false이면 아무것도 렌더링되지 않는다.
여기서 중요한 점은&&연산자가 “참이면 뒤의 값을 보여준다”는 흐름으로 동작한다는 것이다.
그래서 조건이 참일 때만 일부 요소를 보여주는 상황에 잘 맞는다.
31-3. if + return을 사용하는 경우
조건이 복잡하거나, 특정 상태에서는 화면 전체를 일찍 반환해야 할 때는
if + return방식을 사용한다.
이 방식은JSX안에if문을 넣는 것이 아니라,return전에 조건을 검사하는 방식이다.
예를 들어 로딩 중일 때는 다른 화면을 보여주고, 로딩이 끝났을 때 본문을 보여주고 싶다면 다음처럼 작성할 수 있다.// ConditionalIfReturnPatternExample.jsx export default function ConditionalIfReturnPatternExample() { const loading = true; if (loading) { // 로딩 중이면 이 화면을 먼저 반환한다. return <p>로딩 중</p>; } // 로딩이 끝났을 때 보여줄 화면이다. return <h1>데이터 화면</h1>; }이 방식은 조건이 여러 개일 때도 읽기 쉽다.
예를 들어loading,error,empty상태를 각각 먼저 처리하고, 마지막에 정상 화면을 반환하는 식으로 작성할 수 있다.
31-4. loading, error, empty 상태를 나누는 흐름
실제 화면에서는 단순히 로그인 여부만 나누는 것이 아니라 여러 상태를 나누어 처리한다.
대표적으로loading,error,empty,success상태가 있다.
각 상태의 의미는 다음과 같다.
loading은 데이터를 불러오는 중인 상태이다.error는 데이터를 불러오거나 처리하는 중 문제가 생긴 상태이다.empty는 요청은 성공했지만 보여줄 데이터가 없는 상태이다.success는 데이터를 정상적으로 받아 화면에 보여줄 수 있는 상태이다.예를 들어 게시글 목록 화면은 다음처럼 상태를 나눌 수 있다.
// LoadingErrorEmptyExample.jsx export default function LoadingErrorEmptyExample() { const loading = false; const error = false; const posts = []; if (loading) { // 데이터를 불러오는 중이다. return <p>로딩 중입니다.</p>; } if (error) { // 요청 중 오류가 발생했다. return <p>오류가 발생했습니다.</p>; } if (posts.length === 0) { // 요청은 성공했지만 보여줄 데이터가 없다. return <p>게시글이 없습니다.</p>; } return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }이렇게 상태를 먼저 나누면 화면 흐름이 훨씬 명확해진다.
사용자 입장에서도 지금 데이터가 로딩 중인지, 오류가 났는지, 데이터가 없는지 알 수 있다.
조건부 렌더링은 삼항 연산자,
&&연산자,if + return방식으로 상황에 맞게 나누어 작성할 수 있다.
정리하면 조건부 렌더링은 현재 상태에 따라 보여줄 화면을 선택하는 방식이다.
조건이 짧고 둘 중 하나를 보여주면 삼항 연산자를 쓰고, 참일 때만 보여주면&&를 쓰고, 조건이 복잡하면if + return을 사용하는 것이 좋다.
32. 리스트 렌더링 실습 코드
32-1. posts 배열을 화면에 출력하는 흐름
리스트 렌더링은 배열 데이터를 화면에 반복해서 출력하는 방식이다.
게시글 목록, 댓글 목록, 상품 목록처럼 같은 구조가 여러 번 반복되는 화면에서 사용한다.
예를 들어 게시글 데이터가 배열로 준비되어 있다고 생각해 보자.// PostListDataExample.jsx const posts = [ { id: 1, title: '첫 번째 글', content: '첫 번째 내용' }, { id: 2, title: '두 번째 글', content: '두 번째 내용' }, { id: 3, title: '세 번째 글', content: '세 번째 내용' }, ];이 배열을 화면에 보여주려면 각 게시글 객체를 하나씩 꺼내서
JSX로 바꾸어야 한다.
React에서는 이 작업에map()을 많이 사용한다.
32-2. map으로 article을 반복 생성하는 방식
map()은 배열의 각 요소를 하나씩 꺼내서 새로운 결과를 만드는 메서드이다.
React에서는 배열의 각 데이터를JSX로 바꿀 때 사용한다.
게시글 배열을 화면에 출력하는 예시는 다음과 같다.// PostListRenderingExample.jsx export default function PostListRenderingExample() { const posts = [ { id: 1, title: '첫 번째 글', content: '첫 번째 내용' }, { id: 2, title: '두 번째 글', content: '두 번째 내용' }, { id: 3, title: '세 번째 글', content: '세 번째 내용' }, ]; return ( <section> <h1>게시글 목록</h1> {posts.map((post) => ( <article key={post.id}> <h2>{post.title}</h2> <p>{post.content}</p> </article> ))} </section> ); }위 코드에서
posts.map((post) => (...))는posts배열의 게시글을 하나씩 꺼낸다.
그리고 각post를<article>화면 조각으로 바꾼다.
게시글이 3개라면<article>도 3개 만들어진다.
게시글이 10개라면<article>도 10개 만들어진다.
즉, 데이터 개수에 따라 화면도 반복해서 만들어진다.
32-3. key={post.id}가 필요한 이유
리스트 렌더링을 할 때는 반복해서 만들어지는 요소에
key를 넣어야 한다.
key는 각 항목을 구분하기 위한 고유한 이름표이다.
React는 화면이 바뀔 때 어떤 항목이 새로 추가되었는지, 어떤 항목이 삭제되었는지, 어떤 항목의 순서가 바뀌었는지를 알아야 한다.
이때key가 있어야 각 항목을 안정적으로 구분할 수 있다.
예를 들어 게시글에는 보통 고유한id가 있다.
그래서 게시글 목록에서는key={post.id}처럼 작성하는 것이 좋다.// PostListKeyExample.jsx export default function PostListKeyExample() { const posts = [ { id: 101, title: '공지사항' }, { id: 102, title: '수업 자료' }, ]; return ( <ul> {posts.map((post) => ( <li key={post.id}>{post.title}</li> ))} </ul> ); }
key={post.id}는 각 게시글을 구분하는 기준이다.
화면에 직접 표시되는 값은 아니지만,React가 내부적으로 항목을 구분할 때 사용한다.
32-4. 리스트 렌더링에서 자주 헷갈리는 부분
리스트 렌더링에서 초보자가 자주 헷갈리는 부분은
map()의 결과가 화면이라는 점이다.
map()은 배열을 반복하는 도구이지만,React에서는 그 반복 결과로JSX를 반환한다.
또 하나 헷갈리는 부분은key이다.
key는 사용자에게 보여주기 위한 속성이 아니라,React가 항목을 구분하기 위해 사용하는 속성이다.
자주 헷갈리는 부분을 정리하면 다음과 같다.
map()은 배열 데이터를JSX배열로 바꾼다.map()안에서는 각 데이터 하나를 화면 조각으로 만든다.- 반복 요소에는
key가 필요하다.- 가능하면 배열의
index보다 데이터의 고유id를key로 사용한다.- 객체 전체를 화면에 바로 출력하지 말고 필요한 속성을 꺼내서 출력한다.
예를 들어
{post}처럼 객체 전체를 출력하면 문제가 생길 수 있다.
대신{post.title},{post.content}처럼 필요한 값을 꺼내서 출력해야 한다.
정리하면 리스트 렌더링은 배열 데이터를map()으로 반복하면서, 각 데이터를JSX화면 조각으로 바꾸는 방식이다.
반복되는 화면에는key를 넣어 각 항목을 구분해야 한다.
33. 부모와 자식 컴포넌트의 데이터 전달 방향
33-1. 부모에서 자식으로 값 props 전달
React에서 기본 데이터 흐름은 부모에서 자식 방향이다.
부모 컴포넌트가 자식 컴포넌트를 사용하면서 값을props로 내려준다.
자식 컴포넌트는 전달받은 값을 읽어서 화면을 만든다.
예를 들어 부모가 자식에게 게시글 제목을 전달하는 흐름은 다음과 같다.// ParentToChildPropsExample.jsx function PostTitle({ title }) { // 부모에게 받은 title 값을 화면에 출력한다. return <h2>{title}</h2>; } export default function App() { // 자식 컴포넌트에 title props를 전달한다. return <PostTitle title="React 복습 정리" />; }여기서
App은 부모 컴포넌트이다.
PostTitle은 자식 컴포넌트이다.
title="React 복습 정리"는 부모가 자식에게 전달한props이다.
자식은 이 값을 직접 만든 것이 아니라 부모에게 받은 것이다.
그래서props는 기본적으로 읽기 전용으로 다룬다.
33-2. 자식에서 부모로 함수 props 호출
자식이 부모에게 어떤 일을 알려야 할 때도 있다.
예를 들어 자식 컴포넌트의 버튼을 눌렀을 때 부모의 데이터를 바꾸고 싶을 수 있다.
이때 자식이 부모의state를 직접 바꾸는 것이 아니다.
부모가 함수를 만들어 자식에게props로 내려주고, 자식은 그 함수를 호출한다.
예시는 다음과 같다.// ChildToParentFunctionPropsExample.jsx function ChildButton({ onSendData }) { // 버튼을 누르면 부모에게 받은 함수를 호출한다. return ( <button onClick={() => onSendData('자식에서 보낸 값')}> 부모에게 값 보내기 </button> ); } export default function Parent() { function handleData(data) { // 자식이 보낸 값을 부모에서 처리한다. alert(data); } return <ChildButton onSendData={handleData} />; }
Parent는handleData함수를 만든다.
그리고 그 함수를onSendData라는 이름으로 자식에게 전달한다.
자식은 버튼을 클릭했을 때onSendData()를 호출한다.
이 흐름을 사용하면 자식이 부모에게 이벤트를 알릴 수 있다.
33-3. 자식이 부모 state를 직접 바꾸지 않는 이유
자식 컴포넌트는 부모의
state를 직접 바꾸면 안 된다.
그 이유는 데이터 변경 위치를 예측 가능하게 유지하기 위해서이다.
부모의 데이터는 부모가 관리해야 한다.
자식이 마음대로 부모 데이터를 바꾸면, 나중에 문제가 생겼을 때 “어디에서 값이 바뀌었는지” 찾기 어려워진다.
그래서React에서는 다음 흐름을 사용한다.
- 부모가
state를 관리한다.- 부모가 자식에게 현재 값을
props로 내려준다.- 부모가 자식에게 변경 요청 함수도
props로 내려준다.- 자식은 직접 값을 바꾸지 않고 함수를 호출한다.
- 부모는 자식의 요청을 받아
state를 변경한다.이 구조를 단방향 데이터 흐름이라고 한다.
단방향 데이터 흐름은 데이터가 기본적으로 부모에서 자식 방향으로 내려간다는 뜻이다.
반대로 자식이 부모에게 알려야 할 일이 있으면 콜백 함수를 호출한다.
props는 읽기 전용이며, 부모에서 자식 방향으로 전달된다. 자식이 부모에게 이벤트를 알려야 할 때는 콜백 함수를props로 전달받아 호출한다.
33-4. onSendData 예시 흐름
onSendData는 자식이 부모에게 값을 전달할 때 사용할 수 있는 함수props의 예시 이름이다.
이름이 꼭onSendData여야 하는 것은 아니다.
보통 이벤트를 알리는 함수props는on으로 시작하는 이름을 많이 사용한다.
예를 들어 자식에서 입력한 값을 부모에게 전달하는 흐름은 다음과 같다.// OnSendDataFlowExample.jsx function ChildInput({ onSendData }) { const message = 'React'; return ( <button onClick={() => onSendData(message)}> 부모에게 전달 </button> ); } export default function Parent() { function handleSendData(value) { // 자식에게 전달받은 값을 부모에서 처리한다. alert(`전달받은 값: ${value}`); } return <ChildInput onSendData={handleSendData} />; }이 코드의 흐름은 다음과 같다.
- 부모
Parent가handleSendData함수를 만든다.- 부모가 자식
ChildInput에게onSendData라는 이름으로 함수를 전달한다.- 자식은 버튼을 클릭했을 때
onSendData(message)를 호출한다.- 실제 처리 로직은 부모의
handleSendData에서 실행된다.이렇게 하면 자식은 부모의 데이터를 직접 수정하지 않는다.
자식은 “이 일이 발생했다”라고 함수 호출로 알리고, 부모가 실제 처리를 담당한다.
33-5. props와 state 흐름 최종 정리
props와state는 모두 화면에 영향을 주는 데이터이다.
하지만 역할이 다르다.
props는 부모가 자식에게 내려주는 값이다.
자식은 이 값을 읽어서 화면을 만든다.
state는 컴포넌트가 직접 관리하는 값이다.
값이 바뀌면 화면도 다시 렌더링된다.
최종 흐름을 정리하면 다음과 같다.
- 부모에서 자식으로 값은
props로 내려간다.props는 자식이 직접 수정하지 않는다.- 상태 변화가 필요하면
state로 관리한다.- 자식이 부모에게 알려야 할 때는 함수
props를 호출한다.- 부모는 자식의 요청을 받아
state를 변경한다.정리하면
React의 기본 데이터 흐름은 부모에서 자식으로 내려가고, 반대 방향으로 알려야 할 때는 함수props를 호출하는 방식이다.
이 흐름을 이해하면 컴포넌트가 많아져도 데이터가 어디에서 오고 어디에서 바뀌는지 추적하기 쉬워진다.
34. 참고: MVC에서 SPA로 전환하는 흐름
34-1. 서버 중심 화면 개발에서 프론트 중심 화면 개발로 바뀌는 흐름
기존
Spring MVC에서는 서버가 화면을 만드는 역할까지 담당했다.
Controller가 요청을 받고,Model에 데이터를 담고,Thymeleaf같은 템플릿이HTML을 완성해서 브라우저에 보냈다.
반면React + REST구조에서는 화면 생성 역할이 브라우저 쪽으로 이동한다.
서버는 완성된HTML을 보내는 대신JSON데이터를 제공한다.
브라우저에서는React가 이 데이터를 받아 화면을 만든다.
핵심 차이는 다음과 같다.
Spring MVC는 서버에서HTML을 완성해 전송한다.React + REST는 브라우저에서JavaScript로 화면을 렌더링한다.Spring MVC는Model데이터를 템플릿에 전달한다.React + REST는JSON API데이터를React state에 저장한다.Spring MVC는 화면 이동 시 새HTML페이지 요청이 중심이다.React + REST는 클라이언트 라우팅으로 화면을 바꾼다.
Spring MVC는 서버에서 완성된HTML을 보내고,React + REST는 서버가JSON데이터를 제공하면 브라우저가 화면을 렌더링한다.
34-2. Spring Boot가 REST API 역할을 맡는 이유
React + REST구조에서Spring Boot는 화면을 직접 만들기보다REST API역할을 맡는다.
REST API는 프론트엔드가 필요한 데이터를 요청할 수 있도록 서버가 제공하는 데이터 통로이다.
예를 들어 게시글 목록이 필요하면React는/api/boards같은 주소로 요청을 보낸다.
Spring Boot는 게시글 데이터를 조회한 뒤JSON으로 응답한다.
그 다음React는 이 데이터를 받아 화면에 보여준다.
이렇게 역할을 나누면 프론트엔드와 백엔드를 분리해서 개발할 수 있다.
React는 화면과 사용자 상호작용에 집중하고,Spring Boot는 데이터 처리, 인증, 저장, 비즈니스 로직에 집중한다.
Spring MVC중심 구조에서는 서버가 화면까지 담당하지만,React + Spring REST구조에서는 프론트엔드와 백엔드가HTTP JSON REST API로 분리된다.
34-3. React가 화면과 사용자 상호작용을 맡는 이유
React는 브라우저에서 실행되며 화면과 사용자 상호작용을 담당한다.
사용자가 버튼을 클릭하거나, 입력창에 값을 입력하거나, 페이지를 이동할 때React가 화면 상태를 관리한다.
기존Thymeleaf화면에서는 서버가 만든HTML을 보여주고, 폼 제출 후 서버 응답으로 화면이 갱신되는 흐름이 많았다.
하지만React에서는 입력값을state로 관리하고,onSubmit에서fetch또는axios로 서버API를 호출하는 흐름을 사용한다.
React가 담당하는 대표 역할은 다음과 같다.
- 컴포넌트로 화면을 구성한다.
useState와Hooks로 화면 상태를 관리한다.React Router로 클라이언트 라우팅을 처리한다.fetch또는axios로 서버API를 호출한다.- 사용자 입력과 클릭 이벤트를 처리한다.
이 구조에서는 서버가 화면을 매번 다시 만들어 보내지 않아도 된다.
브라우저의React가 상태 변화에 맞춰 필요한 부분을 다시 렌더링한다.
34-4. 전체 흐름 최종 정리
Spring MVC에서React + REST SPA구조로 전환할 때는 단순히 화면 문법만 바뀌는 것이 아니다.
렌더링 위치, 데이터 전달 방식, 화면 이동 방식, 인증 방식, 상태 관리 방식이 함께 바뀐다.
전환 시 반드시 알아야 할 핵심 개념은 다음과 같다.
CORS는 프론트엔드와 백엔드 포트가 다를 때 발생할 수 있는 출처 문제이다.JWT인증은 세션 기반 인증에서 토큰 기반 인증으로 전환할 때 자주 사용된다.state관리는Thymeleaf Model변수 대신React state로 화면 데이터를 관리하는 흐름이다.API설계는 기존 화면Controller주소를REST방식으로 재설계하는 과정이다.- 라우팅은 서버 중심
URL매핑에서React Router중심 클라이언트 라우팅으로 바뀐다.- 폼 처리는
th:action,th:object중심에서controlled component,onSubmit,fetch API흐름으로 바뀐다.
Spring MVC에서React + REST구조로 전환할 때는CORS,JWT, 상태 관리,API설계, 라우팅, 폼 처리 흐름을 함께 이해해야 한다.
기존 코드 흐름도 함께 비교하면 차이가 더 분명하다.
Spring MVC에서는Controller가Model에 데이터를 담고Thymeleaf템플릿 이름을 반환한다.
반면React + REST구조에서는Spring Boot가ResponseEntity로 데이터를 응답하고,React컴포넌트가fetch로 데이터를 받아state에 저장한다.
기존
Spring MVC코드는 서버가 화면 데이터를 템플릿에 넣어 렌더링하지만,React + REST구조는 서버가JSON데이터를 보내고React가 화면에서 렌더링한다.
정리하면MVC에서SPA로 전환한다는 것은 서버가 화면을 만들던 구조에서, 서버는 데이터를 제공하고 브라우저의React가 화면을 만드는 구조로 바뀐다는 뜻이다.
이 흐름을 이해하면React와Spring Boot REST API가 왜 함께 사용되는지 명확하게 보인다.