devlog study react hooks

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.tsx or _app.tsx)์— ๋„ฃ์–ด์„œ ๋ชจ๋“  ์ปดํฌ๋„ŒํŠธ์—์„œ ์„ธ์…˜ ์ •๋ณด๋ฅผ ์“ธ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
    • e.g.useSession()
  • ์ฟ ํ‚ค์— ์ €์žฅ๋œ session์„ ์ฝ์–ด์™€์„œ ๋ฆฌ์•กํŠธ state๋กœ ๊ด€๋ฆฌ

useActionState โœจโœจโœจ

  • useState + useTransition
  • isPending ํ•„์š”ํ•œ ๊ฒฝ์šฐ
  • 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 (or useContext) : ๊ฐ’์„ ์‚ฌ์šฉํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ / Hook
      • useContext(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๋ฅผ ์‹คํ–‰์‹œํ‚ค๊ณ  ๊ทธ ๊ฒฐ๊ณผ๊ฐ’์„ ๋‹ด์•„๋‘๋Š” ๊ฒƒ!