Logo
Hyunsu Blog

๐Ÿ“†Published :Apr 15, 2023 โ€ข

๐Ÿ“†Updated :Apr 15, 2021 โ€ข

โ˜•๏ธ1min

Declarative UI in React <๊ณต์‹๋ฌธ์„œ ์ฐธ๊ณ >

๋ฆฌ์•กํŠธ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜์—ฌ ์˜์—ญํ•œ ๋ถ€๋ถ„๊ณผ ์กฐ๊ธˆ ์‰ฝ๊ฒŒ ํ’€์–ด๋ณด์•˜์Šต๋‹ˆ๋‹ค

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๋ฅผ ๋นจ๋ฆฌ ์—…๋ฐ์ดํŠธํ• ์ง€๋„ ์•Œ์•„๋ƒ…๋‹ˆ๋‹ค.

  • ์„ ์–ธํ˜• ์ฝ”๋“œ๊ฐ€ ๋ช…๋ นํ˜• ์˜ˆ์ œ ์ฝ”๋“œ ๋ณด๋‹ค ๊ธธ์ง€๋งŒ, ํ›จ์”ฌ ๋œ ์ทจ์•ฝํ•ฉ๋‹ˆ๋‹ค. ๋ชจ๋“  ์ƒํ˜ธ ์ž‘์šฉ์„ ์ƒํƒœ ๋ณ€๊ฒฝ์œผ๋กœ ํ‘œํ˜„ํ•˜๋ฉด ๋‚˜์ค‘์— ๊ธฐ์กด ์ƒํƒœ๋ฅผ ์†์ƒํ•˜์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด ์‹œ๊ฐ์  ์ƒํƒœ๋ฅผ ๋„์ž…ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ, ์ƒํ˜ธ ์ž‘์šฉ ์ž์ฒด์˜ ๋…ผ๋ฆฌ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ ๋„ ๊ฐ ์ƒํƒœ์— ํ‘œ์‹œ๋˜์–ด์•ผ ํ•˜๋Š” ํ•ญ๋ชฉ์„ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Hi, I'm Hyunsu ๐Ÿ‘‹

Profile Image

์•ˆ๋…•ํ•˜์„ธ์š”. ํ”„๋ก ํŠธ์—”๋“œ ๊ฐœ๋ฐœ์ž ์ฃผํ˜„์ˆ˜์ž…๋‹ˆ๋‹ค.

๊ฐœ๋ฐœ์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๋“ค์—๊ฒŒ ํ’๋ถ€ํ•˜๊ณ  ๊ฐ€์น˜ ์žˆ๋Š” ๊ฒฝํ—˜์„ ์ œ๊ณตํ•˜๋Š” ์ผ์— ๋ฟŒ๋“ฏํ•จ์„ ๋Š๋‚๋‹ˆ๋‹ค.

์˜ต์‹œ๋””์–ธ(Obsidian)์—์„œ ํ˜„์žฌ ๋ธ”๋กœ๊ทธ๋กœ ํ•˜๋‚˜์”ฉ ๊ธ€์„ ์˜ฎ๊ธฐ๋Š” ๊ณผ์ •์— ์žˆ์–ด์š”. โ˜•๏ธ ๐Ÿ‘ฉโ€๐Ÿ’ป โ›ท

Github on ViewReach Me Out