[REACT] beta.react.org 공식 문서 번역 : [퀵스타트] 리액트 배우기
시작하기에 앞서, 몇가지 방법들이 있습니다.
- 그냥 바로 예시로 배우고 싶다면 바로 리액트로 생각하기 로 가세요. 이 튜토리얼은 디테일을 설명해주지는 않지만 리액트로 UI를 만드는것이 어떤 느낌인지 알려줄거에요.
- 만약에 리액트의 컨셉에 익숙하고 가능한 API들을 대강 훑어보고 싶다면, API 레퍼런스를 확인하세요.
- 이 외의 문서들은 각각의 개념들을 순서대로 많은 인터랙티브한 예시들과, 디테일한 설명들과 이해도를 확인하기 위한 챌린지들로 구성 돼 있습니다. 이것들을 순서대로 읽을 필요는 없지만 각각의 다음 페이지들은 이전 페이지의 개념에 익숙하다는 전제 하에 진행됩니다.
시간을 절약하기 위해서, 아래에 각각의 챕터 개요 브리핑을 제공해드리겠습니다.
챕터 1 개요: UI를 묘사하기
리액트 어플리케이션은 '컴포넌트'라는 독립적인 UI 조각들로 만들어집니다. 하나의 리액트 컴포넌트는 마크업으로 끼얹을 수 있는 자바스크립트 함수입니다. 컴포넌트들은 버튼처럼 아주 작을 수도 있고, 전체 페이지처럼 아주 클 수도 있습니다. 여기, 부모 'Gallery' 컴포넌트는 세개의 자식 'Profile' 컴포넌트들을 렌더합니다.
// App.js
function Profile() {
return (
<img
src="https://i.imgur.com/MK3eW3As.jpg"
alt="Katherine Johnson"
className="avatar"
/>
);
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile />
<Profile />
<Profile />
</section>
);
}
위의 마크업 예시는 HTML처럼 생겼습니다. 이 구문을 우리는 소위 'JSX'라고 부르며, 이것은 조금 엄격합니다(예를 들어, 모든 태그들을 다 닫아야 합니다.). CSS 클래스는 JSX에서 'className'으로 지정 돼 있습니다.
<img>태그로 브라우저에 정보를 보낼 수 있는 것 처럼, <Profile>같이 직접 만든 컴포넌트로 정보를 보내줄 수 있습니다. 그러한 정보들을 'props'라고 합니다. 여기서, 세개의 <Profile>은 다른 props를 보냅니다.
// App.js
function Profile({ name, imageUrl }) {
return (
<img
className="avatar"
src={imageUrl}
alt={name}
/>
);
}
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
<Profile
name="Lin Lanying"
imageUrl="https://i.imgur.com/1bX5QH6.jpg"
/>
<Profile
name="Gregorio Y. Zara"
imageUrl="https://i.imgur.com/7vQD0fPs.jpg"
/>
<Profile
name="Hedy Lamarr"
imageUrl="https://i.imgur.com/yXOvdOSs.jpg"
/>
</section>
);
}
아마 왜 'className="avatar"'는 쌍따옴표를 사용하고 src={imageUrl}은 중괄호를 사용하는지 궁금할 것 입니다. JSX에서 중괄호는 "자바스크립트로 통하는 창문" 같은 것 입니다. 그것들은 마크업 안에 바로 조금의 자바스크립트를 구동시킬 수 있게 해줍니다! 그래서 src={imageUrl}은 첫번째 줄에 정의된 imageUrl prop을 읽고 부모인 Gallery 컴포넌트에 전달됩니다.
위의 예시에는, 모든 데이터가 마크업에 직접적으로 적혀졌습니다. 그러나 당신은 이것들을 종종 분할해서 유지하고 싶을 것 입니다. 여기서, 데이터는 배열로 유지됩니다. 리액트에서는, map 같은 리스트같은 것들을 렌더하는 자바스크립트 함수를 사용합니다.
//App.js
import { people } from './data.js';
import Profile from './Profile.js';
export default function Gallery() {
return (
<section>
<h1>Amazing scientists</h1>
{people.map(person => (
<Profile
key={person.id}
name={person.name}
imageId={person.imageId}
/>
))}
</section>
);
}
// Profile.js
export default function Profile({ name, imageId }) {
const imageUrl = (
'https://i.imgur.com/' +
imageId +
's.jpg'
);
return (
<img
className="avatar"
src={imageUrl}
alt={name}
/>
);
}
// data.js
export const people = [{
id: 0,
name: 'Creola Katherine Johnson',
imageId: 'MK3eW3A'
}, {
id: 1,
name: 'Mario José Molina-Pasquel Henríquez',
imageId: 'mynHUSa'
}, {
id: 2,
name: 'Mohammad Abdus Salam',
imageId: 'bE7W1ji'
}, {
id: 3,
name: 'Percy Lavon Julian',
imageId: 'IOjWm71'
}, {
id: 4,
name: 'Subrahmanyan Chandrasekhar',
imageId: 'lrWQx8l'
}];
챕터 2 개요: 인터랙티브 하게 추가하기
컴표넌트들은 상호작용의 결과로서 무엇이 화면에 있어야 하는지에 따라 종종 변화가 필요합니다. 폼에서의 타이핑은 인풋 필드를 업데이트 해야하고, 이미지 캐러샐에 있는 "다음" 버튼을 클릭하는 것은 보여져야 할 이미지를 바꿔야 하며, "구매하기" 버튼을 누르는 것은 쇼핑카트에 물건을 넣습니다. 컴포넌트들은 무언가를 "기억해야" 합니다. 현재의 인풋 값, 현재 이미지, 현재 쇼핑카트 말이죠. 리액트에서 이러한 컴포넌트-위주의 메모리를 state라고 부릅니다.
당신은 state를 useState 훅을 통해 넣을 수 있습니다. 훅은 당신의 컴포넌트가 리액트 특징들(state는 특징들 중 하나입니다.)을 사용할 수 있게 해주는 특별한 함수입니다. useState 훅은 state 변수를 선언하게 해줍니다. 이것은 원래의 state를 갖고 두개의 값으로 반환합니다. 현재의 state와 그 state를 업데이트 하게 해주는 setter state 함수죠.
이 Gallery 컴포넌트는 현재 이미지 인덱스(처음에는 0)와 사용자가 "Show details"를 토글했을때(처음에는 false), 두 개를 기억해야 합니다.
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
어떻게 버튼을 클릭했을 때 화면에서 업데이트 되는지 주목하세요:
// App.js
import { useState } from 'react';
import { sculptureList } from './data.js';
export default function Gallery() {
const [index, setIndex] = useState(0);
const [showMore, setShowMore] = useState(false);
function handleNextClick() {
setIndex(index + 1);
}
function handleMoreClick() {
setShowMore(!showMore);
}
let sculpture = sculptureList[index];
return (
<>
<button onClick={handleNextClick}>
Next
</button>
<h2>
<i>{sculpture.name} </i>
by {sculpture.artist}
</h2>
<h3>
({index + 1} of {sculptureList.length})
</h3>
<button onClick={handleMoreClick}>
{showMore ? 'Hide' : 'Show'} details
</button>
{showMore && <p>{sculpture.description}</p>}
<img
src={sculpture.url}
alt={sculpture.alt}
/>
</>
);
}
// data.js
export const sculptureList = [{
name: 'Homenaje a la Neurocirugía',
artist: 'Marta Colvin Andrade',
description: 'Although Colvin is predominantly known for abstract themes that allude to pre-Hispanic symbols, this gigantic sculpture, an homage to neurosurgery, is one of her most recognizable public art pieces.',
url: 'https://i.imgur.com/Mx7dA2Y.jpg',
alt: 'A bronze statue of two crossed hands delicately holding a human brain in their fingertips.'
}, {
name: 'Floralis Genérica',
artist: 'Eduardo Catalano',
description: 'This enormous (75 ft. or 23m) silver flower is located in Buenos Aires. It is designed to move, closing its petals in the evening or when strong winds blow and opening them in the morning.',
url: 'https://i.imgur.com/ZF6s192m.jpg',
alt: 'A gigantic metallic flower sculpture with reflective mirror-like petals and strong stamens.'
}, {
name: 'Eternal Presence',
artist: 'John Woodrow Wilson',
description: 'Wilson was known for his preoccupation with equality, social justice, as well as the essential and spiritual qualities of humankind. This massive (7ft. or 2,13m) bronze represents what he described as "a symbolic Black presence infused with a sense of universal humanity."',
url: 'https://i.imgur.com/aTtVpES.jpg',
alt: 'The sculpture depicting a human head seems ever-present and solemn. It radiates calm and serenity.'
}, {
name: 'Moai',
artist: 'Unknown Artist',
description: 'Located on the Easter Island, there are 1,000 moai, or extant monumental statues, created by the early Rapa Nui people, which some believe represented deified ancestors.',
url: 'https://i.imgur.com/RCwLEoQm.jpg',
alt: 'Three monumental stone busts with the heads that are disproportionately large with somber faces.'
}, {
name: 'Blue Nana',
artist: 'Niki de Saint Phalle',
description: 'The Nanas are triumphant creatures, symbols of femininity and maternity. Initially, Saint Phalle used fabric and found objects for the Nanas, and later on introduced polyester to achieve a more vibrant effect.',
url: 'https://i.imgur.com/Sd1AgUOm.jpg',
alt: 'A large mosaic sculpture of a whimsical dancing female figure in a colorful costume emanating joy.'
}, {
name: 'Ultimate Form',
artist: 'Barbara Hepworth',
description: 'This abstract bronze sculpture is a part of The Family of Man series located at Yorkshire Sculpture Park. Hepworth chose not to create literal representations of the world but developed abstract forms inspired by people and landscapes.',
url: 'https://i.imgur.com/2heNQDcm.jpg',
alt: 'A tall sculpture made of three elements stacked on each other reminding of a human figure.'
}, {
name: 'Cavaliere',
artist: 'Lamidi Olonade Fakeye',
description: "Descended from four generations of woodcarvers, Fakeye's work blended traditional and contemporary Yoruba themes.",
url: 'https://i.imgur.com/wIdGuZwm.png',
alt: 'An intricate wood sculpture of a warrior with a focused face on a horse adorned with patterns.'
}, {
name: 'Big Bellies',
artist: 'Alina Szapocznikow',
description: "Szapocznikow is known for her sculptures of the fragmented body as a metaphor for the fragility and impermanence of youth and beauty. This sculpture depicts two very realistic large bellies stacked on top of each other, each around five feet (1,5m) tall.",
url: 'https://i.imgur.com/AlHTAdDm.jpg',
alt: 'The sculpture reminds a cascade of folds, quite different from bellies in classical sculptures.'
}, {
name: 'Terracotta Army',
artist: 'Unknown Artist',
description: 'The Terracotta Army is a collection of terracotta sculptures depicting the armies of Qin Shi Huang, the first Emperor of China. The army consisted of more than 8,000 soldiers, 130 chariots with 520 horses, and 150 cavalry horses.',
url: 'https://i.imgur.com/HMFmH6m.jpg',
alt: '12 terracotta sculptures of solemn warriors, each with a unique facial expression and armor.'
}, {
name: 'Lunar Landscape',
artist: 'Louise Nevelson',
description: 'Nevelson was known for scavenging objects from New York City debris, which she would later assemble into monumental constructions. In this one, she used disparate parts like a bedpost, juggling pin, and seat fragment, nailing and gluing them into boxes that reflect the influence of Cubism’s geometric abstraction of space and form.',
url: 'https://i.imgur.com/rN7hY6om.jpg',
alt: 'A black matte sculpture where the individual elements are initially indistinguishable.'
}, {
name: 'Aureole',
artist: 'Ranjani Shettar',
description: 'Shettar merges the traditional and the modern, the natural and the industrial. Her art focuses on the relationship between man and nature. Her work was described as compelling both abstractly and figuratively, gravity defying, and a "fine synthesis of unlikely materials."',
url: 'https://i.imgur.com/okTpbHhm.jpg',
alt: 'A pale wire-like sculpture mounted on concrete wall and descending on the floor. It appears light.'
}, {
name: 'Hippos',
artist: 'Taipei Zoo',
description: 'The Taipei Zoo commissioned a Hippo Square featuring submerged hippos at play.',
url: 'https://i.imgur.com/6o5Vuyu.jpg',
alt: 'A group of bronze hippo sculptures emerging from the sett sidewalk as if they were swimming.'
}];
state는 복잡한 값도 가질 수 있습니다. 예를 들어, 폼을 채운다고 했을 떄, 다른 필드로 이루어진 state의 객체를 가질 수 있습니다. 아래 예시에 있는 ... 구문은 이미 존재하는 객체를 기반으로 한 새로운 객체를 생성하는 예시를 제공해 줄 겁니다.
// App.js
import { useState } from 'react';
export default function Form() {
const [person, setPerson] = useState({
firstName: 'Barbara',
lastName: 'Hepworth',
email: 'bhepworth@sculpture.com'
});
function handleFirstNameChange(e) {
setPerson({
...person,
firstName: e.target.value
});
}
state에 배열도 가질 수 있습니다. 이것은 사용자 상호작용에 의한 반응으로 리스트 내의 것들의 추가, 삭제 혹은 변화를 가능하게 해줍니다. 당신이 뭘 하고싶은지에 따라, 이미 존재하는 배열에서 새로운 배열을 만드는 것에는 다양한 방법들이 존재합니다.
// App.js
import { useState } from 'react';
let nextId = 0;
export default function List() {
const [name, setName] = useState('');
const [artists, setArtists] = useState([]);
return (
<>
<h1>Inspiring sculptors:</h1>
<input
value={name}
onChange={e => setName(e.target.value)}
/>
<button onClick={() => {
setName('');
setArtists([
...artists,
{ id: nextId++, name: name }
]);
}}>Add</button>
<ul>
{artists.map(artist => (
<li key={artist.id}>{artist.name}</li>
))}
</ul>
</>
);
}
챕터 3 요약: state 관리
종종 정확히 무엇을 state에 넣어야 하나에 대한 선택을 마주칠 것 입니다. state 변수를 하나를 써야하나 많이 써야하나? 객체여야 하나 배열이어야 하나? 어떻게 state 를 구조화 할 수 있을까? 가장 중요한 원칙은 장황한 state를 피하는 것 입니다. 만약에 절대로 변하지 않는 정보들이 있다면, state에 있어서는 안됩니다. 몇 정보들이 부모에 의해 props로 받게 된다면, 이것도 state에 있어서는 안됩니다. 그리고 다른 props 혹은 state에서 온 무언가들을 계산할 수 있다면, 이것 또한 state가 돼서는 안됩니다!
예를 들어, 이 폼은 장황한 fullName state 변수를 가지고 있습니다.
// App.js
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const [fullName, setFullName] = useState('');
function handleFirstNameChange(e) {
setFirstName(e.target.value);
setFullName(e.target.value + ' ' + lastName);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
setFullName(firstName + ' ' + e.target.value);
}
return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
이것을 컴포넌트가 렌더링 되는 동안 fullName을 계산함으로서 간단하게 제거할 수 있습니다.
// App.js
import { useState } from 'react';
export default function Form() {
const [firstName, setFirstName] = useState('');
const [lastName, setLastName] = useState('');
const fullName = firstName + ' ' + lastName;
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
return (
<>
<h2>Let’s check you in</h2>
<label>
First name:{' '}
<input
value={firstName}
onChange={handleFirstNameChange}
/>
</label>
<label>
Last name:{' '}
<input
value={lastName}
onChange={handleLastNameChange}
/>
</label>
<p>
Your ticket will be issued to: <b>{fullName}</b>
</p>
</>
);
}
정말 작은 변화인 것 처럼 보일테지만, 많은 리액트 앱 안의 버그들이 이 방법으로 고쳐졌습니다!
때로는 항상 같이 변하는 두 컴포넌트의 state를 원할 것 입니다. 이것을 하려면, 둘 다에서 state를 제거하고 가장 가까운 부모로 옮긴 후 props를 통해 내려줍니다. 이것은 "state를 올려주기" 라고 알려져 있으며 리액트 코드를 짤 때 할 가장 흔한 것 중 하나입니다. 예를 들어, 아래와 같은 아코디언에서, 하나의 패널만 한번에 활성화 돼야 합니다. 각각의 패널 안에 활성 state를 넣어 유지하는 것 보다, 부모 컴포넌트가 자식 컴포넌트 각각의 state를 갖고 props를 특정합니다.
// App.js
import { useState } from 'react';
export default function Accordion() {
const [activeIndex, setActiveIndex] = useState(0);
return (
<>
<h2>Almaty, Kazakhstan</h2>
<Panel
title="About"
isActive={activeIndex === 0}
onShow={() => setActiveIndex(0)}
>
With a population of about 2 million, Almaty is Kazakhstan's largest city. From 1929 to 1997, it was its capital city.
</Panel>
<Panel
title="Etymology"
isActive={activeIndex === 1}
onShow={() => setActiveIndex(1)}
>
The name comes from <span lang="kk-KZ">алма</span>, the Kazakh word for "apple" and is often translated as "full of apples". In fact, the region surrounding Almaty is thought to be the ancestral home of the apple, and the wild <i lang="la">Malus sieversii</i> is considered a likely candidate for the ancestor of the modern domestic apple.
</Panel>
</>
);
}
function Panel({
title,
children,
isActive,
onShow
}) {
return (
<section className="panel">
<h3>{title}</h3>
{isActive ? (
<p>{children}</p>
) : (
<button onClick={onShow}>
Show
</button>
)}
</section>
);
}