React는 선언적인 방식으로 UI를 조작한다.
개별적인 UI를 직접 조작하는 것 대신에 컴포넌트 내부에 여러 state를 묘사하고 사용자의 입력에 따라 state를 변경한다.
React의 대표 철학을 나타내는 수식이다. UI = f(state)
즉, 상태가 바뀌면 → UI는 자동으로 다시 계산된다는 것을 의미한다.
const button = document.getElementById("btn");
const text = document.getElementById("text");
button.addEventListener("click", () => {
text.innerText = "바뀐 텍스트";
});
순수 JS 코드 : UI를 어떻게 바꿀지 한 단계씩 명령함
1. 버튼을 찾아라
2. 이벤트를 등록해라
3. 텍스트를 직접 수정해라
-> 즉, DOM을 어떻게 조작할지 단계별로 지시한다.
function App() {
const [text, setText] = React.useState("기존 텍스트");
return (
<>
<p>{text}</p>
<button onClick={() => setText("바뀐 텍스트")}>
변경
</button>
</>
);
}
React :
-> 우리는 UI가 어떤 상태(state)일 때 어떻게 생겨야 하는지만 선언하고, DOM을 직접 건드리지 않는다.
<p>{text}</p>
setText("바뀐텍스트")
useState를 사용해서 메모리의 state를 표현하세요.export default function Form({
status = 'Empty' // success, error, empty 등등 ..
}) {
if (status === 'success') {
return <h1>That's right!</h1>
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form>
<textarea />
<br />
<button>
Submit
</button>
</form>
</>
)
}
두 종류의 인풋 유형으로 state 변경을 트리거할 수 있다.
두 가지 경우 모두 UI를 업데이트하기 위해서는 state 변수를 설정해야 한다. 몇 가지 입력에 따라 state를 변경해야 한다.
텍스트 인풋 변경 (휴먼) : 텍스트 상자가 비어있는지 여부에 따라 state를 Empty에서 Typing 으로 또는 그 반대로 변경해야 함.
제출 버튼 클릭 (휴먼) : Submitting state를 변경해야 함.
네트워크 응답이 성공적으로 도착 (컴퓨터) : Success state를 변경해야 함.
네트워크 요청이 실패 (컴퓨터) : 해당하는 오류 메시지와 함께 Error state를 변경해야 함.
참고로 휴먼 인풋은 종종 이벤트 핸들러가 필요할 수도 있다.

useState 를 사용하여 컴포넌트의 시각적 state를 표현해야 한다. 이 과정은 단순함이 핵심이다.
각각의 state는 움직이는 조각이고, 움직이는 조각은 적을수록 좋다.
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [isEmpty, setIsEmpty] = useState(true);
const [isTyping, setIsTyping] = useState(false);
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const [isError, setIsError] = useState(false);
초반에 어떤 state 변수를 사용할지에 대한 좋은 방법이 떠오르지 않는다면 가능한 모든 state를 커버할 수 있는 확실한 것을 먼저 추가하는 방식으로 시작하고, 추후에 state를 리팩토링하면 된다.
state의 중복은 피하고 필수적인 state만 남겨두는 것이 좋다.
state 구조를 리팩터링하는데 시간을 투자하면 컴포넌트는 더 이해하기 쉬워질 것이고 불필요한 중복은 줄어들며 의도하지 않은 의미를 피할 수 있다.
리팩토링의 목표는 state가 사용자에게 유효한 UI를 보여주지 않는 경우를 방지하는 것이다.
1. state가 역설을 일으키지 않는지
-isTyping과 isSubmitting이 동시에 true일 수는 없다. 이러한 역설은 보통 state가 충분히 제한되지 않았음을 의미한다. 여기에는 두 boolean에 대한 네 가지 조합이 있지만 오직 유효한 state는 세 개뿐이다.
2. 다른 state 변수에 이미 같은 정보가 담겨있지 않는지
isEmpty와 isTyping 은 동시에 true 가 될 수 없다. isEmpty를 지우고 answer.length === 0으로 체크할 수 있다.3. 다른 변수를 뒤집었을 때 같은 정보를 얻을 수 있진 않은지
isError는 error !== null 로도 확인할 수 있기 때문에 필요하지 않다. const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
import { useState } from 'react';
export default function Form() {
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing');
if (status === 'success') {
return <h1>That's right!</h1>
}
async function handleSubmit(e) {
e.preventDefault();
setStatus('submitting');
try {
await submitForm(answer);
setStatus('success');
} catch (err) {
setStatus('typing');
setError(err);
}
}
function handleTextareaChange(e) {
setAnswer(e.target.value);
}
return (
<>
<h2>City quiz</h2>
<p>
In which city is there a billboard that turns air into drinkable water?
</p>
<form onSubmit={handleSubmit}>
<textarea
value={answer}
onChange={handleTextareaChange}
disabled={status === 'submitting'}
/>
<br />
<button disabled={
answer.length === 0 ||
status === 'submitting'
}>
Submit
</button>
{error !== null &&
<p className="Error">
{error.message}
</p>
}
</form>
</>
);
}
function submitForm(answer) {
// 네트워크에 접속한다고 가정해봅시다.
return new Promise((resolve, reject) => {
setTimeout(() => {
let shouldError = answer.toLowerCase() !== 'lima'
if (shouldError) {
reject(new Error('Good guess but a wrong answer. Try again!'));
} else {
resolve();
}
}, 1500);
});
}