원글 : How to Get Started with React — A Modern Project-based Guide for Beginners (Including Hooks)
내용 중 일부 오역이 포함되어 있을 수 있으니, 가능하면 원문을 참조하시는 걸 추천드립니다.
리액트 시작하는 방법 - 초보자용 현대적 프로젝트 기반의 가이드(Hook 포함)
자, 리액트를 배우기 시작했나요? 그러면 잘 찾아왔습니다. 이 가이드는 리액트를 시작할 때, 알아야 할 모든 것을 포함합니다.
기본 개념 뒤에 "어떻게 그리고 왜"를 설명합니다. 그리고 API를 통해 데이터를 불러와 작은 프로젝트를 만들어서 모든 걸 직접 해볼 수 있습니다.
이 글은 매우 기니깐, 오른쪽 목차를 보고 필요에 따라 섹션을 넘기거나, 다시 읽으세요. 자 그럼, 음료수 옆에 두고, 안전띠 매고, 출발합니다.
준비물
이 가이드를 읽기 전에 리액트에 대해서 아무것도 몰라도 됩니다. 그러나 이 가이드를 모두 이해하려면, 친숙해야 하는 것이 있습니다.
기본 자바스크립트
리액트는 자바스크립트 라이브러리입니다. 그래서 리액트를 배우기 전에 자바스크립트를 알아야 합니다. 자바스크립트를 속속들이 모른다고 걱정하지 마세요. 알아야 할 것은 기본입니다.
- 변수, 함수 데이터 타입
- 배열과 객체
- ES6 분법 ( let, const, arrow functions, destructuring assignment, classes, importing/exporting, etc )
- 자바스크립트로 DOM을 조작하는 방법.
기본 HTML
리액트에서 웹페이지에서 HTML을 생성하기 위해 JSX라고 부르는 것을 사용합니다. JSX는 더 자세히 설명하겠지만, HTML의 기초는 알고 있어야 합니다.
- HTML의 구조 ( 엘리먼트를 어떻게 구조화하는지 )
- HTML 속성( id, class, onclick 등등 )
개발환경
제일 먼저 할 일은 개발환경을 만드는 것입니다. 이미 Node.js와 Visual Studio Code( 또는 다른 IDE )를 설치했다면 이 섹션은 넘기고 다음 섹션으로 이동하세요.
Node.js
여기로 가서 사용하는 OS(Mac/windows 등등)에 맞는 패키지를 다운로드하여 설치하세요. 설치가 완료되면 터미널을 열어 다음과 같이 입력합니다.
$ node -v
다음과 같이 버전이 출력되어야 합니다.
이 것은 node가 정상적으로 설치가 됐다는 것을 의미합니다. 와~ 만약 에러가 났다면, 다시 다운로드하여 설치해보세요.
Visual Studio Code
Visual Studio Code는 프런트엔드 개발자에게 아주 인기 많은 오픈소스 IDE입니다. 사용할 수 있는 수많은 IDE가 있습니다. 좋아할 만한 걸 찾아보고 맘에 드는 것을 설치해도 됩니다. 여기서는 VS Code를 사용합니다.
여기서 알맞은 버전을 다운로드합니다.
설치 단계를 잘 따라서 진행합니다. 설치가 완료되면 Visual Studio Code를 실행하세요.
지금은 이 정도 개발환경이면 충분합니다. 설치하면 좋은 것들이 더 많지만( VS Code extentions 등) 지금 당장은 필요 없습니다. - 지금은 리액트를 배우는 중이니까요.
리액트 앱 만들기
다음 단계는 리액트 프로젝트를 만드는 겁니다. 다행히도, 페이스북의 멋진 분들이 정말 간단하게 만들어놨어요. 우리가 할 일의 전부는 터미널에 아래 명령을 실행하는 것입니다.
$ npx create-react-app my-app
이건 "my-app"이라는 프로젝트를 생성하고, 모든 설정을 자동으로 해줍니다. 정말 좋아요.
앱을 만들 디렉터리( 예를 들어 projects 폴더)에서 터미널을 여세요. 그리고 명령을 실행합니다. 터미널이 앱을 생성하기 시작하고 잠시 후 몇 가지 명령들과 완료되었음을 알 수 있습니다.
create-react-app의 출력은 앱을 시작하기 위해 무얼 해야 하는지 알려줍니다. 터미널에서 명령을 실행해봅시다.
$ cd my-app
$ yarn start
이제 개발서버가 시작되고 웹 브라우저가 실행될 겁니다.
방금 첫 번째 리액트 앱을 만든 겁니다! 무슨 일이 벌어진 건지 더 알아보시려면 create-react-app 리파지토리에 가보세요. ( github.com/facebook/create-react-app )
Create React App 탐색.
Visual Studio Code( 또는 다른 IDE)를 열고 File -> Open.. 그리고 방금 create-react-app으로 만든 my-app 폴더를 선택하세요. 따끈따끈한 리액트 앱이 IDE에서 열릴 거예요. 이제 코딩을 할 수 있습니다!
먼저 프로젝트 구조를 살펴보세요.
엄청 많네요. 너무 많다고 걱정 마세요. 대부분 이번에는 사용하지 않는 자동으로 생성된 코드와 설정들입니다. 그렇지만, 호기심 많은 당신을 위해, 프로젝트 트리를 살펴보겠습니다.
Node Modules
여기는 우리가 NPM으로 설치하는 패키지들이 위치하는 곳입니다. 만약 NPM에 익숙하지 않다면, 다른 개발자가 코딩하지 않게 하기 위해 공유한 고귀한 코드들이 위치하는 곳이라 생각하시면 됩니다.
HTML을 작성하듯이, 스크립트 태그를 작성하는 대신, 애플리케이션의 일부로써 이 모듈을 설치합니다. 그러고 나서 모듈을 사용하기 위해 import statemet를 사용합니다. 이건 나중에 살펴볼게요.
Public Folder
여기는 우리의 코드가 묶이는 곳입니다. 앱을 배포할 준비가 되었을 때, 빌드 스크립트를 실행하고 만들어진 최종 파일들이 위치하게 됩니다. HTML, Javascript, CSS 파일들입니다. URL로 앱에 접근한 사용자가 볼 수 있도록 하기 위함입니다.
Index.html
index.html은 진입점, 또는 URL로 접근한 사용자에게 보여주기 위해 브라우저가 로딩하는 첫 번째 페이지입니다.
이 파일을 살펴보면, 일반적인 HTML을 가지는 그냥 HTML 파일입니다. body 부분을 살펴보면 비어있습니다. 리액트는 "root" div에 리액트 코드를 변환하여 HTML로 변환하여 채워둡니다.
이러한 과정을, 핵심 코드를 통해서 살펴보도록 하겠습니다.
첫 번째 컴포넌트
프로젝트에서 App.js를 엽니다. 이 파일은 애플리케이션의 메인 컴포넌트입니다. 그리고 이 파일은 화면에 그려질 첫 번째 컴포넌트입니다. 매우 중요한 컴포넌트이죠.
이 중요한 컴포넌트에 가장 먼저 할 일은 모든 걸 지우고, 무엇을 하는지 더 잘 이해하기 위해, 컴포넌트를 처음부터 만드는 것입니다.
이제 빈 공백에 리액트를 임포트 하는 것으로 시작합니다. 리액트 라이브러리를 사용할 수 있도록 하는 기능입니다.
import React from "react";
다음은 함수를 선언합니다. 여기서 ES6 arrow functions을 사용할 겁니다. 컴포넌트는 몇 가지 기능과 마크업을 가지는 함수 그 이상 그 이하도 아닙니다. 그리고 이 함수를 어디서든 사용할 수 있도록 export 합니다.
const App = () => {
}
export default App;
함수에서 return()을 만들 겁니다. 이것이 컴포넌트로부터 반환되는 것이며, HTML이 그려지도록 하는 마크업을 포함합니다.
마지막에 <div>와 <h1> 타이틀 태그를 추가합니다. 우리가 만든 컴포넌트는 다음과 같습니다.
import React from "react";
const App = () => {
return (
<div>
<h1>Hello React World</h1>
<h2>
This is our first React App - isn't it marvellous?!
</h2>
</div>
);
}
export default App;
아마도, HTML이 함수에 있어??라고 생각할 수 있습니다. HTML 같이 생겼지만, 사실은 JSX(JavaScript XML)라고 불리는 것입니다. JSX는 기본적으로 Javascript와 HTML을 섞어서 사용할 수 있도록 해줍니다.
이상해 보일 수도 있습니다. 예전부터 HTML과 JavaScript를 분리하는 것을 배웠습니다. 그러나 Javascript와 앱 설계 방식은 발전했으며, 모든 것을 컴포넌트에 담음으로써 코드를 보다 쉽게 유지 관리하고 다시 사용할 있습니다.
한번 해보죠. 터미널을 열고 아래 명령을 실행하세요.
$ npm start
브라우저가 열리고, 앱이 실행되는 것을 볼 수 있을 겁니다.
축하합니다. 이제 막 첫 번째 컴포넌트를 만들었어요.
JSX
JSX를 생각하면 아마 주변에 물음표가 막 떠다닐 것 같은데요. 한번 자세히 살펴볼게요.
return (
<div>
<h1>Hello React World</h1>
<h2>
This is our first React App - isn't it marvellous?!
</h2>
</div>
);
HTML처럼 생겼지만, 아닙니다. 이건 JSX입니다. 일반적인 HTML처럼 보여도 리액트는 다음 문장으로 뒤에서 엘리먼트 트리를 만들고 있는 겁니다.
React.createElement(component, props, ...children)
- component : 만들려고 하는 HTML 엘리먼트 예. h1, div 등등
- props : 컴포넌트에 전달하고자 하는 props ( props는 뒤에 더 얘기합니다. )
- children: 이 엘리먼트 안에 속하는 HTML 엘리먼트 배열.
자, 같은 컴포넌트를 다음과 같이 코딩할 수도 있습니다.
const App = () => {
return (
React.createElement(
"div",
null,
React.createElement("h1", null, "Hello React World"),
React.createElement(
"h2",
null,
"This is our first React App - isn't it marvellous?!"
)
)
);
}
어떤 게 더 조잡해 보이나요. ( 일부러 조잡해 보이도록 만들었어요 ). 코드를 잘 따라가 보면, div를 만드는 부분을 발견할 수 있습니다. 마지막으로 h1과 h2엘리먼트를 createElement 문장으로 만들었습니다.
JavaScript를 오랫동안 사용해왔다면, 이것은 document.createElement 와 비슷하다는 것을 알아챘을 겁니다. 그리고 그것은 결국 Javascript 라이브러리라는 것이죠.
이게 리액트에서 JSX의 장접입니다. 복잡한 React.createElement() 없이 HTML 같은 문법으로 작성할 수 있게 해 줍니다.
실제로 리액트 개발자는 코드를 작성하는데 JSX를 대부분 사용합니다. 이 섹션은 시간 낭비가 아니었습니다. 항상 어떤 일이 발생하는지 이해하는 것이 좋습니다. 아는 것이 힘입니다.
동적으로 만들기.
JSX는 살펴봤으니, 두려움이 좀 사라졌을 겁니다. 하지만 중요한 것은 왜 JSX를 사용하느냐입니다. HTML을 사용할 수 있기 때문에? 비슷해 보여서? 맞나요?
좋은 질문입니다. 자 JSX가 의미하는 것을 기억해본다면 JavaScript XML입니다. 이건 동적으로 만들기 위해 JavaScript를 사용할 수 있다는 의미입니다. 이전 예제에서 입니다.
const App = () => {
return (
<div>
<h1>Hello React World</h1>
<h2>This is our first React App - isn't it marvellous?!</h2>
</div>
);
}
자 이제 텍스트를 좀 더 동적으로 만들어 볼게요. 먼저 메시지를 담을 변수 하나를 추가해 줍니다.
const message = "This is my first variable rendered in JSX!"
이제 중괄호를 사용해서 자바스크립트를 추가해 줍니다.
const App = () => {
const message = "This is my first variable rendered in JSX!";
return (
<div>
<h1>Hello React World</h1>
<h2>{message}</h2>
</div>
);
}
브라우저에서 실행하면, 메시지 변수의 문자열이 출력되는 걸 보실 수 있을 겁니다. 메시지 변수에 문장을 변경하고 어떤 일이 벌어지는지 살펴보세요.
중괄호를 사용해서 컴파일러에게 "여기서는 Javascript를 실행해"라고 알려줬습니다. 중괄호를 사용하지 않았다면, JavaScript가 실행되는 대신 "message"가 표시되게 됩니다. 한번 해보세요.
이벤트 핸들링
이벤트 핸들리도 같은 식으로 할 수 있습니다. JSX를 사용할 때, 리액트는 이미 익숙할 수 있는 onClick, onPress, onSubmit 같은 이벤트 리스너에 접근할 수 있게 해 줍니다.
메시지가 클릭됐을 때, 경고를 표시하고 싶다고 해봅시다. 먼저 OnClick 속성을 h2 태그에 추가합니다.
onClick 속성은 함수를 받습니다. ( 다시 말해 함수를 인자로 전달할 수 있습니다. ) 이 함수는 alert를 호출합니다.
const App = () => {
const message = "This is my first variable rendered in JSX!";
return (
<div>
<h1>Hello React World</h1>
<h2 onClick={()=> alert("you clicked the message!")}>{message}</h2>
</div>
);
}
arrow function을 사용해서 멋지고 간결한 인라인 함수를 만드는 방법에 주목하세요.
다시 말해, 함수가 Javascript로 실행되도록 코드를 중괄호 안에 넣는 방법을 알아 두세요.
함수 호출
위의 예제에서 인라인 함수를 살펴봤습니다. JSX가 Javascript이기 때문에 return 블록 밖에서 함수를 만들고 참조할 수 있습니다. 마지막 예제는 다음과 같이 할 수 도 있습니다.
const App = () => {
const message = "This is my first variable rendered in JSX!";
const handleClick = () =>{
alert("you clicked the message!");
}
return (
<div>
<h1>Hello React World</h1>
<h2 onClick={handleClick}>{message}</h2>
</div>
);
}
alert를 호출하는 handleClick함수를 선언하는 방법을 보세요. 인라인 함수를 사용하는 대신, onClick 속성에서 이 함수를 참조합니다. 이제 어떤 일이 생기는지 한번 해보세요.
이것은 동적으로 만들기 위해 Javascript를 사용하는 방법의 간단한 예제이고 JSX의 강력함을 보여주고 싶었습니다. 예제를 더 만들어가면서 더 깊게 이해할 수 있을 테니, 아직 잘 모르겠다고 너무 걱정하지 마세요.
컴포넌트가 그려지는 방법
JSX에 대한 궁금증은 어느 정도 해소되었기를 바랍니다. 다음 궁금증은 컴포넌트가 어떻게 그려지나? 어디서? 언제?입니다.
처음부터 시작해보시죠. 우리의 파일 구조를 보면, index.js 파일이 있습니다. 이 파일이 첫 번 채로 실행되는 파일입니다. (가끔은 "Entry Point"라고 부릅니다) 이 것은 관습적으로 정해진 것입니다. - 변경할 수는 있지만, 여기서는 하지 않을 겁니다.
파일을 좀 살펴보면, 아래 줄을 확인 할 수 있습니다.
ReactDOM.render(<App />, document.getElementById("root"));
document.getElementById("root")를 볼 수 있습니다. 일반적인 Javascirpt입니다. DOM에서 root 엘리먼트를 가져와서 App 컴포넌트를 안에 넣습니다. App 컴포넌트는 다음과 같이 임포트 됩니다.
import App from "./App"
App.js의 app 컴포넌트에서 export 한 것을 기억해보세요. 이것은 다른 파일 혹은 컴포넌트에서 import 하여 사용할 수 있습니다.
그래서 root 엘리먼트는 어디서 나타난 겁니까? 자 index.html 파일을 기억해보세요. 이 index.html 이 제일 처음 실행될 파일입니다.
root라는 id를 가지는 div가 있습니다. 이것이 리액트가 컴포넌트를 로딩하는 곳입니다. 개발자 도구로 좀 더 살펴보죠.
크롬( 또는 다른 브라우저)을 열어서 개발자 도구를 여세요. "root" ID를 가지는 div를 찾을 수 있고 앱 컴포넌트가 그려진 것을 확인할 수 있습니다.
요약.
다음으로 가기 전에, 빠르게 요약해보겠습니다.
- 앱의 뼈대가 되는 index.html 파일이 있습니다.
- 앱이 시작되면, index.html 파일이 로드되고 앱 컴포넌트가 임포트 됩니다.
- 앱 컴포넌트의 JSX는 HTML로 변환되어 index.html의 root div에 그려집니다.
연락처 목록 만들기
이제 리액트에 발을 담갔습니다. 어떻게 여기는지, 배운 것을 가지고 앱을 만들면서 더 깊이 이해해 보도록 하겠습니다. 리액트를 시작하기 위해 도움될만한 리액트의 몇 가지 공통 적인 기능들을 배울 겁니다..
연락처 목록은 이름 이메일 나이 프로필을 담고 있는 연락처를 보여줍니다.
차근차근 데이터도 넣어가면서 하겠습니다. 신나지 않으신가요?
스타일 가져오기
이건 리액트 튜토리얼이기 때문에 리액트에 집중하고 멋진 스타일을 만드는 것은 걱정하지 마세요. 소스 폴더에 새로운 파일 style.css를 만들고 아래 코드를 붙여 넣으세요.
.contact-card {
display: flex;
padding: 10px;
color: #ffffff;
background-color: rgb(42, 84, 104);
font-family: "Segoe UI", Tahoma, Geneva, Verdana, sans-serif;
box-shadow: 10px 10px 25px -16px rgba(0, 0, 0, 0.75);
border-radius: 10px;
max-width: 500px;
max-height: 125px;
margin-bottom: 10px;
}
.contact-card p {
margin-left: 10px;
margin-top: 0;
}
button {
margin-left: 10px;
margin-bottom: 10px;
}
다음은 App.js로 이동해서 스타일 시트를 임포트 해줍니다.
import "./styles.css";
연락처 카드 만들기
아직 App.js에 있습니다. 연락처 카드의 기본 구조를 JSX로 추가해 줍니다.
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
<p>Age: 25</p>
</div>
</div>
div가 연락처 카드의 상세, 이미지를 담습니다. 그리고 p태그를 사용해서 상세 내용을 넣고 마지막으로 CSS 클래스를 적어줍니다.
CSS클래스를 참조하기 위해, className을 사용해야 합니다. 이 것은 우리가 JSX를 작성하고 있으며, javascirpt에서 class가 예약어이기 때문입니다.
이제 App.js 파일은 다음과 같이 되었을 겁니다.
import React from "react";
import "./styles.css";
const App = () => {
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
<p>Age: 25</p>
</div>
</div>
);
}
이제 브라우저에서 실행하면 다음과 같이 볼 수 있습니다.
연락처 카드 재사용하기
좋습니다. 이제 연락처 카드가 생겼어요. 하지만, 계속 사용할 수는 없습니다. 다시 사용할 수 있는 코드가 필요하다는 걸 잘 알고 있습니다. 그래서 컴포넌트를 좀 쪼갭니다.
쉽게 따라 하게 하기 위해, 모든 컴포넌트를 App.js에 몰았습니다. 실제로는 컴포넌트를 다른 파일로 분리하여 import/export 하는 방식으로 사용합니다.
App 함수 바로 아래, 연락처 카드 함수를 추가합니다. 그리고 JSX를 복사해 옵니다.
const ContactCard = () => {
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
<p>Age: 25</p>
</div>
</div>
);
};
다시 말하면, 리액트에서 컴포넌트는 단지 JSX를 리턴하는 함수입니다. 이제 JSXJSX를 ContactCard로 옮겼으므로이 구성 요소를 기본 앱 구성 요소 내에서 사용할 수 있습니다.
const App = () => {
return (
<>
<ContactCard />
</>
);
}
이전 HTML / JSX 태그와 같은 컴포넌트를 사용합니다. 컴포넌트 이름을 중괄호 안에 넣습니다. App.js 파일은 다음과 같아야 합니다.
// App.js
import React from "react";
import "./styles.css";
const App = () => {
return (
<>
<ContactCard />
</>
);
};
const ContactCard = () => {
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
<p>Age: 25</p>
</div>
</div>
);
};
이제 브라우저를 실행해 보면, 이전에 했던 것과 같은 결과를 볼 수 있습니다. 이제 연락처 카드 컴포넌트를 가지고 여러 개 만들 수 있습니다.
const App = () => {
return (
<>
<ContactCard />
<ContactCard />
<ContactCard />
</>
);
};
다른 두 개의 연락처를 가지는 App컴포넌트로 변경했습니다. 위 예제는 3개의 컴포넌트를 가지는 예제입니다.
State에 대한 이야기
이미 리액트를 사용해왔다면, state라는 말을 들어봤을 겁니다. state는 리액트에서 매우 중요한 부분입니다. 그래서 뭐?
State는 기본적으로 화면이 반응하는 즉 앱의 변경되는 부분을 표현하는 객체입니다. State는 어떤 것도 가능합니다. 객체, 불리언, 배열, 문자열, 숫자 등
예를 들어보면,
연락처 목록에 있는 어떤 사람들은 부끄러워서 버튼을 클릭할 때까지 나이를 표시하고 싶어 하지 않습니다. 컴포넌트 내에서 userState훅을 사용하여, 나이를 표시해야 하는지 여부를 저장할 수 있습니다.
const [showAge, setShowAge] = useState(false);
"무슨 일이 일어나는 건가요? " 이제 설명할 께요.
useState 객체는 현재 값을 가진 변수와 그 값을 변경할 수 있는 함수를 제공합니다. useState를 호출하면, 초기 값을 정의할 수 있습니다.
초기 값을 얻기 위해 useState 훅에 구조 분해 할당(destructuring assignment)을 사용합니다. 지금은 구조 분해 할당에 대해 고민하지 마세요. 이것만 기억하세요. 첫째 변수는 state 값에 접근할 수 있게 해 준다. 두 번째 변수는 그것을 바꿀 수 있다.
위의 코드를 추가하면 연락처 카드 컴포넌트는 이렇게 됩니다.
const ContactCard = () => {
const [showAge, setShowAge] = useState(false);
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
<p>Age: 25</p>
</div>
</div>
);
};
이제 state 객체가 생겼습니다. 어떻게 사용하냐고요? 이제 showAge라는 변수를 다른 변수처럼 참조할 수 있습니다. 여기서는 showAge가 true일 때만 보여주고 싶습니다.
3항 연산자를 사용할 수 있습니다.
{showAge === true ? <p>Age: 25</p> : null}
이 예제는 showAge가 true 라면 age를 그려주고, 아니면 그리지 않습니다.
연락처 카드 컴포넌트는 이제 다음과 같습니다.
const ContactCard = () => {
const [showAge, setShowAge] = useState(false);
return (
<div className="contact-card">
<img src="https://via.placeholder.com/150" alt="profile" />
<div className="user-details">
<p>Name: Jenny Han</p>
<p>Email: Jenny.Han@notreal.com</p>
{showAge === true ? <p>Age: 25</p> : null}
</div>
</div>
);
};
이제 브라우저를 보면, age가 사라진 것을 볼 수 있습니다. 그것은 showAge 변수가 flase로 초기화되었기 대문입니다. showAge를 true로 초기화하면
const [showAge, setShowAge] = useState(true);
연락처에 나이가 나올 겁니다. 좋습니다. 그래도 아직은 완벽하지 않습니다. 연락처 카드의 나이를 보고 싶을 때마다 코드를 수정할 수는 없습니다.
showAge의 동적인 변화를 어떻게 할 건지 보기 전에, 작은 코드를 먼저 보겠습니다.
{showAge === true ? <p>Age: 25</p> : null}
그리고
{showAge && <p>Age: 25</p> }
두 개는 같은 결과를 가집니다. 단지 좀 더 간단한 방식입니다.
이해하기 쉬운 코드로 줄이세요. 작성하는 모든 줄을 단축해야 할 필요는 없습니다. 가독성이 우선입니다.
State 변경하기.
다시 돌아와서, useState() 훅이 state의 변화를 위한 기능을 준다고 했습니다. 이것을 버튼이랑 역어서 클릭할 때 연락처 카드의 나이가 보이도록 하겠습니다.
아래처럼 합니다.
<button onClick={() => setShowAge(!showAge)}>
Toggle Age
</button>
setShowAge 함수를 호출할 때 하는 것은 showAge 값의 변경합니다.
state가 변경이 되면, 리액트는 컴포넌트를 다시 그릴 겁니다. 그리고 showAge가 true이기 때문에 나이는 보이게 됩니다.
사용자가 버튼을 다시 클릭하면, showAge는 false로 변경되고, 리액트는 다시 컴포넌트를 그리게 되니다.
멋진 토글 액션을 보십시오.
컴포넌트 state가 변경될 때마다, 리액트는 컴포넌트를 다시 그립니다.
Props 소개.
이제 여러 번 사용할 수 있는 연락처 카드 컴포넌트가 생겼습니다. 이름, 이메일, 나이, 아바타가 모든 컴포넌트에 동일하게 들어가기 때문에, 아직은 실제로 재사용할 수는 없습니다. props라는 것을 이용해서 데이터와 연결되도록 만들 수 있습니다.
리액트를 막 시작했기 때문에, props를 컴포넌트끼리 데이터를 주고받고, 컴포넌트에서 사용할 수 있다고 정도로 이해하면 됩니다. 예를 들어, 아바타, 이메일, 이름, 나이를 props로 연락처 컴포넌트에 전달할 수 있습니다.
<ContactCard
avatar="https://via.placeholder.com/150"
name="Jenny Han"
email="jenny.han@notreal.com"
age={25}
/>
보다시피 이름을 지어서 props를 정의했습니다. name은 props의 값에 jenny han을 할당하고 있습니다.
원하는 만큼 props를 만들 수 있고, 원하는 대로 이름을 지울 수 있습니다. 그래서, props를 사용하는 것은 자유롭습니다.
Props는 서로 다른 데이터 타입을 가질 수 있습니다. 예를 들어 string, number, booleans, object, array 등.
props는 따옴표 문자열 또는 중괄호 안에 값이 어야 합니다 ( 예, name="Jenny Han", age={25} ) 만약 문자열 이외의 것을 중괄호를 사용하지 않는다면, 문제가 생깁니다.
이제 앱 컴포넌트 안에 연락처 카드 컴포넌트를 교체합니다.
<ContactCard
avatar="https://via.placeholder.com/150"
name="Jenny Han"
email="jenny.han@notreal.com"
age={25}
/>
<ContactCard
avatar="https://via.placeholder.com/150"
name="Jason Long"
email="jason.long@notreal.com"
age={45}
/>
<ContactCard
avatar="https://via.placeholder.com/150"
name="Peter Pan"
email="peter.pan@neverland.com"
age={100}
/>
여기서 하는 것은 컴포넌트가 필요한 데이터를 props를 이용하여 컴포넌트에 전달하는 것입니다. 각 컴포넌트에 데이터를 어떻게 전달하는지 봐 두세요.
컴포넌트에서 Props 사용하기
앞서 연락처 카드 컴포넌트에 여러 개의 props를 보냈습니다. 이제 연락처 카드에 어떻게 쓰이는지 알아보죠.
지금까지 연락처 카드 함수는 인자를 받지 않았습니다. 리액트는 마법을 부리는 것처럼 자동으로 모든 props를 props 객체에 넣어줍니다. 그리고 컴포넌트에 전달합니다.
const ContactCard = props => {
//...other code
};
props 변수를 보세요. 이게 우리가 전에 정의한 props를 담을 객체입니다.
const ContactCard = props => {
console.log(props.avatar);
console.log(props.name);
console.log(props.email);
console.log(props.age);
//...other code
};
마지막으로 JSX에 하드 코딩한 것을 props로 받은 값으로 바꿀 겁니다.
return (
<div className="contact-card">
<img src={props.avatar} alt="profile" />
<div className="user-details">
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<button onClick={() => setShowAge(!showAge)}>Toggle Age </button>
{showAge && <p>Age: {props.age}</p>}
</div>
</div>
);
props에서 받은 값을 사용해 이미지 소스에 어떻게 설정했는지 보세요. 비슷하게 이름, 이메일, 나이를 설정했습니다. 또 코드를 중괄호로 둘러싸서 자바스크립트를 실행한 방식을 보세요.
App.js의 마지막 모습은 다음과 같습니다.
// App.js
const App = () => {
return (
<>
<ContactCard
avatar="https://via.placeholder.com/150"
name="Jenny Han"
email="jenny.han@notreal.com"
age={25}
/>
<ContactCard
avatar="https://via.placeholder.com/150"
name="Jason Long"
email="jason.long@notreal.com"
age={45}
/>
<ContactCard
avatar="https://via.placeholder.com/150"
name="Peter Pan"
email="peter.pan@neverland.com"
age={100}
/>
</>
);
};
const ContactCard = props => {
const [showAge, setShowAge] = useState(false);
return (
<div className="contact-card">
<img src={props.avatar} alt="profile" />
<div className="user-details">
<p>Name: {props.name}</p>
<p>Email: {props.email}</p>
<button onClick={() => setShowAge(!showAge)}>
Toggle Age
</button>
{showAge && <p>Age: {props.age}</p>}
</div>
</div>
);
};
브라우저에서 실행하면 다음과 같은 화면을 보실 수 있을 겁니다.
만세! 아까처럼 컴포넌트가 동작합니다. 그런데 이제는 좀 더 동적으로 됐어요. 동일한 연락처 카드를 다른 데이터를 써서 재사용할 수 있어요. 레이아웃, 스타일 그리고 상태 객체도 동일합니다.
목록에서 컴포넌트 그리기
연락처 목록은 잘 나왔습니다. 재사용 가능하도록 잘 만들었으니 그만하면 될까요? 아니죠. 좀 더 나가 볼까요?
진짜 애플리케이션에서는 데이터는 배열의 형태로 주어집니다. 예를 들어 API 호출에서 받은 것처럼요. 그럼 데이터베이스에서 몇 명의 사용자 데이터를 조회하는 API 호출을 해서 아래의 데이터를 받았다고 가정해볼게요.
const contacts = [
{ name: "Jenny Han", email: "jenny.han@notreal.com", age: 25 },
{ name: "Jason Long", email: "jason.long@notreal.com", age: 45 },
{ name: "Peter Pan", email: "peter.pan@neverland.com", age: 100 }
];
앱 컴포넌트에 붙여 넣습니다. 여러분 중 좋은 시력을 가졌다면, 우리가 이제 것 봐왔던 데이터와 비슷하다는 것을 눈치챘을 겁니다. 그러나 이 데이터를 어떻게 연락처 카드 컴포넌트에 넣을까요? 자 지금까지 배워온 나날들을 기억해보세요. 배열을 map()을 이용해서 반복하면? 자 이제 해보죠.
컴포넌트 목록을 출력하기 위해서는
- map()을 사용해서 배열을 반복합니다.
- 배열의 각 아이템에서 연락처 카드 컴포넌트를 생성합니다.
- 배열로부터 데이터를 props를 이용해 연락처 카드 컴포넌트에 전달합니다.
어떻게 하는지 한번 보죠. App() 컴포넌트에서 return을 아래와 같이 바꿉니다.
return (
<>
{contacts.map(contact => (
<ContactCard
avatar="https://via.placeholder.com/150"
name={contact.name}
email={contact.email}
age={contact.age}
/>
))}
</>
);
보다시피, 배열을 매핑합니다. 배열의 각 객체에서 새로운 연락처 카드 컴포넌트를 생성합니다. props에 넣기 위해 현재 매핑된 객체 다시 말해, 연락처 변수로부터 이름, 이메일, 나이를 가져오려고 합니다.
지금은 아바타 변수는 그대로 놔뒀습니다. 이건 나중에 변경할 거예요.
다 됐어요. App.js는 이렇게 변했습니다.
//App.js
const App = () => {
const contacts = [
{ name: "Jenny Han", email: "jenny.han@notreal.com", age: 25 },
{ name: "Jason Long", email: "jason.long@notreal.com", age: 45 },
{ name: "Peter Pan", email: "peter.pan@neverland.com", age: 100 },
{ name: "Amy McDonald", email: "amy@email.com", age: 33 }
];
return (
<>
{contacts.map(contact => (
<ContactCard
avatar="https://via.placeholder.com/150"
name={contact.name}
email={contact.email}
age={contact.age}
/>
))}
</>
);
};
브라우저에서 실행하면 똑같이 보여야 합니다. 연락처 카드를 변경하지 않았고, 데이터를 가져오는 위치만 바꿨습니다. 이렇게 하면 좋은 것은 만약 새로운 연락처가 추가될 때, 다른 연락처 컴포넌트가 자동으로 그려지는 것입니다. 새로운 것을 그리기 위해 아무것도 하지 않아도 됩니다. 한번 직접 해보세요.
API에서 데이터 가져오기.
리액트 앱의 좋은 점을 봤습니다. 동적으로 잘 동작합니다. 이건 리액트이기 때문에, 좋은 것입니다. 하지만 약간 정리할 필요가 있습니다. 실제로는 데이터는 API로 가져옵니다.
다음 부분에서, API( randomuser.me/ )로부터 실제 연락처를 가져올 겁니다. ( 실제라고 말하지만, 이미 알고 계시듯이 가짜 연락처입니다. ) 웹 사이트를 가 보시고, 응답을 살펴보세요. 여기서 데이터를 받아 컴포넌트에 넣을 겁니다.
우선, API의 응답받은 데이터를 담을 state 변수를 만듭니다. 기억하세요. state는 변화를 감지하기에 좋습니다. 연락처 목록은 언제든지 변경될 수 있습니다.
App.js에 아래처럼 연락처 배열을 제거합니다.
const [contacts, setContacts] = useState([]);
자, state 객체를 만들고, 비어있는 배열로 초기화합니다. API를 호출할 때, 연락처 목록을 담은 state로 변경할 겁니다. state 객체의 이름을 연락처라고 했기에 JSX에 렌더링 로직을 넣습니다.
다음으로 API로부터 데이터를 가져옵니다. 기본 Fetch API를 사용합니다. 지금은 콘솔에 데이터를 기록합니다. 아래 코드를 만들었습니다.
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => {
console.log(data);
});
이제 우리가 할 일은 이렇습니다.
- randomuser API에 3개의 결과를 요청합니다.
- 응답을 JSON으로 변환합니다.
- JSON을 콘솔에 기록합니다.
브라우저에서 실행하면, 연락처 컴포넌트가 그려지지 않은 것을 볼 수 있습니다. 괜찮아요. 새로운 데이터를 state에 저장하지 않았습니다. 지금 state 변수는 비어있습니다. 브라우저 개발자 도구의 콘솔을 살펴보면, 응답 객체가 기록된 것을 볼 수 있습니다. 이렇게 생겼을 거예요.
여기서 results 배열을 볼 겁니다. 배열은 3개의 객체를 가지고 있습니다. 각 객체는 사용자를 담고 있습니다. 우리가 이전에 수동으로 만들었던 연락처 배열과 유사합니다.
JSX가 데이터를 가져올 수 있도록 App 컴포넌트를 변경합니다. 아래처럼 변경하세요.
return (
<>
{contacts.map(contact => (
<ContactCard
avatar={contact.picture.large}
name={contact.name.first + " " + contact.name.last}
email={contact.email}
age={contact.dob.age}
/>
))}
</>
);
이건 우리가 전에 했던 것과 유사합니다.
- 연락처 변수 배열을 (처음엔 비어있습니다.) 처음부터 순회합니다.
- 응답에서 적당한 것, 사진, 이름, 이메일의 정보를 state에 저장합니다.
다음은 state에 배열을 저장합니다. JSX는 (앞에서 봤던 map을 통해) 반복해서 연락처 카드를 그립니다. fetch 함수에서 setContents 함수를 추가합니다.
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => {
console.log(data);
setContacts(data.results);
});
이제 App컴포넌트는 이렇게 생겼습니다.
//App.js
const App = () => {
const [contacts, setContacts] = useState([]);
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => {
console.log(data);
setContacts(data.results);
});
return (
<>
{contacts.map(contact => (
<ContactCard
avatar={contact.picture.large}
name={contact.name.first + " " + contact.name.last}
email={contact.email}
age={contact.dob.age}
/>
))}
</>
);
};
데이터를 저장했다면, 브라우저에서 실행해보세요. 이런 걸 볼 수 있을 거예요.
왜 이래, 이상하잖아!라고 생각할지도 모르겠습니다.
아직은 당황하지 마세요.( 만약 성능이 모자란 컴퓨터나 잠시 이상해졌다면, setContact를 주석 처리하세요.
왜 여기서 무한루프에 빠지는 현상이 나타난다면.
- fetch를 호출하여 데이터를 가져옵니다.
- 데이터를 state에 저장합니다.
- 기억하세요. 리액트는 state 가 변경될 때, 다시 그립니다.
- 컴포넌트가 다시 그려질 때, fetch API가 다시 호출됩니다. 그리고 state가 설정됩니다.
- state가 변경되었기 때문에 컴포넌트는 다시 그려집니다.
- 컴포넌트가 다 그려졌으면 fetch 가 다시 호출됩니다.
그래서 어떻게 멈춰야 할까요? 모든 걸 지우고 다시 시작합니다. 아니에요 농담입니다. 지우지 마세요. 이거는 리액트 HooK으로 고칠 수 있어요.
useEffect 소개
useEffect 훅은 함수를 실행시키는 아주 특별한 훅입니다. 기본적으로 useEffect 훅은 다시 그려질 때 호출됩니다. 그러나 어떤 상황에서 실행할지 정할 수 있습니다. useEffect 훅은 다음과 같이 생겼습니다.
useEffect(() => {
// code to run
});
이건 계속 실행됩니다. 만약 한 번만 실행되기를 바란다면, 두 번째 인자를 빈 배열을 전달해 주면 됩니다.
useEffect(() => {
// code to run
},[]); //<-- notice the empty array
이것을 의존 배열이라고 합니다. 의존 배열이 비어있을 때, useEffect함수는 첫 번째 로딩될 때만 실행됩니다. 이후의 다시 그려질 때, useEffect 함수는 지나갑니다.
데이터를 한 번만 넣기를 원하고, 컴포넌트가 로딩될 때라면, 여기가 API 호출을 넣기 가장 좋은 위치입니다. App 컴포넌트에 userEffect함수를 넣으세요. 그리고 fetch API 호출하는 부분을 useEffect 함수 안으로 이동하세요. App 컴포넌트는 이렇게 됩니다.
//App.js
const App = () => {
const [contacts, setContacts] = useState([]);
useEffect(() => {
fetch("https://randomuser.me/api/?results=3")
.then(response => response.json())
.then(data => {
setContacts(data.results);
});
}, []);
return (
<>
{contacts.map(contact => (
<ContactCard
avatar={contact.picture.large}
name={contact.name.first + " " + contact.name.last}
email={contact.email}
age={contact.dob.age}
/>
))}
</>
);
};
자, 브라우저에서 실행해보면 3개의 연락처만 표시되는 것을 알 수 있습니다. 새로 고침 하면 다른 연락처를 볼 수 있어요.
결론
축하합니다. 이제 막 첫 번째 앱을 만들었습니다.