์ด๋ฒ ํ ์ค ๋ชจ์๊ณ ์ฌ 2ํ์ฐจ์ ์ฐธ์ฌํ๋ฉด์ ์ ์ง๋ณด์์ ํ์ฅ์ฑ์๋ ์ค๊ณ๋ฅผ ์ํด ์์ธก๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ธฐ์ค, ์ ์ ํ ์ถ์ํ ์์ค์ผ๋ก ์กฐ์ ํ๋ ๋ฒ์ ๋ํด์ ์ ๋ฆฌํ ์ ์์์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ๊ตฌํ์ด๋ , ๋ฆฌํฉํ ๋ง์ด๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ ๊ฒ์ด ๊ฐ์ฅ ์ค์ํ๊ณ ์ ํ ๋์ด์ผํ๋ ์ ์ ๋๊ผ์ต๋๋ค.
โํ์์ค ์์ฝโ ํ๋ฉด์ ๊ตฌํํด์ฃผ์ธ์. ๋ ์ง๋ฅผ ์ ํํด ํ์์ค ์์ฝ ํํฉ์ ํ์ธํ๊ณ , ์ํ๋ ์๊ฐ/์ธ์/์ฅ๋น ์กฐ๊ฑด์ผ๋ก ์์ฝ์ ์๋ํฉ๋๋ค.
์๋น์ค์ ์ ์ง๋ณด์๋ ์ฅ๊ธฐ์ ์ธ ํ์ฅ์ฑ์ ๊ณ ๋ คํ ์ค๊ณ, ์ถ์ํ ๊ด์ ์ ์ง์คํด์ ๊ธฐ๋ฅ์ ๊ตฌํํด์ฃผ์ธ์.
์ด์ 1ํ์ฐจ์ ๋ค๋ฅธ ์ ์ ์ฒ์๋ถํฐ ๊ตฌํํ๋ ๊ฒ์ด ์๋ ์ด๋ฏธ ๊ตฌํ๋์ด ์๋ ๋ ๊ฑฐ์ ์ฝ๋๋ฅผ ๊ฐ์ ํด์ผ ํ๋ ๊ฒ ์ ๋๋ค.
1ํ์ฐจ์ ๋์ผํ ์ ์ ์ ์ง๋ณด์์ ์ฅ๊ธฐ์ ์ธ ํ์ฅ์ฑ์ ๊ณ ๋ คํ ์ค๊ณ์ ์ถ์ํ๋ฅผ ๊ณ ๋ คํด์ผ ํ์ต๋๋ค.
์ฒ์์๋ ์ด ํ์ฅ์ฑ์ ๊ณ ๋ คํ ์ค๊ณ์ ๋ํด ๊ธฐ์ค์ ์ ์ํ๋๋ฐ ์์ฃผ ๋ง์ ์๊ฐ์ ๋ค์๊ณ ํ์คํ ๊ธฐ์ค์ด ์์์ง๋ง ํ ์ค ๋ชจ์๊ณ ์ฌ 1ํ์ฐจ์ ๋ฐฐ์ด ๊ฒ์ ๊ฐ์ง๊ณ ๋ช ํํ๊ฒ ์ ์ํ ์ ์์์ต๋๋ค.
๋ผ์ด๋ธ๋ ์ด 2๋ถ๋ก ์งํ๋๊ณ 1๋ถ๋ ์ฌ์ฝ๋๊ป์ ์ธํฐํ์ด์ค, ๋ชจ๋ ์ฑ ์, ์กฐํฉ์ ๋ํด ์ค๋ช ํ์ จ๊ณ ๋์ฑ๋์ ์์ธกํ๊ธฐ ์ข์ ์ฝ๋์ ์ง์ ์๋ต ์์ฃผ๋ก ์งํํ์ต๋๋ค.
ํ ์ค ๋ชจ์๊ณ ์ฌ 1ํ์ฐจ๋ฅผ ํตํด ์ ์ง๋ณด์๊ฐ ์ฝ๊ณ ํ์ฅ๊ฐ๋ฅํ ์ค๊ณ์ ํต์ฌ์ '์์ธก ๊ฐ๋ฅํ๊ณ ์ฝ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ ๊ฒ'์ด๋ผ๋ ๊ฒ์ ์ดํดํ๊ณ ์ด๊ฒ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ๋ฐฉ๋ฒ์ ๋ํด ์ ๋ฆฌํ์์ต๋๋ค.
1. ์๊ตฌ์ฌํญ ๋ฌธ์๋ฅผ ๋ณด๊ณ '์ด์์ ์ธ ์ธํฐํ์ด์ค' ์ค๊ณ ๋จผ์
2. UI ํํ์ ์ฝ๋ ํํ๊ฐ 1:1 ๋งตํ๋๋ฉด ์ ์ง๋ณด์๊ฐ ์ฝ๋ค
3. ์ฌ์ฌ์ฉ์ ๋ฐ๋ผ์ค๋ ๊ฒ. ์ฑ ์ ๋จ์๋ก ์ถ์ํ๋ฅผ ํ ๋ฟ
๐ ํ ์ค ๋ชจ์๊ณ ์ฌ 1ํ ํต์ฌ ๋ด์ฉ ์ ๋ฆฌ
์ด๋ฒ์๋ ์ฒ์๋ถํฐ ๊ตฌํ์ด ์๋ ๋ฆฌํฉํ ๋ง์ด๊ธฐ ๋๋ฌธ์ ์ด์์ ์ธ ์ธํฐํ์ด์ค์ ๊ธฐ์ค์ ๋ฌธ์๋ก ์ ๋ฆฌํด์ AIํํ ํ์ต์ํค๋ ๊ฒ์ด ํจ์จ์ ์ผ ๊ฒ์ด๋ผ๊ณ ํ๋จํ์ง๋ง ์ ๊ฐ ์ํ๋ ๊ฒฐ๊ณผ๋ฌผ์ด ๋์ค์ง ์์์ต๋๋ค.
๋ผ์ด๋ธ ํด์ค์์ ์ฌ์ฝ๋์ด ๊ธฐ์กด ์ฝ๋๋ฅผ ๋ฌด์ํ๊ณ ์๋กญ๊ฒ ์ด์์ ์ธ ์ธํฐํ์ด์ค๋ฅผ ์์ฌ์ฝ๋๋ก ์์ฑํ์ ๋ค์ AI ํํ ์ ๋ฌํ๋ ๋ชจ์ต์ ๋ณด๊ณ ... ๋๋ ์ ์ด ์๊ฐ์ ๋ชปํ์ง...? ๐ฑ ์ ์ ์ข์ ์๊ฒฌ์ ํํํ์ต๋๋ค.
์ ๊ฐ ์๊ฐํ๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ๋ ๊ณผ์ ์ ํ๋จ์ ์์นํ ๋ฆฌํฉํ ๋ง ๊ณผ์ ์ ์์ฑํ์ต๋๋ค.
์ถ์ํ๋ฅผ ํ ๋ ์ฐฝ์๋ ฅ์ ๋ฐํํ์ง๋ง๋ผ ๋ผ๋ ๋ง์ ํ ์ค ๋ชจ์๊ณ ์ฌ 1ํ์ฐจ ๋ ๋ค์ ๊ธฐ์ต์ด ๋ฉ๋๋ค.
์์ธกํ๊ธฐ ์ฌ์ด ์ฝ๋๋ฅผ ์์ฑํด์ผํ๋ ๋งฅ๋ฝ์ด๋ ๊ฐ์ต๋๋ค.
์ฐฝ์๋ ฅ์ ๋ฐํํ์ง ๋ง๋ผ๋ ์๋ฏธ๋ ์ผ๋ฐ์ ์ธ ํํ, ๋๊ตฌ๋ ์๊ณ ์๋ ์ธํฐํ์ด์ค๋ ํํ์ ๊ฐ๊น๊ฒ ํํํ๋ผ๋ ์๋ฏธ์ ๋๋ค.
์ปดํฌ๋ํธ๋ช ์ ๋ค์ดํฐ๋ธ HTML ์์ ์ด๋ฆ๊ณผ ๋น์ทํ๊ฒ, props ๋ช ์ ๊ทธ ์์์ ์์ฑ๊ณผ ๊ฐ๊น๊ฒ ํํํ๋ฉด ๊ฐ ์ ๋ฌ๋๋ ๊ฐ๋ค์ด ์ด๋ค ์ญํ ๋ก UI์ ํํ๋ ์ ์๋์ง ์์ฐ์ค๋ฝ๊ฒ ์์ธกํ ์ ์์ต๋๋ค.
<DateSelect
date={date}
setDate={setDate}
minDate={minDate}
/>
์ปดํฌ๋ํธ ์ด๋ฆ์ ๋ณด๋ฉด '๋ ์ง๋ฅผ ์ ํํ๋ ์ปดํฌ๋ํธ' ์์ ์์ํ ์ ์์ต๋๋ค.
์ปดํฌ๋ํธ ์ด๋ฆ์ Select๊ฐ ๋ค์ด๊ฐ ์์ต๋๋ค.
Date ๋ฐ์ดํฐ๋ ์๋ฒ์์ ์ ๊ณตํ๋ ํน์ ๋ ์ง์ ๋ํ ์ ๋ณด๋ค ์ผ์๋ ์๊ณ ๋จ์ํ Data ๊ฐ์ฒด๋ input ์์์ date ํ์ ์ ์ด์ฉํด ํด๋ผ์ด์ธํธ์์ ์ ์ํ ์ ์๋ ๋ฐ์ดํฐ ์ผ์๋ ์์ต๋๋ค.
์ ํํ๋ ๊ธฐ๋ฅ์ UI๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ๋ ๋ง์ต๋๋ค. HTML์ select ์์ ์ฒ๋ผ ๋๋กญ๋ค์ด ํํ๊ฑฐ๋ radio, checkbox ํํ์ผ ์๋ ์๊ณ , BottomSheet ํํ๊ฐ ๋ ์ ์์ต๋๋ค.
๋์์ธ ์๊ตฌ ์ฌํญ์ ๋ฐ๋ผ ์ผ๋ง๋ ์ง ๋ฌ๋ผ์ง ์ ์์ ๊ฑฐ ๊ฐ์ต๋๋ค. ๋ด๋ถ ๊ตฌํ์ ์ด๋ป๊ฒ ํ ์ง ์์ธกํ๊ฒ๋ ํํํ๋ ๊ฒ์ ์ฝ๋๋ฅผ ์์ ํ๋ ๊ด์ ์์ ๋ดค์ ๋ ๋งค์นญ๋๊ฒ ์ฌ์ฉํ ํ์๋ ์์ ๊ฑฐ ๊ฐ์ต๋๋ค. ์คํ๋ ค ์์ ๋ฒ์๊ฐ ๋์ด ๋ ์ง๋ ๋ชจ๋ฆ ๋๋ค.
ํ์ง๋ง props ๋ช ๊ณผ ๊ฐ์ ์ด๋ฆ์ด ๋์ผํด์ ์ ๋ฌ๋๋ ๊ฐ์ด ์ด๋ป๊ฒ ์ฐ์ผ์ง ์์ธกํ๊ธฐ ์ด๋ ต์ต๋๋ค.
const [date, setDate] = useState();
์ฝ๋๊ฐ ๊ธธ์ง ์๊ณ ์ปดํฌ๋ํธ์ useState๊ฐ ์ ์ธ๋ ์์น๊ฐ ๊ฐ๊น๋ค๋ฉด date๋ ํด๋ผ์ด์ธํธ์์ ๊ด๋ฆฌํ๊ณ ์๋ ์ํ ๊ฐ์ด๊ณ setDate ๋ ์
๋ฐ์ดํธ ํจ์๋ฅผ props๋ก ์ ๋ฌํ๊ณ ์์ผ๋ ์ํ ๋์ด์ฌ๋ฆฌ๊ธฐ๋ก date๊ฐ ๋ค๋ฅธ ์ปดํฌ๋ํธ์ ๊ณต์ ๋๊ณ ์๋ ๊ฐ์ด๋ผ๊ณ ์์ธกํ ์ ์๊ฒ ์ง๋ง ์ํ๊ฐ ์
๋ฐ์ดํธ ๋๋ ํธ๋ฆฌ๊ฑฐ๊ฐ ๋ฌด์์ธ์ง๋ ์์ธกํ๊ธฐ ์ด๋ ต์ต๋๋ค.
๊ทธ๋ฆฌ๊ณ ์ํฉ์ ๋ฐ๋ผ ์ฝ๋ ์์น๊ฐ ๋ฉ์ด์ง๋ค๋ฉด DateSelect ์ปดํฌ๋ํธ์ props์ ์ ๋ฌ๋๋ ๊ฐ๊ณผ ๋งฅ๋ฝ์ ์ฝ๊ธฐ ์ํด ์ถ์ ํ๊ณ ๋งฅ๋ฝ์ ๊ธฐ์ตํ๊ณ ์์ด์ผํฉ๋๋ค.
์ฌ๋์ ๋๋ ์ค์ํ๋ค๊ณ ์ฌ๊ธฐ์ง ์์ ๊ฒ์ ๊ฑฐ์ ์คํตํ๊ฑฐ๋ ์ ์ฅํ์ง ์๊ธฐ ๋๋ฌธ์ ๋ชจ๋ ๋งฅ๋ฝ๊ณผ ๋ณ์์ด๋ฆ์ ์ธ์ฐ๊ธฐ ์ด๋ ต์ต๋๋ค.
๊ทธ๋ ๊ธฐ ๋๋ฌธ์ ์์ธก ๊ฐ๋ฅํ ์ฝ๋๊ฐ ์ฝํ๊ธฐ ์ฝ์ต๋๋ค.
<DatePicker
value={selectedDate}
onChange={setSelectedDate}
min={formatDate(new Date())}
/>
DatePicker ๋ Native HTML ์์๋ ์๋์ง๋ง ํ์ค์ ์ฐจ์ฉ๋ ๋งํผ ์์ฃผ ์ฌ์ฉํ๋ ์ ์ด์ฟผ๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๋ ์ง๋ฅผ ์ ํํ๋ ์ฉ๋์ ์์ ฏ ์ด๋ฆ์
๋๋ค. ๋ ์ง๋ฅผ ์ ํํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ด๋ฆ์ ๋๋ถ๋ถ DatePicker๋ฅผ ๋ง์ด ์๋๋ค.
์ด DatePicker์ ๋ด๋ถ ๊ตฌํ๋ value์ onChange, min ์์ฑ ์ด๋ฆ๋ค๋ก ๋ด๋ถ ๊ตฌํ์ ๋ค์ฌ๋ค ๋ณด์ง ์์๋ ์ด๋์ ๋ ์์ํ ์ ์์ต๋๋ค.
<input
type="date"
value={value}
min={min}
onChange={(e) => onChange(e.target.value)}
/>
๊ทธ๋ฆฌ๊ณ ๋ ์ง ์บ๋ฆฐ๋๋ฅผ ์ ํํ๋ ์ญํ ์ ์ธํฐํ์ด์ค๋ ๊ฑฐ์ ๊ณ ์ ์ ์ผ๋ก ์ฐ์ฌ์ ๋ด๋ถ ๊ตฌํ์ ์์ ์ผ๋ก ์ธํฐํ์ด์ค๋ ์ปดํฌ๋ํธ๋ช ์ด ์์ ๋ ๊ณ ๋ ค์ฌํญ๋ ๊ฑฐ์ ์์ด๋ณด์ ๋๋ค.
ํ๋ฉด UI ๊ตฌ์ฑ๊ณผ ํ์ด์ง ์ปดํฌ๋ํธ ๋ด ์ฝ๋๋ค์ด 1:1 ๋์์ด ๋๋ ๊ฒ์ด ์ข์ง๋ง ์ผ๋ง๋ ํํํด์ค์ผ ํ ์ง ๊ณ ๋ฏผ์ด ๋๋ ์๊ฐ๋ค์ด ์์ต๋๋ค.
์ํ๋ ์ธต์ ์์ฝํ ์ ์๋ ํ์์ค์ ์ฐพ๊ธฐ ์ํด ์ธต์ ์ ํํด์ผํ๋ Select๋ฅผ ๊ตฌํํด๋ด ๋๋ค.
<FloorSelect>
<FloorSelect.Option>1</FloorSelect.Option>
<FloorSelect.Option>2</FloorSelect.Option>
<FloorSelect.Option>3</FloorSelect.Option>
<FloorSelect.Option>4</FloorSelect.Option>
</FloorSelect>
์ค์ ์ ํ๊ฐ๋ฅํ ์ต์ ์ด 2~4๊ฐ๊ฐ ์๋ 10๊ฐ ์ด์์ด ๋๊ณ ์ฌ๋ฌ๊ณณ์์ ์ฌ์ฉ๋๋ค๋ฉด ์ฌ์ฉํ๊ธฐ ๋ถํธํ๊ณ ์ฝ๋๋ง ๊ธธ์ด์ง๋๋ค. ๊ฒ๋ค๊ฐ ์๋ฒ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์์ ํํํด์ผํ ๊ฒฝ์ฐ๋ ์์ต๋๋ค. ๋งตํํ๋ ๊ตฌํ๋ถ๋ฅผ ๋ณด์ฌ์ค ํ์๊ฐ ์๊ฒ ๋ฉ๋๋ค.
๋ผ์ด๋ธ ํด์ค์์ ํ์ด์ง ์ปดํฌ๋ํธ์์ ๊ตณ์ด ์์ง ์์๋ ๋๋ ์ ๋ณด๋ฅผ ํํํ ๋ "์ฝ๋๊ฐ ๋ฐฐ ๋ฐ์ผ๋ก ๋์๋ค", "๋ด์ฅ์ด ์์์ก๋ค" ๋ผ๊ณ ์ฌ๋ฐ๊ฒ ํํํ์ จ๋๋ฐ ์ง๊ธ ์๊ฐํด๋ ์์์ด ๋ฉ๋๋ค. ๐คฃ
๋ง์์ฒด๋ ์๊ธฐ์ง๋ง ์ฌ์ค ์ ๊ฐ 1ํ์ฐจ ๋ ํ๊ธฐ๋ก ์ด์์ ์ธ ์ธํฐํ์ด์ค๋ก ์ด๋ ๊ฒ ํํํ์ต๋๋ค. ๊ทธ๋ ํด๋ผ์ด์ธํธ์์ ์ ์ํ๊ณ 3๊ฐ๋ฐ์ ์์ด์ 1:1๋ก ๋ณด์ฌ์ค์ผํ๋ค๋ ๋ง์ ๋งค๋ชฐ๋ผ์ ๊ทธ๋ ๊ฒ ์ ์ํ์๋ ๊ธฐ์ต์ด ๋๋ค์. ๐
์ปดํ์ด๋ ์ปดํฌ๋ํธ ํจํด์ ์ ๋ชป ์ฌ์ฉํ๋ฉด ์คํ๋ ค ์ฝ๋๊ฐ ๋ณต์กํด์ง ์ ์์ต๋๋ค.
์ด ๊ฒฝ์ฐ๋ ๋ค์์คํ์ด์ค ๊ด์ ์์๋ง ์ ๊ทผํด์ ์ ์ ํ ์ถ์ํ๋ฅผ ํ์ง ์๊ณ ๋ชจ๋ ์ฑ ์์ ํ ๋ชจ๋์์ ๋ชฐ๋นตํด์ ์๊ธฐ๋ ๋ฌธ์ ์ ๋๋ค.
์ปดํ์ด๋ ์ปดํฌ๋ํธ ํจํด์ ์ค๋จ์ฉํ ๊ฒฝ์ฐ, ์ฌ์ฌ์ฉํ๋ ๊ณณ์ด ๋ง์์ง ๊ฒฝ์ฐ ๋งค๋ฒ ์กฐ๋ฆฝํด์ ์ฌ์ฉํด์ผํ๋ ๋ถํธํจ๊ณผ ์ฌ์ฉ์ฒ์์ ๋ด๋ถ ๊ตฌํ๋ถ์ ๋งฅ๋ฝ์ ์ฝ์ด์ผํ๋ ์ํฉ์ด ์๊ธธ ์ ์์ต๋๋ค. ๋ด๋ถ์์ children props๋ก context๋ฅผ ๊ณต์ ํ๋ ๊ฒฝ์ฐ ํ์
ํ๊ธฐ ๋ ์ด๋ ค์์ง๋๋ค.
<FilterPanel.Loading>
<FilterPanel.Error>
<BookingFilter onFilterChange={handleFilterChange}>
<BookingFilter.Title>์์ฝ ์กฐ๊ฑด</BookingFilter.Title>
<BookingFilter.DateInput label="๋ ์ง" />
<BookingFilter.TimeRange startLabel="์์ ์๊ฐ" endLabel="์ข
๋ฃ ์๊ฐ" />
<div css={css`display: flex; gap: 12px;`}>
<BookingFilter.Attendees label="์ฐธ์ ์ธ์" />
<BookingFilter.FloorSelect label="์ ํธ ์ธต" />
</div>
<BookingFilter.Equipment label="ํ์ ์ฅ๋น" />
<BookingFilter.ValidationError />
</BookingFilter>
AI์ ๋ฐ์ ์ผ๋ก ์์ฐ์ฑ์ด ๋์๋ค๊ณ ๋งํ์ง๋ง, ์์ฐ์ฑ์ ์ ์๋ ๋ฌด์์ธ๊ฐ์? ์์ฐ์ฑ์ ์ ์๊ฐ ๋์ด ์์ง ์์๋ฐ ์ด๋ป๊ฒ ๋์๋ค๊ณ ๋ง ํ ์ ์๋์? ๋ผ๋ ๋์ฑ๋์ ๋ง์์ ๋ค์ ์๊ฐ ๋จธ๋ฆฌ๊ฐ ๋ตํด์ก์ต๋๋ค.
๊ณผ์ ์ ๊ด๋ จ๋ ์ฝ๋ ๋ฆฌ๋ทฐ๋ ๋ฆฌํฉํ ๋งํ ๊ด์ ์ ๋ํด์ ๋ค๋ฅธ ๋ถ๋ค๊ณผ ์๊ธฐ๋ฅผ ๋๋๋ค๊ฐ ์ด ๋ถ๋ถ์ ๋ํด์๋ ๊ด์ ์ ๋๋ด์ต๋๋ค. ์ค์ ๋ก AI ๋๋ถ์ ๊ตฌํ์ ๋นจ๋ฆฌ ๋๋์ PR์ ๋ช ์ญ๊ฐ๊ฐ ์์ด์ง๋ง ์์ง๋ ๋ชจ๋ ์ธ์์ด ์ฝ๋๋ฆฌ๋ทฐ๋ฅผ ํด์ผํ๋ ๊ท์น ๋๋ฌธ์ ์์ฌ์ ธ์๋ PR์ ์ฒ๋ฆฌํ์ง ๋ชปํ๊ณ ์๋ค๋ ์ฌ๋ก๋ฅผ ๋ค์์ต๋๋ค.
AI๋ฅผ ํตํด ๊ตฌํ์ ๋ํ ์์ฐ์ฑ์ ๋์๋ค๊ณ ๋ ๋ถ๋ช ํ๊ฒ ๋งํ ์ ์๊ฒ ์ต๋๋ค๋ง.. ์ ํ์ ๋ํ ์์ฐ์ฑ์ด ๋์๋์ง๋ ์์ง ์ ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
์ ํ์ ์์ฑ ๊ธฐ์ค์ ๋ฐ๋ผ ๋ค๋ฅผ ์๋ ์์ง๋ง ๋ฐฐํฌ๋ฅผ ํ ์ดํ์ ๊ธฐ์กด ๋ ๊ฑฐ์ ์ฝ๋๋ฅผ AI๊ฐ ๋ช ํํ๊ฒ ์์ ํ์ง ์์ ๋ฌธ์ ํ์ผ๋ก ์ธํด ๋ง์๋๋ก ๋ฐ๊ฟ๋ฒ๋ ค์ ์ฌ์ ์ ์ฌ๋์ด ์ฐพ์๋ด์ง ๋ชปํด ์น์ธํ๋ค๋ฉด ์ด์๋ฅผ ๊ธํ๊ฒ ์ฒ๋ฆฌํด์ผํ๋ ๋น์ฉ์ด ๋ค๊ฒ์ด๊ณ ์ด๋ฐ ์ํฉ์ ์ฐจ๋จํ๊ธฐ ์ํด์ ๊ฒน๊ฒน์ด ์ฌ๋์ด QA๋ฅผ ์งํํ๋ค๋ฉด ๋ง์ฐฌ๊ฐ์ง๋ก ๋ณ๋ชฉ์ด ์ฌ์ ํ ์๊ธธ ๊ฒ ์ ๋๋ค.
์ ์ฒด ์ ํ ์์ฐ์ ํ์ดํ๋ผ์ธ ๊ณผ์ ๋ง๋ค ๊ตฌํ๋งํผ AI๊ฐ ํฌ์ ๋์ด์ ๋ณ๋ชฉ์ ํด๊ฒฐํ ์ ์๋ค๊ณ ํด๋ ์์ง๊น์ง ๋ฏฟ์ ์ ์๋ค๋ฉด ์ ์ฒด ์์ฐ์ฑ์ ๋๊ฐ์ง ์์๊น์?
๋ค์ ์ฒ์๋ถํฐ ๊ณผ์ ์ ์ ๊ทผํ๋ค๋ ๋ง์์ผ๋ก ์ ๊ฐ ์๊ฐํ๋ ํ์ด์ง ํ๋ฉด๊ณผ ์ฝ๋๊ฐ 1:1 ๋์๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํด ๋ดค์ต๋๋ค.
โ ๏ธ ์๋ ๋ด์ฉ๊ณผ ๊ธฐ์ค์ ์ ์๊ฐ์ผ ๋ฟ ๋ผ์ด๋ธ์์ ์ ์ํ ํด๋ต์ด ์๋๋๋ค.
// ์์ฝ ํํฉ ํ์ด์ง
<h1>ํ์์ค ์์ฝ</h1>
<section>
<h2>๋ ์ง ์ ํ</h2>
<DatePicker value={์ ํํ๋ ์ง} onChange={์ ํํ๋ ์ง_์
๋ฐ์ดํธํจ์} min={์ค๋๋ ์ง} />
</section>
<section>
<h2>์์ฝ ํํฉ</h2>
<Suspense fallback={<Timeline.Loading />}>
<Timeline date={์ ํํ๋ ์ง} />
</Suspense>
</section>
<section>
<h2>๋ด ์์ฝ</h2>
<Suspense fallback={<MyReservations.Loading />}>
<MyReservations />
</Suspense>
</section>
<CtaButton>์์ฝํ๊ธฐ</CtaButton>
์ ๋ฒ์ ๋ฆฌํฉํ ๋ง์ ํ๋ฉด์ ๊ฐ์ ํ๋ ๊ฒ์ด ๋ ์ฌ๋ผ data fetch ๋๋ฌธ์ suspense๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ ์์ ํ๊ณ ์ธํฐํ์ด์ค๋ฅผ ์ค๊ณํ์ต๋๋ค.
(์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ suspense ๊ธฐ์ค์ผ๋ก ๊ฐ์ธ๋ฉด๋๋ ์ค๊ณ ๋จ๊ณ์์๋ ๋ฌด์ํฉ๋๋ค.)
์์ฝ ํํฉ์ Timeline์ ์ปดํฌ๋ํธ์ ๋ด ์์ฝ์ MyReservations ์ปดํฌ๋ํธ๊ฐ ์ถ์ถ๊ณผ ๋ค๋ฆ์์ ์ ๋๋ก ์จ๊ฒจ์ง๊ฒ ๋์์ต๋๋ค.
์ฝ๊ธฐ ์ฝ๊ณ ์์ธก๊ฐ๋ฅํ ์ฝ๋๋ฅผ ์ํด ํ๋ฉด UI์ ์ฝ๋๋ฅผ 1:1 ๋์ํ๋๋ก ์ค๊ณํ๋ค๊ณ ํ๊ธฐ ์ข ๋ฌด์ํด ๋ณด์ ๋๋ค.
<h2>์์ฝ ํํฉ</h2>
<Suspense fallback={<Timeline.Loading />}>
<Timeline date={์ ํํ๋ ์ง} />
</Suspense>
์ด Timeline์ ์ปดํฌ๋ํธ๋ฅผ ๋ณด๊ณ ์ด๋ค ๊ฒ์ ๊ธฐ๋ํ ์ ์์๊น์?
์ฌ์ฉ์๊ฐ ์ ํํ ๋ ์ง ๋ฐ์ดํฐ๋ฅผ ๋ฐ๊ณ ํ์์ค ์์ฝ ๋ด์ญ์ ์กฐํํ๋ ๊ฒ, ๊ทธ UI์ ํํ๊ฐ ํ์๋ผ์ธ์ธ ๊ฒ์ ์ฝ๋๋ฅผ ์์์ ๋ถํฐ ์ฝ์ด์จ ๋งฅ๋ฝ์ผ๋ก ์ ์ถํด ๋ณผ ์ ์์ ๊ฑฐ ๊ฐ์ต๋๋ค.
์ด ์ ๋ ์์ค์ ์ถ์ํ์ผ๊น ์ถ์ถ์ผ๊น ๊ณ ๋ฏผ์ด ๋์ง๋ง ์ข ๋ UI ํ๋ฉด๊ณผ 1:1 ๋์์ด ๋๋ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ฃผ๋ฉด ๋ ์น์ ํ ๊ฑฐ ๊ฐ์ต๋๋ค.
ํ์์ค๊ณผ ์๊ฐ๋ ๋ฑ ๊ธฐ์ค์ด ๋๋ฌ๋๋ฉด ์ข๊ฒ ๋ค๊ณ ์๊ฐํ์ง๋ง ์๋ฒ๋ก ๋ถํฐ ๋ฐ์์จ data๋ฅผ ํ์ฉํ๊ธฐ์ ๋ฐ์์ ์ ๋ฌํ๋ ค๋ฉด suspense๋ฅผ ๊ฑท์ด๋ด์ผ ํฉ๋๋ค.
<h2>๋ด ์์ฝ</h2>
<Suspense fallback={<MyReservations.Loading />}>
<MyReservations />
</Suspense>
๋ง์ฐฌ๊ฐ์ง๋ก ์ด MyReservations ์ปดํฌ๋ํธ๋ฅผ ๋ณด๊ณ ์ด๋ค ๊ฒ์ ๊ธฐ๋ํ ์ ์์๊น์?
๋ด ์์ฝ ํ
์คํธ์ Suspense๋ก ๊ฐ์ธ์ ธ ์๋ ๊ฒ์ ๋ณด๋ ๋ด๊ฐ ์์ฝํ ํ์์ค ์์ฝ ๊ฑด๋ค์ ์กฐํํด ์ค ๊ฒ ๊ฐ์ต๋๋ค.
๊ทธ ์ด์์ ์ ๋ณด๋ ํ๊ณ ๋ค์ด๊ฐ๋ด์ผ ์ ์ ์์ต๋๋ค.
๋ฆฌ์คํธ์ ๊ตฌ์กฐ๋ผ๋๊ฐ ๋ด๊ฐ ์์ฝํ ๋ฐ์ดํฐ์ด๊ธฐ ๋๋ฌธ์ ์ธํฐ๋์ ์ ํตํด ๋ณ๊ฒฝ ๊ฐ๋ฅ์ฑ๋ ์์ ์ ์๊ฒ ์ง๋ง ์ง๊ธ ์ฝ๋๋ก๋ ์ ํ ์์ธกํ ์ ์์ต๋๋ค.
1ํ์ฐจ์ ๊ฒฝ์ฐ ๋์ผํ UI์ ๋ฆฌ์คํธ๊ฐ ์ฌ์ฌ์ฉ๋์ด์ filter์ sort ๋ฑ์ ์กฐ๊ฑด์ ์์ํจ์๋ก props๋ก ์ ๋ฌํด ์ฐจ๋ณ๋๋ ์ ๋ณด์ ๊ธฐ์ค์ ๋๋ฌ๋๊ฒ ํ์ง๋ง ์ฌ์ ํ ๋ฆฌ์คํธ์ UI ๊ตฌ์กฐ๋ ์ ์ ์์์ต๋๋ค.
<section>
<h2>์ ๊ธ ์ํ ๋ชฉ๋ก</h2>
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<ProductList.Error message="์ถ์ฒ ์ํ์ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค." onRetry={resetErrorBoundary} />
)}
>
<Suspense fallback={<ProductList.Loading text={'์ํ์ ๋ถ๋ฌ์ค๋ ์ค...'} />}>
<ProductList
filters={[
product => filterByMonthlyAmount(product, savingsStates.monthlyAmount),
product => filterByTerm(product, savingsStates.term),
]}
fallback={'์กฐ๊ฑด์ ๋ง๋ ์ํ์ด ์์ต๋๋ค.'}
/>
</Suspense>
</ErrorBoundary>
</section>
<section>
<h2>์ถ์ฒ ์ํ ๋ชฉ๋ก</h2>
<ErrorBoundary
onReset={reset}
fallbackRender={({ resetErrorBoundary }) => (
<ProductList.Error message="์ถ์ฒ ์ํ์ ๋ถ๋ฌ์ค๋ ๋ฐ ์คํจํ์ต๋๋ค." onRetry={resetErrorBoundary} />
)}
>
<Suspense fallback={<ProductList.Loading text={'์ถ์ฒ ์ํ์ ๋ถ๋ฌ์ค๋ ์ค...'} />}>
<ProductList
filters={[
product => filterByMonthlyAmount(product, savingsStates.monthlyAmount),
product => filterByTerm(product, savingsStates.term),
]}
sortBy={sortByAnnualRateDesc}
limit={2}
fallback={'์ถ์ฒ ์ํ์ด ์์ต๋๋ค.'}
/>
</Suspense>
</ErrorBoundary>
</section>
ํ์ง๋ง ๊ผญ ํ๋ฉด์ ์ฝ๋์ UI์ ๋ด์ฉ์ด 1:1 ๋์๋๋๋ก ์์ธํ ํํํ ํ์๋ ๋๋ผ์ง ์์์ต๋๋ค.
<ProductList
filters={[
product => filterByMonthlyAmount(product, savingsStates.monthlyAmount),
product => filterByTerm(product, savingsStates.term),
]}
sortBy={sortByAnnualRateDesc}
limit={2}
fallback={'์ถ์ฒ ์ํ์ด ์์ต๋๋ค.'}
/>
์ฝ๊ธฐ ์ ์ฉ์ ํํฐ ์กฐ๊ฑด์ ํด๋น๋๋ ๋ด์ญ์ ์กฐํํ๊ณ ๋ค๋ฅธ ๊ณณ์์ ์ฌ์ฌ์ฉ ๋ ๋ filter, sort, limit ์กฐ๊ฑด์ props๋ก ์ด์ด์ ํ์ํ ์์ค์ ์ ๋ณด๋ฅผ ๋๋ฌ๋ด๊ณ ์ ์ ํ ์์ค์ผ๋ก ์ถ์ํ๋ฅผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค.
2ํ์ฐจ์ ๊ฒฝ์ฐ ์์ฝ ํํฉ ํ์ด์ง์ ๋ด ์์ฝ ๋ฆฌ์คํธ์ ๋ค๋ฅธ ํ์ด์ง์ธ ์์ฝํ๊ธฐ ํ์ด์ง์์ ์์ฝ ๊ฐ๋ฅํ ๋ฆฌ์คํธ ๋ ๋ด ์์ฝ์ ๊ฒฝ์ฐ์๋ง ์ทจ์ ๋ฒํผ์ด ์๊ธฐ๋ ํ๊ณ ๋์์ธ์ด ๋ค๋ฆ
๋๋ค. ํ์ง๋ง ๋ฌํ๊ฒ ๋ฎ์ ๋ถ๋ถ์ด ์์ต๋๋ค.
์ถ์ํ ์์ค์ ์ด๋ป๊ฒ ์ก์ผ๋ฉด ์ข์๊น์?
์ฌ์ฝ๋๊ป์ ๋ง์ํ์ ์ฑ๊ณต ์ผ์ด์ค์์๋ง ๋ค๋ฃจ๋ ๊ฒ์ ๊ด์ ์ ๋ง์ถฐ์ ๋ก๋ฉ ์ํ๋ฅผ ๊ณ ๋ คํ suspense๋ฅผ ๋ฌด์ํด ๋ดค์ต๋๋ค.
โ ๏ธ ์๋ ์ฝ๋๋ ๋ผ์ด๋ธ์์ ์ ์ํ ํด๋ต์ด ์๋๋๋ค. ๋ผ์ด๋ธ ์ดํ ์ ๊ฐ ์๊ฐํ๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค์ ๊ธฐ์ค์ ๋๋ค.
// ์์ฝ ํํฉ ํ์ด์ง
<h1>ํ์์ค ์์ฝ</h1>
<section>
<h2>๋ ์ง ์ ํ</h2>
<DatePicker
value={์ ํํ๋ ์ง}
onChange={์ ํํ๋ ์ง_์
๋ฐ์ดํธํจ์}
min={์ค๋๋ ์ง}
/>
</section>
<section>
<h2>์์ฝ ํํฉ</h2>
<Timeline
selected={์ ํํ๋ ์ง}
rowField={์๊ฐ๋}
colField={ํ์์ค์ด๋ฆ}
/>
</section>
<section>
<h2>๋ด ์์ฝ</h2>
<ul>
{reservations.map((reservation) => (
<li key={reservation.id}>
<Card
top={reservation.ํ์์ค์ด๋ฆ}
bottom={reservation.ํ์์ค์คํ}
right={<Card.CancelButton />}
/>
</li>
))}
</ul>
</section>
<CtaButton>์์ฝํ๊ธฐ</CtaButton>
<Timeline
selected={์ ํํ๋ ์ง}
rowField={์๊ฐ๋}
colField={ํ์์ค์ด๋ฆ}
/>
<ul>
{reservations.map((reservation) => (
<li key={reservation.id}>
<Card
top={reservation.ํ์์ค์ด๋ฆ}
bottom={reservation.ํ์์ค์คํ}
right={<Card.CancelButton />}
/>
</li>
))}
</ul>
Card ์ปดํฌ๋ํธ์ UI๋ ์ด๋ป๊ฒ ์ฃผ์
ํ ์ ์์์ง๋ ๋ฆฌํฉํ ๋งํ๋ฉด์ ๋ ๊ณ ๋ฏผํด๋ด์ผํ ๊ฑฐ ๊ฐ์ต๋๋ค. suspense์ ์๋ฌ ๋ฐ์ด๋๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ค๋ฉด ๊ฒฐ๊ตญ query ๊ฒฐ๊ณผ๋ฅผ ๋ฐํํ๋ ์ปดํฌ๋ํธ๋ฅผ ๊ฐ์ธ์ผํ๋๋ฐ ์ด๋ป๊ฒ ์ ๊ฐ ์๊ฐํ๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค ๊ธฐ์ค์ผ๋ก ํ์ด์ง ์ปดํฌ๋ํธ ์ฝ๋ ๋ ๋ฒจ์์ ๋๋ฌ๋๊ฒ ํ ์ ์์๊น์?
Suspensive ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ ๊ธฐ์กด React์ ๋ค๋ฅธ ์๋ฒ ์ํ ๊ด๋ฆฌ ๋ฐ ์ ์ญ ์ํ ๊ด๋ฆฌ ๋๊ตฌ๋ค๋ก๋ถํฐ ํด๊ฒฐํ ์ ์๋ ๋ฌธ์ ๋ค์ ํด๊ฒฐํ๊ธฐ ์ํด ๋์จ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ ๋๋ค.
React์ Suspense๋ ์์ ์ปดํฌ๋ํธ๊ฐ ๋์ง๋ Promise ๊ฐ์ฒด๋ฅผ catchํ๋ ํํ๋ก์ ๋์ํ๊ธฐ ๋๋ฌธ์ query์ suspense๋ฅผ ๋์ผํ ์ฝ๋ ๋ ๋ฒจ์ ์์นํ๊ธฐ ์ด๋ ต์ต๋๋ค.
์ ๊ฐ ์๊ฐํ๋ ์ด์์ ์ธ ์ธํฐํ์ด์ค ์ค๊ณ์์ query์ ๊ฒฐ๊ณผ ๊ฐ์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ ๊ตฌ์กฐ๊ฐ ๋๋ฌ๋๊ธธ ์ํ๊ธฐ ๋๋ฌธ์ Suspensive ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ๊ณตํ๋ SuspenseQuery API๋ฅผ ์ฌ์ฉํ์ต๋๋ค.
// https://suspensive.org/ko/docs/react-query/SuspenseQuery
// ๊ณต์๋ฌธ์์ ์ฝ๋ ์ค ์ผ๋ถ ๋ฐ์ท
<Suspense fallback={'loading...'}>
<SuspenseQuery {...userQueryOptions(userId)}>
{({ data: user }) => <UserProfile key={user.id} {...user} />}
</SuspenseQuery>
</Suspense>
Suspensive ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ Suspense๋ ์์ ์ปดํฌ๋ํธ๋ก SuspenseQuery๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
SuspenseQuery ์ปดํฌ๋ํธ ๋ด๋ถ์์ render props ํจํด์ผ๋ก ์ธ๋ถ์์ ์ ๋ฌ๋ฐ์ queryOptions๋ฅผ ๊ฐ์ง๊ณ useSuspenseQuery๋ฅผ ์คํํฉ๋๋ค.
import { SuspenseQuery } from '@suspensive/react-query'
// You can use QueryOptions as props.
<SuspenseQuery {...queryOptions()}>
{({ data, isLoading }) => {
return <></>
}
</SuspenseQuery>
SuspenseQuery ์ปดํฌ๋ํธ์ children์ jsx๋ก ๋ฐํ๋๋ ์ปดํฌ๋ํธ๊ฐ ์๋ ํจ์์ ํํ๋ก ๋ฐ๊ธฐ ๋๋ฌธ์ children prop์ผ๋ก ์ ๋ฌ๋ ํจ์์ ์ธ์๋ก useSuspenseQuery ๊ฒฐ๊ณผ ๊ฐ์ ๋ฐ์ ์ปดํฌ๋ํธ๊ฐ ๋ฐํ๋์ด ๋ ๋๋งํฉ๋๋ค.
export const SuspenseQuery = <
TQueryFnData = unknown,
TError = unknown,
TData = TQueryFnData,
TQueryKey extends QueryKey = QueryKey,
>({
children,
...options
}: UseSuspenseQueryOptions<TQueryFnData, TError, TData, TQueryKey> & {
children: (queryResult: UseSuspenseQueryResult<TData, TError>) => ReactNode
}) => <>{children(useSuspenseQuery(options))}</>
์ด๋ ๊ฒ ๋์ฆ๋ฅผ ๋ฐ๋ก ์ถฉ์กฑ์ํฌ ์ ์๋ ์ ์ ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์์๋ค๋ !!
๋ฐ๋ก ํ ์ค์์ ๋ง๋ค์์ต๋๋ค. ๐๐
์กฐ๊ธ ๋ ๋นจ๋ฆฌ ์๊ฒ ๋์ผ๋ฉด ์ข์์ ํ ๋ฐ ์ด๋ฒ์ ์ ๋๋ก ์๊ฒ ๋ผ์ ์ ์ฌ์ฉํ ์ ์์ ๊ฑฐ ๊ฐ์ต๋๋ค.
์ด๋ฒ์๋ ๋ชจ์๊ณ ์ฌ์ ์ฐธ์ฌํ๊ธธ ์ ๋ง ์ํ๋ค๊ณ ๋๊ผ์ต๋๋ค. ๋์์ฃผ์ ์ข ํ๋, ์ฌ์ฝ๋, ๋์ฑ๋ ๊ฐ์ฌํฉ๋๋ค! ๐โโ๏ธ
์์ฌ์ด ์ ์ ์๊ฐ์ ๋ง์ด ํฌ์ํ์ง ๋ชปํ๊ณ ์กฐ๊ธํ๊ฒ ์ ๊ทผํด์ ๊ณ ๋ฏผ์ ๊น๊ฒ ํ์ง ๋ชปํ ์ ๋ค์ด ๋ง์ด ์์ฌ์ ์ต๋๋ค.
๋ผ์ด๋ธ ์ดํ์ ํ๊ณ ๋ฅผ ๊ฐ๋ ์๊ฐ์ ๋ ์ฐจ๋ก ๊ฒช์ผ๋ฉด์ ๊ณ ๋ คํ ์ฌํญ๋ค๊ณผ ํด๊ฒฐ ๋ฐฉ์๋ค์ ๋น๊ตํด ๋ณด๋ฉด์ ๋ ๋ง์ด ์ป์ด๊ฐ์ต๋๋ค.
์์ผ๋ก ๋ฆฌํฉํ ๋ง์ ์งํํ๋ฉด์ ๋์ค๋ ๊ณ ๋ฏผ ํฌ์ธํธ์ ์ ํ, ๊ทธ ์ด์ ์ ๋ํด์๋ ๋ด์ฉ ์ถ๊ฐํด๋ณด๊ฒ ์ต๋๋ค. ๊ฐ์ฌํฉ๋๋ค!