You will learn
- ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์๊ณผ ๋ช ๋ นํ ๋ฐฉ์์ ์ฐจ์ด
- ์ปดํฌ๋ํธ๊ฐ ๊ฐ์ ธ์ผ ํ ์๊ฐ์ state๋ค์ ๋์ดํ๋ ๋ฒ
- ํ ์๊ฐ์ state์์ ๋ค๋ฅธ ์๊ฐ์ state๋ก ๋์ด๊ฐ๋ ๋ฐฉ๋ฒ
์ ์ธํ UI
UI๋ฅผ ์๊ฐํ ๋ ์ฐ๋ฆฌ๋ค์ ๋ณดํต ์ฌ์ฉ์๋ค์ ํ๋์ ๋ง์ถฐ ๋ณํํ๋๊ฒ์ด๋ผ ์๊ฐํ๋ค. ๊ฐ๋ น ์ฌ์ฉ์๊ฐ ํผ์ ์ ์ถํ๋ ๊ฒฝ์ฐ๋ฅผ ์๊ฐํด๋ณด์ :
- ์ฌ์ฉ์๊ฐ ์ ๋ ฅ์ฐฝ์ ๊ธ์๋ฅผ ์ ์ด์ผ ์ ์ก ๋ฒํผ์ด ํ์ฑํ ๋๋ค.
- ์ ์ก ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ก๋ฉ์ ์๋ฆฌ๋ ์คํผ๋์ ํจ๊ป ์ ์ ๋นํ์ฑํ ๋๋ค.
- ์์ฒญ์ด ์๋ฃ๋๋ฉด ํผ์ด ์ฌ๋ผ์ง๊ณ , ์ ์ถ ์๋ฃ๋ฅผ ์๋ฆฌ๋ ์๋ด๋ฌธ๊ตฌ๊ฐ ๋ํ๋๋ค.
- ์์ฒญ ์ฒ๋ฆฌ์ ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ฉด, ์๋ฌ ์๋ฆผ์ด ๋ํ๋๋ค.
๋ฆฌ์กํธ ์ด์ ์ ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ ์ฒด๊ณ์์, ์์ ์ฌํญ๋ค์ ๋ชจ๋ ๊ฐ๋ฐ์์ ์ํด ๊ตฌํ๋์ด์ผ ํ ์ฌํญ์ด๋ค. ์ฆ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ์ผ์ผ์ด ์ฝ๋๋ฅผ ์์ฑํ์ด์ผ ํ๋ค๋ ๋ง์ด๋ค. ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ์ ๋ง์น ํ์์ ํ์ ํ์ ๊ธฐ์ฌ์๊ฒ ๋ชฉ์ ์ง๋ฅผ ๋งํด์ฃผ๋ ๋์ ๊ทธ๋๋ง๋ค ์ฐํ์ ์ธ์ง ์ขํ์ ์ธ์ง ์ง์งํด์ผํ๋์ง๋ฅผ ๋งํด์ฃผ๋๊ฒ๊ณผ ๊ฐ๋ค.
์์ ํผ ์ ์ถ ์ํฉ์ ๋ช ๋ นํ ์ฝ๋๋ก ์์ฑํ๋ฉด ์๋์ ๊ฐ๋ค.
async function handleFormSubmit(e) {
e.preventDefault();
disable(textarea);
disable(button);
show(loadingMessage);
hide(errorMessage);
try {
await submitForm(textarea.value);
show(successMessage);
hide(form);
} catch (err) {
show(errorMessage);
errorMessage.textContent = err.message;
} finally {
hide(loadingMessage);
enable(textarea);
enable(button);
}
}
function handleTextareaChange() {
if (textarea.value.length === 0) {
disable(button);
} else {
enable(button);
}
}
function hide(el) {
el.style.display = 'none';
}
function show(el) {
el.style.display = '';
}
function enable(el) {
el.disabled = false;
}
function disable(el) {
el.disabled = true;
}
function submitForm(answer) {
// Pretend it's hitting the network.
return new Promise((resolve, reject) => {
setTimeout(() => {
if (answer.toLowerCase() === 'istanbul') {
resolve();
} else {
reject(new Error('Good guess but a wrong answer. Try again!'));
}
}, 1500);
});
}
let form = document.getElementById('form');
let textarea = document.getElementById('textarea');
let button = document.getElementById('button');
let loadingMessage = document.getElementById('loading');
let errorMessage = document.getElementById('error');
let successMessage = document.getElementById('success');
form.onsubmit = handleFormSubmit;
textarea.oninput = handleTextareaChange;
๊ฐ๋จํ ํผ ์ ์ถ UI๋ง ํด๋ ์ฝ๋๊ฐ ์ด๋ ๊ฒ ๊ธธ์ด์ง๋๋ฐ ํ๋ก๊ทธ๋จ์ ๊ท๋ชจ๊ฐ ๊ฑฐ๋ํด์ง๋ฉด ๊ทธ ์ฝ๋์ ๋ณต์ก๋๋ ์ผ๋ง๋งํ ๊น?
๋ฆฌ์กํธ๋ ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํด์ค๋ค. ์ ์ธํ UI๋ ๊ฐ UI๊ฐ ์ด๋์ผ ํ๋์ง๋ฅผ ์ ํ๋ฉด ๋ฆฌ์กํธ๊ฐ ๊ฐ ํ๋ฉด์ ์ด๋ป๊ฒ ๊ตฌํํ ๊ฒ์ธ์ง ์ค์ค๋ก ํด๊ฒฐํ๋ค. ํ์์ ํ์ ๋ชฉ์ ์ง๋ง ๋งํ๋ฉด ๋๋๊ฒ์ฒ๋ผ.
์ ์ธํ ๋ฐฉ์์ผ๋ก UI ์์ฑํ๊ธฐ
- Identify : ์ปดํฌ๋ํธ์ ๊ฐ ์๊ฐ์ ์ํ๋ฅผ ๊ฒฐ์ ํ๋ค.
- Determine : ์ํ์ ๋ณํ๋ฅผ ์ผ์ผํฌ ๊ฒ์ด ๋ฌด์ธ์ธ์ง ๊ฒฐ์ ํ๋ค.
- Represent :
useState๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ํ๋ฅผ ํํํ๋ค. - Remove : ํ์ ์๋ ์ํ ๋ณ์๋ฅผ ์ ๊ฑฐํ๋ค.
- Connect : ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ค.
1. Identify : ์ปดํฌ๋ํธ์ ๊ฐ ์๊ฐ์ ์ํ๋ฅผ ๊ฒฐ์ ํ๋ค.
UI ์ปดํฌ๋ํธ๊ฐ ์ด๋ค ๋ชจ์ต์ ๊ฐ์ง๊ฒ์ธ์ง ๋จผ์ ์๊ฐํด์ผ ํ๋ค. ๊ฐ๋ น ํผ ์ ์ถ์ ์์ด์๋ ๋ค์์ ์ํ๋ค์ด ์์๊ฒ์ด๋ค :
- empty - ์ ์ถ ๋ฒํผ ๋นํ์ฑํ
- typing - ์ ์ถ ๋ฒํผ ํ์ฑํ
- submitting - ์ ๋ ฅ๊ณผ ์ ์ถ ๋ฒํผ ๋นํ์ฑํ
- success - ์ ์ฒด ํผ์ด ์ฌ๋ผ์ง๊ณ ์๋ด ๋ฌธ๊ตฌ ์ถ๋ ฅ
- error - typing์ํ์ ๊ฐ์ง๋ง ์๋ฌ ๋ฌธ๊ตฌ๋ ํจ๊ป ์ถ๋ ฅ
2. Determine : ์ํ์ ๋ณํ๋ฅผ ์ผ์ผํฌ ๊ฒ์ด ๋ฌด์ธ์ธ์ง ๊ฒฐ์ ํ๋ค.
state๋ฅผ ๋ณํ์ํค๋ ํธ๋ฆฌ๊ฑฐ๋ ๋ ๊ฐ์ง๊ฐ ์๋ค :
- ์ฌ์ฉ์ ์ ๋ ฅ - ๋ง์ฐ์ค ํด๋ฆญ, ํค๋ณด๋ ์ ๋ ฅ, ๋งํฌ ์ด๋ ๋ฑ
- ์ปดํจํฐ ์ ๋ ฅ - ๋คํธ์ํฌ ์๋ต ๋์ฐฉ, ํ์์์, ์ด๋ฏธ์ง ๋ก๋ฉ ๋ฑ
์ด์ ๊ฐ ์ ๋ ฅ๋ค์ด ์ด๋ป๊ฒ ์ํ๋ฅผ ๋ณํํ๋์ง ๊ฒฐ์ ํ๋ค.
- ์ ๋ ฅ์ฐฝ์ด ๋น์ด์๋ค๋ฉด empty ์ํ, ๋น์ด์์ง ์๋ค๋ฉด typing ์ํ์ผ ๊ฒ์ด๋ค.
- ์ฌ์ฉ์๊ฐ ์ ์ถ ๋ฒํผ์ ๋๋ฅด๋ฉด submitting ์ํ๊ฐ ๋ ๊ฒ์ด๋ค.
- ์์ฒญ์ด ์ฑ๊ณต์ ์ผ๋ก ์ฒ๋ฆฌ๋์๋ค๋ ๋คํธ์ํฌ ์๋ต์ด ๋์ฐฉํ๋ฉด success์ํ๋ก, ๋ฐ๋๋ก ์ค๋ฅ๊ฐ ๋ฐ์ํ๋ค๋ฉด error ์ํ๊ฐ ๋ ๊ฒ์ด๋ค.
3. Represent : useState๋ก ๋ฉ๋ชจ๋ฆฌ์ ์ํ๋ฅผ ํํํ๋ค.
๋ค์์ผ๋ก ๊ฐ ์ปดํฌ๋ํธ์ ์๊ฐ์ state๋ฅผ useState๋ก ํํํด์ผ ํ๋ค.
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);
4. Remove : ํ์ ์๋ ์ํ ๋ณ์๋ฅผ ์ ๊ฑฐํ๋ค.
์์ ์์ฑํ state๋ค์ ๊ฒํ ํด๋ณด์. ๋ค์์ ์ง๋ฌธ๋ค์ ํตํด์ :
- state๊ฐ ์ญ์ค์ ์ผ์ผํค๋๊ฐ? - ๊ฐ๋ น
isTyping๊ณผisSubmitting์ด ๋์์true์ผ ์๋ ์๋ค. ์ด๋ฌํ ์ญ์ค์ ๋ณดํต state๊ฐ ์ถฉ๋ถํ ์ ํ๋์ง ์์์์ ์๋ฏธํ๋ค. ์ด ๋ state๋ ๋ค ๊ฐ์ง ์กฐํฉ์ ๊ฐ์ง์ง๋ง(์ฐธ-์ฐธ, ์ฐธ-๊ฑฐ์ง, ๊ฑฐ์ง-์ฐธ, ๊ฑฐ์ง-๊ฑฐ์ง) ์ ํจํ ๊ฒ์ ์ธ ๊ฐ ๋ฟ์ด๋ค(๋ ๋ค ์ฐธ์ธ ๊ฒฝ์ฐ๋ฅผ ์ ์ธํด์). ์ด๋ฐ ๊ฒฝ์ฐ ์ด state๋ค์ ํฉ์ณ์ ์ธ ๊ฐ(isTyping,isSubmitting,success)์ ๊ฐ์ง๋ ํ๋์ state๋ก ๋ง๋ค ์ ์๋ค. - ๊ฐ์ ์ ๋ณด๊ฐ ์ด๋ฏธ ๋ค๋ฅธ state์ ์๋๊ฐ? - ์์ ๋น์ทํ ๊ฒฝ์ฐ๋ก,
isEmpty์isTyping์ ๋์์ ์ฐธ์ผ ์ ์๋ค. ์ด๋ฐ ๊ฒฝ์ฐ์ ๋ state๋ฅผ ๋ณ๋๋ก ๋ถ๋ฆฌํ๋ฉด ๋ฒ๊ทธ๊ฐ ๋ฐ์ํ ์ ์๋ค. ์ด ๊ฒฝ์ฐ์isEmpty๋answer.length===0๊ณผ ๊ฐ๊ธฐ ๋๋ฌธ์isEmptystate๋ฅผ ์ ๊ฑฐํ ์ ์๋ค. - ๋ค๋ฅธ state๋ฅผ ๋ฐ๊ฟ์ ๋๊ฐ์ ์ ๋ณด๋ฅผ ์ป์ ์ ์๋๊ฐ? -
isError๋ผ๋ state๋error !=== null๊ณผ ๊ฐ๊ธฐ ๋๋ฌธ์isError๋์error๋ฅผ ์ฌ์ฉํ๋ค.
์ด ๋ฆฌํฉํ ๋ง์ ๊ฒฐ๊ณผ๋ก state๋ ์ด๋ ๊ฒ ์ ๋ฆฌ๋ ์ ์๋ค.
const [answer, setAnswer] = useState('');
const [error, setError] = useState(null);
const [status, setStatus] = useState('typing'); // 'typing', 'submitting', or 'success'
์์ฝ :
isEmpty์ญ์ .isTyping,isSubmitting,success๋ฅผ ํ๋์ ์ํ๋ก ํฉ์ณค๋ค.isError์ญ์ .
5. Connect : ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํ๋ค.
์ด์ state๋ค์ ์ ๋ฆฌํ์ผ๋ ์ด state๋ค์ ์ค์ ํ๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ฅผ ์ฐ๊ฒฐํด์ผ ํ๋ค.
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>
//...
<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) {
// Pretend it's hitting the network.
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);
});
}
์ด ์ฝ๋๋ง ๋ณด๋ฉด ์ฌ์ค ์์ ๋ช ๋ นํ ์ฝ๋๋ณด๋ค ์ฝ๋๋์ ๋ง์ง๋ง, ํจ์ฌ ๊ฐ๋ ์ฑ์ด ์ข๊ณ ๊ทธ๋ฌ๊ธฐ ๋๋ฌธ์ ๋ ์ฐ์ฝ(ํ์ )ํ๋ค. ๊ทธ๋ฆฌ๊ณ ์ฝ๋์ ์ถ๊ฐ์ ๋ณ๊ฒฝ๋ ์ฉ์ดํด์ง๋ค.
์์ ๋ฌธ์
1. CSS ํด๋์ค ํ ๊ธ
export default function Picture() {
return (
<div className="background background--active">
<img
className="picture" // picture--active
alt="Rainbow houses in Kampung Pelangi, Indonesia"
src="https://i.imgur.com/5qwVYb1.jpeg"
/>
</div>
);
}
์ด๋ฏธ์ง๋ฅผ ๋๋ฅด๋ฉด ๋ฐฐ๊ฒฝ์ด ์ฌ๋ผ์ง๊ณ (backgroud--activeํด๋์ค๋ฅผ ์ ๊ฑฐ) ์ด๋ฏธ์ง์ picture--active๋ผ๋ ํด๋์ค๋ฅผ ์ถ๊ฐํ๊ฒ ๋ง๋ค๊ณ , ๋ฐ๋๋ก ๋ค์ ๋ฐฐ๊ฒฝ์ ๋๋ฅด๋ฉด background--active ํด๋์ค๋ฅผ ์ถ๊ฐํ๊ณ picture--active ํด๋์ฌ๋ฅด ์ ๊ฑฐํ๋๋ก ์์ ํด์ผํ๋ค.
export default function Picture() {
const [element,setElement] = useState("");
return (
<div className={`background ${element==="background" ? "background--active" : ""}`}
onClick={()=>setElement("background")}>
<img
className={`picture ${element==="image" ? "picture--active":""}`}
alt="Rainbow houses in Kampung Pelangi, Indonesia"
src="https://i.imgur.com/5qwVYb1.jpeg"
onClick={(e)=>{
e.stopPropagation();
setElement("image");}}
/>
</div>
);
}
๋ต์๊ณผ ๋ค๋ฅด์ง๋ง ๋๋ ์ด๋ ๊ฒ ๊ตฌํํ๋ค.
3. ๋ช ๋ นํ ์ฝ๋ ๋ฆฌํฉํ ๋ง
์ฌ๋ฐ๊ฒ ๋ฆฌ์กํธ๊ฐ ์๋ ์ผ๋ฐ ์๋ฐ์คํฌ๋ฆฝํธ ์ฝ๋๋ฅผ ์์ ํ๋ ๋ฌธ์ . ๋ด๋ถ์ ์ผ๋ก ๋ฆฌ์กํธ๊ฐ ์ด๋ป๊ฒ ์๋ํ๋์ง์ ๋ํ ํํธ๋ฅผ ์ค๋ค.
let firstName = 'Jane';
let lastName = 'Jacobs';
let isEditing = false;
function handleFormSubmit(e) {
e.preventDefault();
setIsEditing(!isEditing);
}
function handleFirstNameChange(e) {
setFirstName(e.target.value);
}
function handleLastNameChange(e) {
setLastName(e.target.value);
}
function setFirstName(value) {
firstName = value;
updateDOM();
}
function setLastName(value) {
lastName = value;
updateDOM();
}
function setIsEditing(value) {
isEditing = value;
updateDOM();
}
function updateDOM() {
if (isEditing) {
editButton.textContent = 'Save Profile';
// TODO: show inputs, hide content
hide(firstNameText);
hide(lastNameText);
show(firstNameInput);
show(lastNameInput);
} else {
editButton.textContent = 'Edit Profile';
// TODO: hide inputs, show content
hide(firstNameInput);
hide(lastNameInput);
show(firstNameText);
show(lastNameText);
}
// TODO: update text labels
firstNameText.textContent = firstName;
lastNameText.textContent = lastName;
helloText.textContent = (
'Hello ' +
firstName + ' ' +
lastName + '!'
);
}
function hide(el) {
el.style.display = 'none';
}
function show(el) {
el.style.display = '';
}
let form = document.getElementById('form');
let editButton = document.getElementById('editButton');
let firstNameInput = document.getElementById('firstNameInput');
let firstNameText = document.getElementById('firstNameText');
let lastNameInput = document.getElementById('lastNameInput');
let lastNameText = document.getElementById('lastNameText');
let helloText = document.getElementById('helloText');
form.onsubmit = handleFormSubmit;
firstNameInput.oninput = handleFirstNameChange;
lastNameInput.oninput = handleLastNameChange;
editButton์ ํด๋ฆญ -> handleFormSubmit -> setIsEditing -> <b>ํ๊ทธ ์จ๊ธฐ๊ณ input ํ๊ทธ ๋
ธ์ถ -> oninput ํธ๋ค๋ฌ -> ๊ธ์ ์
๋ ฅ์๋ง๋ค updateDOM ํธ์ถ -> firstNameText.textContent = firstName ์ผ๋ก ์๋์ ์ถ๋ ฅ๋๋ ๋ฌธ๊ตฌ ์ค์๊ฐ ์
๋ฐ์ดํธ
์ด๋ฐ ํ๋ฆ์ผ๋ก ์คํ๋๋๋ฐ ์ ๋ง ๋ฆฌ์กํธ ์ ์๋ ์ด๊ฑธ ์๋ฐ์คํฌ๋ฆฝํธ๋ก ํ๋ํ๋ ์์ฑํ์ด์ผ ํ๊ตฌ๋.. ๋๋๋ค.

