๊ฐœ๋ฐœ ๊ณต๋ถ€/๋ฐ๋ธŒ์ฝ”์Šค TIL

[ํด๋ผ์šฐ๋”ฉ ์–ดํ”Œ๋ฆฌ์ผ€์ด์…˜ ์—”์ง€๋‹ˆ์–ด๋ง TIL] 240208 - React ๊ธฐ๋ณธ. ๊ฐ„๋‹จํ•œ ์ผ๊ธฐ์žฅ ํ”„๋กœ์ ํŠธ 4

๊ฐ€์šค์ด 2024. 2. 8. 20:03

Intro


์˜ค๋Š˜์€ ๊ฐ„๋‹จํ•œ ์ผ๊ธฐ์žฅ ํ”„๋กœ์ ํŠธ ๋งˆ์ง€๋ง‰ ๊ฐ•์˜!!

 

 

 

 

์˜ค๋Š˜ ํ•™์Šตํ•œ ๋‚ด์šฉ


์ตœ์ ํ™”3 - ์ปดํฌ๋„ŒํŠธ & ํ•จ์ˆ˜ ์žฌ์‚ฌ์šฉํ•˜๊ธฐ

์ปดํฌ๋„ŒํŠธ๋ฅผ ์ตœ์ ํ™”ํ•˜๊ธฐ ์œ„ํ•ด์„  ์–ด๋–ค ์ปดํฌ๋„ŒํŠธ๊ฐ€ ์ตœ์ ํ™” ๋Œ€์ƒ์ธ์ง€ ์ฐพ์•„๋‚ผ ์ˆ˜ ์žˆ์–ด์•ผ ํ•œ๋‹ค.

โžก๏ธ Chrome ํ™•์žฅ ์•ฑ React Developer Tools๋ฅผ ์ž˜ ํ™œ์šฉํ•˜์ž!!

 

useCallback

ํŠน์ • ํ•จ์ˆ˜๋ฅผ ์ƒˆ๋กœ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ๋ฉ”๋ชจ์ด์ œ์ด์…˜๋œ ์ฝœ๋ฐฑ์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

const onCreate = useCallback((author, content, emotion) => {
  const created_date = new Date().getTime();
  const newItem = {
    author,
    content,
    emotion,
    created_date,
    id: dataId.current,
  };
  dataId.current += 1;
  
  // ํ•จ์ˆ˜ํ˜• ์—…๋ฐ์ดํŠธ: setData์— ๊ฐ’์ด ์•„๋‹Œ ํ•จ์ˆ˜๋ฅผ ์ „๋‹ฌํ•œ๋‹ค.
  setData((data) => [newItem, ...data]);
}, []);

 

useMemo์™€ ์ฐจ์ด์ 

โžก๏ธ useMemo๋Š” ์—ฐ์‚ฐ๋œ ๊ฒฐ๊ณผ๊ฐ’์„ ๋ฐ˜ํ™˜ํ•˜๊ณ , useCallback์€ ํ•จ์ˆ˜๋ฅผ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

 

 

 

์ตœ์ ํ™”4 - ํ”„๋กœ์ ํŠธ ์ตœ์ ํ™” ์™„๋ฃŒ

์ตœ์ ํ™”์˜ ์‹œ์ž‘์€ React.memo๋กœ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ฌถ์–ด์ฃผ๋Š” ๊ฒƒ!

import React, { ... } from 'react';

const DiaryItem() => {
  ...
  return (...)
};

export default React.memo(DiaryItem);

 

 

 

 

๋ณต์žกํ•œ ์ƒํƒœ๋ณ€ํ™” ๋กœ์ง ๋ถ„๋ฆฌ - useReducer

dispatch๊ฐ€ ํ˜ธ์ถœ๋˜๋ฉด ์ƒํƒœ๋ณ€ํ™”๊ฐ€ ์ผ์–ด๋‚˜์•ผ ํ•จ

import './App.css';
import DiaryEditor from './DiaryEditor';
import DiaryList from './DiaryList';
import { useRef, useEffect, useMemo, useCallback, useReducer } from 'react';

const reducer = (state, action) => {
  switch (action.type) {
    case 'INIT': {
      return action.data;
    }
    case 'CREATE': {
      const created_date = new Date().getTime();
      const newItem = { ...action.data, created_date };
      return [newItem, ...state];
    }

    case 'REMOVE': {
      return state.filter((item) => item.id !== action.targetId);
    }

    case 'EDIT': {
      return state.map((item) => (action.targetId === item.id ? { ...item, content: action.newContent } : item));
    }

    default:
      return state;
  }
};

function App() {
  ...
  return (...)
}

export default App;
  • reducer ํ•จ์ˆ˜: ํ˜„์žฌ ์ƒํƒœ(state)์™€ ์•ก์…˜ ๊ฐ์ฒด(action)๋ฅผ ํŒŒ๋ผ๋ฏธํ„ฐ๋กœ ๋ฐ›์•„ ์ƒˆ๋กœ์šด ์ƒํƒœ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ฃผ๋Š” ํ•จ์ˆ˜

 

const [data, dispatch] = useReducer(reducer, []);
  • dispatch: ์•ก์…˜์„ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ํ•จ์ˆ˜ ex) dispatch({ type: 'REMOVE', targetId });
  • data(state): ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์ƒํƒœ

 

const reducer = (state, action) => {
  ...
};

function App() {
  ...
  const onCreate = useCallback((author, content, emotion) => {
    dispatch({
      type: 'CREATE',
      data: {
        author,
        content,
        emotion,
        id: dataId.current,
      },
    });

    dataId.current += 1;
  }, []);

  const onRemove = useCallback((targetId) => {
    dispatch({ type: 'REMOVE', targetId });
  }, []);

  const onEdit = useCallback((targetId, newContent) => {
    dispatch({ type: 'EDIT', targetId, newContent });
  }, []);


  return (...)
};

