๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ

react

React / ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ (1) - ๋‚ด๋ถ€

 

React๋Š” ๋Œ€ํ‘œ์ ์ธ SPA(Single Page Application) ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋‹ค.
SPA๋Š” ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ์ด๋ฅผ ๊ฐ์ง€ํ•ด ํ•„์š”ํ•œ ๋ถ€๋ถ„๋งŒ ๋ฆฌ๋ Œ๋”๋ง ํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์—, ์ƒํƒœ ๊ด€๋ฆฌ๊ฐ€ ๋งค์šฐ ์ค‘์š”ํ•˜๋‹ค.

 

React์—์„œ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ๋Š” ํฌ๊ฒŒ ๋‘ ๊ฐ€์ง€๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค.

๋‚ด๋ถ€ ์™ธ๋ถ€
useState
ContextAPI
Redux, Redux Toolkit
Recoil, MobX, Zustand, Jotai ...

 

 

 


 

 

 

์ „์—ญ ์ƒํƒœ๊ด€๋ฆฌ์˜ ์žฅ์ 

Props Drilling

 

์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์—์„œ ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์—๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ „๋‹ฌ ํ•  ๋•Œ, ์ง์ ‘์ ์œผ๋กœ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š” ์ค‘๊ฐ„ ์ปดํฌ๋„ŒํŠธ๋“ค๊นŒ์ง€ props๋ฅผ ๊ณ„์† ๋„˜๊ฒจ์ค˜์•ผ ํ•˜๋Š” ํ˜„์ƒ์„ ์˜๋ฏธํ•œ๋‹ค.

 

์ด์ „์— ํ–ˆ๋˜ todo list ๋งŒ๋“ค๊ธฐ์—๋„ props drilling์„ ์ฐพ์•„๋ณผ ์ˆ˜ ์žˆ๋‹ค. (๊ธธ์ด๊ฐ€ ๊ธธ์–ด์ ธ์„œ ์ƒํƒœ ์™ธ ๋‚ด์šฉ์€ ์ƒ๋žตํ–ˆ๋‹ค..)

 

function App() {
  const [isLoading, data] = useFetch('http://localhost:3000/todo');
  const [todo, setTodo] = useState([]);

  useEffect(() => {
    if (data) setTodo(data);
  }, [isLoading]);

  return (
    <>
      <TodoInput setTodo={setTodo} />
      <TodoList todo={todo} setTodo={setTodo} />
    </>
  );
}

