
React응용예제는 앞에서 배운 개념이 실제 코드에서 어떻게 사용되는지 확인하는 구간이다.
이번 예제는EduApp1.jsx,EduApp2.jsx,EduApp3.jsx,EduApp4.jsx,main.jsx를 기준으로 진행한다.
각 예제는 필요한 개념을 짧게 복습한 뒤, 실제 예제 코드 안의 주석으로 값이 어디에서 만들어지고 어디로 전달되는지 확인한다.
그 다음 브라우저 결과물로 동작을 검증하고, 마지막에는 같은 개념을 조금 바꿔서 적용하는 추가 응용 예시까지 정리한다.
응용예제의 핵심은 코드를 외우는 것이 아니라, 값이 코드 안에서 어떻게 이동하고 화면에 어떻게 출력되는지 이해하는 것이다.
1. main.jsx에서 예제를 바꿔 실행하는 방식
1-1. main.jsx는 React 앱의 시작점이다
main.jsx는React앱을 브라우저 화면에 연결하는 시작 파일이다.
index.html에 있는root요소를 찾고, 그 안에 원하는 컴포넌트를 렌더링한다.
여기서 렌더링은 컴포넌트가 반환한JSX를 화면에 보이도록 그리는 작업이다.
즉, 어떤 예제를 화면에서 확인할지는main.jsx에서 어떤 컴포넌트를 렌더링하느냐에 따라 달라진다.
1-2. import와 렌더링은 서로 다른 단계이다
예제를 확인할 때는
main.jsx에서 필요한 컴포넌트를import한다.
import는 다른 파일에서 내보낸 컴포넌트를 현재 파일로 가져오는 문법이다.
하지만import만 했다고 화면에 바로 보이는 것은 아니다.
가져온 컴포넌트를render()안에서<컴포넌트 />형태로 사용해야 브라우저 화면에 표시된다.
예를 들어EduApp1.jsx를 확인할 때는EduApp1을 가져오고, 렌더링 위치에<EduApp1 />을 넣는다.// main.jsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import EduApp1 from './EduApp1.jsx'; import './index.css'; createRoot(document.getElementById('root')).render( <StrictMode> <EduApp1 /> </StrictMode>, );이 코드에서는
EduApp1만 화면에 렌더링된다.
다른 예제를 확인하고 싶으면import할 컴포넌트와<EduApp1 />자리에 들어갈 컴포넌트를 바꾸면 된다.
1-3. default export와 named export에 따라 import 방식이 다르다
컴포넌트를 가져오는 방식은 파일에서 어떻게 내보냈는지에 따라 달라진다.
EduApp1.jsx처럼export default로 내보낸 컴포넌트는 중괄호 없이 가져온다.// main.jsx import EduApp1 from './EduApp1.jsx';
default export는 파일에서 기본으로 내보내는 값이다.
그래서 가져올 때 중괄호 없이 가져온다.
반면EduApp3.jsx의PostList처럼export function PostList()형태로 이름을 붙여 내보낸 컴포넌트는 중괄호로 가져온다.// main.jsx import { PostList } from './EduApp3.jsx';이 방식은
named export이다.
named export는 내보낸 이름과 같은 이름으로 가져와야 한다.
정리하면 화면에 표시되려면 컴포넌트를import하는 것에서 끝나지 않고,render()안에서 실제로 렌더링해야 한다.
1-4. 예제별 main.jsx 교체 흐름
예제는 한 번에 여러 개를 다 켜는 방식보다, 하나씩 바꿔가며 확인하는 방식이 이해하기 쉽다.
그래야 코드 하나와 결과물 하나를 바로 연결해서 볼 수 있다.
예를 들어EduApp3.jsx의PostList를 확인하려면main.jsx를 다음처럼 바꾼다.// main.jsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { PostList } from './EduApp3.jsx'; import './index.css'; createRoot(document.getElementById('root')).render( <StrictMode> <PostList /> </StrictMode>, );이렇게 하면 브라우저에는
PostList컴포넌트의 결과만 출력된다.
StateTest를 확인하고 싶다면EduApp4.jsx에서StateTest를 가져와서 렌더링하면 된다.// main.jsx import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; import { StateTest } from './EduApp4.jsx'; import './index.css'; createRoot(document.getElementById('root')).render( <StrictMode> <StateTest /> </StrictMode>, );이렇게 예제마다
main.jsx에서 가져오는 컴포넌트와 렌더링하는 컴포넌트를 바꿔 확인한다.
정리하면main.jsx는 예제를 실행하는 연결 지점이다.
실행할 예제를 바꾸려면import할 컴포넌트와 렌더링할 컴포넌트를 함께 바꿔야 한다.
EduApp1.jsx는 부모 컴포넌트가 자식 컴포넌트에게 값을 전달하는props기본 흐름을 확인하는 예제이다.
props는 컴포넌트 사이에서 데이터를 전달할 때 사용하는 값이다.
이 예제에서는EduApp1이Welcome컴포넌트에게 문자열"React"를name이라는 이름으로 전달한다.
Welcome은 전달받은name값을JSX중괄호 위치에 넣어 화면에 출력한다.
2. props 개념 복습
2-1. props는 부모가 자식에게 전달하는 값이다
props는 부모 컴포넌트가 자식 컴포넌트에게 전달하는 값이다.
컴포넌트 태그에 속성처럼 작성해서 값을 넘긴다.
예를 들어<Welcome name="React" />에서name="React"가props이다.
여기서"React"라는 문자열 값이name이라는 이름으로Welcome컴포넌트에 전달된다.
2-2. 구조 분해 할당으로 props 값을 꺼낼 수 있다
자식 컴포넌트는 전달받은
props를 객체 형태로 받을 수 있다.
하지만props.name처럼 매번 객체 이름을 붙여 쓰면 코드가 길어진다.
그래서 필요한 값만 바로 꺼내기 위해 구조 분해 할당을 사용한다.
구조 분해 할당은 객체 안의 값을 변수처럼 바로 꺼내는 문법이다.
예를 들어function Welcome({ name })은 전달받은props객체 안에서name값만 꺼내겠다는 뜻이다.
2-3. JSX 중괄호로 전달받은 값을 화면에 출력한다
JSX안에서JavaScript값을 출력하려면 중괄호{}를 사용한다.
그래서 전달받은name값을 화면에 출력하려면{name}처럼 작성한다.
props로 받은 값은JSX중괄호 안에서 화면에 출력할 수 있다.
3. EduApp1.jsx 코드 흐름
3-1. 전체 코드
// EduApp1.jsx function Welcome({ name }) { // EduApp1에서 전달한 name props의 값 "React"가 name 변수에 들어온다. // 그래서 아래 {name} 위치에는 문자열 "React"가 들어간다. return <h1>안녕? {name}</h1>; } export default function EduApp1() { // 문자열 "React"를 Welcome 컴포넌트의 name props로 전달한다. // 이 코드 때문에 Welcome 내부의 name 값은 "React"가 된다. return <Welcome name="React" />; }이 파일에는
Welcome컴포넌트와EduApp1컴포넌트가 있다.
EduApp1은Welcome을 사용하면서name이라는 이름으로 문자열"React"를 전달한다.
Welcome은name값을 받아<h1>안의{name}위치에 넣는다.
그래서 최종적으로 화면에는안녕? React가 출력된다.
3-2. 실행 흐름
코드 실행 흐름은 다음과 같다.
main.jsx에서<EduApp1 />을 렌더링한다.EduApp1컴포넌트가 실행된다.EduApp1은<Welcome name="React" />를 반환한다.- 문자열
"React"가Welcome의nameprops로 전달된다.Welcome의 매개변수{ name }안에"React"가 들어온다.Welcome내부의{name}위치에"React"가 들어간다.- 브라우저에는
안녕? React가 출력된다.이 흐름에서 중요한 점은
Welcome이 직접"React"라는 값을 만든 것이 아니라는 점이다.
"React"라는 값은EduApp1이 전달했고,Welcome은 그 값을 받아서 화면에 출력했다.
4. EduApp1.jsx 결과물 확인
4-1. 브라우저 출력 결과
main.jsx에서EduApp1을 렌더링하면 브라우저에는 다음 결과가 출력된다.
부모 컴포넌트가 전달한
name="React"값이 자식 컴포넌트에서{name}으로 출력된다.
결과 화면의안녕? React는Welcome컴포넌트가 만든 문장이다.
하지만 문장 안의React값은EduApp1컴포넌트가 전달한props이다.
정리하면EduApp1.jsx는 부모에서 자식으로props를 전달하고, 자식이 그 값을 화면에 출력하는 기본 흐름을 보여준다.
5. EduApp1.jsx 추가 응용 예시
5-1. name 외에 message props도 함께 전달하기
이번에는
name뿐 아니라message값도 함께 전달해 본다.
하나의 컴포넌트에는props를 여러 개 전달할 수 있다.
// EduApp1ApplyExample.jsx function Welcome({ name, message }) { // name에는 "React"가 들어오고, message에는 "props를 복습하는 중이다."가 들어온다. return ( <section> <h1>안녕? {name}</h1> <p>{message}</p> </section> ); } export default function EduApp1ApplyExample() { // name props에는 "React"를 전달한다. // message props에는 "props를 복습하는 중이다."를 전달한다. return <Welcome name="React" message="props를 복습하는 중이다." />; }// 출력결과 // 안녕? React // props를 복습하는 중이다.이 예시에서는
name과message를 함께 전달한다.
자식 컴포넌트는{ name, message }로 두 값을 꺼낸 뒤 화면에 출력한다.
이렇게 하면 같은Welcome컴포넌트를 더 다양한 문구를 출력하는 컴포넌트로 사용할 수 있다.
EduApp2.jsx는JSX에서 여러 요소를 반환할 때 하나의 부모 요소로 감싸야 한다는 규칙을 확인하는 예제이다.
React컴포넌트는 한 번에 하나의 최상위 구조를 반환해야 한다.
여러 태그를 나란히 반환하려면div또는Fragment로 묶어야 한다.
이 예제에서는div로 묶는 방식과Fragment로 묶는 방식을 비교한다.
6. JSX 부모 요소 규칙 복습
6-1. JSX는 최상위 요소 하나를 반환해야 한다
JSX에서 여러 요소를 반환할 때는 반드시 하나의 부모 요소로 감싸야 한다.
예를 들어<h1>과<p>를 나란히 반환하면 최상위 요소가 두 개가 되므로 오류가 난다.
그래서 여러 요소를 반환할 때는 하나의 부모 요소로 묶어야 한다.
6-2. div는 실제 DOM 태그를 만든다
div로 감싸면 문법 오류를 막을 수 있다.
하지만 실제 브라우저의DOM구조에도<div>태그가 추가된다.
DOM은 브라우저가HTML문서를 객체 구조로 해석한 것이다.
즉,div를 쓰면 화면 구조 안에 실제 박스가 하나 생긴다고 보면 된다.
6-3. Fragment는 실제 태그를 만들지 않고 묶기만 한다
Fragment는 여러 요소를 묶어 주지만 실제HTML태그를 추가하지 않는다.
짧은 문법으로는<>...</>를 사용한다.
불필요한div를 만들고 싶지 않을 때는Fragment를 사용하는 것이 좋다.
div는 실제 태그를 만들고,Fragment는 실제 태그 없이 여러 요소를 묶는다.
7. EduApp2.jsx 코드 흐름
7-1. 전체 코드
// EduApp2.jsx export function GoodExample1() { // <h1>제목1</h1>과 <p>본문1</p>를 실제 div 태그 안에 넣어 반환한다. // 이 div는 JSX를 하나로 묶는 역할도 하고, 실제 DOM에도 남는다. return ( <div> <h1>제목1</h1> <p>본문1</p> </div> ); } export function GoodExample2() { // <h1>제목2</h1>와 <p>본문2</p>를 Fragment로 묶어 반환한다. // Fragment는 JSX를 하나로 묶지만, 실제 DOM에는 별도 태그를 만들지 않는다. return ( <> <h1>제목2</h1> <p>본문2</p> </> ); }
GoodExample1은<div>로<h1>과<p>를 감싼다.
GoodExample2는Fragment로<h1>과<p>를 감싼다.
두 방식 모두JSX문법상 하나의 최상위 구조를 만든다.
차이는 실제HTML태그가 추가되는지 여부이다.
7-2. 코드 흐름을 비교해서 보면
GoodExample1은 실제DOM에 다음처럼 감싸는<div>가 남는다.// GoodExample1ResultShape.jsx <div> <h1>제목1</h1> <p>본문1</p> </div>이 구조에서는 최상위 요소가
<div>하나이다.
그래서React가 정상적으로 렌더링할 수 있다.
반면GoodExample2는Fragment로 묶었기 때문에 실제DOM에는 감싸는 태그가 남지 않는다.// GoodExample2ResultShape.jsx <h1>제목2</h1> <p>본문2</p>즉,
Fragment는 문법적으로 여러 요소를 하나로 묶지만, 실제 화면 구조에는 불필요한 태그를 추가하지 않는다.
8. EduApp2.jsx 결과물 확인
8-1. 브라우저 출력 결과
GoodExample1과GoodExample2를 함께 렌더링하면 브라우저에는 다음 결과가 출력된다.
div와Fragment는 모두 여러 요소를 묶을 수 있지만,div는 실제 태그를 만들고Fragment는 실제 태그를 추가하지 않는다.
결과 화면에서는제목1,본문1,제목2,본문2가 모두 정상 출력된다.
겉으로 보이는 결과는 비슷할 수 있지만 내부 구조는 다르다.
정리하면 화면 구조상 실제 박스가 필요하면div, 단순히 묶기만 하면 되면Fragment를 사용한다.
9. EduApp2.jsx 추가 응용 예시
9-1. Fragment로 카드 내용을 묶기
이번에는 카드 하나를 반환한다고 생각해 보자.
카드 전체를 감싸는div가 이미 필요하지 않은 상황이라면Fragment로 여러 요소만 묶을 수 있다.
// EduApp2ApplyExample.jsx export default function EduApp2ApplyExample() { // <h2>, <p>, <button>을 Fragment로 묶어 반환한다. // 실제 DOM에는 감싸는 div 없이 세 요소만 출력된다. return ( <> <h2>React 학습 카드</h2> <p>컴포넌트와 props를 복습한다.</p> <button>자세히 보기</button> </> ); }// 출력결과 // React 학습 카드 // 컴포넌트와 props를 복습한다. // 자세히 보기이 예시에서는
<h2>,<p>,<button>을Fragment로 묶었다.
불필요한 감싸는 태그를 추가하지 않으면서 여러 요소를 함께 반환할 수 있다.
다만 스타일을 적용할 박스가 필요하다면Fragment보다div가 더 적절하다.
EduApp3.jsx는JSX표현식, 이벤트 처리, 인라인 스타일, 조건부 렌더링, 리스트 렌더링,children props를 나누어 확인하는 종합 예제이다.
각 함수는 하나의 개념을 확인하기 위한 작은 예제 컴포넌트이다.
이 파일은React의 핵심 문법이 실제 코드에서 어떻게 보이는지 빠르게 확인하기 좋다.
하나씩 렌더링하면서 결과를 확인하면JSX문법이 화면과 어떻게 연결되는지 이해하기 쉽다.
10. UserGreeting: JSX 표현식과 삼항 연산자
10-1. 개념 복습
JSX안에서JavaScript값을 출력하려면 중괄호{}를 사용한다.
변수 값, 계산 결과, 삼항 연산자처럼 결과값이 나오는 표현식을 중괄호 안에 넣을 수 있다.
삼항 연산자는 조건에 따라 둘 중 하나의 값을 선택하는 표현식이다.
형태는조건 ? 참일 때 값 : 거짓일 때 값이다.
10-2. 코드 흐름
// EduApp3.jsx export function UserGreeting() { const name = '도우너'; // name 변수에는 문자열 "도우너"가 저장된다. const score = 90; // score 변수에는 숫자 90이 저장된다. return ( <section> <h1>{name}님 환영한다.</h1> {/* name 값 "도우너"가 {name} 위치에 들어가 "도우너님 환영한다."가 된다. */} <p>점수: {score}</p> {/* score 값 90이 {score} 위치에 들어가 "점수: 90"이 된다. */} <p>합격 여부: {score >= 60 ? '합격' : '불합격'}</p> {/* score가 90이므로 score >= 60은 true이다. */} {/* true이면 "합격"이 선택되어 화면에 출력된다. */} </section> ); }
name에는"도우너"가 저장된다.
score에는90이 저장된다.
{name}은name값을 화면에 출력한다.
{score}는score값을 화면에 출력한다.
{score >= 60 ? '합격' : '불합격'}은 점수가60이상이면합격, 아니면불합격을 출력한다.
현재score는90이므로score >= 60조건이 참이다.
따라서 화면에는합격이 출력된다.
10-3. 결과물 확인
JSX중괄호 안에서는 변수와 삼항 연산자처럼 결과값을 만드는 표현식을 사용할 수 있다.
결과 화면에는도우너님 환영한다.,점수: 90,합격 여부: 합격이 출력된다.
이 결과는name,score, 삼항 연산자 결과가 각각 화면에 들어갔다는 뜻이다.
정리하면UserGreeting예제는JSX중괄호 안에 변수와 표현식을 넣어 화면을 만드는 흐름을 보여준다.
10-4. 추가 응용 예시
이번에는 점수에 따라 등급을 출력해 본다.
조건이 두 가지보다 많아지면 삼항 연산자를 중첩해서 사용할 수 있다.
// UserGreetingGradeExample.jsx export default function UserGreetingGradeExample() { const name = '도우너'; const score = 90; const grade = score >= 90 ? 'A' : score >= 80 ? 'B' : 'C'; // score가 90 이상이면 grade에 "A"가 저장된다. // score가 80 이상이면 grade에 "B"가 저장된다. // 둘 다 아니면 grade에 "C"가 저장된다. return ( <section> <h1>{name}님 성적표</h1> <p>점수: {score}</p> <p>등급: {grade}</p> </section> ); }// 출력결과 // 도우너님 성적표 // 점수: 90 // 등급: A이 예시는 단순히 합격 여부만 출력하지 않는다.
score값에 따라A,B,C중 하나를 계산해서 화면에 출력한다.
다만 조건이 너무 많아지면 삼항 연산자가 복잡해질 수 있다.
그럴 때는 함수를 따로 만들어 등급을 계산하는 방식이 더 읽기 쉽다.
11. ClickButton: 이벤트 처리
11-1. 개념 복습
이벤트는 사용자가 화면에서 일으키는 동작이다.
예를 들어 버튼 클릭, 입력창 입력, 폼 제출 같은 동작이 이벤트이다.
React에서는 클릭 이벤트를onClick으로 작성한다.
그리고onClick에는 실행할 함수를 전달한다.
중요한 점은onClick={handleClick}처럼 함수 이름을 전달해야 한다는 것이다.
onClick={handleClick()}처럼 괄호를 붙이면 렌더링되는 순간 함수가 바로 실행될 수 있다.
11-2. 코드 흐름
// EduApp3.jsx export function ClickButton() { function handleClick() { // 버튼을 클릭했을 때만 이 함수가 실행된다. // 실행되면 "버튼을 클릭했습니다."라는 문자열이 alert()로 전달된다. alert('버튼을 클릭했습니다.'); } // onClick에는 handleClick()의 실행 결과가 아니라 handleClick 함수 자체를 전달한다. // 그래서 화면이 처음 렌더링될 때는 alert가 실행되지 않는다. // 사용자가 버튼을 클릭하는 순간 handleClick 함수가 실행된다. return <button onClick={handleClick}>클릭</button>; }
handleClick함수는 알림창을 띄우는 함수이다.
button태그의onClick에handleClick함수를 전달했다.
그래서 버튼이 처음 렌더링될 때는 함수가 실행되지 않는다.
사용자가 버튼을 클릭했을 때handleClick이 실행된다.
11-3. 결과물 확인
onClick={handleClick}은 렌더링 순간 실행하는 것이 아니라, 버튼을 클릭했을 때 실행할 함수를 연결하는 방식이다.
결과물에서는클릭버튼을 누른 뒤버튼을 클릭했습니다.알림창이 뜬다.
즉, 이벤트와 함수가 정상적으로 연결된 것이다.
정리하면 이벤트 속성에는 실행 결과가 아니라, 나중에 실행할 함수를 전달해야 한다.
11-4. 추가 응용 예시
이번에는 버튼마다 다른 메시지를 출력해 본다.
인자가 필요한 경우에는 화살표 함수로 감싸서 전달한다.
// ClickButtonMessageExample.jsx export default function ClickButtonMessageExample() { function showMessage(message) { // 클릭한 버튼에 따라 전달받은 message 값이 달라진다. alert(message); } return ( <section> <button onClick={() => showMessage('저장 버튼 클릭')}>저장</button> <button onClick={() => showMessage('삭제 버튼 클릭')}>삭제</button> </section> ); }// 저장 버튼 클릭 결과 // 저장 버튼 클릭 // 삭제 버튼 클릭 결과 // 삭제 버튼 클릭
onClick={() => showMessage('저장 버튼 클릭')}은 클릭하기 전까지showMessage()를 실행하지 않는다.
버튼을 클릭했을 때 화살표 함수가 실행되고, 그 안에서showMessage()가 실행된다.
이 방식은 이벤트 함수에 값을 전달해야 할 때 자주 사용한다.
12. Notice: 인라인 스타일 객체
12-1. 개념 복습
JSX에서style속성은 문자열이 아니라 객체로 작성한다.
HTML에서는style="color:red"처럼 문자열로 쓸 수 있지만,JSX에서는style={{ color: 'red' }}처럼 객체를 전달한다.
스타일 정보가 많아지면 객체를 따로 만들고, 그 객체를style속성에 전달할 수 있다.
이렇게 하면 코드가 더 읽기 쉬워진다.
12-2. 코드 흐름
// EduApp3.jsx export function Notice() { const boxStyle = { backgroundColor: 'magenta', // backgroundColor 값 "magenta"가 div의 배경색으로 적용된다. padding: '16px', // padding 값 "16px"가 div의 안쪽 여백으로 적용된다. borderRadius: '12px', // borderRadius 값 "12px"가 div의 모서리 둥글기로 적용된다. }; // boxStyle 객체 전체가 style 속성으로 전달된다. // React는 boxStyle 안의 backgroundColor, padding, borderRadius를 div 스타일로 적용한다. return <div style={boxStyle}>공지사항이다.</div>; }
boxStyle은 스타일 정보를 담은 객체이다.
backgroundColor는 배경색을 지정한다.
padding은 안쪽 여백을 지정한다.
borderRadius는 모서리를 둥글게 만든다.
<div style={boxStyle}>는div에boxStyle객체를 스타일로 적용한다는 뜻이다.
12-3. 결과물 확인
JSX의style속성에는 스타일 정보를 담은JavaScript객체를 전달한다.
결과물에서는공지사항이다.문구에magenta배경, 여백, 둥근 모서리가 적용되어 있다.
즉,boxStyle객체가div에 정상 적용된 것이다.
정리하면JSX인라인 스타일은 문자열이 아니라 객체로 전달해야 한다.
12-4. 추가 응용 예시
이번에는
type값에 따라 다른 스타일을 적용해 본다.
props로 받은 값에 따라 스타일 객체를 다르게 만들 수 있다.
// NoticeTypeExample.jsx function NoticeBox({ type, message }) { const boxStyle = { backgroundColor: type === 'important' ? 'magenta' : 'gray', // type 값이 "important"이면 배경색으로 "magenta"를 사용한다. // type 값이 "important"가 아니면 배경색으로 "gray"를 사용한다. padding: '16px', borderRadius: '12px', }; return <div style={boxStyle}>{message}</div>; } export default function NoticeTypeExample() { return ( <section> <NoticeBox type="important" message="중요 공지사항이다." /> <NoticeBox type="normal" message="일반 공지사항이다." /> </section> ); }// 출력결과 // 중요 공지사항이다. // 일반 공지사항이다.이 예시는
type값에 따라 배경색을 다르게 적용한다.
type이important이면magenta, 그렇지 않으면gray를 사용한다.
또한message값을props로 전달해 공지 문구를 다르게 출력한다.
이렇게 하면 같은 컴포넌트를 상황에 따라 다른 스타일과 다른 문구로 사용할 수 있다.
13. LoginStatus: 삼항 연산자 조건부 렌더링
13-1. 개념 복습
조건부 렌더링은 조건에 따라 다른 화면을 보여주는 방식이다.
삼항 연산자는 둘 중 하나를 반드시 보여줘야 할 때 사용하기 좋다.
LoginStatus예제에서는isLogin값이true인지false인지에 따라 다른 문구를 출력한다.
13-2. 코드 흐름
// EduApp3.jsx export function LoginStatus({ isLogin }) { // isLogin props에 true가 들어오면 "로그인 상태"가 선택된다. // isLogin props에 false가 들어오면 "로그아웃 상태"가 선택된다. return <p>{isLogin ? '로그인 상태' : '로그아웃 상태'}</p>; }
LoginStatus는isLogin이라는props를 받는다.
isLogin이true이면로그인 상태가 출력된다.
isLogin이false이면로그아웃 상태가 출력된다.
즉, 삼항 연산자가 조건에 따라 둘 중 하나의 문자열을 선택한다.
13-3. true 결과물 확인
isLogin값이true이면 삼항 연산자의 참 결과인로그인 상태가 출력된다.
13-4. false 결과물 확인
isLogin값이false이면 삼항 연산자의 거짓 결과인로그아웃 상태가 출력된다.
정리하면 삼항 연산자는 조건에 따라 둘 중 하나의 화면을 반드시 보여줘야 할 때 사용한다.
13-5. 추가 응용 예시
이번에는 로그인 상태에 따라 버튼 문구를 바꿔 본다.
문구뿐 아니라 버튼도 조건부 렌더링으로 바꿀 수 있다.
// LoginButtonStatusExample.jsx function LoginButtonStatus({ isLogin }) { return ( <button> {isLogin ? '로그아웃' : '로그인'} {/* isLogin이 true이면 버튼 안에 "로그아웃"이 들어간다. */} {/* isLogin이 false이면 버튼 안에 "로그인"이 들어간다. */} </button> ); } export default function LoginButtonStatusExample() { return <LoginButtonStatus isLogin={false} />; }// 출력결과 // 로그인
isLogin이false이므로 버튼에는로그인이 출력된다.
만약isLogin을true로 바꾸면 버튼 문구는로그아웃으로 바뀐다.
이처럼 조건부 렌더링은 안내 문구뿐 아니라 버튼, 메뉴, 입력 영역 등 다양한 화면에 적용할 수 있다.
14. AdminMenu: && 조건부 렌더링
14-1. 개념 복습
&&연산자는 조건이 참일 때만 특정 화면을 보여주고 싶을 때 사용한다.
거짓일 때 따로 보여줄 내용이 없다면 삼항 연산자보다&&가 더 간단하다.
AdminMenu예제에서는Home링크는 항상 보이고,Admin링크는 관리자일 때만 보인다.
14-2. 코드 흐름
// EduApp3.jsx export function AdminMenu({ isAdmin }) { return ( <nav> <a href="/">Home</a> {/* Home 링크는 조건 없이 항상 화면에 출력된다. */} <br /> {isAdmin && <a href="/admin">Admin</a>} {/* isAdmin이 true이면 <a href="/admin">Admin</a>가 화면에 출력된다. */} {/* isAdmin이 false이면 Admin 링크는 화면에 출력되지 않는다. */} </nav> ); }
Home링크는 조건 없이 항상 렌더링된다.
반면Admin링크는{isAdmin && <a href="/admin">Admin</a>}안에 있다.
isAdmin이true이면&&뒤의Admin링크가 렌더링된다.
isAdmin이false이면Admin링크는 렌더링되지 않는다.
14-3. false 결과물 확인
isAdmin값이false이면&&뒤의Admin링크는 렌더링되지 않는다.
그래서 화면에는Home만 보인다.
14-4. true 결과물 확인
isAdmin값이true이면&&뒤의Admin링크도 함께 렌더링된다.
정리하면&&조건부 렌더링은 조건이 참일 때만 특정 요소를 보여주고 싶을 때 사용한다.
14-5. 추가 응용 예시
이번에는 관리자일 때만 관리자 안내 문구를 추가로 보여준다.
메뉴뿐 아니라 특정 영역 전체도&&로 조건부 렌더링할 수 있다.
// AdminMessageExample.jsx function AdminMessage({ isAdmin }) { return ( <section> <h1>메인 화면</h1> {isAdmin && ( <div> <h2>관리자 전용 영역</h2> <p>회원 관리와 게시글 관리를 사용할 수 있다.</p> </div> )} {/* isAdmin이 true이면 div 전체가 화면에 출력된다. */} {/* isAdmin이 false이면 div 전체가 출력되지 않는다. */} </section> ); } export default function AdminMessageExample() { return <AdminMessage isAdmin={true} />; }// 출력결과 // 메인 화면 // 관리자 전용 영역 // 회원 관리와 게시글 관리를 사용할 수 있다.
isAdmin이true이므로 관리자 전용 영역이 화면에 표시된다.
isAdmin이false이면<div>전체가 렌더링되지 않는다.
15. PostList: map과 key로 리스트 렌더링
15-1. 개념 복습
리스트 렌더링은 배열 데이터를 반복해서 화면에 출력하는 방식이다.
React에서는 배열의map()메서드를 사용해 각 데이터를JSX로 바꾼다.
반복해서 만들어지는 요소에는key가 필요하다.
key는 각 항목을 구분하기 위해React가 내부적으로 사용하는 값이다.
15-2. 코드 흐름
// EduApp3.jsx export function PostList() { const posts = [ { id: 1, title: 'React 기초' }, { id: 2, title: 'JSX 문법' }, { id: 3, title: '컴포넌트 분리' }, ]; return ( <ul> {posts.map((post) => { return <li key={post.id}>{post.title}</li>; })} </ul> ); }
posts배열에는 게시글 객체 3개가 들어 있다.
각 객체는id와title을 가진다.
먼저posts배열의 모양은 아래와 같다.const posts = [ { id: 1, title: 'React 기초' }, { id: 2, title: 'JSX 문법' }, { id: 3, title: '컴포넌트 분리' }, ];이 배열에서 첫 번째 객체는 아래 데이터이다.
{ id: 1, title: 'React 기초' }두 번째 객체는 아래 데이터이다.
{ id: 2, title: 'JSX 문법' }세 번째 객체는 아래 데이터이다.
{ id: 3, title: '컴포넌트 분리' }
이제 `map()`이 배열 안의 객체를 하나씩 꺼낸다. ```jsx posts.map((post) => { return{post.title} ; }); ``` 첫 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 1, title: 'React 기초' } ``` 그러면 `post.id`는 `1`이고, `post.title`은 `'React 기초'`이다. ```jsxReact 기초 ``` 두 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 2, title: 'JSX 문법' } ``` 그러면 `post.id`는 `2`이고, `post.title`은 `'JSX 문법'`이다. ```jsxJSX 문법 ``` 세 번째 반복에서 `post`에는 아래 객체가 들어온다. ```jsx { id: 3, title: '컴포넌트 분리' } ``` 그러면 `post.id`는 `3`이고, `post.title`은 `'컴포넌트 분리'`이다. ```jsx컴포넌트 분리 ``` 결국 `posts` 배열은 아래와 같은 `li` 목록으로 바뀐다. 이때 `key`는 `React`가 내부에서 항목을 구분하기 위해 사용하는 값이다. 브라우저 화면에 직접 보이는 값은 아니다. ```jsx // React가 리스트 항목을 구분할 때 사용하는 JSX 구조``` 실제 화면에는 `key`가 보이지 않고, 아래 텍스트만 목록으로 출력된다. ```javascript // 출력결과 // React 기초 // JSX 문법 // 컴포넌트 분리 ``` `map()`은 배열의 데이터를 하나씩 꺼내서 화면에 보여줄 `JSX` 요소로 바꾸는 역할을 한다.
- React 기초
- JSX 문법
- 컴포넌트 분리
### 15-3. 결과물 확인
map()은 배열 데이터를 반복하면서 각 항목을JSX화면 조각으로 바꾼다.
결과물에는React 기초,JSX 문법,컴포넌트 분리가 목록으로 출력된다.
이것은posts배열의 각 객체가<li>요소로 바뀐 결과이다.
정리하면 리스트 렌더링은 배열 데이터를map()으로 반복하면서JSX로 변환하는 방식이다.
15-4. 추가 응용 예시
이번에는 게시글 제목뿐 아니라 작성자도 함께 출력해 본다.
객체 배열에서는 필요한 속성을 여러 개 꺼내 화면에 사용할 수 있다.
// PostListWriterExample.jsx export default function PostListWriterExample() { const posts = [ { id: 1, title: 'React 기초', writer: '둘리' }, { id: 2, title: 'JSX 문법', writer: '도우너' }, { id: 3, title: '컴포넌트 분리', writer: '마이콜' }, ]; // 각 게시글 객체에는 id, title, writer 값이 함께 들어 있다. return ( <ul> {posts.map((post) => ( <li key={post.id}> {post.title} - 작성자: {post.writer} </li> ))} </ul> ); }// 출력결과 // React 기초 - 작성자: 둘리 // JSX 문법 - 작성자: 도우너 // 컴포넌트 분리 - 작성자: 마이콜이 예시는
post.title과post.writer를 함께 출력한다.
배열 안의 객체에서 여러 속성을 꺼내 화면을 구성하는 흐름이다.
16. ChildrenTest: children props
16-1. 개념 복습
children은 컴포넌트 태그 사이에 작성한 내용을 전달받는 특별한props이다.
일반props는 태그 속성으로 전달하지만,children은 시작 태그와 종료 태그 사이의 내용이 전달된다.
예를 들어<Panel>내용</Panel>에서내용부분이children으로 전달된다.
자식 컴포넌트는{children}위치에 그 내용을 출력한다.
16-2. 코드 흐름
// EduApp3.jsx function Panel({ children }) { return <section className="panel">{children}</section>; } export function ChildrenTest() { return ( <Panel> <h2>공지사항</h2> <p>React 수업을 시작한다.</p> </Panel> ); }
children은 컴포넌트 태그 사이에 작성한 내용이 자동으로 전달되는 특별한props이다.
먼저 부모 역할을 하는ChildrenTest는Panel태그 사이에 아래 내용을 넣는다.<Panel> <h2>공지사항</h2> <p>React 수업을 시작한다.</p> </Panel>이때
Panel태그 사이에 있는 내용은 아래 두 개이다.<h2>공지사항</h2> <p>React 수업을 시작한다.</p>이 두 요소가
Panel컴포넌트의children값으로 전달된다.
즉,Panel이 받는 값은 아래와 비슷하게 이해하면 된다.children = ( <> <h2>공지사항</h2> <p>React 수업을 시작한다.</p> </> );이제
Panel컴포넌트를 보면 된다.function Panel({ children }) { return <section className="panel">{children}</section>; }
Panel은 전달받은children값을<section>안의{children}위치에 넣는다.
그래서 최종 구조는 아래처럼 만들어진다.<section className="panel"> <h2>공지사항</h2> <p>React 수업을 시작한다.</p> </section>
children은 컴포넌트 태그 사이에 작성한 내용을 다른 컴포넌트 안으로 그대로 옮겨 넣는 역할을 한다.
16-3. 결과물 확인
컴포넌트 태그 사이에 작성한 내용은
children이라는 특별한props로 전달된다.
결과물에서는Panel내부에공지사항과React 수업을 시작한다.가 출력된다.
즉, 태그 사이에 작성한 내용이{children}위치에 들어간 것이다.
정리하면children은 컴포넌트의 공통 틀은 유지하면서 내부 내용만 바꿔 넣고 싶을 때 사용한다.
16-4. 추가 응용 예시
이번에는
Card컴포넌트를 만들어 본다.
카드의 겉 틀은Card가 담당하고, 내부 내용은children으로 받는다.
// CardChildrenApplyExample.jsx function Card({ children }) { return ( <section className="card"> {children} </section> ); } export default function CardChildrenApplyExample() { return ( <Card> <h2>회원 정보</h2> <p>이름: 둘리</p> <button>수정하기</button> </Card> ); }// 출력결과 // 회원 정보 // 이름: 둘리 // 수정하기이 예시는
Card컴포넌트를 공통 레이아웃처럼 사용한다.
안에 들어가는 제목, 설명, 버튼은 부모가 자유롭게 작성한다.
그래서children을 사용하면 카드, 모달, 패널 같은 재사용 가능한 컴포넌트를 만들기 좋다.
EduApp4.jsx는useState()를 사용해 화면에 영향을 주는 값을 관리하고, 버튼 이벤트로 상태를 변경하는 예제이다.
state는 컴포넌트 내부에서 직접 관리하는 변경 가능한 데이터이다.
버튼 클릭으로 값이 바뀌고, 바뀐 값이 화면에 다시 표시된다면state를 사용해야 한다.
이 예제에서는 숫자 상태, 객체 상태, 배열 상태를 각각 변경하는 흐름을 확인한다.
17. Counter: useState로 숫자 상태 변경하기
17-1. 개념 복습
useState()는 함수형 컴포넌트에서state를 만들 때 사용하는Hook이다.
Hook은 함수형 컴포넌트에서React기능을 사용할 수 있게 해주는 특별한 함수이다.
useState(0)을 호출하면 현재 상태 값과 그 값을 변경하는 함수가 함께 만들어진다.
보통const [count, setCount] = useState(0)처럼 작성한다.
count는 현재 값이고,setCount는 값을 바꾸도록React에게 요청하는 함수이다.
17-2. 코드 흐름
// EduApp4.jsx import { useState } from 'react'; export function Counter() { const [count, setCount] = useState(0); // count에는 처음에 숫자 0이 저장된다. // setCount는 count 값을 새 값으로 바꾸도록 React에 요청하는 함수이다. return ( <section> <p>현재 값: {count}</p> {/* 처음에는 count 값 0이 {count} 위치에 들어가 "현재 값: 0"이 출력된다. */} <button onClick={() => setCount(count + 1)}>증가</button> {/* 버튼을 클릭하면 현재 count 값에 1을 더한 값이 setCount로 전달된다. */} {/* count가 0이면 setCount(1)이 실행되고, 화면은 "현재 값: 1"로 다시 렌더링된다. */} </section> ); }처음 렌더링될 때
count의 값은0이다.
그래서 화면에는현재 값: 0이 출력된다.
버튼을 클릭하면onClick에 연결된 화살표 함수가 실행된다.
그 안에서setCount(count + 1)이 호출된다.
그러면count값이1증가하고,React가 화면을 다시 렌더링한다.
17-3. 결과물 확인
setCount()로 상태 변경을 요청하면React가 변경된count값을 기준으로 화면을 다시 렌더링한다.
결과물에서는 처음에현재 값: 0이 보이고,증가버튼을 누를 때마다 값이1씩 증가한다.
이 화면 변화는 일반 변수를 바꿔서 생기는 것이 아니라,setCount()로state변경을 요청했기 때문에 일어난다.
state변경 함수가 호출되면React는 변경된 값을 기준으로 화면을 다시 렌더링한다.
17-4. 추가 응용 예시
이번에는 증가 버튼과 감소 버튼을 함께 만들어 본다.
하나의state값을 여러 이벤트로 변경할 수 있다.
// CounterPlusMinusExample.jsx import { useState } from 'react'; export default function CounterPlusMinusExample() { const [count, setCount] = useState(0); return ( <section> <p>현재 값: {count}</p> <button onClick={() => setCount(count + 1)}>증가</button> <button onClick={() => setCount(count - 1)}>감소</button> </section> ); }// 초기 출력결과 // 현재 값: 0 // 증가 버튼 클릭 후 // 현재 값: 1 // 감소 버튼 클릭 후 // 현재 값: 0이 예시에서는
증가버튼과감소버튼이 같은count값을 바꾼다.
setCount(count + 1)은 값을 증가시키고,setCount(count - 1)은 값을 감소시킨다.
하나의state를 여러 이벤트에서 변경할 수 있다는 점을 확인할 수 있다.
18. StateTest: 객체 state 변경
18-1. 개념 복습
객체를
state로 관리할 때는 기존 객체를 직접 수정하지 않는다.
기존 객체를 직접 바꾸면React가 변경을 안정적으로 인식하기 어렵다.
그래서 객체state를 변경할 때는 기존 객체를 복사한 새 객체를 만든다.
이때 스프레드 연산자...를 자주 사용한다.
예를 들어{ ...user, name: '또치' }는 기존user객체의 내용을 복사하고,name만 새 값으로 덮어쓴 새 객체이다.
18-2. 코드 흐름
// EduApp4.jsx import { useState } from 'react'; export function StateTest() { const [user, setUser] = useState({ name: '둘리', age: 10 }); const updateUserName = () => { // 기존 user를 펼치고 name만 덮어쓴다. const nextUser = { ...user, name: '또치' }; setUser(nextUser); }; const increaseUserAge = () => { // 기존 user를 펼치고 age만 1 증가시킨다. const nextUser = { ...user, age: user.age + 1 }; setUser(nextUser); }; return ( <div style={{ padding: '20px' }}> <h1>React State 테스트</h1> <section style={{ marginBottom: '30px', borderBottom: '1px solid #ccc' }}> <h2>객체 상태 변경</h2> <p>이름: {user.name} / 나이: {user.age}</p> <button onClick={updateUserName}>이름 변경 (또치)</button> <button onClick={increaseUserAge}>나이 증가</button> </section> </div> ); }이 코드는 객체
state인user를 변경하는 흐름을 보기 쉽게 정리한 예시이다.
처음user값은{ name: '둘리', age: 10 }이다.
그래서 화면에는이름: 둘리 / 나이: 10이 출력된다.
객체state를 바꿀 때 핵심은 기존 객체를 직접 고치지 않는 것이다.
기존 객체를 펼쳐서 새 객체를 만들고, 바꾸고 싶은 속성만 뒤에서 덮어쓴다.
이름 변경 흐름
이름 변경 (또치)버튼에는updateUserName함수가 연결되어 있다.<button onClick={updateUserName}>이름 변경 (또치)</button>버튼을 클릭하면 아래 함수가 실행된다.
const updateUserName = () => { const nextUser = { ...user, name: '또치' }; setUser(nextUser); };여기서 가장 중요한 코드는 아래 한 줄이다.
const nextUser = { ...user, name: '또치' };현재
user값이 아래와 같다고 생각하면 된다.{ name: '둘리', age: 10 }먼저
...user는 기존user객체 안의 값을 새 객체 안으로 펼친다.{ ...user }이 단계의 결과는 아래와 같다.
{ name: '둘리', age: 10 }그 다음 뒤에
name: '또치'가 붙는다.{ ...user, name: '또치' }객체에서 같은 속성 이름이 뒤에 다시 나오면, 뒤에 있는 값이 앞의 값을 덮어쓴다.
그래서name: '둘리'가name: '또치'로 바뀐다.{ name: '둘리', age: 10, name: '또치' }위 흐름은 실제로는 아래 객체로 정리된다.
{ name: '또치', age: 10 }따라서
nextUser에는 아래 객체가 저장된다.const nextUser = { name: '또치', age: 10 };이 새 객체를
setUser()에 전달한다.setUser(nextUser);그러면
React는user값이 새 객체로 바뀌었다고 판단하고 화면을 다시 렌더링한다.// 출력결과 // 이름: 또치 / 나이: 10이 흐름에서
age는 바뀌지 않는다.
...user로 기존age: 10을 복사했고, 뒤에서age를 다시 덮어쓰지 않았기 때문이다.
나이 증가 흐름
나이 증가버튼에는increaseUserAge함수가 연결되어 있다.<button onClick={increaseUserAge}>나이 증가</button>버튼을 클릭하면 아래 함수가 실행된다.
const increaseUserAge = () => { const nextUser = { ...user, age: user.age + 1 }; setUser(nextUser); };여기서 가장 중요한 코드는 아래 한 줄이다.
const nextUser = { ...user, age: user.age + 1 };현재
user값이 아래와 같다고 생각하면 된다.{ name: '둘리', age: 10 }먼저
...user는 기존user객체 안의 값을 새 객체 안으로 펼친다.{ ...user }이 단계의 결과는 아래와 같다.
{ name: '둘리', age: 10 }그 다음 뒤에
age: user.age + 1이 붙는다.{ ...user, age: user.age + 1 }현재
user.age는10이다.
그래서user.age + 1은11이 된다.age: 10 + 1계산 결과는 아래와 같다.
age: 11객체에서 같은 속성 이름이 뒤에 다시 나오면, 뒤에 있는 값이 앞의 값을 덮어쓴다.
그래서age: 10이age: 11로 바뀐다.{ name: '둘리', age: 10, age: 11 }위 흐름은 실제로는 아래 객체로 정리된다.
{ name: '둘리', age: 11 }따라서
nextUser에는 아래 객체가 저장된다.const nextUser = { name: '둘리', age: 11 };이 새 객체를
setUser()에 전달한다.setUser(nextUser);그러면
React는 변경된user값을 기준으로 화면을 다시 렌더링한다.// 출력결과 // 이름: 둘리 / 나이: 11이 흐름에서
name은 바뀌지 않는다.
...user로 기존name: '둘리'를 복사했고, 뒤에서name을 다시 덮어쓰지 않았기 때문이다.
만약이름 변경 (또치)버튼을 먼저 누른 상태라면 현재user의name은또치이다.
그 상태에서나이 증가버튼을 누르면name은또치로 유지되고,age만 증가한다.// 이름 변경 후 나이 증가 버튼 클릭 결과 // 이름: 또치 / 나이: 11객체
state에서 특정 속성만 바꾸면, 나머지 속성은...user로 복사된 기존 값이 그대로 유지된다.
18-3. 결과물 확인
객체
state를 변경할 때는 기존 객체를 직접 고치지 않고, 스프레드 연산자로 복사한 새 객체를 전달한다.
결과물에서는 처음에이름: 둘리 / 나이: 10이 보인다.
이름 변경 (또치)버튼을 누르면 이름만또치로 바뀌고, 나이는 그대로 유지된다.
나이 증가버튼을 누르면 이름은 유지되고 나이만 증가한다.
즉,{ ...user, name: '또치' }와{ ...user, age: user.age + 1 }은 기존 객체 전체를 직접 수정하는 것이 아니라, 기존 값을 복사한 새 객체를 만들어 필요한 속성만 바꾸는 방식이다.
18-4. 추가 응용 예시
이번에는
user객체에address값을 추가해 본다.
기존name과age는 유지하면서address만 변경하는 흐름이다.
// ObjectStateAddressExample.jsx import { useState } from 'react'; export default function ObjectStateAddressExample() { const [user, setUser] = useState({ name: '둘리', age: 10, address: '쌍문동', }); return ( <section> <p>이름: {user.name}</p> <p>나이: {user.age}</p> <p>주소: {user.address}</p> <button onClick={() => setUser({ ...user, address: '고길동 집' })}> 주소 변경 </button> </section> ); }// 초기 출력결과 // 이름: 둘리 // 나이: 10 // 주소: 쌍문동 // 주소 변경 버튼 클릭 후 // 이름: 둘리 // 나이: 10 // 주소: 고길동 집
setUser({ ...user, address: '고길동 집' })는 기존user객체를 복사한 뒤address만 바꾼다.
그래서name과age는 그대로 유지된다.
객체state를 바꿀 때는 바꾸고 싶은 속성만 덮어쓴 새 객체를 만들어 전달해야 한다.
19. StateTest: 배열 state 변경
19-1. 개념 복습
배열을
state로 관리할 때도 기존 배열을 직접 수정하지 않는다.
예를 들어items.push('포도')처럼 기존 배열을 직접 바꾸는 방식은 피해야 한다.
대신 새 배열을 만들어setItems()에 전달한다.
배열에 값을 추가할 때는[...items, '포도']처럼 작성할 수 있다.
배열에서 마지막 값을 제거할 때는items.slice(0, -1)처럼 새 배열을 만들 수 있다.
19-2. 코드 흐름
// EduApp4.jsx import { useState } from 'react'; export function StateTest() { const [items, setItems] = useState(['사과', '바나나']); const addItem = () => { const nextItems = [...items, '포도']; setItems(nextItems); }; const removeLastItem = () => { const nextItems = items.slice(0, -1); setItems(nextItems); }; return ( <section> <h2>배열 상태 변경</h2> <ul> {items.map((item, index) => { return <li key={index}>{item}</li>; })} </ul> <button onClick={addItem}>항목 추가 (포도)</button> <button onClick={removeLastItem}>마지막 항목 삭제</button> </section> ); }이 코드는 배열
state인items를 변경하는 흐름을 보기 쉽게 정리한 예시이다.
처음items값은['사과', '바나나']이다.
그래서 화면에는사과,바나나가 목록으로 출력된다.
배열state를 바꿀 때 핵심은 기존 배열을 직접 고치지 않는 것이다.
기존 배열을 펼쳐 새 배열을 만들거나, 기존 배열에서 일부를 잘라낸 새 배열을 만들어setItems()에 전달해야 한다.
항목 추가 흐름
항목 추가 (포도)버튼에는addItem함수가 연결되어 있다.<button onClick={addItem}>항목 추가 (포도)</button>버튼을 클릭하면 아래 함수가 실행된다.
const addItem = () => { const nextItems = [...items, '포도']; setItems(nextItems); };여기서 가장 중요한 코드는 아래 한 줄이다.
const nextItems = [...items, '포도'];현재
items값이 아래와 같다고 생각하면 된다.['사과', '바나나']먼저
...items는 기존items배열 안의 값을 새 배열 안으로 펼친다.[...items]이 단계의 결과는 아래와 같다.
['사과', '바나나']그 다음 뒤에
'포도'가 붙는다.[...items, '포도']그러면 새 배열은 아래처럼 만들어진다.
['사과', '바나나', '포도']따라서
nextItems에는 아래 배열이 저장된다.const nextItems = ['사과', '바나나', '포도'];이 새 배열을
setItems()에 전달한다.setItems(nextItems);그러면
React는items값이 새 배열로 바뀌었다고 판단하고 화면을 다시 렌더링한다.// 출력결과 // 사과 // 바나나 // 포도
마지막 항목 삭제 흐름
마지막 항목 삭제버튼에는removeLastItem함수가 연결되어 있다.<button onClick={removeLastItem}>마지막 항목 삭제</button>버튼을 클릭하면 아래 함수가 실행된다.
const removeLastItem = () => { const nextItems = items.slice(0, -1); setItems(nextItems); };여기서 가장 중요한 코드는 아래 한 줄이다.
const nextItems = items.slice(0, -1);현재
items값이 아래와 같다고 생각하면 된다.['사과', '바나나', '포도']
slice(0, -1)은 처음부터 마지막 바로 전까지의 값을 잘라 새 배열을 만든다.items.slice(0, -1)이 단계의 결과는 아래와 같다.
['사과', '바나나']따라서
nextItems에는 아래 배열이 저장된다.const nextItems = ['사과', '바나나'];이 새 배열을
setItems()에 전달한다.setItems(nextItems);그러면
React는items값이 새 배열로 바뀌었다고 판단하고 화면을 다시 렌더링한다.// 출력결과 // 사과 // 바나나
map으로 화면에 출력되는 흐름
items배열은map()을 통해 화면에 출력된다.{items.map((item, index) => { return <li key={index}>{item}</li>; })}처음
items값이 아래와 같다면,['사과', '바나나']첫 번째 반복에서
item에는'사과'가 들어온다.
index에는0이 들어온다.<li key={0}>사과</li>두 번째 반복에서
item에는'바나나'가 들어온다.
index에는1이 들어온다.<li key={1}>바나나</li>이때
key는React가 반복 항목을 구분하기 위해 내부에서 사용하는 값이다.
브라우저 화면에 직접 보이는 값은 아니다.
실제 화면에는 아래처럼 항목 내용만 출력된다.// 출력결과 // 사과 // 바나나
항목 추가 (포도)버튼을 눌러items가['사과', '바나나', '포도']가 되면,map()은 세 번째 반복에서'포도'도 출력한다.<li key={2}>포도</li>실제 화면에는 아래처럼 보인다.
// 출력결과 // 사과 // 바나나 // 포도배열
state를 변경할 때는 기존 배열을 직접 고치지 않고, 새 배열을 만들어setItems()에 전달해야 한다.
19-3. 결과물 확인
배열
state를 변경할 때도 기존 배열을 직접 수정하지 않고, 새 배열을 만들어 상태 변경 함수에 전달해야 한다.
결과물에서는 처음에사과,바나나가 보인다.
항목 추가 (포도)버튼을 누르면 기존 항목 뒤에포도가 추가된다.
마지막 항목 삭제버튼을 누르면 배열의 마지막 항목이 제거된다.
이 흐름에서 핵심은items배열을 직접 수정하지 않는다는 점이다.
[...items, '포도']는 새 항목이 추가된 새 배열을 만들고,items.slice(0, -1)은 마지막 항목을 제외한 새 배열을 만든다.
배열state도 기존 배열을 직접 고치는 것이 아니라 새 배열을 만들어 변경해야 한다.
19-4. 추가 응용 예시
이번에는 입력창에 적은 값을 배열에 추가하는 예시를 만든다.
이 예시는 입력값state와 배열state를 함께 사용한다.
// ArrayStateInputAddExample.jsx import { useState } from 'react'; export default function ArrayStateInputAddExample() { const [text, setText] = useState(''); const [items, setItems] = useState(['사과', '바나나']); function addItem() { if (text.trim() === '') { return; } const nextItems = [...items, text]; setItems(nextItems); setText(''); } return ( <section> <input value={text} onChange={(event) => setText(event.target.value)} placeholder="추가할 항목 입력" /> <button onClick={addItem}>추가</button> <ul> {items.map((item, index) => { return <li key={index}>{item}</li>; })} </ul> </section> ); }// 출력결과 // 사과 // 바나나 // 입력창에 포도 입력 후 추가 버튼 클릭 // 사과 // 바나나 // 포도이 예시는 입력창의 값과 목록 배열을 각각
state로 관리한다.
text는 입력창에 현재 적힌 값을 저장한다.
items는 화면에 출력할 목록 데이터를 저장한다.
입력값이 text에 저장되는 흐름
입력창은 아래 코드로 만들어진다.
<input value={text} onChange={(event) => setText(event.target.value)} placeholder="추가할 항목 입력" />처음
text값은 빈 문자열이다.const [text, setText] = useState('');그래서 입력창도 처음에는 비어 있다.
사용자가 입력창에포도를 입력하면onChange가 실행된다.onChange={(event) => setText(event.target.value)}이때
event.target.value에는 사용자가 입력한 문자열이 들어 있다.event.target.value사용자가
포도를 입력했다면 값은 아래와 같다.'포도'이 값이
setText()에 전달된다.setText('포도');그러면
React는textstate를'포도'로 바꾸고 화면을 다시 렌더링한다.
이때 결과적으로text값은 아래처럼 이해하면 된다.// 변경 후 text state 값 // '포도'
추가 버튼을 눌렀을 때 흐름
추가버튼에는addItem함수가 연결되어 있다.<button onClick={addItem}>추가</button>버튼을 클릭하면 아래 함수가 실행된다.
function addItem() { if (text.trim() === '') { return; } const nextItems = [...items, text]; setItems(nextItems); setText(''); }먼저 입력값이 비어 있는지 검사한다.
if (text.trim() === '') { return; }
text가'포도'라면 빈 문자열이 아니므로 아래 코드로 넘어간다.const nextItems = [...items, text];현재
items값이 아래와 같다고 생각하면 된다.['사과', '바나나']현재
text값은 아래와 같다.'포도'
...items는 기존 배열의 값을 새 배열 안으로 펼친다.[...items]이 단계의 결과는 아래와 같다.
['사과', '바나나']그 뒤에
text값인'포도'가 붙는다.[...items, text]그래서
nextItems는 아래 배열이 된다.['사과', '바나나', '포도']이 새 배열을
setItems()에 전달한다.setItems(nextItems);그러면 화면 목록은 아래처럼 바뀐다.
// 출력결과 // 사과 // 바나나 // 포도마지막으로
setText('')가 실행된다.setText('');이 코드는 입력창의 값을 다시 빈 문자열로 바꾼다.
그래서 항목을 추가한 뒤 입력창이 비워진다.
입력값을 배열에 추가할 때는 입력값state와 배열state가 함께 움직인다. 입력창에 적은 값은text에 저장되고, 버튼을 누르면 그text값이 새 배열에 추가된다.