export default App;

 

 

 

์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ์— ๋ฐ์ดํ„ฐ ๊ณต๊ธ‰ - Context API

props๋ฅผ ์ „๋‹ฌ๋งŒ ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ๊ฐ€ ๋งŽ์ด ์ƒ๊ธฐ๋ฉด (Props Drilling) ์ฝ”๋“œ ์œ ์ง€๋ณด์ˆ˜๊ฐ€ ์–ด๋ ค์›Œ์ง„๋‹ค.

Context API๋ฅผ ์‚ฌ์šฉํ•ด ์ปดํฌ๋„ŒํŠธ ํŠธ๋ฆฌ ๋‹จ๊ณ„๋งˆ๋‹ค ๋ช…์‹œ์ ์œผ๋กœ props๋ฅผ ๋„˜๊ฒจ์ฃผ์ง€ ์•Š๊ณ  ๊ฐ’์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • Context ์ƒ์„ฑ
const MyContext = React.createContext(defaultValue);

 

  • Context Provider๋ฅผ ํ†ตํ•œ ๋ฐ์ดํ„ฐ ๊ณต๊ธ‰
<MyContext.Provider value={์ „์—ญ์œผ๋กœ ์ „๋‹ฌํ•˜๊ณ ์žํ•˜๋Š” ๊ฐ’}>
  // ์ด Context์•ˆ์— ์œ„์น˜ํ•  ์ž์‹ ์ปดํฌ๋„ŒํŠธ๋“ค
</MyContext.Provider>
return (
  <DiaryStateContext.Provider value={data}>
    <DiaryDispatchContext.Provider value={memoizedDispatches}>
      <div className="App">
        <DiaryEditor />
        <div>์ „์ฒด ์ผ๊ธฐ: {data.length}</div>
        <div>๊ธฐ๋ถ„ ์ข‹์€ ์ผ๊ธฐ ๊ฐœ์ˆ˜: {goodCount}</div>
        <div>๊ธฐ๋ถ„ ๋‚˜์œ ์ผ๊ธฐ ๊ฐœ์ˆ˜: {badCount}</div>
        <div>๊ธฐ๋ถ„ ์ข‹์€ ์ผ๊ธฐ ๋น„์œจ: {goodRatio}%</div>
        <DiaryList />
      </div>
    </DiaryDispatchContext.Provider>
  </DiaryStateContext.Provider>
  );

 

  • ๋ฐ์ดํ„ฐ state๊ฐ€ ๋ฐ”๋€” ๋•Œ๋งˆ๋‹ค ๋ Œ๋”๋ง๋˜๊ธฐ ๋•Œ๋ฌธ์— ๋ Œ๋”๋ง์ด ๋ถˆํ•„์š”ํ•œ ๊ฐ’์„ ๊ตณ์ด ํ•จ๊ป˜ ์“ฐ์ง€ ์•Š๊ณ  ์ƒˆ๋กœ์šด Context๋ฅผ ์‚ฌ์šฉํ•ด ๋ถ„๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. (๊ฐœ์ˆ˜ ์ œํ•œ x)
export const DiaryDispatchContext = React.createContext();

const onCreate = useCallback((author, content, emotion) => {
  dispatch({
    type: 'CREATE',
    data: {
      author,
      content,
      emotion,
      id: dataId.current,
    },
  });

  dataId.current += 1;
}, []);

const onRemove = useCallback((targetId) => {
  dispatch({ type: 'REMOVE', targetId });
}, []);

const onEdit = useCallback((targetId, newContent) => {
  dispatch({ type: 'EDIT', targetId, newContent });
}, []);


/* useMemo๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ  */
/* App์ปดํฌ๋„ŒํŠธ๊ฐ€ ์žฌ์ƒ์„ฑ์ด ๋  ๋•Œ, ๊ฐ์ฒด๋“ค๋„ ์žฌ์ƒ์„ฑ์ด ๋˜๋ฏ€๋กœ ์žฌ์ƒ์„ฑ๋˜์ง€ ์•Š๊ฒŒ useMemo ๊ฐ์ฒด๋กœ ๋ฌถ์–ด์ค˜์•ผ ํ•œ๋‹ค. */
const memoizedDispatches = useMemo(() => {
  return { onCreate, onRemove, onEdit };
}, []);

 

  • ์‚ฌ์šฉ๋ฐฉ๋ฒ• - useContext()
import React, { useState, useRef, useContext } from 'react';
import { DiaryDispatchContext } from './App';

const DiaryItem = () => {
  ...
  const { onEdit, onRemove } = useContext(DiaryDispatchContext);
  return (...)
};

export default React.memo(DiaryItem);

 

 

 

 

๋งˆ๋ฌด๋ฆฌ


useContext๋ฅผ ๋งˆ์ง€๋ง‰์œผ๋กœ ๊ฐ„๋‹จํ•œ ์ผ๊ธฐ์žฅ ํ”„๋กœ์ ํŠธ๋ฅผ ๋งˆ๋ฌด๋ฆฌํ–ˆ๋‹ค.

๋ฆฌ์•กํŠธ์— ๋Œ€ํ•ด ์–ด๋А์ •๋„ ๊ฐ์ด ์žกํžŒ ๊ฒƒ ๊ฐ™๋‹ค.

๋ฐฐ์šด ๊ฒƒ์ด ๋งŽ์•„์„œ ๋ณต์Šต์„ ์ฒ ์ €ํžˆ ํ•ด์•ผ๊ฒ ๋‹ค.