๋ฆฌ์กํธ ๊ณต์๋ฌธ์๋ฅผ ์ฐธ๊ณ ํ์ฌ ์์ญํ ๋ถ๋ถ๊ณผ ์กฐ๊ธ ์ฝ๊ฒ ํ์ด๋ณด์์ต๋๋ค
How declarative UI compares to imperative
๋จผ์ ๋ช ๋ นํ ๋ณผ๊ฒ์. ๋ค์๊ณผ ๊ฐ์ UI ์ธํฐ๋์ ์ด ์๋ค๊ณ ์๊ฐํด๋ณด์ธ์.
- form ์ ๋ฌด์ธ๊ฐ๋ฅผ ์ ๋ ฅํ๋ฉด "์ ์ถ" ๋ฒํผ์ด ํ์ฑํ๋ฉ๋๋ค.
- "์ ์ถ" ๋ฒํผ์ ๋๋ฅด๋ฉด ํผ๊ณผ ๋ฒํผ์ด ๋นํ์ฑํ๋๊ณ ๋ก๋ฉ ์คํผ๋๊ฐ ๋ํ๋ฉ๋๋ค.
- ๋คํธ์ํฌ ์์ฒญ์ด ์ฑ๊ณตํ๊ฒ ๋๋ฉด form์ด ์จ๊ฒจ์ง๊ณ "๊ฐ์ฌํฉ๋๋ค" ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ค๋๋ค.
- ๋คํธ์ํฌ ์์ฒญ์ด ์คํจํ๋ฉด ์ค๋ฅ ๋ฉ์์ง๋ฅผ ๋ณด์ฌ์ฃผ๊ณ ํผ์ด ๋ค์ ํ์ฑํ๋ฉ๋๋ค.
๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ์์ , ์์์ ์ด์ผ๊ธฐํ ๋ถ๋ถ๋ค์ด ๋ฐ๋ก ์ธํฐ๋์ ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๊ณผ ์ง์ ์ ์ผ๋ก ์ผ์นํฉ๋๋ค. ์ ์ํธ ์์ฉ์ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๊ณผ ์ง์ ์ ์ผ๋ก ์ผ์นํฉ๋๋ค. ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ์ UI๋ฅผ ์กฐ์ํ๊ธฐ ์ํด ์ผ์ด๋ ์ผ์ ๋ํด ์ ํํ ๊ฐ์ด๋๋ฅผ ์ค์ผ ํฉ๋๋ค. ์ด์ ํ๋ ์ฌ๋ ์์ ํ๋ฉด์ ์ด๋๋ก ๊ฐ์ผ ํ๋์ง ์๋ ค์ฃผ๋ ๊ฒ์ ์๊ฐํด๋ณด๋ฉด ์ด๊ฒ๋ ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ๊ณผ ๊ฐ์ ๋ฐฉ๋ฒ์ ๋๋ค. UI์์๋ ๊ฐ๋ฐ์๊ฐ ๋ง๋ ๋ช ๋ น์ ์์์ ๋ฐ๋ผ 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
์์ค์ฝ๋๋ฅผ ๋ณด๋ฉด ๋ช ๋ นํ์์ DOM์ ์ง์ ์ ๊ทผํ์ฌ DOM API๋ฅผ ์ฌ์ฉํ์ฌ ์ฒ๋ฆฌํ๊ณ ์์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ์ํ์ ๋ฐ๋ผ ํ ์คํธ ์์, ๋ฒํผ, ์๋ฌ๋ฉ์์ง, ์ ์ฌ๋ฉ์์ง ๋ค์ด ๊ฐ๊ฐ ์ด๋ป๊ฒ ํด์ผ ํ๋์ง ์ง์ ์จ์ค์ผ ํฉ๋๋ค.
๋ช ๋ นํ ์ฝ๋๋ก๋ ์ถฉ๋ถํ ์ ์๋๋๋ ๊ฑด ์ฌ์ค์ ๋๋ค. ํ์ง๋ง ๋ณต์กํ ์์คํ ๋ด์์ ๊ด๋ฆฌํ๊ธฐ๊ฐ ๊ธฐํ๊ธ์์ ์ผ๋ก ํ๋ค์ด์ง๋๋ค. ํ์ฌ ๋ง๋ค์ด์ง ์์๊ณผ ๊ฐ์ ์๋ก์ด ์์์ ์ถ๊ฐํ๋ค๊ฑฐ๋ ์๋ก์ด ์ํธ์์ฉ์ด ์๊ธด๋ค๋ฉด ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ค์ ์ฐฌ์ฐฌํ ์ดํด ์๋ก์ด ๋ฒ๊ทธ๊ฐ ์๊ธฐ์ง ์์๋์ง ํ์ธํด์ผ ํฉ๋๋ค.
์ด ๋ถ๋ถ์ ํด๊ฒฐํ๊ธฐ ์ํด ๋ฆฌ์กํธ๊ฐ ๋ง๋ค์ด์ก์ต๋๋ค.
๋ฆฌ์กํธ์์๋ ์ด๋ ๊ฒ ์ง์ UI๋ฅผ ์กฐ์ํ์ง ์์ต๋๋ค. ์ฆ, ์ปดํฌ๋ํธ๋ฅผ enable/disable, show/hidden ํ์ง ์๊ณ ๋ณด์ฌ์ฃผ๊ณ ์ถ์ ๊ฒ์ ์ ์ธํฉ๋๋ค.
์ ์ธ์ด๋ผ๋ ๋จ์ด๊ฐ ๋์์ต๋๋ค.
์๊น ์์์ ์ด์ผ๊ธฐํ ์ด์ ํ๋ ์ฌ๋ ์์์ ์ฌ๊ธฐ ๋๋ ์ ๊ธฐ๋ก ๊ฐ์ผ ํ๋ค๊ณ ๋ฐฉํฅ์ ์ง์ ์ ์ด์ผ๊ธฐํ๋ ๋ช ๋ นํ ํ๋ก๊ทธ๋๋ฐ๊ณผ๋ ๋ค๋ฅด๊ฒ ์ ์ธํ ํ๋ก๊ทธ๋๋ฐ์ ํ์ ๊ธฐ์ฌ ์์ ์์ ์ด๋๋ก ๊ฐ์ฃผ์ธ์. ๋ผ๊ณ ๋งํ๋ ๊ฒ๊ณผ ๊ฐ์ต๋๋ค.
๋ชฉ์ ์ง์ ์ด๋ป๊ฒ ๊ฐ์ง ์๊ฐํ๋ ์ฌ๋์ ํ์ ๊ธฐ์ฌ์ด๊ณ , ์ด ํ์๊ธฐ์ฌ๋ ์คํ๋ ค ์ด๋ป๊ฒ ํ๋ฉด ๋ ์ผ์ฐ ๋์ฐฉํ ์ง ๋ํ ํจ์จ์ ์ธ ๋ฐฉ๋ฒ์ ์ ์ ์์ต๋๋ค.
๊ทธ๋ผ ์ฝ๋๋ก๋ ์ด๋ป๊ฒ ์ ์ธ์ ์ผ๋ก ๋ํ๋ผ ์ ์์๊น?
๋ฆฌ์กํธ์์๋ ์ด๋ค ์ฃผ์ด์ง ์ํ ํํ์ ๋ํด UI๋ฅผ ์ค๋ช ํฉ๋๋ค.
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) {
// 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)
})
}
async function handleSubmit(e) {
e.preventDefault()
setStatus('submitting')
try {
await submitForm(answer)
setStatus('success')
} catch (err) {
setStatus('typing')
setError(err)
}
}
button ์์์ ๊ฒฝ์ฐ ์ํ๋ฅผ ์ด์ฉํ์ฌ ๋ฒํผ์ ์ํ๊ฐ submitting๊ณผ ๊ฐ๊ฑฐ๋ answer์ ๊ธธ์ด๊ฐ ์์ง 0 ์ด๋ผ๋ฉด disabled ์ฒ๋ฆฌํฉ๋๋ค. ์ํ๋ ์ด๋์ ๋ณ๊ฒฝ๋๋ ๊ฑธ๊น์?
submit ๋ฒํผ์ ๋๋ฅธ ์ง ํ๋ฅผ ๋ด๋นํ๋ ์ฝ๋๋ ๋ช ๋ นํ๊ณผ๋ ๋ค๋ฅด๊ฒ ์ํ๋ฅผ ์ธํ ํฉ๋๋ค. ๊ฐ๊ฐ์ ๋จ๊ณ์ ๋ฐ๋ผ ๋ค๋ฅธ ์ํ๊ฐ ์ธํ ๋ฉ๋๋ค. (submit ting / success/ typing )
dom ์ ์ง์ ์ด๋ ๊ฒ ํ๋ผ ์ ๋ ๊ฒ ํ๋ฌ ๊ฐ ์๋, ์ํ๊ฐ ์ด๋ด ๋ ์ด๋ ๊ฒ ํด์ฃผ๋ผ๊ณ ๋ฆฌ์กํธ์๊ฒ ์ด์ผ๊ธฐํ๊ณ ๋ฆฌ์กํฐ๊ฐ UI๋ฅผ ์ ๋ฐ์ดํธํ๋ ๋ฐฉ๋ฒ์ ์์๋ ๋๋ค. ์์์ ์ธ๊ธํ ํ์๊ธฐ์ฌ๊ฐ ๋ฆฌ์กํฐ๋ก ๊ฐ์ฃผํ ์ ์์ ๊ฒ ๊ฐ์ต๋๋ค. ๋ํ ๋ฆฌ์กํฐ๊ฐ ์ด๋ป๊ฒ ํ๋ฉด UI๋ฅผ ๋นจ๋ฆฌ ์ ๋ฐ์ดํธํ ์ง๋ ์์๋ ๋๋ค.
์ ์ธํ ์ฝ๋๊ฐ ๋ช ๋ นํ ์์ ์ฝ๋ ๋ณด๋ค ๊ธธ์ง๋ง, ํจ์ฌ ๋ ์ทจ์ฝํฉ๋๋ค. ๋ชจ๋ ์ํธ ์์ฉ์ ์ํ ๋ณ๊ฒฝ์ผ๋ก ํํํ๋ฉด ๋์ค์ ๊ธฐ์กด ์ํ๋ฅผ ์์ํ์ง ์๊ณ ์๋ก์ด ์๊ฐ์ ์ํ๋ฅผ ๋์ ํ ์ ์์ต๋๋ค. ๋ํ, ์ํธ ์์ฉ ์์ฒด์ ๋ ผ๋ฆฌ๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ๋ ๊ฐ ์ํ์ ํ์๋์ด์ผ ํ๋ ํญ๋ชฉ์ ๋ณ๊ฒฝํ ์ ์์ต๋๋ค.