๐Ÿฐ

16. Hook Library

ย 
ย 

16.1 ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ž€?

ย 
ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” React์šฉ Hooks ๋ฐ ์œ ํ‹ธ๋ฆฌํ‹ฐ์˜ ๋ชจ์Œ์ž…๋‹ˆ๋‹ค. ์ฆ‰, ๊ธฐ์กด React ๊ธฐ๋Šฅ์„ ๋Œ€์ฒดํ•˜๊ฑฐ๋‚˜ ํ–ฅ์ƒ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์‚ฌ์šฉ์ž ์ •์˜ Hook ๋ชจ์Œ์ด๋ผ๊ณ  ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React๋Š” ๊ทธ ์ž์ฒด๋กœ๋„ ๋งค์šฐ ๊ฐ•๋ ฅํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์ด์ง€๋งŒ, React ์ปค๋ฎค๋‹ˆํ‹ฐ์—์„œ ๋งŒ๋“ค์–ด์ง„ ํ›…์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฝ”๋“œ๋ฅผ ํ›จ์”ฌ ๋ช…ํ™•ํ•˜๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ์ง์ ‘ ๋งŒ๋“  ์ปค์Šคํ…€ ํ›…์„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋ฐฐํฌํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” ๋‹ค์–‘ํ•œ ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘์—์„œ๋„ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๋ช‡ ๊ฐ€์ง€ ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

16.2 React Hook Form

ย 

16.2.1 React Hook Form์ด๋ž€?

ย 
React์—์„œ form์˜ ์ž…๋ ฅ๊ณผ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ณผ์ •์ด ๋ณต์žกํ•ด์ง€๊ณ  ๋‹ค์–‘ํ•œ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ•ด์•ผํ•  ๊ฒฝ์šฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ„ํŽธํ•˜๊ฒŒ ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. React form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ค‘์—๋Š” formik, react final form ๋“ฑ ๋‹ค์–‘ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์กด์žฌํ•˜์ง€๋งŒ ๊ฐ€์žฅ ์ธ๊ธฐ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” React Hook Form์ž…๋‹ˆ๋‹ค. React Hook Form์˜ ํŠน์ง•์„ ์‚ดํŽด๋ณด๋ฉฐ ๊ฐ€์žฅ ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์ด์œ ๋ฅผ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
React Hook Form์˜ ๊ณต์‹ ๋ฌธ์„œ์—์„œ๋Š” ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์œ„ํ•ด ๋งŒ๋“ค์–ด์กŒ๋‹ค๊ณ  ์ด์•ผ๊ธฐํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. uncontrolled components(๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ)๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ์ œ์ž‘๋˜์—ˆ์œผ๋ฉฐ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์œผ๋กœ ์ธํ•ด ๋ฐœ์ƒํ•˜๋Š” ์ˆ˜๋งŽ์€ ๋ฆฌ๋ Œ๋”๋ง์„ ๋ฐฉ์ง€ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ref๋ฅผ ํ†ตํ•ด form ๊ฐ’์„ ๊ฐ€์ ธ์˜ค๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋ง์„ ์ตœ์†Œํ™”ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฌผ๋ก , controlled components(์ œ์–ด ์ปดํฌ๋„ŒํŠธ) ๋ฐฉ์‹๋„ ์ œ๊ณตํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์‚ฌ์šฉ์ž์˜ ์ž…๋ ฅ์ด ๋งŽ๊ฑฐ๋‚˜ ๋ณต์žกํ•˜๋‹ค๋ฉด ์ฝ”๋“œ์˜ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ•˜๋Š” ๊ฒƒ์ด ์–ด๋ ค์›Œ์ง€๋ฉฐ ๋ Œ๋”๋ง ๋ฐœ์ƒ ๋นˆ๋„๊ฐ€ ์ฆ๊ฐ€ํ•˜๋ฉด์„œ ์„ฑ๋Šฅ์— ์˜ํ–ฅ์„ ๋ฏธ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
์ œ์–ด ์ปดํฌ๋„ŒํŠธย vsย ๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ
React ๊ณต์‹ ๋ฌธ์„œ์—์„œ ์ œ์–ด ์ปดํฌ๋„ŒํŠธ์™€ ๋น„์ œ์–ด ์ปดํฌ๋„ŒํŠธ์— ๋Œ€ํ•ด ์„ค๋ช…ํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค.
React ๊ธฐ์ˆ  ์šฉ์–ด ๋ชจ์Œ - React
์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜(Single-page application, SPA)์€ ํ•˜๋‚˜์˜ HTML ํŽ˜์ด์ง€์™€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰์— ํ•„์š”ํ•œ JavaScript์™€ CSS ๊ฐ™์€ ๋ชจ๋“  ์ž์‚ฐ์„ ๋กœ๋“œํ•˜๋Š” ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ž…๋‹ˆ๋‹ค. ํŽ˜์ด์ง€ ๋˜๋Š” ํ›„์† ํŽ˜์ด์ง€์˜ ์ƒํ˜ธ์ž‘์šฉ์€ ์„œ๋ฒ„๋กœ๋ถ€ํ„ฐ ์ƒˆ๋กœ์šด ํŽ˜์ด์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค์ง€ ์•Š์œผ๋ฏ€๋กœ ํŽ˜์ด์ง€๊ฐ€ ๋‹ค์‹œ ๋กœ๋“œ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. React๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์‹ฑ๊ธ€ ํŽ˜์ด์ง€ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์ง€๋งŒ, ํ•„์ˆ˜ ์‚ฌํ•ญ์€ ์•„๋‹™๋‹ˆ๋‹ค. ๊ธฐ์กด ์›น์‚ฌ์ดํŠธ ์ผ๋ถ€๋ถ„์˜ ์ƒํ˜ธ์ž‘์šฉ์„ ๊ฐœ์„ ํ•˜๊ธฐ ์œ„ํ•˜์—ฌ React๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
React ๊ธฐ์ˆ  ์šฉ์–ด ๋ชจ์Œ - ReactReact ๊ธฐ์ˆ  ์šฉ์–ด ๋ชจ์Œ - React
ย 
react-hook-form์˜ Dependencies๋ฅผ ํ™•์ธํ•ด๋ณด๋ฉด ์˜์กดํ•˜๊ณ  ์žˆ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ๋ฒ„์ „ 7.41.5 ๊ธฐ์ค€ 818kB๋กœ ์ ์€ ์šฉ๋Ÿ‰์„ ๊ฐ€์ง€๊ณ  ์žˆ์œผ๋ฉฐ, ๋ณ„๋„์˜ ์˜์กด์„ฑ ๊ด€๋ฆฌ๊ฐ€ ํ•„์š”ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
ย 
ย 
๋˜ํ•œ input ์‚ฌ์šฉ ์‹œ, ๋งŽ์ด ์‚ฌ์šฉ๋˜๋Š” ์ด๋ฒคํŠธ๋ฅผ React Hook Form์œผ๋กœ ์ง๊ด€์ ์ด๊ณ  ๊ฐ„๊ฒฐํ•˜๊ฒŒ ์ž‘์„ฑํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— ์œ ์ง€๋ณด์ˆ˜์—๋„ ์šฉ์ดํ•˜๋ฉฐ ์ฝ”๋“œ์˜ ์–‘์„ ์ค„์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ ์ฑ•ํ„ฐ์—์„œ React Hook Form์—์„œ ์ œ๊ณตํ•˜๋Š” Hook์„ ์‚ดํŽด๋ณด๋ฉฐ ์ž์„ธํ•˜๊ฒŒ ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

16.2.2 React Hook Form ์„ค์น˜๋ฐฉ๋ฒ•

ย 
React ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ”„๋กœ์ ํŠธ ํด๋”๋กœ ์ด๋™ํ•œ ๋’ค, npm ํ˜น์€ yarn ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ React Hook Form ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm install react-hook-form
npm์œผ๋กœ react-hook-form ์„ค์น˜ํ•˜๊ธฐ
ย 
yarn add react-hooks-form
yarn์œผ๋กœ react-hooks-form ์„ค์น˜ํ•˜๊ธฐ
ย 
React Hook Form์€ useForm Hook์„ ๋ถˆ๋Ÿฌ์˜ค๋Š” ๊ฒƒ์ด ๊ธฐ๋ณธ์ด๋ฉฐ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ importํ•œ ํ›„, ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ํ•จ์ˆ˜ ๋‚ด๋ถ€์—์„œ ํ˜ธ์ถœํ•˜์—ฌ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
ย 
import { useForm } from "react-hook-form"; const { register, handleSubmit, formState: { errors }, } = useForm();
ย 

16.2.3 React Hook Form ์‚ฌ์šฉํ•˜์—ฌ validation ํ•˜๊ธฐ

ย 
์ด๋ฒˆ์—๋Š” ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ์ž…๋ ฅ๋ฐ›์€ ๊ฐ’์„ validation ํ•˜๋Š” ์˜ˆ์‹œ๋ฅผ ํ†ตํ•ด React Hook Form์„ ์‚ฌ์šฉํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
React Hook Form์—์„œ useForm์„ ๋ถˆ๋Ÿฌ์™€ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ˜ธ์ถœํ•˜๋ฉด, register์™€ handleSubmit์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. register๋Š” React Hook Form์ด ์ œ๊ณตํ•ด์ฃผ๋Š” API๋กœ, validation ํ•  form ์š”์†Œ๋ฅผ React Hook Form์— ๋“ฑ๋กํ•ด์ฃผ๋Š” ์—ญํ• ์„ ํ•ฉ๋‹ˆ๋‹ค. ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์•„๋ž˜์™€ ๊ฐ™์ด ์Šคํ”„๋ ˆ๋“œ ๋ฌธ๋ฒ•์œผ๋กœ ์ž‘์„ฑํ•ด์•ผ ํ•˜๊ณ  ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ๋Š” form ์š”์†Œ์˜ name์œผ๋กœ ์‚ฌ์šฉ๋  string ๊ฐ’์„ ๋„ฃ์–ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
ย 
<input {...register("age")} />
ย 
validation์„ ํ†ต๊ณผํ•œ ๊ฐ’์€ handleSubmit์„ ์‚ฌ์šฉํ•ด์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์ด form์— ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋กœ onSubmit์„ ๋‹ฌ์•„์ฃผ๋ฉด validation์„ ์ •์ƒ์ ์œผ๋กœ ํ†ต๊ณผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์•„ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•˜์—ฌ ์ฝ˜์†”์ด ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { useForm } from "react-hook-form"; function App() { const { register, handleSubmit } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <label>๋‚˜์ด</label> <input {...register("age")} /> <input type="submit" /> </form> ); } export default App;
App.jsx - React Hook Form์— age๋“ฑ๋ก ํ›„ data๋กœ ๋ฐ›๊ธฐ
ย 
๊ทธ๋ฆผ 16-1๊ทธ๋ฆผ 16-1
๊ทธ๋ฆผ 16-1
ย 
์ด๋ฒˆ์—๋Š” validation ์กฐ๊ฑด์„ ์ถ”๊ฐ€ํ•ด ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค. ๋งŒ์•ฝ ์ž…๋ ฅ๋ฐ›๋Š” ๋‚˜์ด๋ฅผ 20์„ธ๋ถ€ํ„ฐ 99์„ธ๊นŒ์ง€๋กœ ์ œํ•œํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด, ์ด๋Ÿฌํ•œ ์กฐ๊ฑด์„ย register์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ž‘์„ฑํ•˜์—ฌ ๋“ฑ๋กํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.
ย 
... <input {...register("age", { min: 20, max: 99 })} /> ...
validation ์กฐ๊ฑด ์ถ”๊ฐ€
ย 
์กฐ๊ฑด์„ ์ถฉ์กฑํ•˜์ง€ ์•Š๋Š” ์ˆซ์ž๋ฅผ ์ž…๋ ฅํ•œ ๋’ค ์ œ์ถœํ•˜๋‹ˆ ์ฝ˜์†”์ด ์ฐ์ง€ ์•Š๋Š” ๊ฒƒ์œผ๋กœ ๋ณด์•„ validation์ด ์ž˜ ์ผ์–ด๋‚˜๊ณ  ์žˆ๋‹ค๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-2๊ทธ๋ฆผ 16-2
๊ทธ๋ฆผ 16-2
ย 
min/max ์ด์™ธ์—๋„ pattern, required, minLength/maxLength, validate ๋“ฑ ๋‹ค์–‘ํ•œ ์กฐ๊ฑด ํƒ€์ž…์ด ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์กฐ๊ฑด ํƒ€์ž…
๊ธฐ๋Šฅ
pattern
์ž…๋ ฅ์— ๋Œ€ํ•œ ์ •๊ทœํ‘œํ˜„์‹
required
ํ•„์ˆ˜ ์ž…๋ ฅ ๊ฐ’
min/max
ํ—ˆ์šฉ ์ตœ์†Œ๊ฐ’/ํ—ˆ์šฉ ์ตœ๋Œ€๊ฐ’
minLength/maxLength
ํ—ˆ์šฉ ์ตœ์†Œ ๊ธธ์ด/ํ—ˆ์šฉ ์ตœ๋Œ€ ๊ธธ์ด
validate
custom validation ํ•จ์ˆ˜
ย 
validation ์กฐ๊ฑด์— ๋งž์ง€ ์•Š์œผ๋ฉด submit ๋˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ํ™•์ธํ–ˆ์œผ๋‹ˆ, ๋งˆ์ง€๋ง‰์œผ๋กœ validation error๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ์–ด๋–ป๊ฒŒ ์‚ฌ์šฉ์ž์—๊ฒŒ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ณด์—ฌ์ค„ ์ˆ˜ ์žˆ๋Š”์ง€ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useForm } from "react-hook-form"; function App() { const { register, handleSubmit, formState: { errors }, } = useForm(); const onSubmit = (data) => console.log(data); return ( <form onSubmit={handleSubmit(onSubmit)}> <label>๋‚˜์ด</label> <input {...register("age", { min: 20, max: 99 })} /> {errors.age && <p>20์„ธ๋ถ€ํ„ฐ 99์„ธ๊นŒ์ง€๋งŒ ์ด์šฉ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.</p>} <input type="submit" /> </form> ); } export default App;
validation ์—๋Ÿฌ ๋ฉ”์‹œ์ง€ ํ‘œ์‹œ
ย 
์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์ด useForm์—์„œ errors๋ฅผ ๋ถˆ๋Ÿฌ์˜จ ๋’ค, ํ•ด๋‹น ํผ์—์„œ ์—๋Ÿฌ๊ฐ€ ๋‚ฌ์„ ๋•Œ formState์˜ error ๊ฐ’์ด ๋“ค์–ด ์˜ค๊ฒŒ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด validation์ด ์‹คํŒจํ–ˆ์„ ๋•Œ ์‚ฌ์šฉ์—๊ฒŒ ๋ณด์—ฌ์ค„ ์˜ค๋ฅ˜ ๋ฉ”์‹œ์ง€๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
ย 
๊ทธ๋ฆผ 16-3๊ทธ๋ฆผ 16-3
๊ทธ๋ฆผ 16-3
ย 

