ย
16.1 ํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ๋?16.2 React Hook Form16.2.1 React Hook Form์ด๋?16.2.2 React Hook Form ์ค์น๋ฐฉ๋ฒ16.2.3 React Hook Form ์ฌ์ฉํ์ฌ validation ํ๊ธฐ16.2.4 React Hook Form ์ฌ์ฉํ์ฌ ํ์๊ฐ์
validation ํ๊ธฐ16.3 react-router-dom์์ ์ง์ํ๋ Hook16.3.1 react-router-dom์ด๋?16.3.2 react-router-dom ์ค์น๋ฐฉ๋ฒ16.3.3 useLocation16.3.4 useNavigate16.3.5 useParams16.3.6 ์ฅ๋จ์ 16.4. React Query์์ ์ง์ํ๋ Hook16.4.1 React Query์ด๋?16.4.2 React Query ์ค์น๋ฐฉ๋ฒ16.4.3 React Query ์
ํ
๋ฐ ์ฌ์ฉ๋ฐฉ๋ฒ16.4.4 useQuery16.4.4 useMutation16.5. react-use16.5.1 react-use๋?16.5.2 react-use ์ค์น ๋ฐฉ๋ฒ16.5.3 useUpdateEffect 16.5.4 useDebounce16.5.5 useLocalStorage16.5.6 useClickAway16.6 ์์ ์ด ๋ง๋ ์ปค์คํ
ํ
์ผ๋ก ํ
๋ผ์ด๋ธ๋ฌ๋ฆฌ ๋ฐฐํฌํ๋ ๋ฐฉ๋ฒ
ย
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-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
ย
yarn add 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;
ย
ย
์ด๋ฒ์๋ validation ์กฐ๊ฑด์ ์ถ๊ฐํด ๋ณด๊ฒ ์ต๋๋ค. ๋ง์ฝ ์
๋ ฅ๋ฐ๋ ๋์ด๋ฅผ 20์ธ๋ถํฐ 99์ธ๊น์ง๋ก ์ ํํ๊ณ ์ถ๋ค๋ฉด, ์ด๋ฌํ ์กฐ๊ฑด์ย
register
์ ๋ ๋ฒ์งธ ์ธ์๋ก ์์ฑํ์ฌ ๋ฑ๋กํ๋ฉด ๋ฉ๋๋ค. ย
... <input {...register("age", { min: 20, max: 99 })} /> ...
ย
์กฐ๊ฑด์ ์ถฉ์กฑํ์ง ์๋ ์ซ์๋ฅผ ์
๋ ฅํ ๋ค ์ ์ถํ๋ ์ฝ์์ด ์ฐ์ง ์๋ ๊ฒ์ผ๋ก ๋ณด์ validation์ด ์ ์ผ์ด๋๊ณ ์๋ค๋ ๊ฒ์ ์ ์ ์์ต๋๋ค.
ย
ย
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;
ย
์ ์ฝ๋์ ๊ฐ์ด useForm์์ errors๋ฅผ ๋ถ๋ฌ์จ ๋ค, ํด๋น ํผ์์ ์๋ฌ๊ฐ ๋ฌ์ ๋ formState์ error ๊ฐ์ด ๋ค์ด ์ค๊ฒ ์ฝ๋๋ฅผ ์์ฑํ๋ฉด validation์ด ์คํจํ์ ๋ ์ฌ์ฉ์๊ฒ ๋ณด์ฌ์ค ์ค๋ฅ ๋ฉ์์ง๋ฅผ ์ค์ ํ ์ ์์ต๋๋ค.
ย
ย
ย
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> ); }
ย
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> ); }
ย
โต ์ด๋ฉ์ผ, ์ด๋ฆ, ๋น๋ฐ๋ฒํธ, ๋น๋ฐ๋ฒํธ ํ์ธ ์
๋ ฅ์ฐฝ์ ๊ฐ๊ฐ ๋ง๋ค์ด์ค๋๋ค. ํ์ ์
๋ ฅ ๊ฐ์ด๊ธฐ ๋๋ฌธ์ 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'
ย
ย
ย
์คํํ๋ฉด์ ์์ ๊ฐ์ต๋๋ค. ์
๋ ฅ๊ฐ์ด ์ ํจ์ฑ ๊ฒ์ฌ๋ฅผ ํต๊ณผํ์ง ๋ชปํ ๊ฒฝ์ฐ ์๋ฌ ๋ฉ์์ง๋ฅผ ์ถ๋ ฅํ๋ฉฐ, ์ ํจ์ฑ ๊ฒ์ฌ์ ํต๊ณผํ์ฌ ์ ์ถํ๋ฉด 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>
ย
๋จผ์ ์ฝ๋์์ ๋ณผ ์ ์๋ฏ์ด 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 ์์ ์ฝ๋๋ฅผ ํตํด ๊ฐ๋จํ๊ฒ ๋ผ์ฐํ
ํ๋ ๋ฒ์ ์์๋ณด์์ต๋๋ค. ์ง๊ธ๋ถํฐ๋ ๋ณธ๊ฒฉ์ ์ผ๋ก react-router-dom์ด ์ง์ํ๋ ๋ํ์ ์ธ Hook ๋ช ๊ฐ์ง๋ฅผ ์๊ฐํ๊ฒ ์ต๋๋ค.
ย
16.3.2 react-router-dom ์ค์น๋ฐฉ๋ฒ
ย
React ํ๋ก์ ํธ๋ฅผ ์์ฑํ๊ณ ํ๋ก์ ํธ ํด๋๋ก ์ด๋ํ ๋ค, npm ํน์ yarn ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ฌ react-router-dom ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ค์นํ ์ ์์ต๋๋ค.
ย
npm install react-router-dom
ย
yarn add 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;
ย
ย
์์ ์์ ์ฝ๋๋ฅผ npm start ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ฌ ์คํํ๊ณ URL์
/home
์ ๋ํ๋ฉด ์ฝ์์ฐฝ์์ useLocation์์ ๋ฐํ๋ location ๊ฐ์ฒด๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์๋์์ ์กฐ๊ธ ๋ ๊ตฌ์ฒด์ ์ธ location ๊ฐ์ฒด ์์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.ย
{ pathname: "/home", search: "?event=instagram", hash: "#product", state: null, key: "abcd12ef" }
ย
์์ 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", }, });
ย
const location = useLocation(); const userData = { ...location.state };
ย
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;
ย
๋ฒํผ์ ํด๋ฆญํ๋ ์ด๋ฒคํธ๊ฐ ๋ฐ์ํ๋ฉด
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;
ย
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์ ๋ฉ์ธ ์ปจ์
์ค ํ๋๋ React Router๊ฐ ์ ๊ณตํ๋ ๊ธฐ๋ฅ์ ํตํด ๊ธฐ๋ก ์คํ(history stack)์ ์กฐ์ํ๋ ๊ฒ์
๋๋ค.
useNavigate
ํ
์์๋ ๊ธฐ๋ก ์คํ์ ์กฐ์ํ ์ ์์ต๋๋ค.ย
- navigate ํจ์ ๋๋ฒ์งธ ์ธ์์
{ replace: true }
๋ฅผ ์ ๋ฌํ๋ฉด replace ์ก์ ์ด ์คํ๋์ด ๊ธฐ๋ก ์คํ์ ๊ฐ์ฅ ์๋จ ํ์ด์ง๋ฅผ ์ด๋ํ๋ ค๋ ํ์ด์ง๋ก ๊ต์ฒดํฉ๋๋ค.
- navigate ํจ์์ ์ซ์๋ฅผ ์ธ์๋ก ์ ๋ฌํ๋ฉด ํด๋น ์ซ์๋งํผ ์ /ํ์ ๋ฐฉ๋ฌธํ๋ ํ์ด์ง๋ก ์ด๋ํ๊ฒ ๋ฉ๋๋ค. ์๋ฅผ ๋ค์ด,
navigate(-1)
์ ์ง์ ์ ๋ฐฉ๋ฌธํ๋ ํ์ด์ง๋ก ์ด๋ํฉ๋๋ค.
ย
๊ธฐ๋ก ์คํ(history stack)์ด๋?
DOM์ window ๊ฐ์ฒด๋ history ๊ฐ์ฒด๋ฅผ ํตํด ๋ธ๋ผ์ฐ์ ์ ์ธ์
๊ธฐ๋ก์ ์ ๊ทผํ๋ ๋ค์ํ ๋ฐฉ๋ฒ์ ์ ๊ณตํฉ๋๋ค. history ๊ฐ์ฒด ๋ด
back()
, forward()
, go()
๋ฉ์๋๋ฅผ ์ฌ์ฉํด ๋ธ๋ผ์ฐ์ ๊ธฐ๋ก์ ์กฐ์ํ ์ ์์ต๋๋ค. ๋ธ๋ผ์ฐ์ ๊ธฐ๋ก์ ์ ์ผ ์ต๊ทผ์ ๋ฐฉ๋ฌธํ ํ์ด์ง๊ฐ ๊ธฐ๋ก ์ ์ผ ์๋จ์ ์๊ณ , ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๋๋ฅด๋ฉด ๋ฐ๋ก ์ด์ ํ์ด์ง๋ฅผ ๋ค์ ๋ฐฉ๋ฌธํ ์ ์๋ ์คํ์ ํํ๋ก ๊ตฌํ๋์ด ์์ต๋๋ค. ๊ธฐ๋ก ์คํ์ ํ ๋๋ก ์ฐ๋ฆฌ๋ ๋ธ๋ผ์ฐ์ ์์ ๋ค๋ก ๊ฐ๊ธฐ ๋ฒํผ์ ๊ณ์ ๋๋ฅด๊ณ ์์ผ๋ฉด ๋ฐฉ๋ฌธ ๊ธฐ๋ก(๊ทธ๋ฆผ 3-1)์ ํ์ธํ ์ ์์ต๋๋ค.ย
์ถ๊ฐ์ ์ผ๋ก, 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;
ย
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;
ย
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;
ย
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.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
ย
yarn add @tanstack/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> );
ย
์์ ๊ฐ์ด ์ปดํฌ๋ํธ ์ต์๋จ์์
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์ ๋ค์ด๊ฐ๋ ์ธ์ ๊ฐ
queryKey
: useQuery๋ง๋ค ๋ถ์ฌ๋๋ ๊ณ ์ Key ์ ๋๋ค. ๋จ์ ๋ฌธ์์ด์ ํ ์ ์๊ณ ๋ฐฐ์ด์ ํํ๋ก๋ ์ฌ์ฉ๋ ์ ์์ต๋๋ค.
queryFn
: ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ดํฐ๋ฅผ ์์ฒญํ๋ ๋ฐ ์ฌ์ฉํ ํจ์์ ๋๋ค. fetch๋ axios๋ฅผ ์ฌ์ฉํ์ฌ promise๋ฅผ ๋ฐํํ๋ ํจ์๊ฐ ์์ผํฉ๋๋ค.
options
: ์๋ต๊ฐ๋ฅํ ์ธ์๋ก ์ต์ ์ ์ถ๊ฐํ ๋ ์ฌ์ฉ๋ฉ๋๋ค. enable, retry ๋ฑ๋ฑ ๋ง์ ์ต์ ๋ค์ด ์์ต๋๋ค.
ย
์๋๋ ์ค์ ์ฌ์ฉ ์์์
๋๋ค.
export const fetchPosts = async () => { const res = await fetch("https://jsonplaceholder.typicode.com/posts"); const data = await res.json(); return data; };
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> ); }
ย
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์ ๋ค์ด๊ฐ๋ ์ธ์ ๊ฐ
mutationFn
: ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ณ ์ ๋ฐ์ดํธํ๊ณ ์ญ์ ํ๋๋ฐ ์ฌ์ฉํ ํจ์์ ๋๋ค. fetch๋ axios๋ฅผ ์ฌ์ฉํ์ฌ promise๋ฅผ ๋ฐํํ๋ ํจ์๊ฐ ์์ผํฉ๋๋ค.
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, }); };
ย
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> ); }
ย
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
ย
yarn add 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๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ํ๋ฉด์ด ์ฒ์ ๋ ๋๋ง ๋ ๋์๋ ํจ์๊ฐ ์คํ๋๊ธฐ ๋๋ฌธ์ 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
๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ํ๋ฉด์ด ์ฒ์ ๋ ๋๋ง ๋ ๋์๋ ์คํ์ด ๋์ง ์๊ธฐ ๋๋ฌธ์ console์ฐฝ์ ์๋ฌด๊ฒ๋ ์ถ๋ ฅ์ด ๋์ง ์๊ณ , ๊ฐ์ด update ๋๋ ๊ฒฝ์ฐ์๋ง console์ ๋ด์ฉ์ ์ถ๋ ฅํด ์ฃผ๋ ๊ฒ์ ํ์ธํด ๋ณผ ์ ์์ต๋๋ค.ย
16.5.4 useDebounce
ย
debounce๋ ํจ์๋ฅผ ์ฌ๋ฌ ๋ฒ ํธ์ถํ๊ณ ๋ง์ง๋ง ํธ์ถ์์ ์ผ์ ์๊ฐ์ด ์ง๋ ํ ํด๋น ํจ์์ ๊ธฐ๋ฅ์ด ๋์ํ๋ ๊ธฐ๋ฒ์ ๋งํฉ๋๋ค. ๊ฒ์์ฒ๋ผ ์
๋ ฅํ๋ ๊ธ์์ ๋ฐ๋ผ ๊ฒฐ๊ณผ๋ฅผ ์ถ๋ ฅํด ์ฃผ๊ณ ์ถ์ ๋, ์ฌ์ฉ์๊ฐ ๋จ์ด๋ฅผ ์
๋ ฅํ ๋๋ง๋ค API ์์ฒญ์ ๋ณด๋ธ๋ค๋ฉด ๋ถํ์ํ API ์์ฒญ์ด ์๊ธฐ๊ฒ ๋๊ณ , ์๋ฒ์ ๋ถํ๊ฐ ์๊ธธ ์ ์์ต๋๋ค. ์ด๋ด ๊ฒฝ์ฐ debounce ๊ธฐ๋ฒ์ ์ฌ์ฉํ๊ฒ ๋๋ค๋ฉด ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๊ฒ ๋ฉ๋๋ค.
ย
debounce๋ฅผ ๊ตฌํํ๋ ๋ฐฉ๋ฒ์๋ ์ปค์คํ
ํ
๋ง๋ค๊ธฐ์ ๊ฐ์ ๋ค์ํ ๋ฐฉ๋ฒ์ด ์์ง๋ง, react-use์
useDebounce
๋ฅผ ์ฌ์ฉํ๊ฒ ๋๋ฉด ๋ณด๋ค ์ฝ๊ฒ debounce์ ๊ตฌํ์ด ๊ฐ๋ฅํฉ๋๋ค.ย
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;
ย
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 ํ
์ฌ์ฉ ์์
ย
ย
์์ ์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์์ ๊ฐ์ด ์
ํ
์ด ๋๋๋ฐ
useLocalStorage
์ ๋ ๋ฒ์งธ ์ธ์์ โ๊ธฐ๋ณธ๊ฐโ ์ด๋ผ๊ณ ์์ฑํ๊ธฐ ๋๋ฌธ์ ์ด๊ธฐ์ LocalStorage์ ๊ฐ์ด โ๊ธฐ๋ณธ๊ฐโ์ผ๋ก ์ค์ ๋์ด ์๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ฒํผ์ ํด๋ฆญํ์ฌ value๋ฅผ ๋ณ๊ฒฝํด๋ณด๊ฒ ์ต๋๋ค.ย
ย
์์ ์ด๋ฏธ์ง์ ๊ฐ์ด LocalStorage์ ์๋ โ๊ธฐ๋ณธ๊ฐโ์ด ํฌ๋๊ฐ ๋ ๊ฑธ ํ์ธํ ์ ์์ผ๋ฉฐ ์ด์ ๋ฐ๋ผ ํ๋ฉด์ ๋ ๋๋ง ๋๋ โ๊ธฐ๋ณธ๊ฐโ์ด๋ผ๋ ํ
์คํธ ๋ํ โํฌ๋โ๋ก ๋ฐ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. LocalStorage์ ๊ฐ์ ์ญ์ ํ๋ ๋ฒํผ์ ํด๋ฆญํด๋ณด๊ฒ ์ต๋๋ค.
ย
ย
ย
์์ ๊ฐ์ด 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
ย
ย
์์ ๋ ์์ ์ด๋ฏธ์ง์ ๊ฐ์ด ๋ถํ๋ฐ์ค ๋ฐ๊นฅ ๋ถ๋ถ์ ํด๋ฆญํ๋ฉด ์ฝ์ ์ฐฝ์ ๋ฉ์์ง๊ฐ ์ถ๋ ฅ๋๊ณ ๋ถํ๋ฐ์ค๋ฅผ ํด๋ฆญํ๋ฉด ์๋ฌด ์ผ๋ ์ผ์ด๋์ง ์๋ ๊ฐ๋จํ ์์ ์
๋๋ค. ์ด๋ฅผ ํตํด ์ ์ ์๋ ๊ฒ์
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
ย
ย
ย
์์ ์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ๋ฉด ์์ ๊ฐ์ด ๋ชจ๋ฌ์ฐฝ์ ๋์ธ ์ ์๋ ๋ฒํผ์ด ์์ฑ๋ฉ๋๋ค. ๋ฒํผ์ ๋๋ฅด๊ฒ ๋๋ฉด ๋ชจ๋ฌ์ฐฝ์ด ํ์ฑํ๊ฐ ๋๋ฉฐ โ์โ๋ฅผ ๋๋ฅด๋ฉด ๋ชจ๋ฌ์ฐฝ์ด ๋ซํ๊ฒ ๋ฉ๋๋ค. ํ์ฌ์ ์ํ์์๋ ๋ชจ๋ฌ์ฐฝ์ ์ธ๋ถ๋ฅผ ํด๋ฆญํ์ฌ๋ ๋ชจ๋ฌ์ฐฝ์ ๊ทธ๋๋ก ๋จ์์๋ ๊ฒ์ ์ ์ ์๋๋ฐ ์ด๋ ์ฌ์ฉ์ ํธ์์ฑ์ ๊ณ ๋ คํด๋ณด์์ ๋ ์ฝ๊ฐ์ ๋ถํธํ๋ค๊ณ ์๊ฐ์ด ๋ค ์๋ ์์ต๋๋ค. ๋ฐ๋ผ์ ๋ชจ๋ฌ์ฐฝ์ ์ธ๋ถ๋ฅผ ํด๋ฆญํ์์ ๋์๋ ๋ชจ๋ฌ์ฐฝ์ด ๋ซํ ์ ์๊ฒ
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์ ๋ก๊ทธ์ธ ํ ์ ์์ต๋๋ค.
ย
ย
๋ก๊ทธ์ธ์ด ์๋ฃ๋์๋ค๋ฉด ๋ฐฐํฌํ๊ณ ์ ํ๋ ์ปค์คํ
ํ
์ด ์กด์ฌํ๋ ํด๋๋ก ์ด๋ํด์ผ ํฉ๋๋ค. ํ์ฌ useTitle ํ
์ src/useTitle/ ํด๋์ ์กด์ฌํ๋ฏ๋ก ์๋์ ๊ฐ์ ๋ช
๋ น์ด๋ฅผ ํตํด ํฐ๋ฏธ๋์ ๋๋ ํ ๋ฆฌ๋ฅผ ์ด๋ํด์ผ ํฉ๋๋ค.
ย
cd src/useTitle
ย
ํฐ๋ฏธ๋์ ํ์ฌ ๋๋ ํ ๋ฆฌ๊ฐ ์ปค์คํ
ํ
์ด ์กด์ฌํ๋ ํด๋๋ฅผ ๊ฐ๋ฆฌํจ๋ค๋ฉด ์ด์ ๋ฐฐํฌ๋ฅผ ์ํ package.json ํ์ผ์ ์์ฑํด์ผ ํฉ๋๋ค. ์๋์ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ์ฌ package.json ํ์ผ์ ์ด๊ธฐํํฉ๋๋ค.
ย
npm init
ย
์์ ๋ช
๋ น์ด๋ฅผ ์คํ์ํค๋ฉด ์๋์ ๊ทธ๋ฆผ๊ณผ ๊ฐ์ด ์ด๊ธฐ๊ฐ๋ค์ ์ค์ ํ ์ ์์ต๋๋ค.
ย
ย
์ด ์ค ๋ค๋ฅธ ๊ฐ๋ค์ ๊ธฐ๋ณธ ๊ฐ์ผ๋ก ์ค์ ํ๊ฑฐ๋ ์ค์ ํ์ง ์์๋ ๊ด๊ณ ์์ง๋ง name๊ณผ main(entry point)์ ๊ฐ๋ค์ ๋ฐ๋์ ์ค์ ํด์ฃผ์ด์ผ ํฉ๋๋ค.
ย
name์๋ ๋ฐฐํฌํ๊ณ ์ ํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ด๋ฆ์ด ๋ค์ด๊ฐ์ผ ํฉ๋๋ค. ์ถํ npm์ ํตํด ๋ฐฐํฌ๋ฅผ ํ ๋ ๊ธฐ์กด์ ์กด์ฌํ๋ ๋ค๋ฅธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ๊ฐ์ ์ด๋ฆ์ ๊ฐ์ง๊ณ ์๋ค๋ฉด ์๋ฌ๊ฐ ๋ฐ์ํ๊ฒ ๋ฉ๋๋ค. ๊ทธ๋ฆฌ๊ณ main์๋ ์ปค์คํ
ํ
์ด ์ ์๋์ด ์๋ ํ์ผ์ ์ด๋ฆ์ด ๋ค์ด๊ฐ์ผ ํฉ๋๋ค. ์ด๋ฅผ ํตํด ์ถํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ installํ๊ณ , ์ฌ์ฉํ ๋ ํด๋น ํ์ผ์ ์ฐธ์กฐํ ์ ์๊ฒ ๋ฉ๋๋ค.
ย
๊ธฐ๋ณธ์ ์ธ ๊ฐ๋ค์ ์ค์ ์ด ๋๋ฌ์ผ๋ฉด ์ปค์คํ
ํ
์ ์ข
์์ฑ์ ์ค์ ํด์ฃผ์ด์ผ ํฉ๋๋ค. ์ด๋ ๊ธฐ์กด์ Dependency๊ฐ ์๋, ์์ฑํ๊ณ ์ ํ๋ ํจํค์ง๊ฐ ๋ค๋ฅธ ํธ์คํธ ํจํค์ง์์ ์ฌ์ฉ๋ ๋ ํ์ํ ์ข
์์ฑ์ ์๋ฏธํ๋ peerDependency๋ผ๋ ํค์ ์ปค์คํ
ํ
์ด ์ข
์๋๋ ๊ฒ๋ค์ ์
๋ ฅํด์ฃผ์ด์ผ ํฉ๋๋ค. ํ์ฌ ํจํค์ง์ ๊ฒฝ์ฐ, useEffect๋ฅผ ์ฌ์ฉํ๋ฏ๋ก, ์๋์ ๊ฐ์ด ๋ฆฌ์กํธ์ ๋ํ ์ข
์์ฑ๋ง ์
๋ ฅํด์ฃผ๋ฉด ๋ฉ๋๋ค.
ย
ย
package.json์ ์ค์ ์ด ์๋ฃ ๋์๋ค๋ฉด ์ด์ ๋ฐฐํฌ๋ฅผ ์์ํ ์ ์์ต๋๋ค. ํฐ๋ฏธ๋์ ์๋์ ๊ฐ์ ๋ช
๋ น์ด๋ฅผ ์
๋ ฅํ๋ฉด npm์ ํตํด ๋ฐฐํฌ๋ฅผ ์์ํ ์ ์์ต๋๋ค.
ย
npm publish
ย
ย
์ ์์ ์ผ๋ก ๋ฐฐํฌ๊ฐ ์๋ฃ ๋์๋ค๋ฉด npm ํํ์ด์ง์์ ๋ฐฐํฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ฐพ์๋ณผ ์ ์์ต๋๋ค. ํ์ฌ ๋ฐฐํฌํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ด๋ฆ์ use-document-title-react์ด๋ฏ๋ก ์ด๋ฅผ npm ํํ์ด์ง์ ๊ฒ์ํ๋ฉด ์๋์ ๊ฐ์ด ์ ์์ ์ผ๋ก ๋ฐฐํฌ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ย
ย
์ ์์ ์ผ๋ก ๋ฐฐํฌ๊ฐ ์๋ฃ๋์์ผ๋ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ์์ ์ผ๋ก ๋์ํ๋์ง ํ์ธํด ๋ณผ ํ์๊ฐ ์์ต๋๋ค. react ํ๋ก์ ํธ์ ์ต์์ ํด๋ ์ฆ, package.json ํ์ผ์ด ์กด์ฌํ๋ ์์น๋ก ์ด๋ํ์ฌ ์๋์ ๋ช
๋ น์ด๋ฅผ ์คํํ๋ฉด ์ง์ ์ ์ํ์ฌ ๋ฐฐํฌํ ์ปค์คํ
ํ
์ ์ค์นํ ์ ์์ต๋๋ค.
ย
npm i use-document-title-react
ย
ํด๋น ๋ช
๋ น์ด๋ฅผ ์คํํ๋ฉด package.json์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ์ ์์ ์ผ๋ก ์ค์น๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ย
ย
์ฌ๊ธฐ๊น์ง ์งํ๋์๋ค๋ฉด ์ด์ ํ
์ ์ฌ์ฉํ๊ณ ์ ํ๋ ๊ณณ์์ ํด๋น ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ import ํ ์ ์์ต๋๋ค. use-document-title-react์ ๊ฒฝ์ฐ useTitle์ return ํ์์ผ๋ฏ๋ก ์๋์ ๊ฐ์ด import ํ ์ ์์ต๋๋ค.
ย
ย
์ด์ react๋ฅผ ์คํ์ํค๋ฉด useTitle์ด ์ ์์ ์ผ๋ก ๋์ํ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค.
ย