const TodoInput = ({ setTodo }) => {
  const inputRef = useRef(null);
  const addTodo = () => {
    const newTodo = {
      // ์ƒ๋žต
  };
  return (
    <div className="todo-input">
      <input ref={inputRef} />
      <button onClick={addTodo}>์ถ”๊ฐ€</button>
    </div>
  );
};

const TodoList = ({ todo, setTodo }) => {
  return (
    <ul className="todo-list">
      {todo.map((el) => (
        <Todo key={el.id} todo={el} setTodo={setTodo} />
      ))}
    </ul>
  );
};

// UPDATE, DELETE
const Todo = ({ todo, setTodo }) => {
  const [isEdit, setIsEdit] = useState(false);
  const [editValue, setEditValue] = useState(todo.content);

  // ๋„ˆ๋ฌด ๊ธธ์–ด์„œ ์ƒ๋žต..............

  return (
    <li>
      {isEdit ? (
        <>
          <input value={editValue}
            onChange={(e) => setEditValue(e.target.value)} />
          <div className="btn-container">
            <button onClick={updateTodo}>์ €์žฅ</button>
            <button onClick={() => setIsEdit(false)}>์ทจ์†Œ</button>
          </div>
        </> ) : (
        <>
          <div>{todo.content}</div>
          <div className="btn-container">
            <button onClick={editHandler}>์ˆ˜์ •</button>
            <button onClick={deleteTodo}>์‚ญ์ œ</button>
          </div>
        </>
      )}
    </li>
  );
};

 

 

๊ธฐ์กด ๋ฐฉ์‹

 

์ฝ”๋“œ๋ฅผ ๋ณด๋ฉด const [todo, setTodo] = useState([])๋ฅผ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š” ๊ณณ์ด Todo์™€ TodoInput ์ปดํฌ๋„ŒํŠธ์ธ๋ฐ, ๋‘ ์ปดํฌ๋„ŒํŠธ ๋ชจ๋‘์—๊ฒŒ ์ƒํƒœ๋ฅผ ๋„˜๊ฒจ์ฃผ๊ธฐ ์œ„์—์„œ๋Š” ์ตœ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ์ธ App์—์„œ ์ƒํƒœ๋ฅผ ์ƒ์„ฑํ•ด props๋ฅผ ๋„˜๊ฒจ์ค„ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.

์ด๋•Œ TodoList ์ปดํฌ๋„ŒํŠธ์—์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์žˆ์–ด๋„ Todo ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•ด์•ผ ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์˜ค์ง ์ „๋‹ฌ ๋ชฉ์ ์œผ๋กœ props๋ฅผ ๋„˜๊ฒจ์ฃผ๊ณ  ์žˆ๋Š”๋ฐ, ๋“œ๋ฆด์งˆ ํ•˜๋Š” ๊ฒƒ์ฒ˜๋Ÿผ Props๋ฅผ ๊ณ„์† ๋‚ด๋ ค์ค€๋‹ค๊ณ  ํ•˜์—ฌ Props Drilling ํ˜„์ƒ์ด๋ผ๊ณ  ํ•œ๋‹ค.

 

 

์ง€๊ธˆ์˜ ๊ฒฝ์šฐ๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ 3๋‹จ๊ณ„ ๊นŠ์ด์˜ ๊ณ„์ธต์ด๊ณ , ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐœ์ˆ˜๋„ ์ ์–ด์„œ ์•„์ง๊นŒ์ง„ ํฌ๊ฒŒ ๋ฌธ์ œ๊ฐ€ ์—†์ง€๋งŒ ๋งŒ์•ฝ ๊ทธ ๊นŠ์ด๊ฐ€ ๋” ๊นŠ์–ด์ง€๊ณ  ์ปดํฌ๋„ŒํŠธ์˜ ๊ฐœ์ˆ˜๊ฐ€ ๋Š˜์–ด๋‚˜๊ณ  ์ƒํƒœ๋“ค์ด ๋Š˜์–ด๋‚œ๋‹ค๋ฉด ์—ฌ๋Ÿฌ ๋ฌธ์ œ์ ์ด ๋ฐœ์ƒํ•œ๋‹ค.

  • ์ปดํฌ๋„ŒํŠธ์˜ ๊ตฌ์กฐ๊ฐ€ ๋” ๋ณต์žกํ•ด์ง
  • ์ฝ”๋“œ์˜ ๊ฐ€๋…์„ฑ์ด ๋งค์šฐ ๋–จ์–ด์ง€๊ณ  ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ง
  • props๋ฅผ ์ „๋‹ฌํ•˜๋Š” ๊ณผ์ •์—์„œ ์ค‘๊ฐ„์— ์ „๋‹ฌ ๋ชฉ์ ์œผ๋กœ๋งŒ ๊ด€์—ฌํ•˜๊ฒŒ ๋œ ์ปดํฌ๋„ŒํŠธ๋“ค๊นŒ์ง€ ๋ฆฌ๋ Œ๋”๋ง์ด ๋ฐœ์ƒ => ์„ฑ๋Šฅ ์ €ํ•˜

 

useState๋กœ๋งŒ ์ƒํƒœ๋ฅผ ๊ด€๋ฆฌํ•˜๋ฉด ์•„๋ฌด๋ฆฌ ์ฒด๊ณ„์ ์ธ ์„ค๊ณ„์—ฌ๋„ ๊ทœ๋ชจ๊ฐ€ ์ปค์ง€๋ฉด ์ด๋Ÿฌํ•œ ๋ถ€์ž‘์šฉ์ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค.

๋•Œ๋ฌธ์— ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด ์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ๋ฅผ ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋„๊ตฌ๊ฐ€ ํ•„์š”ํ•˜๋‹ค

 

 

์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ–ˆ์„ ๋•Œ

 

 

์ „์—ญ ์ƒํƒœ ๊ด€๋ฆฌ ๋„๊ตฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด App์—์„œ ์ง์ ‘ ์ƒํƒœ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š์•„๋„ ๋˜๋ฉฐ, ์ค‘๊ฐ„์— ๊ณ ํ†ต๋ฐ›๋˜ TodoList๋„ props ์ „๋‹ฌ์„ ํ•˜์ง€ ์•Š์•„๋„ ๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค. ๋•Œ๋ฌธ์— ๊ทœ๋ชจ๊ฐ€ ํฐ ํ”„๋กœ์ ํŠธ์ผ์ˆ˜๋ก ์ด ๋ฐฉ๋ฒ•์œผ๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

 

 

 


 

 

 

Context API

 

๋ฆฌ์•กํŠธ์— ๋‚ด์žฅ๋œ API๋กœ ์ „์—ญ์ ์œผ๋กœ ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•  ๋•Œ ์‚ฌ์šฉ๋œ๋‹ค. 

๊ณต์‹๋ฌธ์„œ : https://react.dev/learn/passing-data-deeply-with-context

 

 

Context API๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ 

  • props drilling ๋ฌธ์ œ ํ•ด๊ฒฐ (props๋ฅผ ์ผ์ผ์ด ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๊ฐ’ ๊ณต์œ ๊ฐ€ ๊ฐ€๋Šฅํ•ด์ง)
  • ์ƒํƒœ๋ฅผ ํ•„์š”ํ•œ ๊ณณ์—์„œ ๋ฐ”๋กœ ๊บผ๋‚ด ์‚ฌ์šฉ => ์œ ์ง€ ๋ณด์ˆ˜๊ฐ€ ์‰ฌ์›Œ์ง€๋ฉฐ ํ”„๋กœ์ ํŠธ๊ฐ€ ์ปค์งˆ ์ˆ˜๋ก ์ฝ”๋“œ๊ฐ€ ๊ฐ„๊ฒฐํ•ด์ง

=> context๋Š” props๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ ๋„ ํ•„์š”ํ•œ ์ปดํฌ๋„ŒํŠธ์—์„œ ๋ฐ”๋กœ ๊บผ๋‚ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์–ด ์‚ฌ์šฉ๋œ๋‹ค!

 

 

์‚ฌ์šฉ ๋ฐฉ๋ฒ•

์•„๋ž˜ ์„ธ๊ฐœ๋Š” React์—์„œ ๊ธฐ๋ณธ์œผ๋กœ ์ œ๊ณต๋˜๋Š” ๋‚ด์žฅ ๊ฐ์ฒด/ํ•จ์ˆ˜๋“ค์ด๋‹ค. 

  • createContext() - ์ „์—ญ ์ƒํƒœ ์ƒ์„ฑ, React ๋‚ด์žฅ ํ•จ์ˆ˜
  • Provider - ์ „์—ญ ์ƒํƒœ ์—ฐ๊ฒฐ, createContext๋กœ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์˜ ์†์„ฑ
  • useContext() - context ๊ฐ์ฒด์—์„œ ์ „์—ญ ์ƒํƒœ๋ฅผ ๊บผ๋‚ด์˜ด, React ๋‚ด์žฅ Hook

 

 


 

 

์˜ˆ์‹œ

 

ํ‰๋ฒ”ํ•œ ์นด์šดํ„ฐ ๊ธฐ๋Šฅ์˜ ์ฝ”๋“œ๋‹ค.

์›๋ž˜๋Š” ๊ทธ๋ƒฅ app.jsx์—์„œ useState ๋ถˆ๋Ÿฌ์™€์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๋Š”๋ฐ

context API๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์ผ๋‹จ ๋”ฐ๋กœ context ํŒŒ์ผ์„ ํ•˜๋‚˜ ๋งŒ๋“ค์–ด์ค€๋‹ค.

(๋ฌด์กฐ๊ฑด ์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์•ผํ•˜๋Š” ๊ฑด ์•„๋‹ˆ์ง€๋งŒ ๊ถŒ์žฅ๋˜๋Š” ๋ฐฉ์‹์ด๋ผ๊ณ  ํ•œ๋‹ค.

src/
โ”œโ”€โ”€ context/
 โ”‚         โ””โ”€โ”€ counterContext.jsx

 

 

 

counterContext.jsx

import { useContext, createContext, useState } from 'react';

// ์ „์—ญ context ๊ฐ์ฒด ์ƒ์„ฑ
const CounterContext = createContext();

// Provider ์ปดํฌ๋„ŒํŠธ
export function CounterProvider({ children }) {
  // ์ƒํƒœ ์ƒ์„ฑ
  const [Counter, setCounter] = useState(0);
  return (
    // context ๊ฐ์ฒด์—๊ฒŒ counter์™€ setCounter๋ฅผ ์ „๋‹ฌ
    <counterContext.Provider value={[Counter, setCounter]}>
      // ์ด Provider ์•ˆ์— ๊ฐ์‹ธ์ง„ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์˜๋ฏธํ•จ
      {children}
    </counterContext.Provider>
  );
}

// useCounter() : ์ปค์Šคํ…€ํ›…
// ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‰ฝ๊ฒŒ useContext๋ฅผ ์“ธ ์ˆ˜ ์žˆ๊ฒŒ ํ•จ
export function useCounter() {
  return useContext(CounterContext);
}

 

 

  • createContext()๋กœ ์ƒˆ context ๊ฐ์ฒด CounterContext ์ƒ์„ฑ
  • CounterProvider : Context์˜ Provider ์—ญํ• ์„ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ
    • CounterContext.Provider : context๋ฅผ ์ œ๊ณตํ•˜๊ณ , value๋กœ counter, setCounter๋ฅผ ๋„ฃ์Œ
    • CounterContext๋กœ {children}์„ ๊ฐ์‹ธ ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค์ด ๋ชจ๋‘ context ๊ฐ์ฒด์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋„๋ก ํ•จ
  • useCounter๋ผ๋Š” ์ปค์Šคํ…€ ํ›…์„ ์ƒ์„ฑ
    • useContext(counterContext)๋กœ [counter, setCounter]๋ฅผ ๋ฐ”๋กœ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•จ 

 

 

Main.jsx

createRoot(document.getElementById('root')).render(
  <CounterProvider>
    <App />
  </CounterProvider>,
);

 

App์„ CounterProvider๋กœ ๊ฐ์‹ธ App ๋‚ด๋ถ€์—์„œ ์ „์—ญ ์ƒํƒœ๋ฅผ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•จ (App์ด children ์—ญํ• )

์ด ๋•Œ CounterProvider๋Š” ๋‹น์—ฐํžˆ ์œ„์—์„œ import ํ•ด์•ผ๋จ..

 

 

App.jsx

import './App.css';
import { useCounter } from './context/counterContext';

function App() {
  const [counter, setCounter] = useCounter();
  return (
    <>
      <div>counter:{counter}</div>
      <button
        onClick={() => {
          setCounter((prev) => prev + 1);
        }}>
        +
      </button>
      <button
        onClick={() => {
          setCounter((prev) => prev - 1);
        }}>
        -
      </button>
    </>
  );
}

export default App;

 

 

  • [counter, setCounter] =useCounter => ์ „์—ญ์ ์œผ๋กœ ๊ด€๋ฆฌ๋˜๊ณ  ์žˆ๋Š” ์ƒํƒœ
  • useState๊ฐ€ ์•„๋‹Œ ์ปค์Šคํ…€ ํ›… useCounter๋ฅผ ๋ถˆ๋Ÿฌ์™€ ์ด๋ฏธ ๋งŒ๋“ค์–ด์ ธ์žˆ๋Š” ์ƒํƒœ๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ์Œ