16.2.4 React Hook Form ์‚ฌ์šฉํ•˜์—ฌ ํšŒ์›๊ฐ€์ž… validation ํ•˜๊ธฐ

ย 
ํšŒ์›๊ฐ€์ž… validation ๊ตฌํ˜„์„ ํ†ตํ•ด React Hook Form์„ ์‚ฌ์šฉ ์ „๊ณผ ํ›„๋ฅผ ๋น„๊ตํ•ด๋ณด๋ฉฐ React Hook Form์— ๋Œ€ํ•ด ์ž์„ธํžˆ ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; export default function App() { const [email, setEmail] = useState(""); const [name, setName] = useState(""); const [password, setPassword] = useState(""); const [passwordConfirm, setPasswordConfirm] = useState(""); const [emailMessage, setEmailMessage] = useState(""); const [nameMessage, setNameMessage] = useState(""); const [passwordMessage, setPasswordMessage] = useState(""); const [passwordConfirmMessage, setPasswordConfirmMessage] = useState(""); const [isEmail, setIsEmail] = useState(false); const [isName, setIsName] = useState(false); const [isPassword, setIsPassword] = useState(false); const [isPasswordConfirm, setIsPasswordConfirm] = useState(false); const onSubmit = (e) => { e.preventDefault(); console.log("data", e.target); }; const onEmailChange = (e) => { setEmail(e.target.value); const emailCurrent = e.target.value; const emailRegex = /^\S+@\S+$/i; if (e.target.value.length === 0) { setEmailMessage("์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsEmail(false); } else if (!emailRegex.test(emailCurrent)) { setEmailMessage("์ด๋ฉ”์ผ ํ˜•์‹์œผ๋กœ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”"); setIsEmail(false); } else { setIsEmail(true); } }; const onNameChange = (e) => { setName(e.target.value); if (e.target.value.length === 0) { setNameMessage("์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsName(false); } else if (e.target.value.length > 10) { setNameMessage("10๊ธ€์ž ์ด๋‚ด๋กœ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsName(false); } else { setIsName(true); } }; const onPasswordChange = (e) => { setPassword(e.target.value); if (e.target.value.length === 0) { setPasswordMessage("๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsPassword(false); } else if (e.target.value.length < 6) { setPasswordMessage("6์ž ์ด์ƒ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsPassword(false); } else { setIsPassword(true); } }; const onPasswordConfirmChange = (e) => { setPasswordConfirm(e.target.value); if (e.target.value.length === 0) { setPasswordConfirmMessage("๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”"); setIsPasswordConfirm(false); } else if (e.target.value !== password) { setPasswordConfirmMessage("๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค"); setIsPasswordConfirm(false); } else { setIsPasswordConfirm(true); } }; return ( <form onSubmit={onSubmit}> <input placeholder="์ด๋ฉ”์ผ" type="email" value={email} onChange={onEmailChange} /> <input placeholder="์ด๋ฆ„" value={name} onChange={onNameChange} /> <input placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" type="password" value={password} onChange={onPasswordChange} /> <input placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ" type="password" value={passwordConfirm} onChange={onPasswordConfirmChange} /> <input type="submit" /> {isEmail ? undefined : <p>{emailMessage}</p>} {isName ? undefined : <p>{nameMessage}</p>} {isPassword ? undefined : <p>{passwordMessage}</p>} {isPasswordConfirm ? undefined : <p>{passwordConfirmMessage}</p>} </form> ); }
App.jsx - React Hook Form์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ํšŒ์›๊ฐ€์ž… validation
ย 
React Hook Form์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ํšŒ์›๊ฐ€์ž… form validation์„ ๊ตฌํ˜„ํ•  ๊ฒฝ์šฐ, ์œ„ ์ฝ”๋“œ์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ํšŒ์›๊ฐ€์ž… ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์šฉ์ž์—๊ฒŒ ์ด๋ฉ”์ผ, ์ด๋ฆ„, ๋น„๋ฐ€๋ฒˆํ˜ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ๋“ฑ ์—ฌ๋Ÿฌ๊ฐ€์ง€์˜ ์ž…๋ ฅ๊ฐ’์„ ๋ฐ›์•„์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์œ„ ์ฝ”๋“œ์—์„œ๋Š” state๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ž…๋ ฅ๊ฐ’์„ ๊ด€๋ฆฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ž…๋ ฅ๋ฐ›์•„์•ผ ํ•˜๋Š” ์š”์†Œ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋งŽ์•„์งˆ์ˆ˜๋ก ๋‹ค์ˆ˜์˜ state๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ์—๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€์ด ์žˆ์œผ๋ฉฐ ์ฝ”๋“œ์˜ ์–‘์ด ๋Š˜์–ด๋‚˜๊ธฐ ๋•Œ๋ฌธ์— ์ด๋ฅผ ๋ฐฉ์ง€ํ•˜๊ธฐ ์œ„ํ•ด React Hook Form์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useForm } from "react-hook-form"; export default function App() { const { register, handleSubmit, watch, formState: { errors } } = useForm({ mode: "onChange" }); // --- โ“ท const onSubmit = (data) => { console.log("data", data); }; // --- โ“น return ( <form onSubmit={handleSubmit(onSubmit)}> <input // --- โ“ต placeholder="์ด๋ฉ”์ผ" type="email" {...register("email", { required: { value: true, message: "์ด๋ฉ”์ผ์„ ์ž…๋ ฅํ•˜์„ธ์š”" }, pattern: { value: /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/i, // ์ด๋ฉ”์ผ ํ˜•์‹ ํ™•์ธ message: "์ด๋ฉ”์ผ ํ˜•์‹์ด ์•„๋‹™๋‹ˆ๋‹ค" } })} /> <input placeholder="์ด๋ฆ„" {...register("name", { required: { value: true, message: "์ด๋ฆ„์„ ์ž…๋ ฅํ•˜์„ธ์š”" }, maxLength: { value: 10, message: "10๊ธ€์ž ์ด๋‚ด๋กœ ์ž…๋ ฅํ•˜์„ธ์š”" } })} /> <input placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ" type="password" {...register("password", { required: { value: true, message: "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" }, minLength: { value: 6, message: "6์ž ์ด์ƒ ์ž…๋ ฅํ•˜์„ธ์š”" } })} /> <input placeholder="๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ" type="password" {...register("passwordConfirm", { required: { value: true, message: "๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•˜์„ธ์š”" }, validate: (value) => { if (watch("password") !== value) { return "๋น„๋ฐ€๋ฒˆํ˜ธ๊ฐ€ ์ผ์น˜ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค"; } } })} // --- โ“ธ /> <input type="submit" /> {errors.email && <p>{errors.email.message}</p>} // --- โ“ถ {errors.name && <p>{errors.name.message}</p>} {errors.password && <p>{errors.password.message}</p>} {errors.passwordConfirm && <p>{errors.passwordConfirm.message}</p>} </form> ); }
App.jsx - React Hook Form์„ ์‚ฌ์šฉํ•œ ํšŒ์›๊ฐ€์ž… validation
ย 
โ“ต ์ด๋ฉ”์ผ, ์ด๋ฆ„, ๋น„๋ฐ€๋ฒˆํ˜ธ, ๋น„๋ฐ€๋ฒˆํ˜ธ ํ™•์ธ ์ž…๋ ฅ์ฐฝ์„ ๊ฐ๊ฐ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค. ํ•„์ˆ˜ ์ž…๋ ฅ ๊ฐ’์ด๊ธฐ ๋•Œ๋ฌธ์— required๋ฅผ true๋กœ ์„ค์ •ํ•œ ํ›„, ์ž…๋ ฅํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ ์„ค์ •ํ•œ message๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
โ“ถ ํ•ด๋‹นํ•˜๋Š” ์ด๋ฆ„์— ์ผ์น˜ํ•˜๋Š” errors๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ, ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•ฉ๋‹ˆ๋‹ค.
โ“ท mode๋Š” ์‚ฌ์šฉ์ž๊ฐ€ form์„ submitํ•˜๊ธฐ ์ „์— ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ฉ๋‹ˆ๋‹ค. onChange ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ๋งˆ๋‹ค ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค.
โ“ธ watch๋Š” 'password'๋ผ๋Š” name์„ ๊ฐ€์ง€๋Š” input element๋ฅผ ๊ด€์ฐฐํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ โ€˜passwordโ€™์™€ โ€˜passwordConfirmโ€™์ด ์ผ์น˜ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
โ“น ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ data๊ฐ€ ๋“ค์–ด์˜ค๋ฉฐ submit์‹œ, data๊ฐ€ console์— ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿšจ
useForm()์˜ mode
์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ฐ’์€ ์•„๋ž˜์™€ ๊ฐ™์œผ๋ฉฐ, ๊ฐ๊ฐ์˜ ์†์„ฑ์— ๋Œ€ํ•œ ์„ค๋ช…์€ react-hook-form ๊ณต์‹ ๋ฌธ์„œ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
mode: onChange | onBlur | onSubmit | onTouched | all = 'onSubmit'
ย 
๊ทธ๋ฆผ 16-4๊ทธ๋ฆผ 16-4
๊ทธ๋ฆผ 16-4
๊ทธ๋ฆผ 16-5๊ทธ๋ฆผ 16-5
๊ทธ๋ฆผ 16-5
ย 
ย 
์‹คํ–‰ํ™”๋ฉด์€ ์œ„์™€ ๊ฐ™์Šต๋‹ˆ๋‹ค. ์ž…๋ ฅ๊ฐ’์ด ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ๋ฅผ ํ†ต๊ณผํ•˜์ง€ ๋ชปํ•  ๊ฒฝ์šฐ ์—๋Ÿฌ ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋ฉฐ, ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ์— ํ†ต๊ณผํ•˜์—ฌ ์ œ์ถœํ•˜๋ฉด data๋ฅผ ์ œ๋Œ€๋กœ ์ถœ๋ ฅํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์œ„์ฒ˜๋Ÿผ React Hook Form์„ ํ™œ์šฉํ•˜์—ฌ ํšŒ์›๊ฐ€์ž… form validation์„ ๊ตฌํ˜„ํ•ด๋ณด์•˜์Šต๋‹ˆ๋‹ค. input๋งˆ๋‹ค onChange ์ด๋ฒคํŠธ์— ์ž…๋ ฅ๊ฐ’์˜ state์™€ ๋น„๊ตํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ์—ฐ๊ฒฐํ•˜๋Š” ๊ฒƒ๋„ ๊ฐ€๋Šฅํ•˜์ง€๋งŒ, React Hook Form์„ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์ฝ”๋“œ์˜ ์–‘์ด ์ค„์–ด๋“  ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.3 react-router-dom์—์„œ ์ง€์›ํ•˜๋Š” Hook

ย 

16.3.1 react-router-dom์ด๋ž€?

ย 
์ผ๋ฐ˜์ ์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ์–‘์‹์„ ์ œ์ถœํ•˜๊ฑฐ๋‚˜ ๋’ค๋กœ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ์„œ๋ฒ„์— ์š”์ฒญ์„ ๋ณด๋‚ด ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจ ํ•ฉ๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ react-router-dom์˜ ๋‚ด์žฅ ์ปดํฌ๋„ŒํŠธ์ธ BrowserRouter, Routes, Route, Link ๋“ฑ์„ ํ™œ์šฉํ•˜์—ฌ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจ ํ•˜์ง€ ์•Š๊ณ ๋„ URL ์ฃผ์†Œ๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. Router๋ฅผ ์ด์šฉํ•˜์—ฌ URL์˜ ๋งํฌ์— ๋”ฐ๋ผ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. React Router๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์ž…๋ ฅํ•œ ์ฃผ์†Œ๋ฅผ ๊ฐ์ง€ํ•˜๋Š” ์—ญํ• ์„ ํ•˜๋ฉฐ, ์—ฌ๋Ÿฌ ํ™˜๊ฒฝ์—์„œ ๋™์ž‘ํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฌ๋Ÿฌ ์ข…๋ฅ˜์˜ Router ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ณตํ•˜๋Š”๋ฐ ๊ทธ ์ค‘ BrowserRouter ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด์šฉํ•˜์—ฌ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ผ์šฐํŒ…ํ•˜๋Š” ์˜ˆ์ œ๋ฅผ ์‚ดํŽด๋ณด๊ณ  ๋„˜์–ด๊ฐ€๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
<BrowserRouter> <Routes> <Route path="๊ฒฝ๋กœ" element={<์—ฐ๊ฒฐํ•  ์ปดํฌ๋„ŒํŠธ />} /> </Routes> </BrowserRouter>
์ผ๋ฐ˜์ ์ธ Router ์ž‘์„ฑ ํฌ๋งท
ย 
๋จผ์ € ์ฝ”๋“œ์—์„œ ๋ณผ ์ˆ˜ ์žˆ๋“ฏ์ด BrowserRouter ์ปดํฌ๋„ŒํŠธ ์•ˆ์— Routes ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•ฉ๋‹ˆ๋‹ค. Routes ์ปดํฌ๋„ŒํŠธ๋Š” ์—ฌ๋Ÿฌ ๊ฐœ์˜ Route๋ฅผ ๊ฐ€์งˆ ์ˆ˜ ์žˆ๊ณ  Route์˜ ์ค‘์ฒฉ๋„ ๊ฐ€๋Šฅํ•œ๋ฐ, ๊ทธ ์ค‘ ๊ทœ์น™์ด ์ผ์น˜ํ•˜๋Š” Route 1๊ฐœ๋ฅผ ๋ Œ๋”๋ง ํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ Route์˜ path ์†์„ฑ์—๋Š” ๊ฒฝ๋กœ๋ฅผ, element ์†์„ฑ์—๋Š” ์—ฐ๊ฒฐํ•˜๊ณ ์ž ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ž‘์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { BrowserRouter, Routes, Route } from "react-router-dom"; // ... function App() { return ( <BrowserRouter> <Routes> {/* ์ค‘์ฒฉ Route */} <Route path="/" element={<Home />}> <Route path="about" element={<About />} /> <Route path="product" element={<Product />} /> </Route> {/* ์ผ์น˜ํ•˜๋Š” Route๊ฐ€ ์—†์„ ๊ฒฝ์šฐ NotFound ์ปดํฌ๋„ŒํŠธ ์—ฐ๊ฒฐ */} <Route path="*" element={<NotFound />} /> </Routes> </BrowserRouter> ); } export default App;
๊ฐ„๋‹จํ•œ Router ์˜ˆ์ œ
ย 
์œ„์˜ Router ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ํ†ตํ•ด ๊ฐ„๋‹จํ•˜๊ฒŒ ๋ผ์šฐํŒ… ํ•˜๋Š” ๋ฒ•์„ ์•Œ์•„๋ณด์•˜์Šต๋‹ˆ๋‹ค. ์ง€๊ธˆ๋ถ€ํ„ฐ๋Š” ๋ณธ๊ฒฉ์ ์œผ๋กœ react-router-dom์ด ์ง€์›ํ•˜๋Š” ๋Œ€ํ‘œ์ ์ธ Hook ๋ช‡ ๊ฐ€์ง€๋ฅผ ์†Œ๊ฐœํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 

16.3.2 react-router-dom ์„ค์น˜๋ฐฉ๋ฒ•

ย 
React ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ”„๋กœ์ ํŠธ ํด๋”๋กœ ์ด๋™ํ•œ ๋’ค, npm ํ˜น์€ yarn ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ react-router-dom ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm install react-router-dom
npm์œผ๋กœ react-router-dom ์„ค์น˜ํ•˜๊ธฐ
ย 
yarn add react-router-dom
yarn์œผ๋กœ react-router-dom ์„ค์น˜ํ•˜๊ธฐ
ย 

16.3.3 useLocation

ย 
useLocation์€ ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด์žฅ๋œ window.location ๊ฐ์ฒด๋ฅผ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋Š” React Router์˜ location ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๊ฐ€ ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๊ฑฐ๋‚˜ ํ™”๋ฉด์„ ์ด๋™ํ•˜๋Š” ๋“ฑ ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ ๋˜์—ˆ์„ ๋•Œ ์ˆ˜ํ–‰ํ•˜๊ณ  ์‹ถ์€ ๋ถ€์ˆ˜์ ์ธ ํšจ๊ณผ(Side Effect)๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
Side Effect๋ž€?
React ์ปดํฌ๋„ŒํŠธ๊ฐ€ ํ™”๋ฉด์— ๋ Œ๋”๋ง๋œ ์ดํ›„์— ๋น„๋™๊ธฐ์ ์œผ๋กœ ์ฒ˜๋ฆฌ๋˜์–ด์•ผ ํ•˜๋Š” ๋ถ€์ˆ˜์ ์ธ ํšจ๊ณผ
ย 
ํ˜„์žฌ URL ์ฃผ์†Œ์— ๋Œ€ํ•œ ์ •๋ณด๋ฅผ useLocation Hook์ด ๋ฐ˜ํ™˜ํ•˜๋Š” location ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ์˜ˆ์ œ ์ฝ”๋“œ์—์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ console.log๋กœ ํ™•์ธํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { BrowserRouter, Routes, Route, useLocation } from "react-router-dom"; // Home ์ปดํฌ๋„ŒํŠธ function Home() { const location = useLocation(); console.log(location); return <h1>Home</h1>; } // App ์ปดํฌ๋„ŒํŠธ function App() { return ( <BrowserRouter> <Routes> <Route path="/home" element={<Home />} /> </Routes> </BrowserRouter> ); } export default App;
location ๊ฐ์ฒด ํ™•์ธํ•˜๋Š” ์ฝ”๋“œ
ย 
๊ทธ๋ฆผ 16-6 ์œ„์˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ console ํ™”๋ฉด๊ทธ๋ฆผ 16-6 ์œ„์˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ console ํ™”๋ฉด
๊ทธ๋ฆผ 16-6 ์œ„์˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ ์‹คํ–‰ํ•œ console ํ™”๋ฉด
ย 
์œ„์˜ ์˜ˆ์‹œ ์ฝ”๋“œ๋ฅผ npm start ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ ์‹คํ–‰ํ•˜๊ณ  URL์— /home์„ ๋”ํ•˜๋ฉด ์ฝ˜์†”์ฐฝ์—์„œ useLocation์—์„œ ๋ฐ˜ํ™˜๋œ location ๊ฐ์ฒด๋ฅผ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์•„๋ž˜์—์„œ ์กฐ๊ธˆ ๋” ๊ตฌ์ฒด์ ์ธ location ๊ฐ์ฒด ์˜ˆ์‹œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
{ pathname: "/home", search: "?event=instagram", hash: "#product", state: null, key: "abcd12ef" }
(์˜ˆ์‹œ) Location ๊ฐ์ฒด
ย 
์œ„์˜ Location ๊ฐ์ฒด ์˜ˆ์‹œ์—์„œ pathname, search, hash ๊ฐ’์„ ๋”ํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €์—์„œ ์‚ฌ์šฉ์ž์—๊ฒŒ ๋ณด์—ฌ์ง€๋Š” URL ์ฃผ์†Œ(/home?event=instagram#product)๋ฅผ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ state์™€ key๋Š” Router ๋งˆ๋‹ค ๋‹ค๋ฅธ ๊ฐ’์„ ๊ฐ€์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. location.state๋Š” ์ปดํฌ๋„ŒํŠธ ๊ฐ„ props๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ฒƒ ์ฒ˜๋Ÿผ 16.3.4์—์„œ ๋‹ค๋ฃฐ useNavigate๋ฅผ ์ด์šฉํ•˜์—ฌ ์ „๋‹ฌํ•˜๋ ค๋Š” ๋ฐ์ดํ„ฐ๋ฅผ Router ์ฃผ์†Œ์™€ ํ•จ๊ป˜ ๊ฐ์ฒด ํ˜•ํƒœ๋กœ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๊ณ , ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ ๋ฐ›๋Š” ์ปดํฌ๋„ŒํŠธ์—์„œ useLocation์„ ์ด์šฉํ•˜์—ฌ location.state์˜ ํ˜•ํƒœ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
const navigate = useNavigate(); navigate("/user/userdetail", { state: { data: "my data", username: "minji", userID: "ming123", }, });
useNavigate๋กœ ๋ฐ์ดํ„ฐ ์ „๋‹ฌํ•˜๊ธฐ
ย 
const location = useLocation(); const userData = { ...location.state };
useLocation์œผ๋กœ ๋ฐ์ดํ„ฐ ํ™œ์šฉํ•˜๊ธฐ
ย 

16.3.4 useNavigate

ย 
useNavigate ํ›…์„ ํ˜ธ์ถœํ•˜๋ฉด ํŠน์ • ๊ฒฝ๋กœ๋ฅผ ๊ฐ€์ง„ ํŽ˜์ด์ง€๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋Š” ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค. ๋ฐ˜ํ™˜๋œ navigate ํ•จ์ˆ˜์— ์ด๋™ํ•˜๋ ค๋Š” ํŽ˜์ด์ง€ ๊ฒฝ๋กœ๋ฅผ ์ธ์ž๋กœ ์ „๋‹ฌํ•˜์—ฌ ํŠน์ • ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
ํ™ˆํŽ˜์ด์ง€์—์„œ ๋ฒ„ํŠผ์„ ํด๋ฆญํ–ˆ์„ ๋•Œ ํ”„๋กœํ•„ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๋„๋ก ํ•˜๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ ์ฝ”๋“œ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
๋จผ์ €, Route ์ปดํฌ๋„ŒํŠธ์— path์™€ element props๋ฅผ ๋„ฃ์–ด ํŠน์ • ๊ฒฝ๋กœ์™€ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.
ย 
import { BrowserRouter, Routes, Route } from "react-router-dom"; // ... function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Layout />}> <Route index element={<HomePage />} /> <Route path="profile/:nickname" element={<Profile />} /> </Route> <Route path="*" element={<ErrorPage />} /> </Routes> </BrowserRouter> ); } export default App;
์˜ˆ) Route ์„ ์–ธ
ย 
๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋Š” ์ด๋ฒคํŠธ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด navigate ํ•จ์ˆ˜๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด์„œ ์ธ์ž๋กœ ์ „๋‹ฌ๋œ url๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
import { useNavigate } from "react-router-dom"; function HomePage() { const navigate = useNavigate(); return ( <main> <h1>hook ๋‹˜์˜ ํ™ˆํŽ˜์ด์ง€์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.</h1> <button onClick={() => navigate("/profile/hook")}> hook ๋‹˜์˜ ํ”„๋กœํ•„ ๋ณด๊ธฐ </button> </main> ); } export default HomePage;
useNavigate๋ฅผ ์‚ฌ์šฉํ•ด ํŽ˜์ด์ง€ ์ด๋™์‹œํ‚ค๊ธฐ
ย 
React Router๋Š” ๋ฒ„์ „ 5์—์„œ ๋ฒ„์ „ 6์œผ๋กœ ์—…๊ทธ๋ ˆ์ด๋“œ๋ฅผ ํ•˜๋ฉด์„œ useHistory ํ›…์„ useNavigate ํ›…์œผ๋กœ ๊ต์ฒดํ–ˆ์Šต๋‹ˆ๋‹ค. ํ›…์˜ ์ด๋ฆ„๋งŒ ๋ฐ”๋€ ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํŠน์ • ๊ฒฝ๋กœ์˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ธฐ ์œ„ํ•ด ํ˜ธ์ถœํ•˜๋Š” ๋ถ€๋ถ„์ธ history.push ์™€ history.replace๋„ ๋ณ€๊ฒฝ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { useHistory } from "react-router-dom"; function HomePage() { const history = useHistory(); return ( <main> <h1>hook ๋‹˜์˜ ํ™ˆํŽ˜์ด์ง€์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.</h1> <button onClick={() => history.push("/profile/hook")}> hook ๋‹˜์˜ ํ”„๋กœํ•„ ๋ณด๊ธฐ </button> </main> ); } export default HomePage;
React Router v5์—์„œ useHistory ํ›…์„ ์‚ฌ์šฉํ•ด ํŽ˜์ด์ง€ ์ด๋™์‹œํ‚ค๊ธฐ
ย 
React Router์˜ ๋ฉ”์ธ ์ปจ์…‰ ์ค‘ ํ•˜๋‚˜๋Š” React Router๊ฐ€ ์ œ๊ณตํ•˜๋Š” ๊ธฐ๋Šฅ์„ ํ†ตํ•ด ๊ธฐ๋ก ์Šคํƒ(history stack)์„ ์กฐ์ž‘ํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. useNavigate ํ›…์—์„œ๋„ ๊ธฐ๋ก ์Šคํƒ์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
  • navigate ํ•จ์ˆ˜ ๋‘๋ฒˆ์งธ ์ธ์ž์— { replace: true } ๋ฅผ ์ „๋‹ฌํ•˜๋ฉด replace ์•ก์…˜์ด ์‹คํ–‰๋˜์–ด ๊ธฐ๋ก ์Šคํƒ์˜ ๊ฐ€์žฅ ์ƒ๋‹จ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•˜๋ ค๋Š” ํŽ˜์ด์ง€๋กœ ๊ต์ฒดํ•ฉ๋‹ˆ๋‹ค.
  • navigate ํ•จ์ˆ˜์— ์ˆซ์ž๋ฅผ ์ธ์ž๋กœ ์ „๋‹ฌํ•˜๋ฉด ํ•ด๋‹น ์ˆซ์ž๋งŒํผ ์ „/ํ›„์— ๋ฐฉ๋ฌธํ–ˆ๋˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, navigate(-1)์€ ์ง์ „์— ๋ฐฉ๋ฌธํ–ˆ๋˜ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•ฉ๋‹ˆ๋‹ค.
ย 
๐Ÿ’ก
๊ธฐ๋ก ์Šคํƒ(history stack)์ด๋ž€?
DOM์˜ window ๊ฐ์ฒด๋Š” history ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ๋ธŒ๋ผ์šฐ์ €์˜ ์„ธ์…˜ ๊ธฐ๋ก์— ์ ‘๊ทผํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. history ๊ฐ์ฒด ๋‚ด back(), forward(), go() ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉํ•ด ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ก์„ ์กฐ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ธŒ๋ผ์šฐ์ € ๊ธฐ๋ก์€ ์ œ์ผ ์ตœ๊ทผ์— ๋ฐฉ๋ฌธํ•œ ํŽ˜์ด์ง€๊ฐ€ ๊ธฐ๋ก ์ œ์ผ ์ƒ๋‹จ์— ์žˆ๊ณ , ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ๋ฐ”๋กœ ์ด์ „ ํŽ˜์ด์ง€๋ฅผ ๋‹ค์‹œ ๋ฐฉ๋ฌธํ•  ์ˆ˜ ์žˆ๋Š” ์Šคํƒ์˜ ํ˜•ํƒœ๋กœ ๊ตฌํ˜„๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค. ๊ธฐ๋ก ์Šคํƒ์„ ํ† ๋Œ€๋กœ ์šฐ๋ฆฌ๋Š” ๋ธŒ๋ผ์šฐ์ €์—์„œ ๋’ค๋กœ ๊ฐ€๊ธฐ ๋ฒ„ํŠผ์„ ๊ณ„์† ๋ˆ„๋ฅด๊ณ  ์žˆ์œผ๋ฉด ๋ฐฉ๋ฌธ ๊ธฐ๋ก(๊ทธ๋ฆผ 3-1)์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
๊ทธ๋ฆผ 16-7๊ทธ๋ฆผ 16-7
๊ทธ๋ฆผ 16-7
ย 
์ถ”๊ฐ€์ ์œผ๋กœ, navigate ํ•จ์ˆ˜์˜ ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ state ํ”„๋กœํผํ‹ฐ๋ฅผ ๋„˜๊ฒจ์„œ ์ด๋™ํ•˜๋Š” ํŽ˜์ด์ง€์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { useNavigate } from "react-router-dom"; function HomePage() { const navigate = useNavigate(); const handleClick = () => { navigate("/profile/hook", { state: { id: 1, job: "๊ฐœ๋ฐœ์ž", }, }); }; return ( <main> <h1>hook ๋‹˜์˜ ํ™ˆํŽ˜์ด์ง€์— ์˜ค์‹  ๊ฒƒ์„ ํ™˜์˜ํ•ฉ๋‹ˆ๋‹ค.</h1> <button onClick={handleClick}>hook ๋‹˜์˜ ํ”„๋กœํ•„ ๋ณด๊ธฐ</button> </main> ); } export default HomePage;
navigate ํ•จ์ˆ˜์— state ์ „๋‹ฌํ•˜๊ธฐ
ย 

16.3.5 useParams

ย 
useParams ํ›…์„ ํ˜ธ์ถœํ•˜๋ฉด ํ˜„์žฌ ํŽ˜์ด์ง€ URL์˜ ํŒŒ๋ผ๋ฏธํ„ฐ ๊ฐ’์ด ๋‹ด๊ธด ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ, ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด๋Š” Route ์ปดํฌ๋„ŒํŠธ์˜ path props๋กœ ์„ค์ •ํ•œ URL ์ค‘ : ๋’ค์— ์˜ค๋Š” ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ ์ด๋ฆ„(์˜ˆ. :nickname)์„ ํ‚ค๊ฐ’์œผ๋กœ ๊ฐ€์ง‘๋‹ˆ๋‹ค.
ย 
์‹ค์ œ๋กœ ๋ฐ˜ํ™˜๋œ ๊ฐ์ฒด์˜ ํ‚ค์™€ ๊ฐ’์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•ด 16.3.4 useNavigate์—์„œ ์„ ์–ธํ•œ Route๋ฅผ ๋‹ค์‹œ ๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { BrowserRouter, Routes, Route } from "react-router-dom"; // ... function App() { return ( <BrowserRouter> <Routes> <Route path="/" element={<Layout />}> <Route index element={<HomePage />} /> <Route path="profile/:nickname" element={<Profile />} /> </Route> <Route path="*" element={<ErrorPage />} /> </Routes> </BrowserRouter> ); } export default App;
์˜ˆ) Route ์„ ์–ธ
ย 
Profile ์ปดํฌ๋„ŒํŠธ์˜ ๊ฒฝ๋กœ๊ฐ€ profile/:nickname ์œผ๋กœ ์„ค์ •๋˜์—ˆ๊ณ , ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ์˜ ์ด๋ฆ„์ด nickname์ธ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { useParams } from "react-router-dom"; function Profile() { // url์—์„œ ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ nickname ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. const {nickname} = useParams(); // console.log(useParams()); return <h2>{nickname}๋‹˜์˜ ํ”„๋กœํ•„ ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค.</h2>; } export default Profile;
๊ทธ๋ฆผ 16-8 ์‹คํ–‰ ๊ฒฐ๊ณผ๊ทธ๋ฆผ 16-8 ์‹คํ–‰ ๊ฒฐ๊ณผ
๊ทธ๋ฆผ 16-8 ์‹คํ–‰ ๊ฒฐ๊ณผ
๊ทธ๋ฆผ 16-9. useParams ํ›… ํ˜ธ์ถœ ์‹œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด๊ทธ๋ฆผ 16-9. useParams ํ›… ํ˜ธ์ถœ ์‹œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด
๊ทธ๋ฆผ 16-9. useParams ํ›… ํ˜ธ์ถœ ์‹œ ๋ฐ˜ํ™˜๋˜๋Š” ๊ฐ์ฒด
ย 
useParams ํ›…์„ ํ˜ธ์ถœํ•œ ๊ฒฐ๊ณผ { nickname: โ€œhookโ€ } ๊ฐ์ฒด๊ฐ€ ๋ฐ˜ํ™˜๋˜์–ด url์—์„œ ๋™์  ํŒŒ๋ผ๋ฏธํ„ฐ nickname ๊ฐ’์„ ์–ป์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ, ๋‹‰๋„ค์ž„ hook์ด ์ž˜ ์ถœ๋ ฅ๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.3.6 ์žฅ๋‹จ์ 

ย 
React Router๋ฅผ ์ด์šฉํ•˜์—ฌ SPA(Single Page Application)๋กœ ์›น ์‚ฌ์ดํŠธ๋ฅผ ๊ฐœ๋ฐœํ•˜๋ฉด ๋‹จ ํ•˜๋‚˜์˜ HTML ๋ฌธ์„œ๋ฅผ ์ด์šฉํ•˜์—ฌ ์„œ๋ฒ„์— ์š”์ฒญํ•˜์ง€ ์•Š๊ณ ๋„ ํŽ˜์ด์ง€๋ฅผ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๊ณ  ํ•„์š”ํ•œ ๋ฆฌ์†Œ์Šค๋งŒ ๋ถ€๋ถ„์ ์œผ๋กœ ๋กœ๋”ฉํ•˜์—ฌ UX(User Experience)์˜ ๊ด€์ ์—์„œ ์„ฑ๋Šฅ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ MPA(Multiple Page Application)์— ๋น„ํ•ด JavaScript ํŒŒ์ผ์˜ ์˜์กด๋„๊ฐ€ ๋†’์•„ ์ตœ์ดˆ ๋กœ๋”ฉ์ด ๋น„๊ต์  ๋Š๋ฆฌ๊ณ , ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”(SEO)์— ๋ถˆ๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๊ฒ€์ƒ‰ ๋…ธ์ถœ์ด ์ค‘์š”ํ•œ ์›น ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ๊ฒฝ์šฐ ๊ฒ€์ƒ‰ ์—”์ง„ ์ตœ์ ํ™”๋ฅผ ์œ„ํ•ด ์ถ”๊ฐ€๋กœ ๋Œ€์‘ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.4. React Query์—์„œ ์ง€์›ํ•˜๋Š” Hook

ย 
๊ทธ๋ฆผ 16-9๊ทธ๋ฆผ 16-9
๊ทธ๋ฆผ 16-9
ย 

16.4.1 React Query์ด๋ž€?

React Query๋Š” ์„œ๋ฒ„์—์„œ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๊ฑฐ๋‚˜, ๋™๊ธฐํ™” ๋ฐ ์—…๋ฐ์ดํŠธ๋ฅผ ์‰ฝ๊ฒŒ ๋งŒ๋“ค์–ด์ฃผ๊ณ  ์บ์‹ฑ ๊ธฐ๋Šฅ๊นŒ์ง€ ์ง€์›ํ•ด์ฃผ๋ฉฐ ๋น„๋™๊ธฐ ๋กœ์ง์„ ๋ณด๋‹ค ์‰ฝ๊ฒŒ ๋‹ค๋ฃจ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ฆฌ์•กํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ž…๋‹ˆ๋‹ค.
โ“
์บ์‹ฑ์ด๋ž€? ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜๋ณตํ•ด์„œ ๊ฐ€์ ธ์˜ค๋Š” ๊ฒƒ์„ ์ค„์ด๊ธฐ ์œ„ํ•ด ๋ฉ”๋ชจ๋ฆฌ์— ์ €์žฅํ•ด๋‘๋Š” ํ–‰์œ„
ย 
React Query๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ๋ฌด์—‡์ผ๊นŒ์š”? ์„œ๋ฒ„์—์„œ ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ๋Š” ์–ธ์ œ๊นŒ์ง€ ์‹ ์„ (fresh)ํ•œ ์ƒํƒœ๋ผ๊ณ  ๋ณด์žฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค. ์‹œ๊ฐ„์ด ํ˜๋Ÿฌ ์œ ์ €๊ฐ€ ์ƒํ˜ธ์ž‘์šฉํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ณ€ํ•˜์˜€์„ ์ˆ˜๋„ ์žˆ๊ณ , ์„œ๋ฒ„์—์„œ ๋ณ€๊ฒฝ๋˜์„œ ์ƒˆ๋กœ์šด ์ •๋ณด๋ฅผ ๋ฐ›์•„์™€์•ผํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ํด๋ผ์ด์–ธํŠธ์—์„œ ํ•ด๋‹น ๋กœ์ง๋“ค์„ ์ผ์ผํžˆ ๊ตฌํ˜„ํ•˜๋Š” ์ผ์€ ๋ถ€๋‹ด์ด ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์—ฌ๊ธฐ์„œ React Query๋Š” ์ตœ์‹ ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ„ํŽธํ•˜๊ฒŒ ์œ ์ง€ํ•˜๋Š” ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ React Hook ์ฒ˜๋Ÿผ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.4.2 React Query ์„ค์น˜๋ฐฉ๋ฒ•

React ํ”„๋กœ์ ํŠธ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ”„๋กœ์ ํŠธ ํด๋”๋กœ ์ด๋™ํ•œ ๋’ค, npm ๋˜๋Š” yarn ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ React Query ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
npm install @tanstack/react-query
npm์œผ๋กœ react-query ์„ค์น˜ํ•˜๊ธฐ
ย 
yarn add @tanstack/react-query
yarn์œผ๋กœ react-query ์„ค์น˜ํ•˜๊ธฐ
ย 

16.4.3 React Query ์…‹ํŒ… ๋ฐ ์‚ฌ์šฉ๋ฐฉ๋ฒ•

import React from "react"; import ReactDOM from "react-dom/client"; import App from "./App"; import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; const queryClient = new QueryClient(); const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <React.StrictMode> <QueryClientProvider client={queryClient}> <App /> </QueryClientProvider> </React.StrictMode> );
index.js
ย 
์œ„์™€ ๊ฐ™์ด ์ปดํฌ๋„ŒํŠธ ์ตœ์ƒ๋‹จ์—์„œ new QueryClient ์ƒ์„ฑ์ž ํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ queryClient ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๊ณ  <QueryClientProvider> ์ปดํฌ๋„ŒํŠธ์— client prop๋กœ ์ „๋‹ฌํ•˜์—ฌ ์คŒ์œผ๋กœ์จ ์ดˆ๊ธฐ ์…‹ํŒ…์€ ๋๋‚ฉ๋‹ˆ๋‹ค.
ย 

16.4.4 useQuery

import { useQuery } from "@tanstack/react-query"; // useQuery๊ฐ€ return ํ•˜๋Š” ๊ฐ์ฒด์—๋Š” ์ด ์„ธ๊ฐ€์ง€ ๋ง๊ณ ๋„ ์—ฌ๋Ÿฌ ์†์„ฑ๊ฐ’์ด ์žˆ์Šต๋‹ˆ๋‹ค. const { data, isLoading, error } = useQuery(queryKey, queryFn, options);
useQuery๋Š” ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ์ด์šฉํ•ด GET ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ ์˜ค๋Š”๋ฐ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค.๋น„๋™๊ธฐ ํ†ต์‹ ์ด ๋๋‚˜๋ฉด isLoading์€ true์—์„œ false๋กœ ๋ฐ”๋€Œ๊ณ  data์— ์š”์ฒญํ•œ ๋ฐ์ดํ„ฐ๊ฐ€ ๋‹ด๊ธฐ๊ฒŒ ๋˜๋ฉฐ error ๋ฐœ์ƒํ–ˆ์„ ์‹œ error์— ๋ฐ˜ํ™˜ํ•ด์ค๋‹ˆ๋‹ค.
ย 
useQuery์— ๋“ค์–ด๊ฐ€๋Š” ์ธ์ž ๊ฐ’
  1. queryKey: useQuery๋งˆ๋‹ค ๋ถ€์—ฌ๋˜๋Š” ๊ณ ์œ  Key ์ž…๋‹ˆ๋‹ค. ๋‹จ์ˆœ ๋ฌธ์ž์—ด์„ ํ•  ์ˆ˜ ์žˆ๊ณ  ๋ฐฐ์—ด์˜ ํ˜•ํƒœ๋กœ๋„ ์‚ฌ์šฉ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  1. queryFn: ์ฟผ๋ฆฌ๊ฐ€ ๋ฐ์ดํ„ฐ๋ฅผ ์š”์ฒญํ•˜๋Š” ๋ฐ ์‚ฌ์šฉํ•  ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. fetch๋‚˜ axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์™€์•ผํ•ฉ๋‹ˆ๋‹ค.
  1. options: ์ƒ๋žต๊ฐ€๋Šฅํ•œ ์ธ์ž๋กœ ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•  ๋•Œ ์‚ฌ์šฉ๋ฉ๋‹ˆ๋‹ค. enable, retry ๋“ฑ๋“ฑ ๋งŽ์€ ์˜ต์…˜๋“ค์ด ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์•„๋ž˜๋Š” ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
export const fetchPosts = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const data = await res.json(); return data; };
api.js
import { useQuery } from "@tanstack/react-query"; import { fetchPosts } from "./api.js" export default function App() { const { isLoading, data: posts } = useQuery("posts", fetchPosts); return ( <div> {isLoading ? "LOADING..." : posts.map((post, index) => <div>{`${index}) ${post.title}`}</div>)} </div> ); }
app.jsx
ย 
React-Query๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ์ฒ˜์Œ ๋น„๋™๊ธฐ ํ†ต์‹ ์„ ํ•œ๋ฒˆ๋งŒ ๋ณด๋‚ด๊ธฐ ์œ„ํ•ด useEffect๋ฅผ ์“ธ ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. ๋˜ํ•œ ๋ฐ›์•„ ์˜จ ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด์—ฌ์ฃผ๊ธฐ ์œ„ํ•ด, ๋กœ๋”ฉ ์ƒํƒœ๋ฅผ ์ง€์ •ํ•˜๊ธฐ ์œ„ํ•ด useState๋ฅผ ์‚ฌ์šฉํ•  ํ•„์š”๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค. React-Query๋Š” ์ด ๋ชจ๋“ ๊ฑธ useQuery Hook์œผ๋กœ ๊ฐ„๋‹จํ•˜๊ฒŒ ๋งŒ๋“ค์–ด์ค๋‹ˆ๋‹ค.
ย 
ย 

16.4.4 useMutation

import { useMutation } from "@tanstack/react-query"; // useMutation์ด return ํ•˜๋Š” ๊ฐ์ฒด์—๋Š” ์ด ์„ธ๊ฐ€์ง€ ๋ง๊ณ ๋„ ์—ฌ๋Ÿฌ ์†์„ฑ๊ฐ’์ด ์žˆ์Šต๋‹ˆ๋‹ค. const { data, isLoading, mutate } = useMutation(mutationFn, options);
ย 
useQuery๊ฐ€ GET ์š”์ฒญ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฅผ ๋ถˆ๋Ÿฌ ์˜ค๋Š”๋ฐ ์‚ฌ์šฉ๋œ๋‹ค๋ฉด useMutation์€ React Query๋ฅผ ์ด์šฉํ•ด ์„œ๋ฒ„์— ๋ฐ์ดํ„ฐ ๋ณ€๊ฒฝ ์ž‘์—…์„ ์š”์ฒญํ•  ๋•Œ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. isLoading ๊ณผ data๋Š” useQuery์™€ ๊ฐ™์ด ์™„๋ฃŒ ์—ฌ๋ถ€์™€, ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ˜ํ™˜ํ•˜์ง€๋งŒ mutate์€ mutationFn์œผ๋กœ ์ œ๊ณต๋œ ํ•จ์ˆ˜๋ฅผ ์ž‘๋™์‹œํ‚ค๋Š” ํŠธ๋ฆฌ๊ฑฐ ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค.
ย 
useMutation์— ๋“ค์–ด๊ฐ€๋Š” ์ธ์ž ๊ฐ’
  1. mutationFn: ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์ •ํ•˜๊ณ  ์—…๋ฐ์ดํŠธํ•˜๊ณ  ์‚ญ์ œํ•˜๋Š”๋ฐ ์‚ฌ์šฉํ•  ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. fetch๋‚˜ axios๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ promise๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๊ฐ€ ์™€์•ผํ•ฉ๋‹ˆ๋‹ค.
  1. options: useMutation์—๋„ ๋งŽ์€ ์˜ต์…˜์ด ์žˆ์ง€๋งŒ ๋งŽ์ด ์“ฐ๋Š” 4๊ฐ€์ง€ ์˜ต์…˜์ด ์žˆ์Šต๋‹ˆ๋‹ค.
      • onMutate: Promise ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” Callback ํ•จ์ˆ˜๋กœ์จ mutation ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ์ „์— ์‹คํ–‰๋˜๊ณ  mutation ํ•จ์ˆ˜์™€ ๋™์ผํ•œ ๋ณ€์ˆ˜๊ฐ€ ์ œ๊ณต๋ฉ๋‹ˆ๋‹ค.
      • onError: mutation์ด ์‹คํŒจํ•œ ๊ฒฝ์šฐ ์‹คํ–‰ํ•  Callback ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค.
      • onSettled: mutation์ด ์‹คํŒจ ๋˜๋Š” ์„ฑ๊ณต ์—ฌ๋ถ€์— ๊ด€๊ณ„ ์—†์ด ์‹คํ–‰ํ•  Callback ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค.
      • onSuccess: mutation์ด ์„ฑ๊ณตํ•œ ๊ฒฝ์šฐ ์‹คํ–‰ํ•  Callback ํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค.
ย 
ย 
์•„๋ž˜๋Š” ์‹ค์ œ ์‚ฌ์šฉ ์˜ˆ์‹œ์ž…๋‹ˆ๋‹ค.
ย 
// getPosts ํ•จ์ˆ˜๋Š” ๊ฒŒ์‹œ๋ฌผ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. export const getPosts = async () => { const { data }= await axios.get("http://localhost:8080/post"); return data; }; // createPost ํ•จ์ˆ˜๋Š” ๊ฒŒ์‹œ๋ฌผ์„ ์ƒ์„ฑํ•˜๋Š” ํ•จ์ˆ˜์ž…๋‹ˆ๋‹ค. export const createPost = async (title, content) => { await axios.post("http://localhost:8080/post", { title, content, }); };
api.js
ย 
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { getPosts, createPost } from "./api.js" export default function App() { const queryClient = useQueryClient(); const { isLoading, data: posts } = useQuery("posts", getPosts); const createMutate = useMutation( ({ title, content }) => { createPost(title, content); }, { onSuccess: () => { // ๋น„๋™๊ธฐ ํ†ต์‹ ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์ด๋ฃจ์–ด ์กŒ์„๋•Œ // posts ๋ผ๋Š” queryKey๋ฅผ ์ฐพ์•„ ๋ฐ์ดํ„ฐ๋ฅผ ์ƒˆ๋กœ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค. queryClient.invalidateQueries("posts"); }, } ); const handleSubmit = async (e) => { e.preventDefault(); const { title, content } = e.target; // ์œ„์—์„œ ์„ ์–ธํ•œ createMutate์˜ mutateํ•จ์ˆ˜๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ createPost ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ต๋‹ˆ๋‹ค. createMutate.mutate({ title: title.value, content: content.value }); }; return ( <div> <form onSubmit={handleSubmit}> {...์ค‘๋žต} </form> {isLoading ? "LOADING..." : posts.map((post, index) => <div>{`${index}) ${post.title}`}</div>)} </div> ); }
app.jsx
ย 
useMutation๋ฅผ ์‚ฌ์šฉํ•ด ์ƒ์„ฑ, ์—…๋ฐ์ดํŠธ, ์‚ญ์ œ ๋“ฑ์˜ ์ฒ˜๋ฆฌ ํ•  ์ˆ˜ ์žˆ๊ณ , queryClient๊ฐ€ ๊ฒ€์ƒ‰ํ•  ์ˆ˜ ์žˆ๋Š” Query key๋ผ๋ฉด queryClient.invalidateQueries(queryKey)๋ฅผ ํ†ตํ•ด ๋ฐ์ดํ„ฐ๋ฅผ ์†์‰ฝ๊ฒŒ ๊ฐฑ์‹  ํ•ด ์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐฑ์‹ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ์ด ๋ฐ–์—๋„ ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋Š” invalidateQueries ๋งŒ ๋‹ค๋ฃจ์–ด ๋ณด์•˜์Šต๋‹ˆ๋‹ค.
ย 
์ด ๋ฐ–์—๋„ React-Query์˜ ๊ฐ€๋Šฅ์„ฑ์€ ๋ฌด๊ถ๋ฌด์ง„ํ•ฉ๋‹ˆ๋‹ค. ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์— ๋Œ€ํ•ด ์กฐ๊ธˆ ๋” ์•Œ๊ณ  ์‹ถ๋‹ค๋ฉด ๊ณต์‹ ์‚ฌ์ดํŠธ๋ฅผ ๋ฐฉ๋ฌธํ•ด ์ฃผ์„ธ์š”.
๐Ÿ’ก
React-Query v4 ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€ https://tanstack.com/query/v4/docs/react/quick-start
ย 

16.5. react-use

16.5.1 react-use๋ž€?

React๋Š” UI, ๋ผ์ดํ”„์‚ฌ์ดํด, State ๋“ฑ ๋‹ค์–‘ํ•œ ์ธก๋ฉด์—์„œ ์œ ์šฉํ•œ Hook๋“ค์„ ๋งŒ๋“ค์–ด ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋กœ ๋ฐฐํฌํ•˜๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. react-use ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋Š” ๋งค์šฐ ๋‹ค์–‘ํ•œ Hook๋“ค์„ ์ง€์›ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋ณด๋‹ค ํŽธ๋ฆฌํ•œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฒˆ ์ฑ•ํ„ฐ์—์„œ๋Š” React use์—์„œ ์ œ๊ณตํ•˜๋Š” ๋‹ค์–‘ํ•œ Hook ์ค‘์—์„œ ๋ช‡ ๊ฐ€์ง€ ์œ ์šฉํ•œ Hook๋“ค์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
ย 

16.5.2 react-use ์„ค์น˜ ๋ฐฉ๋ฒ•

ย 
React ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ ํ›„ ํ”„๋กœ์ ํŠธ ํด๋”๋กœ ์ด๋™ํ•œ ๋’ค, npm ํ˜น์€ yarn ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ React use ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm install react-use
npm์œผ๋กœ react-use ์„ค์น˜ํ•˜๊ธฐ
ย 
yarn add react-use
yarn์œผ๋กœ react-use ์„ค์น˜ํ•˜๊ธฐ
ย 

16.5.3 useUpdateEffect

ย 
useEffect Hook์€ React์—์„œ ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ์—…๋ฐ์ดํŠธ ๋  ๋•Œ ํ•จ์ˆ˜๋ฅผ ํ˜ธ์ถœํ•  ๊ฒฝ์šฐ ์‚ฌ์šฉ๋˜๋Š” Hook์ž…๋‹ˆ๋‹ค. useUpdateEffect Hook์€ useEffect์™€ ๊ฐ™์ด ํŠน์ • ํ•จ์ˆ˜๊ฐ€ ์—…๋ฐ์ดํŠธ ๋  ๋•Œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ, useUpdateEffect ๋Š” ์ตœ์ดˆ ๋งˆ์šดํŠธ ์‹œ ์‹คํ–‰์ด ๋˜์ง€ ์•Š๋Š”๋‹ค๋Š” ์ฐจ์ด์ ์„ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋งˆ์šดํŠธ๊ฐ€ ๋  ๋•Œ ์‹คํ–‰์„ ๊ฑด๋„ˆ๋›ฐ๊ณ  ์‹ถ์„ ๊ฒฝ์šฐ ์œ ์šฉํ•˜๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ, ์‚ฌ์šฉ๋ฒ•์€ useEffect์™€ ๋™์ผํ•ฉ๋‹ˆ๋‹ค.
ย 
์•„๋ž˜ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด useEffect์™€ useUpdateEffect์˜ ์ฐจ์ด์ ์— ๋Œ€ํ•ด ์ข€ ๋” ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useEffect, useState } from "react"; function App() { const [input, setInput] = useState(""); useEffect(() => { console.log(input); console.log("์ฒซ ๋งˆ์šดํŠธ ์‹œ์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค."); }, [input]); return <input value={input} onChange={(e) => setInput(e.target.value)} />; } export default App;
useEffect ํ›… ์‚ฌ์šฉ ์˜ˆ์ œ
ย 
๊ทธ๋ฆผ 16-10๊ทธ๋ฆผ 16-10
๊ทธ๋ฆผ 16-10
ย 
๊ทธ๋ฆผ 16-11๊ทธ๋ฆผ 16-11
๊ทธ๋ฆผ 16-11
ย 
useEffect๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ํ™”๋ฉด์ด ์ฒ˜์Œ ๋ Œ๋”๋ง ๋  ๋•Œ์—๋„ ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— console์— ํ˜„์žฌ input๊ฐ’๊ณผ โ€˜์ฒซ ๋งˆ์šดํŠธ ์‹œ์— ์‹คํ–‰๋ฉ๋‹ˆ๋‹ค.โ€™ ๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ์ถœ๋ ฅ์ด ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; import { useUpdateEffect } from "react-use"; function App() { const [input, setInput] = useState(""); useUpdateEffect(() => { console.log(input); console.log("์ฒซ ๋งˆ์šดํŠธ ์‹œ์— ์‹คํ–‰๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); }, [input]); return <input value={input} onChange={(e) => setInput(e.target.value)} />; } export default App;
useUpdateEffect ํ›… ์‚ฌ์šฉ ์˜ˆ์ œ
ย 
๊ทธ๋ฆผ 16-12๊ทธ๋ฆผ 16-12
๊ทธ๋ฆผ 16-12
ย 
๊ทธ๋ฆผ 16-13๊ทธ๋ฆผ 16-13
๊ทธ๋ฆผ 16-13
ย 
๋ฐ˜๋ฉด useUpdateEffect๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ, ํ™”๋ฉด์ด ์ฒ˜์Œ ๋ Œ๋”๋ง ๋  ๋•Œ์—๋Š” ์‹คํ–‰์ด ๋˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— console์ฐฝ์— ์•„๋ฌด๊ฒƒ๋„ ์ถœ๋ ฅ์ด ๋˜์ง€ ์•Š๊ณ , ๊ฐ’์ด update ๋˜๋Š” ๊ฒฝ์šฐ์—๋งŒ console์— ๋‚ด์šฉ์„ ์ถœ๋ ฅํ•ด ์ฃผ๋Š” ๊ฒƒ์„ ํ™•์ธํ•ด ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.5.4 useDebounce

ย 
debounce๋Š” ํ•จ์ˆ˜๋ฅผ ์—ฌ๋Ÿฌ ๋ฒˆ ํ˜ธ์ถœํ•˜๊ณ  ๋งˆ์ง€๋ง‰ ํ˜ธ์ถœ์—์„œ ์ผ์ • ์‹œ๊ฐ„์ด ์ง€๋‚œ ํ›„ ํ•ด๋‹น ํ•จ์ˆ˜์˜ ๊ธฐ๋Šฅ์ด ๋™์ž‘ํ•˜๋Š” ๊ธฐ๋ฒ•์„ ๋งํ•ฉ๋‹ˆ๋‹ค. ๊ฒ€์ƒ‰์ฒ˜๋Ÿผ ์ž…๋ ฅํ•˜๋Š” ๊ธ€์ž์— ๋”ฐ๋ผ ๊ฒฐ๊ณผ๋ฅผ ์ถœ๋ ฅํ•ด ์ฃผ๊ณ  ์‹ถ์„ ๋•Œ, ์‚ฌ์šฉ์ž๊ฐ€ ๋‹จ์–ด๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค API ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค๋ฉด ๋ถˆํ•„์š”ํ•œ API ์š”์ฒญ์ด ์ƒ๊ธฐ๊ฒŒ ๋˜๊ณ , ์„œ๋ฒ„์— ๋ถ€ํ•˜๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ debounce ๊ธฐ๋ฒ•์„ ์‚ฌ์šฉํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ์„œ๋ฒ„์˜ ๋ถ€ํ•˜๋ฅผ ์ค„์ผ ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
debounce๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๋ฐฉ๋ฒ•์—๋Š” ์ปค์Šคํ…€ ํ›… ๋งŒ๋“ค๊ธฐ์™€ ๊ฐ™์€ ๋‹ค์–‘ํ•œ ๋ฐฉ๋ฒ•์ด ์žˆ์ง€๋งŒ, react-use์˜ useDebounce๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋˜๋ฉด ๋ณด๋‹ค ์‰ฝ๊ฒŒ debounce์˜ ๊ตฌํ˜„์ด ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.
ย 
useDebounce(ํ˜ธ์ถœ๋  ํ•จ์ˆ˜, ์ง€์—ฐ ๋‹จ์œ„, [๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ๊ฐ’])
useDebounce ํ›…์˜ ๊ธฐ๋ณธ ๊ตฌ์กฐ
ย 
useDebounce๋Š” ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋กœ debounce ๋  ๋•Œ ํ˜ธ์ถœ๋  ํ•จ์ˆ˜, ๋‘ ๋ฒˆ์งธ ์ธ์ž๋กœ ์ง€์—ฐ ๋‹จ์œ„, ์„ธ ๋ฒˆ์งธ ์ธ์ž๋กœ ๋ณ€ํ™”๋ฅผ ๊ฐ์ง€ํ•  ๊ฐ’์˜ ๋ฐฐ์—ด์„ ๊ฐ€์ง‘๋‹ˆ๋‹ค. ์ง€์—ฐ ๋‹จ์œ„๋Š” ms(๋ฐ€๋ฆฌ์ดˆ) ๋‹จ์œ„๋กœ ์ž‘์„ฑํ•ด ์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ย 
import { useDebounce } from "react-use"; function App() { const [input, setInput] = useState(""); const [isLoading, setIsLoading] = useState(false); useDebounce( () => { setIsLoading(true); // API ์š”์ฒญ // ๊ตฌํ˜„์ฝ”๋“œ // ... setIsLoading(false); }, 1000, [input] ); return <input value={input} onChange={(e) => setInput(e.target.value)} />; } export default App;
useDebounce ํ›… ์‚ฌ์šฉ ์˜ˆ์ œ
ย 

16.5.5 useLocalStorage

ย 
LocalStorage๋Š” key์™€ value๋กœ ์ด๋ฃจ์–ด์ง„ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•  ์ˆ˜ ์žˆ๋Š” ์›น ์Šคํ† ๋ฆฌ์ง€ ์ž…๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ์ž ๋„๊ตฌ๋ฅผ ์—ด์–ด โ€œ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜โ€ ํƒญ์œผ๋กœ ๋“ค์–ด๊ฐ€ ์ ‘๊ทผ์ด ๊ฐ€๋Šฅํ•˜๋ฉฐ, LocalStorage๋Š” ํ•ด๋‹น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋‹ซ๋”๋ผ๋„ ๋ฐ์ดํ„ฐ๊ฐ€ ๊ณ„์†ํ•ด์„œ ์œ ์ง€๋˜๋Š” ํŠน์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ํŠน์„ฑ์œผ๋กœ ์ธํ•ด ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ์œ ์ง€ํ•˜๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•˜๊ธฐ๋„ ํ•ฉ๋‹ˆ๋‹ค.
ย 
์ด๋Ÿฌํ•œ LocalStorage๋Š” react-use์˜ useLocalStorage๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ข€ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ๊ด€๋ฆฌ ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ๊ฐ„๋‹จํ•˜๊ฒŒ useLocalStorage ์˜ ํ˜•ํƒœ์™€ ์‚ฌ์šฉ ์˜ˆ์ œ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
const [value, setValue, removeValue] = useLocalStorage("key๊ฐ’", "๊ธฐ๋ณธ๊ฐ’");
ย 
useLocalStorage๋Š” ์œ„์™€ ๊ฐ™์ด useState์™€ ๋น„์Šทํ•œ ํ˜•ํƒœ๋ฅผ ๋„๋Š”๋ฐ ๋จผ์ € value๋Š” LocalStorage์˜ value๊ฐ’์ด ๋“ค์–ด๊ฐ€๊ณ  setValue๋Š” value๋ฅผ ๋ฐ”๊พธ์–ด์ฃผ๋Š” ์—ญํ• ์„ ํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜์ด๋ฉฐ, removeValue๋Š” LocalStorage์— ์žˆ๋Š” ๊ฐ’์„ ๋น„์›Œ์ฃผ๋Š” ์—ญํ• ์„ ํ•˜๋Š” ์ฝœ๋ฐฑํ•จ์ˆ˜ ์ž…๋‹ˆ๋‹ค. useLocalStorage ์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž๋Š” LocalStorage์˜ key๊ฐ’์ด ๋“ค์–ด๊ฐ€๊ณ  ๋‘ ๋ฒˆ์งธ ์ธ์ž๋Š” LocalStorage์˜ ๊ฐ’์ด ๋น„์–ด์žˆ๋‹ค๋ฉด ์ดˆ๊ธฐ๋กœ ์„ธํŒ…ํ•ด์ค„ ๊ฐ’์„ ์ ์–ด์ฃผ๋Š”๋ฐ ์ด๋Š” ์ ์–ด์ฃผ์–ด๋„ ๋˜๊ณ  ์ ์–ด์ฃผ์ง€ ์•Š์•„๋„ ๋˜๋Š” ์˜ต์…˜๊ฐ’์ž…๋‹ˆ๋‹ค.
ย 
import { useLocalStorage } from "react-use"; const App = () => { const [value, setValue, removeValue] = useLocalStorage("ํ‚ค", "๊ธฐ๋ณธ๊ฐ’"); return ( <div> <div> <h1>Value: {value}</h1> <button onClick={() => setValue("ํฌ๋„")}>value๋ฅผ ํฌ๋„๋กœ ๋ณ€๊ฒฝ</button> <br></br> <button onClick={() => setValue("๋ฐ”๋‚˜๋‚˜")}>value๋ฅผ ๋ฐ”๋‚˜๋‚˜๋กœ ๋ณ€๊ฒฝ</button> <br></br> <button onClick={() => removeValue()}>LocalStorage์˜ ๊ฐ’์„ ์‚ญ์ œ</button> <br></br> </div> </div> ); }; export default App;
useLocalStorage ํ›… ์‚ฌ์šฉ ์˜ˆ์ œ
ย 
notion imagenotion image
ย 
์˜ˆ์ œ์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์œ„์™€ ๊ฐ™์ด ์…‹ํŒ…์ด ๋˜๋Š”๋ฐ useLocalStorage ์˜ ๋‘ ๋ฒˆ์งธ ์ธ์ž์— โ€œ๊ธฐ๋ณธ๊ฐ’โ€ ์ด๋ผ๊ณ  ์ž‘์„ฑํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ดˆ๊ธฐ์˜ LocalStorage์˜ ๊ฐ’์ด โ€œ๊ธฐ๋ณธ๊ฐ’โ€์œผ๋กœ ์„ค์ •๋˜์–ด ์žˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜์—ฌ value๋ฅผ ๋ณ€๊ฒฝํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
notion imagenotion image
ย 
์œ„์˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด LocalStorage์— ์žˆ๋˜ โ€œ๊ธฐ๋ณธ๊ฐ’โ€์ด ํฌ๋„๊ฐ€ ๋œ ๊ฑธ ํ™•์ธํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด์— ๋”ฐ๋ผ ํ™”๋ฉด์— ๋ Œ๋”๋ง ๋˜๋˜ โ€œ๊ธฐ๋ณธ๊ฐ’โ€์ด๋ผ๋Š” ํ…์ŠคํŠธ ๋˜ํ•œ โ€œํฌ๋„โ€๋กœ ๋ฐ”๋€ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. LocalStorage์˜ ๊ฐ’์„ ์‚ญ์ œํ•˜๋Š” ๋ฒ„ํŠผ์„ ํด๋ฆญํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
notion imagenotion image
ย 
ย 
์œ„์™€ ๊ฐ™์ด LocalStorage์˜ key์™€ value๊ฐ€ ๋ชจ๋‘ ์‚ญ์ œ๋˜๊ณ  ํ™”๋ฉด์— ๋ Œ๋”๋ง ๋˜์–ด์ง€๋Š” Value๊ฐ’ ๋˜ํ•œ ์ง€์›Œ์ง€๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์ด์ฒ˜๋Ÿผ useLocalStorage ์„ ํ™œ์šฉํ•œ๋‹ค๋ฉด LocalStorage์˜ ๊ฐ’์„ ์ถ”๊ฐ€ํ•˜๊ณ , ์‚ญ์ œํ•˜๊ณ , ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ์ข€ ๋” ๊ฐ„ํŽธํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.5.6 useClickAway

ย 
useClickAway Hook์€ ํŠน์ • UI์˜ ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ–ˆ์„ ๋•Œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” Hook์ž…๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด ๋ชจ๋‹ฌ์ฐฝ์ด ํ™œ์„ฑํ™”๊ฐ€ ๋˜์–ด์žˆ๋Š” ์ƒํƒœ์—์„œ ๋‹ซ๊ธฐ ๋ฒ„ํŠผ์ด ์•„๋‹Œ ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜์—ฌ ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๊ณ ์‹ถ์€ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Ÿด ๋•Œ useClickAway๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์ด๋ฅผ ๋”์šฑ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋Š”๋ฐ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด useClickAway ์˜ ๊ธฐ๋ณธ๊ตฌ์กฐ์™€ ์‚ฌ์šฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ณ  ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
useClickAway(ref, fn, ['click']);
ย 
useClickAway์˜ ๊ธฐ๋ณธํ˜•ํƒœ๋Š” ์œ„์™€ ๊ฐ™์œผ๋ฉฐ ์•„๋ž˜์˜ ์˜ˆ์ œ๋ฅผ ํ†ตํ•ด ์ž์„ธํ•œ ์šฉ๋ฒ•์— ๋Œ€ํ•ด ์•Œ์•„๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useClickAway } from "react-use"; import { useRef } from "react"; const App = () => { const foo = () => { console.log("ref์˜ ์™ธ๋ถ€๊ฐ€ ํด๋ฆญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); }; const ref = useRef(null); useClickAway(ref, foo, ['click']); return ( <div ref={ref} style={{ width: 100, height: 100, background: "pink", }} /> ); }; export default App;
App.jsx
ย 
notion imagenotion image
ย 
์˜ˆ์ œ๋Š” ์œ„์˜ ์ด๋ฏธ์ง€์™€ ๊ฐ™์ด ๋ถ„ํ™๋ฐ•์Šค ๋ฐ”๊นฅ ๋ถ€๋ถ„์„ ํด๋ฆญํ•˜๋ฉด ์ฝ˜์†” ์ฐฝ์— ๋ฉ”์‹œ์ง€๊ฐ€ ์ถœ๋ ฅ๋˜๊ณ  ๋ถ„ํ™๋ฐ•์Šค๋ฅผ ํด๋ฆญํ•˜๋ฉด ์•„๋ฌด ์ผ๋„ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š” ๊ฐ„๋‹จํ•œ ์˜ˆ์ œ์ž…๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์•Œ ์ˆ˜ ์žˆ๋Š” ๊ฒƒ์€ useClickAway์˜ ์ฒซ ๋ฒˆ์งธ ์ธ์ž์—๋Š” ์™ธ๋ถ€ ์˜์—ญ ์ด์™ธ์˜ ์š”์†Œ๋ฅผ ๊ฐ€๋ฆฌํ‚ค๋ฉฐ ๋‘ ๋ฒˆ์งธ ์ธ์ž์—๋Š” ref์˜ ์™ธ๋ถ€ ์š”์†Œ๊ฐ€ ํด๋ฆญ ๋˜์—ˆ์„ ๋•Œ ์‹คํ–‰๋  ํ•จ์ˆ˜๋ฅผ ๊ฐ€๋ฆฌํ‚ต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์„ธ ๋ฒˆ์งธ ์ธ์ž์—๋Š” ์›ํ•˜๋Š” ์ด๋ฒคํŠธ ์ข…๋ฅ˜๋ฅผ ๋„ฃ๊ฒŒ ๋˜๋Š”๋ฐ ์„ธ ๋ฒˆ์งธ ์ธ์ž๋ฅผ ์ƒ๋žตํ•  ๊ฒฝ์šฐ ๊ธฐ๋ณธ์ ์œผ๋กœ โ€˜clickโ€™ ์ด๋ฒคํŠธ์‹œ ํŠธ๋ฆฌ๊ฑฐ๋˜๋„๋ก ์„ค์ •์ด ๋ฉ๋‹ˆ๋‹ค.
ย 
useClickAway(ref, () => { console.log("ref์˜ ์™ธ๋ถ€์š”์†Œ๊ฐ€ ํด๋ฆญ๋˜์—ˆ์Šต๋‹ˆ๋‹ค."); });
ย 
๋˜ํ•œ ์œ„์˜ ์˜ˆ์ œ์ฒ˜๋Ÿผ ๋‘ ๋ฒˆ์งธ ์ธ์ž์—์„œ ๋ฐ”๋กœ ํ•จ์ˆ˜๋ฅผ ์„ ์–ธํ•˜์—ฌ ์‚ฌ์šฉํ•  ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.์ง€๊ธˆ๊นŒ์ง€useClickAway์˜ ๊ธฐ๋ณธํ˜•ํƒœ์— ๋Œ€ํ•ด ์•Œ์•„๋ณด์•˜์œผ๋‹ˆ ์ด์–ด์ง€๋Š” ์˜ˆ์ œ์—์„œ ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๋Š” ๊ธฐ๋Šฅ์„ ๊ตฌํ˜„ํ•ด๋ณด๋„๋ก ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useState } from "react"; const App = () => { const [onModal, setOnModal] = useState("none"); const Modal = () => { onModal ? setOnModal(false) : setOnModal("none"); }; return ( <> <button style={{ margin: 20, }} onClick={Modal} > ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์›Œ์ฃผ์„ธ์š”! </button> <div style={{ display: onModal, position: "absolute", top: 10, background: "pink", width: 200, }} > <p style={{ textAlign: "center", }} > ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๊ฒ ์Šต๋‹ˆ๊นŒ? </p> <button onClick={Modal} style={{ width: 200 }}> ์˜ˆ </button> </div> </> ); }; export default App;
App.jsx
ย 
notion imagenotion image
ย 
notion imagenotion image
ย 
์˜ˆ์ œ์™€ ๊ฐ™์ด ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•˜๋ฉด ์œ„์™€ ๊ฐ™์ด ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์šธ ์ˆ˜ ์žˆ๋Š” ๋ฒ„ํŠผ์ด ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๊ฒŒ ๋˜๋ฉด ๋ชจ๋‹ฌ์ฐฝ์ด ํ™œ์„ฑํ™”๊ฐ€ ๋˜๋ฉฐ โ€œ์˜ˆโ€๋ฅผ ๋ˆ„๋ฅด๋ฉด ๋ชจ๋‹ฌ์ฐฝ์ด ๋‹ซํžˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ์˜ ์ƒํƒœ์—์„œ๋Š” ๋ชจ๋‹ฌ์ฐฝ์˜ ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜์—ฌ๋„ ๋ชจ๋‹ฌ์ฐฝ์€ ๊ทธ๋Œ€๋กœ ๋‚จ์•„์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ๋Š”๋ฐ ์ด๋Š” ์‚ฌ์šฉ์ž ํŽธ์˜์„ฑ์„ ๊ณ ๋ คํ•ด๋ณด์•˜์„ ๋•Œ ์•ฝ๊ฐ„์€ ๋ถˆํŽธํ•˜๋‹ค๊ณ  ์ƒ๊ฐ์ด ๋“ค ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ๋ชจ๋‹ฌ์ฐฝ์˜ ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜์˜€์„ ๋•Œ์—๋„ ๋ชจ๋‹ฌ์ฐฝ์ด ๋‹ซํž ์ˆ˜ ์žˆ๊ฒŒ useClickAway ๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ฝ”๋“œ๋ฅผ ๊ฐœ์„ ํ•ด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
import { useClickAway } from "react-use"; import { useRef } from "react"; import { useState } from "react"; const App = () => { const ref = useRef(null); useClickAway(ref, () => { if (onModal === "none") { return; } setOnModal("none"); }); const [onModal, setOnModal] = useState("none"); const Modal = () => { onModal ? setOnModal(false) : setOnModal("none"); }; return ( <> <button style={{ margin: 20 }} onClick={Modal}> ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์›Œ์ฃผ์„ธ์š”! </button> <div ref={ref} style={{ display: onModal, position: "absolute", top: 10, background: "pink", width: 200, }} > <p style={{ textAlign: "center", }} > ๋ชจ๋‹ฌ์ฐฝ์„ ๋‹ซ๊ฒ ์Šต๋‹ˆ๊นŒ? </p> <button onClick={Modal} style={{ width: 200 }}> ์˜ˆ </button> </div> </> ); }; export default App;
App.jsx
ย 
์•ž์„  ์˜ˆ์ œ๋ฅผ ์œ„์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•ด์ฃผ๋ฉด ๋ชจ๋‹ฌ์ฐฝ์˜ ์™ธ๋ถ€๋ฅผ ํด๋ฆญํ•˜์—ฌ๋„ ๋ชจ๋‹ฌ์ฐฝ์ด ๋‹ซํžˆ๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
์ด์ฒ˜๋Ÿผ useClickAway ๋ฅผ ํ™œ์šฉํ•˜๋ฉด ํŠน์ • UI์˜ ์™ธ๋ถ€ ์š”์†Œ๊ฐ€ ํด๋ฆญ ๋˜์—ˆ์„ ๋•Œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ณ  ํด๋ฆญ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ๋‹ค์–‘ํ•œ event๋ฅผ ์ ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 

16.6 ์ž์‹ ์ด ๋งŒ๋“  ์ปค์Šคํ…€ ํ›…์œผ๋กœ ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ฐฐํฌํ•˜๋Š” ๋ฐฉ๋ฒ•

ย 
์ง€๊ธˆ๊นŒ์ง€์™€ ๊ฐ™์ด ์šฐ๋ฆฌ๋Š” ๋ฐฐํฌ๋˜์–ด ์žˆ๋Š” ํ›… ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๊ฐ€์ ธ์™€์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ, 15์žฅ์—์„œ ์ง์ ‘ ๋งŒ๋“ค์–ด ๋ณธ โ€˜์ปค์Šคํ…€ ํ›…โ€™์„ npm์„ ํ†ตํ•ด ๋ฐฐํฌํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. 16.6์žฅ์—์„œ๋Š” 15.4.1์žฅ์—์„œ ๋งŒ๋“  useTitle ํ›…์„ ์ด์šฉํ•˜์—ฌ ์ปค์Šคํ…€ ํ›…์„ ๋ฐฐํฌํ•˜๋Š” ๊ณผ์ •์„ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.
ย 
์ปค์Šคํ…€ ํ›…์„ ๋ฐฐํฌํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” npm ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€(https://www.npmjs.com/)์— ๋“ฑ๋ก๋œ ๊ณ„์ •์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๊ณ„์ •์ด ์—†๋‹ค๋ฉด ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์˜ ๊ณ„์ • ๋“ฑ๋ก ํŽ˜์ด์ง€(https://www.npmjs.com/signup)๋ฅผ ํ†ตํ•ด ๊ณ„์ •์„ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm login
ย 
๊ณ„์ •์ด ์ƒ์„ฑ๋˜์—ˆ๋‹ค๋ฉด ์œ„์˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ„ฐ๋ฏธ๋„ ์ฐฝ์—์„œ npm์— ๋กœ๊ทธ์ธ ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-14๊ทธ๋ฆผ 16-14
๊ทธ๋ฆผ 16-14
ย 
๋กœ๊ทธ์ธ์ด ์™„๋ฃŒ๋˜์—ˆ๋‹ค๋ฉด ๋ฐฐํฌํ•˜๊ณ ์ž ํ•˜๋Š” ์ปค์Šคํ…€ ํ›…์ด ์กด์žฌํ•˜๋Š” ํด๋”๋กœ ์ด๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ useTitle ํ›…์€ src/useTitle/ ํด๋”์— ์กด์žฌํ•˜๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด ํ„ฐ๋ฏธ๋„์˜ ๋””๋ ‰ํ† ๋ฆฌ๋ฅผ ์ด๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ย 
cd src/useTitle
ย 
ํ„ฐ๋ฏธ๋„์˜ ํ˜„์žฌ ๋””๋ ‰ํ† ๋ฆฌ๊ฐ€ ์ปค์Šคํ…€ ํ›…์ด ์กด์žฌํ•˜๋Š” ํด๋”๋ฅผ ๊ฐ€๋ฆฌํ‚จ๋‹ค๋ฉด ์ด์ œ ๋ฐฐํฌ๋ฅผ ์œ„ํ•œ package.json ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜์—ฌ package.json ํŒŒ์ผ์„ ์ดˆ๊ธฐํ™”ํ•ฉ๋‹ˆ๋‹ค.
ย 
npm init
ย 
์œ„์˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด ์•„๋ž˜์˜ ๊ทธ๋ฆผ๊ณผ ๊ฐ™์ด ์ดˆ๊ธฐ๊ฐ’๋“ค์„ ์„ค์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-15๊ทธ๋ฆผ 16-15
๊ทธ๋ฆผ 16-15
ย 
์ด ์ค‘ ๋‹ค๋ฅธ ๊ฐ’๋“ค์€ ๊ธฐ๋ณธ ๊ฐ’์œผ๋กœ ์„ค์ •ํ•˜๊ฑฐ๋‚˜ ์„ค์ •ํ•˜์ง€ ์•Š์•„๋„ ๊ด€๊ณ„ ์—†์ง€๋งŒ name๊ณผ main(entry point)์˜ ๊ฐ’๋“ค์€ ๋ฐ˜๋“œ์‹œ ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
ย 
name์—๋Š” ๋ฐฐํฌํ•˜๊ณ ์ž ํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ด๋ฆ„์ด ๋“ค์–ด๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ถ”ํ›„ npm์„ ํ†ตํ•ด ๋ฐฐํฌ๋ฅผ ํ•  ๋•Œ ๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” ๋‹ค๋ฅธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ๊ฐ™์€ ์ด๋ฆ„์„ ๊ฐ€์ง€๊ณ  ์žˆ๋‹ค๋ฉด ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  main์—๋Š” ์ปค์Šคํ…€ ํ›…์ด ์ •์˜๋˜์–ด ์žˆ๋Š” ํŒŒ์ผ์˜ ์ด๋ฆ„์ด ๋“ค์–ด๊ฐ€์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์ถ”ํ›„ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ installํ•˜๊ณ , ์‚ฌ์šฉํ•  ๋•Œ ํ•ด๋‹น ํŒŒ์ผ์„ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋ฉ๋‹ˆ๋‹ค.
ย 
๊ธฐ๋ณธ์ ์ธ ๊ฐ’๋“ค์˜ ์„ค์ •์ด ๋๋‚ฌ์œผ๋ฉด ์ปค์Šคํ…€ ํ›…์˜ ์ข…์†์„ฑ์„ ์„ค์ •ํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋•Œ ๊ธฐ์กด์˜ Dependency๊ฐ€ ์•„๋‹Œ, ์ƒ์„ฑํ•˜๊ณ ์ž ํ•˜๋Š” ํŒจํ‚ค์ง€๊ฐ€ ๋‹ค๋ฅธ ํ˜ธ์ŠคํŠธ ํŒจํ‚ค์ง€์—์„œ ์‚ฌ์šฉ๋  ๋•Œ ํ•„์š”ํ•œ ์ข…์†์„ฑ์„ ์˜๋ฏธํ•˜๋Š” peerDependency๋ผ๋Š” ํ‚ค์— ์ปค์Šคํ…€ ํ›…์ด ์ข…์†๋˜๋Š” ๊ฒƒ๋“ค์„ ์ž…๋ ฅํ•ด์ฃผ์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ ํŒจํ‚ค์ง€์˜ ๊ฒฝ์šฐ, useEffect๋ฅผ ์‚ฌ์šฉํ•˜๋ฏ€๋กœ, ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฆฌ์•กํŠธ์— ๋Œ€ํ•œ ์ข…์†์„ฑ๋งŒ ์ž…๋ ฅํ•ด์ฃผ๋ฉด ๋ฉ๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-16๊ทธ๋ฆผ 16-16
๊ทธ๋ฆผ 16-16
ย 
package.json์˜ ์„ค์ •์ด ์™„๋ฃŒ ๋˜์—ˆ๋‹ค๋ฉด ์ด์ œ ๋ฐฐํฌ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ„ฐ๋ฏธ๋„์— ์•„๋ž˜์™€ ๊ฐ™์€ ๋ช…๋ น์–ด๋ฅผ ์ž…๋ ฅํ•˜๋ฉด npm์„ ํ†ตํ•ด ๋ฐฐํฌ๋ฅผ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm publish
ย 
๊ทธ๋ฆผ 16-17๊ทธ๋ฆผ 16-17
๊ทธ๋ฆผ 16-17
ย 
์ •์ƒ์ ์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ ๋˜์—ˆ๋‹ค๋ฉด npm ํ™ˆํŽ˜์ด์ง€์—์„œ ๋ฐฐํฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ํ˜„์žฌ ๋ฐฐํฌํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ์ด๋ฆ„์€ use-document-title-react์ด๋ฏ€๋กœ ์ด๋ฅผ npm ํ™ˆํŽ˜์ด์ง€์— ๊ฒ€์ƒ‰ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์ •์ƒ์ ์œผ๋กœ ๋ฐฐํฌ๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-18๊ทธ๋ฆผ 16-18
๊ทธ๋ฆผ 16-18
ย 
์ •์ƒ์ ์œผ๋กœ ๋ฐฐํฌ๊ฐ€ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š”์ง€ ํ™•์ธํ•ด ๋ณผ ํ•„์š”๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค. react ํ”„๋กœ์ ํŠธ์˜ ์ตœ์ƒ์œ„ ํด๋” ์ฆ‰, package.json ํŒŒ์ผ์ด ์กด์žฌํ•˜๋Š” ์œ„์น˜๋กœ ์ด๋™ํ•˜์—ฌ ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ง์ ‘ ์ œ์ž‘ํ•˜์—ฌ ๋ฐฐํฌํ•œ ์ปค์Šคํ…€ ํ›…์„ ์„ค์น˜ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
npm i use-document-title-react
ย 
ํ•ด๋‹น ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋ฉด package.json์— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ์„ค์น˜๋œ ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-19๊ทธ๋ฆผ 16-19
๊ทธ๋ฆผ 16-19
ย 
์—ฌ๊ธฐ๊นŒ์ง€ ์ง„ํ–‰๋˜์—ˆ๋‹ค๋ฉด ์ด์ œ ํ›…์„ ์‚ฌ์šฉํ•˜๊ณ ์ž ํ•˜๋Š” ๊ณณ์—์„œ ํ•ด๋‹น ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ import ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. use-document-title-react์˜ ๊ฒฝ์šฐ useTitle์„ return ํ•˜์˜€์œผ๋ฏ€๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด import ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-20๊ทธ๋ฆผ 16-20
๊ทธ๋ฆผ 16-20
ย 
์ด์ œ react๋ฅผ ์‹คํ–‰์‹œํ‚ค๋ฉด useTitle์ด ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
ย 
๊ทธ๋ฆผ 16-21๊ทธ๋ฆผ 16-21
๊ทธ๋ฆผ 16-21