React hooks ๐
์ฑ๋ฅ ์ต์ ํ hooks
useDefferedValue โจ
- UI ์๋ต์ฑ์ ์ํด ์ฐ์ ์์ ๋ฎ์ ์
๋ฐ์ดํธ๋ฅผ ๋ฆ์ถค
- Que๊ฐ ๋น๋๊น์ง ๋ฐ๋์ง X
debounce์๋ ๋ค๋ฆ- debounce : ์๊ฐ ๊ธฐ๋ฐ
- deferred : React scheduling ๊ธฐ๋ฐ
- e.g. ๊ฒ์์ฐฝ ์ ๋ ฅ์ ๋ฐ๋ก ๋ฐ์ but ๋ฆฌ์คํธ๋ ์ฒ์ฒํ ์ ๋ฐ์ดํธ ํ๋ ํจํด `
useOptimistic โจ
- ์๋ฒ ์๋ต ๊ธฐ๋ค๋ฆฌ๊ธฐ ์ ์ UI ๋จผ์ ๊ฐฑ์ (optimistic UI)
- ์๋ฒ์ ๊ฐ์ ๋ฏธ๋ฆฌ ๋ณด๋ด๊ธฐ ์ ์ ๊ฐ์ ๋จผ์ ๋ณด์ฌ์ฃผ๋ ๊ฒ
- ์คํจ์ rollback ํ์
- e.g. ์ข์์
- ๐ Server action + optimistic UI ์กฐํฉ
useCallback / useMemo
- React 19๋ compiler ์๋์ต์ ํ๋ก ์ง์ ์ฌ์ฉX
- but, โ์ฐธ์กฐ ๋์ผ์ฑ ์ ์งโ๊ฐ ํ์ํ ๊ฒฝ์ฐ์ ์์๋ฌ์ผํจ
DOM & Effect ๊ด๋ จ hooks
useLayoutEffect
- DOM์ด โcommit๋ ์งํโ ์คํ โ ๋ ์ด์์ ๊ณ์ฐ/ ์ธก์ ํ ๋ ์ฌ์ฉ
- ๋๊ธฐ ์คํ โ paint๋๊ธฐ ์ ์ ์คํ๋จ : ์ฑ๋ฅ์ํฅ O
useEffect
- Runs after React has rendered the component to the DOM
- after rendering(paint ์ดํ ์คํ(๋น๋๊ธฐ))
- state ๊ฐ ๊ทธ ์์ฒด๋ฅผ ๊ด๋ฆฌํ๋๊ฒ X โ ๋ ๋๋ง์ ๋ฐ๋ฅธ
side effect๋ฅผ ์ฒ๋ฆฌํ๋๋ฐ ์ฌ์ฉ- e.g. data fetch, event listener ๋ฑ๋ก, DOM ์กฐ์
[]deps : dependency array- no array: run after every render
- life cycle ๊ด๋ฆฌ์ ์ฌ์ฉ
- ๋ผ์ดํ์ฌ์ดํด์ ์ด์ฉํด ํน์ ์์ ์ ํ ์ ์์
import {useState, useEffect} from 'react';
function TitleUpdater(){
const [count, setCount] = useState(0);
// whenever state is changed, update browser tab's title(side effect)
useEffect(()=>{
document.title = `current count : ${count}`;
}, [count]); // only when count is changed
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
</div>
);
}Ref & Imperative Handle ๊ด๋ จ hooks
useRef
ref.current์ฌ์ฉ
useImperativeHandle โจโจ
- ๋ถ๋ชจ๊ฐ ์์์ control
- ๊ฐ์
Ref์ ๋ด์์ ๋ณด๋ด๋ฉดref.current๋ก ์ฌ์ฉ ๐ ๊ณต๋ถ ํฌ์ธํธ: ์ธ์ ์ฌ์ฉํ๋์ง ํ์คํ ์ ๋ฆฌํด๋๊ธฐ- Modal, Input, Scroll
์ํ๊ด๋ฆฌ ๊ด๋ จ hooks
useState
const [state, setState] = useState(initVal);- useStae returns a list with state, a function to update the state
- React ๋ด๋ถ์์ Queue ์๋ฃ๊ตฌ์กฐ๋ก ์ ๋ฐ์ดํธ
- throttle ๋๋ ์ฌ์ด์ return ๊ฐ์ ๋ฏธ๋ฆฌ ์ ์ฅํด๋ ๐ ๊ณต๋ถ ํฌ์ธํธ: throttle/debounce ๊ฐ์ ๊ฐ๋ ๊ณผ ์ฐจ์ด๋ ์์๋๊ธฐ
useSession
- ํด๋ผ์ด์ธํธ ์ปดํฌ๋ํธ์์ ํ์ฌ ๋ก๊ทธ์ธ ์ํ๋ฅผ ๊ฐ์ ธ์ค๋ ํ
- return
{data: session, status}session: ๋ก๊ทธ์ธ๋ ์ฌ์ฉ์ ์ ๋ณด(์์ผ๋ฉด null)status:"loading" | "authenticated" | "unauthenticated"โ ๋ก๊ทธ์ธ ๋ฒํผ, ํ๋กํ ํ์, ๋ก๊ทธ์์ ๋ฒํผ ๊ฐ์ UI์์๋ ์ฟ ํค๋ฅผ ์ง์ ๋ค๋ฃจ์ง ์๊ณ , ์ธ์ ์ํ๋ง ํ์ธํ๋ฉด ๋จ
SessionProvider๋ session์ ๋ฆฌ์กํธ state๋ก ์ ์ญ์ ์ ๊ณตํจ,useSession์ ๊ฐ๋ณ ์ปดํฌ๋ํธ์์ ๊ทธ ์ํ(ํ์ฌ ๋ก๊ทธ์ธ ์ ๋ณด, ์ฌ์ฉ์ ์ ๋ณด)๋ฅผ ์ฝ์ด์ฌ์ ์๊ฒ ํจ โ ๊ฐ๋ฐ์๋ ์ฟ ํค๋ฅผ ์ง์ ๋ค๋ฃจ์ง ์๊ณ ์ธ์ฆ ์ํ๋ฅผ ๊ด๋ฆฌํ ์ ์๊ฒ ๋จ
SessionProvider
๋ฆฌ์กํธ์ Context Provider(API) ์ ๊ฐ์ ์ญํ
- ์ฑ์ ์ต์์(
layout.tsxor_app.tsx)์ ๋ฃ์ด์ ๋ชจ๋ ์ปดํฌ๋ํธ์์ ์ธ์ ์ ๋ณด๋ฅผ ์ธ์ ์๊ฒ ํจ- e.g.
useSession()
- e.g.
- ์ฟ ํค์ ์ ์ฅ๋ session์ ์ฝ์ด์์ ๋ฆฌ์กํธ state๋ก ๊ด๋ฆฌ
useActionState โจโจโจ
useState+useTransitionisPendingํ์ํ ๊ฒฝ์ฐ- form์ ์ถ, ์๋ฒ mutation ๊ฐ์ ๊ณณ์์ ์ ์ฉ
๐ ๊ณต๋ถ ํฌ์ธํธ: ์๋ฒ ์ก์
(Form) + optimistic UI +
useActionState์กฐํฉ- form ์ฒ๋ฆฌ ํ๋ฆ
useFormStatus
- button์ด ์ํด์๋ form ์ํ๋ฅผ ์ฝ์
isPending์ํ๋ฅผ ์ฝ๊ฒ ํ์ธ ๊ฐ๋ฅ ๐ ๊ณต๋ถ ํฌ์ธํธ: ์ฌ๋ฌ ๋ฒํผ์ด ์๋ Form์์ ๋ฒํผ๋ณ ์ํ ์ด๋ป๊ฒ ๋ค๋ค์ง๋์ง
useReducer
- ์ค์ ์ํ ๊ด๋ฆฌ ๐ ๊ณต๋ถ ํฌ์ธํธ: Todo App์ useReducer + Context API๋ง์ผ๋ก ๊ตฌํํด๋ณด๊ธฐ
context API โจโจโจ
- โprops drilling(์ค์ฒฉ๋ props ์ ๋ฌ)โ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ์ฌ์ฉ
- component ์ธ๋ถ ๊ฐ์ ์ ๊ทผ
- ๊ตฌ์ฑ ์์
React.createContext(defaultValue): Context ๊ฐ์ฒด ์์ฑProvider: ๊ฐ์ ๊ณต๊ธ(์ค์ )ํ๋ ์ปดํฌ๋ํธConsumer(oruseContext) : ๊ฐ์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ / HookuseContext(Context๊ฐ์ฒด)
- ์ ์ญ์ผ๋ก ๊ณต์ ๋๋ ๊ฐ์ด ํ์ํ ๋ ์ฌ์ฉ
- theme(light/dark)
- language(i18n)
- login data(Auth)
- global state
// context.ts
import {createContext} from "react";
export const ThemeContext = createContext("light");
// --------------------------------------------------
// Child.tsx
import {useContext} from "react";
import {ThemeContext} from "./context";
export default function Child() {
const theme = useContext(ThemeContext);
return <div>Current Theme : {theme}</div>;
}
// --------------------------------------------------
// App.tsx
import {ThemeContext} from "./context";
import Child from "./Child";
export default function App() {
return (
<ThemeContext.Provider value="dark">
<Child />
</ThemeContext.Provider>
);
}useReducer + contextAPI ๋ก ์ํ๊ด๋ฆฌ
useReducer๋ก logic(์ํ ์ ํ) ์ ์ โ Context๋ก ์ ์ญ ๊ณต์- Redux ๋์ฒด - ๋ฌด๊ฑฐ์์ ์ฌ์ฉX
import {createContext, useReducer, useContext, ReactNode} from "react";
// 1. Reducer
type State = {count: number};
type Action = {type: "inc" | "dec"};
function reducer(state: State, action: Action): State {
switch (action.type) {
case "inc" : return {count: state.count + 1};
case "dec" : return {count: state.count - 1};
default : return state;
}
}
// 2. Context
const CounterContext = createContext<
{state: State; dispatch: React.Dispatch<Action>} | undefined
>(undefined);
// 3. Provider
export function CounterProvider({children}:{children:ReactNode}){
const [state, dispatch] = useReducer(reducer, {count : 0});
return (
<CounterContext.Provider value=({state, dispatch})>
{children}
</CounterContext.Provider>
);
}
// 4. use custon Hook
export function useCoutner(){
const context = useContext(CounterContext);
if (!context) throw new Error("useCounter should be used INSIDE Provider");
return context;
}
// ------------------------------
function CounterButton(){
const {state, dispatch} = useCounter();
return (
<div>
<p>Count : {state.count}</p>
<button onClick={()=> dispatch({type:"inc"})}>+</button>
<button onClick={()=> dispatch({type:"desc"})}>-</button>
</div>
)
}
๐ก onclick eventlistener์์ ์คํํ ํจ์๋ ํ์ดํํจ์๋ก ๊ฐ์ธ์ ์ ๋ฌํ๊ธฐ!
onClick={() => onUpdate(todo.id)}onClick={onUpdate(todo.id)}์ด๋ฐ์์ผ๋ก ๋๊ธฐ๋ฉด X- ์ด๊ฑด onClick์ด ๋ฐ์ํ๊ธฐ๋ ์ ์
onUpdate๋ฅผ ์คํ์ํค๊ณ ๊ทธ ๊ฒฐ๊ณผ๊ฐ์ ๋ด์๋๋ ๊ฒ!
- ์ด๊ฑด onClick์ด ๋ฐ์ํ๊ธฐ๋ ์ ์