state는 component 간에 독립적이다. React는 UI트리에서의 위치에 따라 어떤 state가 어떤 component에 속하는지 추적한다. state를 보존할 시기와 다시 렌더링 사이에 state를 재설정할 시기를 제어할 수 있다.
DOM은 HTML 요소를 나타내고 CSSOM은 CSS에 대해 동일한 작업을 수행한다.
React는 또한 트리 구조를 사용하여 만든 UI를 관리하고 모델링한다. React는 JSX에서 UI 트리를 만든다. 그런 다음 React DOM은 해당 UI 트리와 일치하도록 브라우저 DOM 요소를 업데이트 한다.
component state를 제공할 때 state가 component 내부에 존재한다고 생각할 수 있다. 그러나 state는 실제로 React 내부에 있다. React는 해당 component가 UI트리에 있는 위치에 따라 보유하고 있는 각 state 조각을 올바른 component와 연결한다.
React는 동일한 위치에서 동일한 component를 렌더링 하는 한 state를 유지한다.
React는 component가 UI 트리의 해당 위치에서 렌더링되는 동안 component의 state를 유지한다. 제거되거나 다른 component가 동일한 위치에서 렌더링되면 React는 해당 state를 버린다.
import { useState } from 'react';
export default function App() {
const [isFancy, setIsFancy] = useState(false);
return (
<div>
{isFancy ? (
<Counter isFancy={true} />
) : (
<Counter isFancy={false} />
)}
<label>
<input
type="checkbox"
checked={isFancy}
onChange={e => {
setIsFancy(e.target.checked)
}}
/>
Use fancy styling
</label>
</div>
);
}
function Counter({ isFancy }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
if (isFancy) {
className += ' fancy';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
React에서 중요한 것은 JSX 마크업이 아니라 UI 트리의 위치이다.
import { useState } from 'react';
export default function App() {
const [isPaused, setIsPaused] = useState(false);
return (
<div>
{isPaused ? (
<p>See you later!</p>
) : (
<Counter />
)}
<label>
<input
type="checkbox"
checked={isPaused}
onChange={e => {
setIsPaused(e.target.checked)
}}
/>
Take a break
</label>
</div>
);
}
function Counter() {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
take a break를 누름에 따라 자식요소가 Counter -> p, p -> Counter로 변화한다. 이렇게 교체하는 순간 state가 파괴된다.
또한 동일한 위치에서 다른 component를 렌더링하면 전체 하위 트리의 state가 재설정된다.
import { useState } from 'react';
export default function App() {
const [isFancy, setIsFancy] = useState(false);
return (
<div>
{isFancy ? (
<div>
<Counter isFancy={true} />
</div>
) : (
<section>
<Counter isFancy={false} />
</section>
)}
<label>
<input
type="checkbox"
checked={isFancy}
onChange={e => {
setIsFancy(e.target.checked)
}}
/>
Use fancy styling
</label>
</div>
);
}
function Counter({ isFancy }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
if (isFancy) {
className += ' fancy';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
Counter의 위치는 계속 같더라도 위의 section->div, div->section으로 인해 하위에 있는 Counter의 state 또한 재설정된다. 일반적으로 재렌더링 간에 state를 유지하려면 트리 구조가 한 렌더에서 다른 렌더로 일치해야 한다. 구조가 다르면 React가 트리에서 component를 제거할 때 state를 파괴한다.
기본적으로 React는 component가 동일한 위치에 있는 동안 state를 유지한다. 그러나 때로는 component의 state를 재설정해야 할 수도 있다.
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter person="Taylor" />
) : (
<Counter person="Sarah" />
)}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person}'s score: {score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
위 코드와 같은 경우 사람이 바뀌면서 state 또한 바뀌어야 한다. 그러나 현재는 변경시에도 state가 유지된다.
이러한 경우 state를 재설정하는 두 가지 방법이 있다.
1. 다른 위치에서 component 렌더링
2. key를 사용하여 각 component에 명시적 ID부여
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA &&
<Counter person="Taylor" />
}
{!isPlayerA &&
<Counter person="Sarah" />
}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person}'s score: {score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
기본적으로 React는 부모 내의 순서를 사용하여 component를 식별한다. 그러나 key를 사용하면 이것이 특정 component임을 React에 알릴 수 있다.
import { useState } from 'react';
export default function Scoreboard() {
const [isPlayerA, setIsPlayerA] = useState(true);
return (
<div>
{isPlayerA ? (
<Counter key="Taylor" person="Taylor" />
) : (
<Counter key="Sarah" person="Sarah" />
)}
<button onClick={() => {
setIsPlayerA(!isPlayerA);
}}>
Next player!
</button>
</div>
);
}
function Counter({ person }) {
const [score, setScore] = useState(0);
const [hover, setHover] = useState(false);
let className = 'counter';
if (hover) {
className += ' hover';
}
return (
<div
className={className}
onPointerEnter={() => setHover(true)}
onPointerLeave={() => setHover(false)}
>
<h1>{person}'s score: {score}</h1>
<button onClick={() => setScore(score + 1)}>
Add one
</button>
</div>
);
}
주의할점: key는 전역적으로 고유하지 않다. 부모내의 위치만 지정한다
import { useState } from 'react';
import Chat from './Chat.js';
import ContactList from './ContactList.js';
export default function Messenger() {
const [to, setTo] = useState(contacts[0]);
return (
<div>
<ContactList
contacts={contacts}
selectedContact={to}
onSelect={contact => setTo(contact)}
/>
<Chat key={to.id} contact={to} />
</div>
)
}
const contacts = [
{ id: 0, name: 'Taylor', email: 'taylor@mail.com' },
{ id: 1, name: 'Alice', email: 'alice@mail.com' },
{ id: 2, name: 'Bob', email: 'bob@mail.com' }
];
export default function ContactList({
selectedContact,
contacts,
onSelect
}) {
return (
<section className="contact-list">
<ul>
{contacts.map(contact =>
<li key={contact.id}>
<button onClick={() => {
onSelect(contact);
}}>
{contact.name}
</button>
</li>
)}
</ul>
</section>
);
}
import { useState } from 'react';
export default function Chat({ contact }) {
const [text, setText] = useState('');
return (
<section className="chat">
<textarea
value={text}
placeholder={'Chat to ' + contact.name}
onChange={e => setText(e.target.value)}
/>
<br />
<button>Send to {contact.email}</button>
</section>
);
}