[TIL] 0616 | React with Redux, Next.js, TypeScript

TeasanΒ·2022λ…„ 6μ›” 15일
0

TIL

λͺ©λ‘ 보기
9/36
post-thumbnail
post-custom-banner

λͺ©μ°¨

  • μ‚¬μš©μž μ •μ˜ ν›… 둜직 μ‘°μ •ν•˜κΈ°
  • 더 λ§Žμ€ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ‚¬μš©μž μ •μ˜ ν›… μ‚¬μš©ν•˜κΈ°

✧ μ‚¬μš©μž μ •μ˜ ν›… 둜직 μ‘°μ •ν•˜κΈ°

  • 이제 App μ»΄ν¬λ„ŒνŠΈμ˜ useEffect에 fetchTasksλ₯Ό μ˜μ‘΄μ„±μœΌλ‘œ μΆ”κ°€ν•΄μ•Ό ν•œλ‹€. μ™œλƒν•˜λ©΄ μ»΄ν¬λ„ŒνŠΈ ν•¨μˆ˜ μ•ˆμ—μ„œ μ„€μ •λœ λͺ¨λ“  데이터, λ˜λŠ” ν•¨μˆ˜λŠ” κ·Έ ν•¨μˆ˜μ˜ μ˜μ‘΄μ„±μœΌλ‘œμ„œ μΆ”κ°€λ˜μ–΄μ•Ό ν•˜κΈ° λ•Œλ¬Έμ΄λ‹€.

🚨 λ¬΄ν•œλ£¨ν”„ λ°œμƒ

useEffect(() => {
  fetchTasks();
}, [fetchTasks]);
  • ν•˜μ§€λ§Œ μ—¬κΈ°μ„œ, 이전에 κ±°λ‘ ν•œ λ¬Έμ œκ°€ λ°œμƒν•œλ‹€. λ¬΄ν•œλ£¨ν”„κ°€ λ°œμƒν•˜λŠ” 것이닀. useEffect λ‚΄λΆ€μ—μ„œ fetchTasks() ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ”λ°, κ·Έλ ‡κ²Œ 되면 μ»€μŠ€ν…€ ν›… μ•ˆμ— μžˆλŠ” sendRequest ν•¨μˆ˜λ₯Ό μ‹€ν–‰ν•˜κ²Œ 되고, λͺ‡ λͺ‡ μƒνƒœ(state)κ°€ μƒˆλ‘œ μ„€μ • 되기 λ•Œλ¬Έμ΄λ‹€.

μ»€μŠ€ν…€ ν›…μ˜ μƒνƒœ(state) 섀정이 λ°œμƒν•˜λ©΄ μ»€μŠ€ν…€ 훅을 μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈλŠ” μž¬λ Œλ”λ§ λœλ‹€.

  • μƒνƒœ(state)λ₯Ό μ„€μ •ν•˜λŠ” μ»€μŠ€ν…€ 훅을 λ§Œλ“€κ³ , App μ»΄ν¬λ„ŒνŠΈ λ‚΄λΆ€μ—μ„œ 훅을 μ‚¬μš©ν•˜κ²Œ 되면, μ»΄ν¬λ„ŒνŠΈλŠ” λ¬΅μ‹œμ μœΌλ‘œ μ»€μŠ€ν…€ 훅이 μ„€μ •ν•œ μƒνƒœ(state)λ₯Ό μ‚¬μš©ν•˜κ²Œ λœλ‹€. 즉, μ»€μŠ€ν…€ ν›…μ—μ„œ κ΅¬μ„±λœ μƒνƒœ(state)κ°€ κ·Έ μ»€μŠ€ν…€ 훅을 μ‚¬μš©ν•˜λŠ” μ»΄ν¬λ„ŒνŠΈμ— μ„€μ •λ˜κ²Œ λ˜λŠ” κ²ƒμ΄λΌλŠ” λœ»μ΄λ‹€.

use-fetch.js

const sendRequest = async (taskText) => {
  setIsLoading(true);
  setError(null);
  try {
    const response = await fetch(requestConfig.url, {
      method: requestConfig.method ? requestConfig.method : "GET",
      headers: requestConfig.headers ? requestConfig.headers : {},
      body: JSON.stringify(requestConfig.body)
        ? JSON.stringify(requestConfig.body)
        : null,
    });

    if (!response.ok) {
      throw new Error("Request failed!");
    }

    const data = await response.json();

    applyData(data);
  } catch (err) {
    setError(err.message || "Something went wrong!");
  }
  setIsLoading(false);
};
  • μ—¬κΈ° sendRequest ν•¨μˆ˜μ—μ„œ setIsLoading κ³Ό setErrerλ₯Ό ν˜ΈμΆœν•˜λ©΄ μ΄λŠ” App μ»΄ν¬λ„ŒνŠΈμ˜ μž¬λ Œλ”λ§μ„ λ°œμƒμ‹œν‚¨λ‹€. μ™œ 그럴까? μ΄λŠ” App μ»΄ν¬λ„ŒνŠΈ μ•ˆμ— μžˆλŠ” μ»€μŠ€ν…€ 훅을 μ‚¬μš©ν•˜λŠ” 것이기 λ•Œλ¬Έμ΄λ‹€. κ·Έλ ‡κ²Œ 되면, μž¬λ Œλ”λ§μ΄ λ˜λŠ” μˆœκ°„ μ»€μŠ€ν…€ ν›…(useFetch)이 λ‹€μ‹œ 호좜되고, μ»€μŠ€ν…€ ν›…(useFetch)이 λ‹€μ‹œ 호좜되면 sendRequest ν•¨μˆ˜κ°€ 또 λ‹€μ‹œ μž¬μƒμ„±λ˜λ©΄μ„œ μƒˆλ‘œμš΄ ν•¨μˆ˜ 객체λ₯Ό λ°˜ν™˜ν•˜κ³  App μ»΄ν¬λ„ŒνŠΈμ˜ useEffectκ°€ μž¬μ‹€ν–‰λœλ‹€.

App.js

useEffect(() => {
  fetchTasks();
}, [fetchTasks]);

λ¬΄ν•œ 루프가 λ°œμƒν•˜λŠ” 이유

  • μ΄μ „μ˜ κ°•μ˜(section-12) μ—μ„œ μ‚΄νŽ΄λ΄€λ“―μ΄ μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ ν•¨μˆ˜λŠ” 객체이닀. 그리고 내뢀에 같은 λ‘œμ§μ„ ν’ˆκ³  μžˆλ”λΌλ„ ν•¨μˆ˜κ°€ μž¬μƒμ„±λ˜λ©΄ μ΄λŠ” λ©”λͺ¨λ¦¬μ—μ„œ μƒˆλ‘œμš΄ 객체둜 μΈμ‹λ˜κΈ° λ•Œλ¬Έμ— useEffectκ°€ 이λ₯Ό μƒˆλ‘œμš΄ κ°’μœΌλ‘œ λ°›μ•„λ“€μ΄κ²Œ 되며 기술적으둜 같은 ν•¨μˆ˜μΌμ§€λΌλ„ μž¬μ‹€ν–‰μ„ μœ λ°œν•˜κ²Œ λœλ‹€.

useCallback 으둜 λ¬΄ν•œ 루프λ₯Ό λ°©μ§€ν•˜κΈ°

  • 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ‹€μ‹œ useFetch μ»€μŠ€ν…€ ν›…μœΌλ‘œ λŒμ•„κ°€μ„œ sendRequest 뢀뢄을 μˆ˜μ •ν•΄μ•Ό ν•œλ‹€.
const sendRequest = useCallback(async (taskText) => {
  setIsLoading(true);
  setError(null);
  try {
    const response = await fetch(requestConfig.url, {
      method: requestConfig.method ? requestConfig.method : "GET",
      headers: requestConfig.headers ? requestConfig.headers : {},
      body: JSON.stringify(requestConfig.body)
        ? JSON.stringify(requestConfig.body)
        : null,
    });

    if (!response.ok) {
      throw new Error("Request failed!");
    }

    const data = await response.json();

    applyData(data);
  } catch (err) {
    setError(err.message || "Something went wrong!");
  }
  setIsLoading(false);
}, []);
  • async/await ꡬ문이 μžˆλ”λΌλ„ λ¬Έμ œκ°€ μ—†κΈ° λ•Œλ¬Έμ— ν•΄λ‹Ή ν•¨μˆ˜ 뢀뢄을 useCallback으둜 κ°μ‹Έμž. μ•Œλ‹€μ‹œν”Ό useCallback은 μ˜μ‘΄μ„± 배열이 ν•„μš”ν•˜λ‹€. 여기에 무엇을 μ£Όμž…ν•΄μ•Ό ν• κΉŒ?
const useFetch = (requestConfig, applyData) => {
  ...

  const sendRequest = useCallback(
    async (taskText) => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestConfig.url, {
          method: requestConfig.method ? requestConfig.method : "GET",
          headers: requestConfig.headers ? requestConfig.headers : {},
          body: JSON.stringify(requestConfig.body)
            ? JSON.stringify(requestConfig.body)
            : null,
        });

        if (!response.ok) {
          throw new Error("Request failed!");
        }

        const data = await response.json();

        applyData(data);
      } catch (err) {
        setError(err.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    // ⚑️ --------
    [requestConfig, applyData]
    // ⚑️ --------
  );

  ...
};
  • μ˜μ‘΄μ„± 배열은 이 μ•ˆμ—μ„œ μ‚¬μš©λ˜λŠ” λͺ¨λ“  것을 λ‚˜μ—΄ν•΄μ•Ό ν•œλ‹€. 이 κ²½μš°μ—λŠ” useFetch μ»€μŠ€ν…€ 훅이 λ§€κ°œλ³€μˆ˜λ‘œ λ°›μ•„μ˜€λŠ” requestConfig 와 applyDataλ₯Ό μΆ”κ°€ν•  수 μžˆμ„ 것이닀. ν•˜μ§€λ§Œ μ΄λ ‡κ²Œ ν–ˆμ„ 경우 또 λ‹€λ₯Έ λ¬Έμ œκ°€ λ°œμƒν•œλ‹€. λ‹Ήμ—°ν•œ μ΄μ•ΌκΈ°κ² μ§€λ§Œ 이 두가지 λͺ¨λ‘ 객체이닀. requestConfigλŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ κΈ°λ³Έ 객체이고, applyDataλŠ” ν•¨μˆ˜μΈλ° μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œλŠ” ν•¨μˆ˜ μ—­μ‹œ 객체이기 λ•Œλ¬Έμ΄λ‹€. λ‹€μ‹œ App μ»΄ν¬λ„ŒνŠΈλ‘œ λŒμ•„κ°€μ„œ useFetch에 μ „λ‹¬ν•œ 객체듀을 μ‚΄νŽ΄λ³΄μž.

App.js

const transformTasks = (taskObj) => {
  const loadedTasks = [];

  for (const taskKey in taskObj) {
    loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
  }

  setTasks(loadedTasks);
};

const httpData = useFetch(
  {
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  },
  transformTasks
);

const { isLoading, error, sendRequest: fetchTasks } = httpData;
  • μš°λ¦¬λŠ” useFetch에 인자둜 μ „λ‹¬ν•˜λŠ” 두 개의 객체가 App μ»΄ν¬λ„ŒνŠΈκ°€ μž¬μ‹€ν–‰λ  λ•Œλ§ˆλ‹€ μž¬μƒμ„±λ˜μ§€ μ•Šλ„λ‘ ν•΄μ•Ό ν•œλ‹€. ν˜„μž¬λ‘œμ„œλŠ” App μ»΄ν¬λ„ŒνŠΈκ°€ μž¬μ‹€ν–‰λ  λ•Œλ§ˆλ‹€ μž¬μ‹€ν–‰λ˜κ³  있기 λ•Œλ¬Έμ΄λ‹€.
const transformTasks = useCallback((taskObj) => {
  const loadedTasks = [];

  for (const taskKey in taskObj) {
    loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
  }

  setTasks(loadedTasks);
}, []);
  • λ¨Όμ €, transformTasks ν•¨μˆ˜λ₯Ό useCallback으둜 λž˜ν•‘ν•œλ‹€. μ—¬κΈ°μ—μ„œμ˜ μ˜μ‘΄μ„± λ°°μ—΄μ—λŠ” 아무 것도 μΆ”κ°€ν•˜μ§€ μ•ŠλŠ”λ‹€. μƒνƒœ(state) μ—…λ°μ΄νŠΈλ₯Ό ν•˜λŠ” setTasks μ™Έμ—λŠ” κ·Έ μ–΄λ– ν•œ 것도 μ™ΈλΆ€μ—μ„œ μ“°κ³  μžˆμ§€ μ•ŠκΈ° λ•Œλ¬Έμ΄λ‹€. 이제 μš°λ¦¬λŠ” transformTasks ν•¨μˆ˜κ°€ λ³€ν•˜μ§€ μ•ŠμŒμ„ useCallback을 톡해 λ³΄μ¦ν–ˆλ‹€.
const httpData = useFetch(
  {
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  },
  transformTasks
);
  • ν•˜μ§€λ§Œ 이 객체(httpData) λ‚΄λΆ€μ—μ„œ 첫 번째 인자둜 url 을 λ‹΄μ•„ μ „λ‹¬ν•˜λŠ” κ°μ²΄λŠ” App μ»΄ν¬λ„ŒνŠΈκ°€ μž¬ν‰κ°€ν•  λ•Œλ§ˆλ‹€ μž¬μƒμ„±λ˜κ³  μžˆλ‹€. 이λ₯Ό λ§‰μœΌλ €λ©΄, useMemo와 같은 것을 μ΄μš©ν•΄μ„œ 객체가 λ³€ν•˜μ§€ μ•Šλ„λ‘ 보증해야 ν•  것이닀. λ˜λŠ” useMemoλ₯Ό μ‚¬μš©ν•˜λŠ” λŒ€μ‹  μ»€μŠ€ν…€ 훅을 쑰금 μˆ˜μ •ν•˜λŠ” 방법을 μ‚¬μš©ν•΄λ„ 될 것이닀.

use-fetch.js

const useFetch = (requestConfig, applyData) => {
  ...

  const sendRequest = useCallback(
    async (taskText) => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestConfig.url, {
          method: requestConfig.method ? requestConfig.method : "GET",
          headers: requestConfig.headers ? requestConfig.headers : {},
          body: JSON.stringify(requestConfig.body)
            ? JSON.stringify(requestConfig.body)
            : null,
        });

        if (!response.ok) {
          throw new Error("Request failed!");
        }

        const data = await response.json();

        applyData(data);
      } catch (err) {
        setError(err.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    // ⚑️ --------
    [requestConfig, applyData]
    // ⚑️ --------
  );

  ...
};
  • useFetch μ»€μŠ€ν…€ ν›…μ—μ„œ λ§€κ°œλ³€μˆ˜λ‘œ λ°›λŠ” requestConfigλŠ” ν›… μžμ²΄μ—μ„œ 받지 μ•Šκ³  ν•΄λ‹Ή requestConfigλ₯Ό μ‚¬μš©ν•˜λŠ” sendRequest ν•¨μˆ˜μ˜ 인자둜 직접 λ°›μ•„μ˜¨λ‹€λ©΄ μ–΄λ–¨κΉŒ? κ²°κ΅­ sendRequest ν•¨μˆ˜ μ—­μ‹œ ν•¨κ»˜ 호좜되기 λ•Œλ¬Έμ΄λ‹€.
const useFetch = (applyData) => {
  ...

  const sendRequest = useCallback(
    // ⚑️ --------
    async (requestConfig) => {
    // ⚑️ --------
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestConfig.url, {
          method: requestConfig.method ? requestConfig.method : "GET",
          headers: requestConfig.headers ? requestConfig.headers : {},
          body: JSON.stringify(requestConfig.body)
            ? JSON.stringify(requestConfig.body)
            : null,
        });

        if (!response.ok) {
          throw new Error("Request failed!");
        }

        const data = await response.json();

        applyData(data);
      } catch (err) {
        setError(err.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    // ⚑️ --------
    [applyData]
    // ⚑️ --------
  );

  ...
};
  • 이제 requestConfigλ₯Ό μ»€μŠ€ν…€ ν›… μžμ²΄κ°€ μ•„λ‹Œ sendRequestμ—μ„œ 직접 λ°›μ•„μ˜¬ 수 μžˆλ„λ‘ μˆ˜μ •ν–ˆλ‹€. μ΄λ ‡κ²Œ 되면 requestConfigλ₯Ό μ˜μ‘΄μ„± 배열에 μΆ”κ°€ν•˜μ§€ μ•Šμ•„λ„ 될 것이닀. 이제 requestConfigλŠ” μ™ΈλΆ€μ˜ μ˜μ‘΄μ„± 배열이 μ•„λ‹ˆλΌ, useCallback으둜 λž˜ν•‘λœ ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜κ°€ λ˜μ—ˆκΈ° λ•Œλ¬Έμ΄λ‹€.

App.js

const httpData = useFetch(
  {
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  },
  transformTasks
);
  • λ‹€μ‹œ App μ»΄ν¬λ„ŒνŠΈλ‘œ μ΄λ™ν•΄μ„œ 이 url 객체λ₯Ό μ œκ±°ν•˜λ„λ‘ ν•˜μž.
const httpData = useFetch(
  {
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  },
  transformTasks
);
  • κ·Έλ ‡κ²Œ 되면 transformTasksκ°€ useFetch에 μ „λ‹¬λ˜λŠ” μœ μΌν•œ λ§€κ°œλ³€μˆ˜κ°€ λœλ‹€.
const httpData = useFetch(transformTasks);
const { isLoading, error, sendRequest: fetchTasks } = httpData;
  • 둜직이 κΈΈμ–΄μ§€λŠ” κ±Έ λ°©μ§€ν•˜κΈ° μœ„ν•΄ κ°„λ‹¨ν•˜κ²Œ ν•œ μ€„λ‘œ μž‘μ„±ν•˜λ©΄ 가독성이 더 λ†’μ•„μ§ˆ 것이닀.
const { isLoading, error, sendRequest: fetchTasks } = useFetch(transformTasks);

useEffect(() => {
  fetchTasks();
}, [fetchTasks]);
  • useEffectμ—μ„œλŠ” fetchTasks()λ₯Ό ν†΅ν•΄μ„œ μ΅œμ’…μ μœΌλ‘œ μš”μ²­μ„ μ „λ‹¬ν•˜κ³  μžˆλ‹€. fetchTasks()κ°€ κ²°κ΅­μ—λŠ” μš°λ¦¬κ°€ 이전에 useFetch μ»€μŠ€ν…€ ν›… λ‚΄λΆ€μ—μ„œ μ„€μ •ν–ˆλ˜ sendRequest ν•¨μˆ˜λΌλŠ” κ±Έ κΈ°μ–΅ν•΄λ³΄μž. 여기에 μš°λ¦¬κ°€ μ „λ‹¬ν•˜κΈ°λ‘œ ν•œ λ§€κ°œλ³€μˆ˜ requestConfigλ₯Ό fetchTasks()에 직접 전달 ν•΄μ£Όμ–΄μ•Ό ν•  것이닀.
useEffect(() => {
  fetchTasks({
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  });
}, [fetchTasks]);
  • 이것은 μš°λ¦¬κ°€ ν•  수 μžˆλŠ” 수 λ§Žμ€ 방법 쀑에 ν•˜λ‚˜μΌ λΏμž„μ„ κΈ°μ–΅ν•˜μž. 그리고 λ‹Ήμ—°νžˆ 데이터λ₯Ό μ²˜λ¦¬ν•΄μ£ΌλŠ” ν•¨μˆ˜(transformTasks)에도 이와 λ™μΌν•œ μž‘μ—…μ„ 해쀄 수 μžˆλ‹€.
const transformTasks = useCallback((taskObj) => {
  const loadedTasks = [];

  for (const taskKey in taskObj) {
    loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
  }

  setTasks(loadedTasks);
}, []);

const { isLoading, error, sendRequest: fetchTasks } = useFetch(transformTasks);

useEffect(() => {
  fetchTasks({
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  });
}, [fetchTasks]);
  • App μ»΄ν¬λ„ŒνŠΈμ—μ„œ useCallback을 μ‚¬μš©ν•˜λŠ” 것이 λ„ˆλ¬΄ λ²ˆκ±°λ‘­λ‹€κ³  μƒκΉŒν•  경우 useCallback을 μ œκ±°ν•˜κ³  transformTasks ν•¨μˆ˜λ₯Ό 직접 fetchTasks() 에 μ „λ‹¬ν•˜λŠ” 방법도 μžˆμ„ 것이닀.
// ⚑️ --------
const transformTasks = (taskObj) => {
  const loadedTasks = [];

  for (const taskKey in taskObj) {
    loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
  }

  setTasks(loadedTasks);
};

// ⚑️ --------
const { isLoading, error, sendRequest: fetchTasks } = useFetch();

useEffect(() => {
  fetchTasks(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
    },
    // ⚑️ --------
    transformTasks
  );
}, [fetchTasks]);
  • 그리고, transformTasksλ₯Ό useEffectλ₯Ό λ‚΄λΆ€μ—μ„œ μ‹€ν–‰ν•  수 μžˆλ„λ‘ μˆ˜μ •ν•΄μ€€λ‹€.
const { isLoading, error, sendRequest: fetchTasks } = useFetch();

useEffect(() => {
  const transformTasks = (taskObj) => {
    const loadedTasks = [];

    for (const taskKey in taskObj) {
      loadedTasks.push({ id: taskKey, text: taskObj[taskKey].text });
    }

    setTasks(loadedTasks);
  };

  fetchTasks(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
    },
    transformTasks
  );
}, [fetchTasks]);
  • μ΄λ ‡κ²Œ transformTasks ν•¨μˆ˜λ₯Ό useEffect λ‚΄λΆ€μ—μ„œ μ‹€ν–‰ν•˜κ²Œ 되면, Effect ν•¨μˆ˜ μ•ˆμ— λ‚¨μ•„μžˆλŠ” μ™ΈλΆ€ μ˜μ‘΄μ„±μ€ μ‚¬λΌμ§€κ²Œ λœλ‹€. 이제 useFetch μ»€μŠ€ν…€ 훅은 μ˜μ‘΄μ„±μ΄λ‚˜ μ–΄λ– ν•œ λ§€κ°œλ³€μˆ˜ 없이도 호좜이 κ°€λŠ₯해진 것이닀.
fetchTasks(
  {
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  },
  transformTasks
);
  • μ™œλƒν•˜λ©΄, μš°λ¦¬κ°€ μš”μ²­μ— λŒ€ν•œ μ„€μ •(requestConfig)κ³Ό 데이터 전솑 ν›„ 데이터 처리λ₯Ό ν•΄μ£ΌλŠ” 둜직(transformTasks)을 직접 보내기 λ•Œλ¬Έμ΄λ‹€.

μ§€κΈˆκΉŒμ§€ 두 가지 방법을 μ‚¬μš©ν•΄μ„œ useEffect의 λ¬΄ν•œ 루프에 λŒ€ν•œ μ†”λ£¨μ…˜μ„ μ μš©ν•΄λ΄€λ‹€. 두 방법 λͺ¨λ‘ μ‚¬μš© κ°€λŠ₯ν•œ λ°©λ²•μ΄λ‹ˆ μ·¨ν–₯λŒ€λ‘œ μ„ νƒν•΄μ„œ μ‚¬μš©ν•˜λ©΄ 될 것이닀.

  • λ§ˆμ§€λ§‰μœΌλ‘œ μš°λ¦¬κ°€ 직접 보내고 μžˆλŠ” transformTasks 데이터 처리 ν•¨μˆ˜λ„ useFetch μ»€μŠ€ν…€ ν›…μ—μ„œ 직접 받지 μ•Šκ³  sendRequest ν•¨μˆ˜μ—μ„œ λ§€κ°œλ³€μˆ˜λ‘œ 받을 수 μžˆλ„λ‘ μˆ˜μ •ν•΄μ€€λ‹€.
const useFetch = () => {
  ...

  const sendRequest = useCallback(
    async (requestConfig, applyData) => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestConfig.url, {
          method: requestConfig.method ? requestConfig.method : "GET",
          headers: requestConfig.headers ? requestConfig.headers : {},
          body: JSON.stringify(requestConfig.body)
            ? JSON.stringify(requestConfig.body)
            : null,
        });

        if (!response.ok) {
          throw new Error("Request failed!");
        }

        const data = await response.json();

        applyData(data);
      } catch (err) {
        setError(err.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    [applyData]
  );

  ...
};
  • 그리고 λ§ˆμ§€λ§‰μœΌλ‘œ useCallback의 μ˜μ‘΄μ„±μœΌλ‘œ μΆ”κ°€ν–ˆλ˜ applyDataλ₯Ό μ‚­μ œν•΄μ€€λ‹€. useFetch μ»€μŠ€ν…€ ν›…μ—μ„œλŠ” 더이상 μ˜μ‘΄μ„±μ΄ ν•„μš”ν•˜μ§€ μ•ŠμœΌλ©°, μ΄λŠ” ν•΄λ‹Ή μ»€μŠ€ν…€ 훅이 λ‹€λ£¨λŠ” λͺ¨λ“  λ°μ΄ν„°λŠ” useCallback으둜 λž˜ν•‘λœ ν•¨μˆ˜μ—μ„œ λ§€κ°œλ³€μˆ˜λ‘œ 직접 λ°›μ•„μ˜€κ³  있기 λ•Œλ¬Έμ΄λ‹€.
const useFetch = () => {
  ...

  const sendRequest = useCallback(
    async (requestConfig, applyData) => {
      setIsLoading(true);
      setError(null);
      try {
        const response = await fetch(requestConfig.url, {
          method: requestConfig.method ? requestConfig.method : "GET",
          headers: requestConfig.headers ? requestConfig.headers : {},
          body: JSON.stringify(requestConfig.body)
            ? JSON.stringify(requestConfig.body)
            : null,
        });

        if (!response.ok) {
          throw new Error("Request failed!");
        }

        const data = await response.json();

        applyData(data);
      } catch (err) {
        setError(err.message || "Something went wrong!");
      }
      setIsLoading(false);
    },
    // ⚑️ --------
    []
    // ⚑️ --------
  );

  ...
};

ezgif com-gif-maker (86)

  • μ €μž₯ν•˜κ³ , μƒˆλ‘œκ³ μΉ¨ 해보면 λ™μΌν•˜κ²Œ μž‘μ—…μ΄ 문제 없이 λŒμ•„κ°€κ³  μžˆλ‹€λŠ” κ±Έ μ•Œ 수 μžˆλ‹€.


✧ 더 λ§Žμ€ μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ‚¬μš©μž μ •μ˜ ν›… μ‚¬μš©ν•˜κΈ°

  • λ§ˆμ§€λ§‰μœΌλ‘œ NeweTasks μ»΄ν¬λ„ŒνŠΈμ— μ»€μŠ€ν…€ 훅을 μ μš©ν•΄λ³΄λ €κ³  ν•œλ‹€. λ¨Όμ € NewTasks μ»΄ν¬λ„ˆνŠΈμ— useFetchλ₯Ό import ν•΄μ˜¨ ν›„,
import useFetch from "../../hooks/use-fetch";
  • useFetchλ₯Ό ν˜ΈμΆœν•œλ‹€.
useFetch();
  • App μ»΄ν¬λ„ŒνŠΈμ—μ„œ μž‘μ„±ν–ˆλ˜ κ²ƒμ²˜λŸΌ NewTasks μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ‚¬μš©ν•˜κ³ μž ν•˜λŠ” μ»€μŠ€ν…€ ν›…(useFetch)의 μƒνƒœ(state)와 ν•¨μˆ˜λ₯Ό ꡬ쑰 λΆ„ν•΄ ν• λ‹ΉμœΌλ‘œ μΆ”μΆœν•œλ‹€.
const { isLoading, error, sendRequest: sendTaskRequest } = useFetch();
  • 이제 enterTaskHandler ν•¨μˆ˜κ°€ 트리거 될 λ•Œλ§ˆλ‹€ ν•΄λ‹Ή μ»€μŠ€ν…€ 훅을 ν˜ΈμΆœν•˜λ„λ‘ μž‘μ„±ν•΄μ€€λ‹€. 즉, 맀번 TaskForm μ»΄ν¬λ„ŒνŠΈμ—μ„œ 폼이 제좜될 λ•Œλ§ˆλ‹€ enterTaskHandler ν•¨μˆ˜κ°€ 트리거 되고, λ‚΄λΆ€μ˜ ν•¨μˆ˜sendTaskRequest ν•¨μˆ˜κ°€ 호좜될 수 μžˆλ„λ‘ ν•˜λŠ” 것이닀.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest();

  ...
};
  • λ¨Όμ €, App μ»΄ν¬λ„ŒνŠΈμ—μ„œ μ»€μŠ€ν…€ 훅에 μ „λ‹¬ν•˜μ˜€λ˜ 것과 λ™μΌν•˜κ²Œ sendTaskRequest ν•¨μˆ˜ 내뢀에 μ ν•©ν•œ λ§€κ°œλ³€μˆ˜λ“€μ„ μ „λ‹¬ν•΄μ€˜μ•Ό ν•œλ‹€. 첫 번째 μΈμžλ‘œλŠ”, response λ₯Ό ν•  λ•Œ ν•„μš”ν–ˆλ˜ 속성듀이 λ‹΄κΈ΄ 객체가 ν•„μš”ν•  것이닀.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest({
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
  });

  ...
};
  • μ—¬κΈ°μ„œ App μ»΄ν¬λ„ŒνŠΈμ™€λŠ” λ‹€λ₯΄κ²Œ "POST" μš”μ²­μ„ 보낼 것이기 λ•Œλ¬Έμ—, "POST" μš”μ²­μ„ 보낼 λ•Œ ν•„μš”ν•œ 속성(method, headers, body) λ˜ν•œ ν¬ν•¨ν•΄μ„œ μž‘μ„±ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest({
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
  });

  ...
};
  • body μ†μ„±μ˜ 값은 JSON.stringify({text: taskText})둜 λ³€ν™˜ν•˜μ—¬ λ³΄λƒˆμ§€λ§Œ, 이미 μ»€μŠ€ν…€ ν›… λ‚΄λΆ€μ—μ„œ JSON λ³€ν™˜μ„ ν•΄μ£Όκ³  μžˆμœΌλ―€λ‘œ 객체 ν˜•νƒœλ‘œ μž‘μ„±ν•΄μ„œ 보내쀀닀.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest({
    url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: { text: taskText },
  });

  ...
};
  • 이제 useFetch μ»€μŠ€ν…€ ν›…μ—μ„œ 받을 첫 번째 인자인 requestConfig 객체가 μ™„μ„±λ˜μ—ˆλ‹€. 두 번째둜 전달할 μΈμžλŠ” 응닡 데이터λ₯Ό λ°›μ•„μ„œ μ²˜λ¦¬ν•΄μ£ΌλŠ” ν•¨μˆ˜ applyData이닀. NewTasks μ»΄ν¬λ„ŒνŠΈμ—μ„œ 데이터λ₯Ό μ²˜λ¦¬ν•˜λŠ” 방식을 μ‚΄νŽ΄λ³΄λ©΄,
const data = await response.json();
// ⚑️ --------
const generatedId = data.name;
const createdTask = { id: generatedId, text: taskText };
props.onAddTask(createdTask);
// ⚑️ --------
  • 이 λ‘œμ§λ“€μ΄ ν•΄λ‹Ή 역할을 λ‹΄λ‹Ήν•˜κ³  μžˆλŠ” κ±Έ μ•Œ 수 μžˆλ‹€. generatedId λŠ” Firebase κ°€ μžλ™μœΌλ‘œ λ§Œλ“€μ–΄μ£ΌλŠ” name으둜 idλ₯Ό 생성해쀀 것이고, 이 idλ₯Ό λΉ„λ‘―ν•˜μ—¬ 폼으둜 λΆ€ν„° 전달받은 taskText와 ν•¨κ»˜ ν¬ν•¨ν•˜κ³  μžˆλŠ” 것이 createdTask 객체이닀. 그리고 이것듀을 props.onAddTask(createdTask)둜 ν˜ΈμΆœν•΄μ„œ λ„£μ–΄μ£Όμ–΄μ•Ό ν•œλ‹€.

데이터 처리 ν•¨μˆ˜ λ§Œλ“€κΈ°

const createTask = (taskData) => {
  const generatedId = data.name;
  const createdTask = { id: generatedId, text: taskText };
  props.onAddTask(createdTask);
};
  • createTask ν•¨μˆ˜λŠ” μ•žμ˜ λ‘œμ§λ“€μ„ 담은 μƒˆλ‘œμš΄ ν•¨μˆ˜μ΄λ‹€. 이 ν•¨μˆ˜μ—μ„œλŠ” taskDataλ₯Ό 인자둜 받도둝 μž‘μ„±ν•΄μ€€λ‹€. 그리고 κ·Έ μΈμžμ— 따라 λ‘œμ§λ“€μ„ μˆ˜μ •ν•΄μ€€λ‹€.
const createTask = (taskData) => {
  const generatedId = taskData.name;
  const createdTask = { id: generatedId, text: taskText };

  props.onAddTask(createdTask);
};
  • 그리고,sendTaskRequest의 두 번째 인자둜 ν•΄λ‹Ή ν•¨μˆ˜(createTask)λ₯Ό ν¬μΈν„°ν•œλ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask
  );
};
  • ν•΄λ‹Ή λ‘œμ§μ—μ„œ μ—­μ‹œ useCallback을 μ‚¬μš©ν•΄μ•Ό 할지 κ³ λ―Όλ˜κ² μ§€λ§Œ μ—¬κΈ°μ—μ„œλŠ” ν•΄λ‹Ή 훅을 μ‚¬μš©ν•˜μ§€ μ•Šμ•„λ„ λœλ‹€. App μ»΄ν¬λ„ŒνŠΈμ²˜λŸΌ useEffectλ₯Ό μ‚¬μš©ν•΄μ„œ sendRequestλ₯Ό ν˜ΈμΆœν•˜λŠ” 것이 μ•„λ‹ˆλΌ, enterTaskHandlerμ—μ„œλ§Œ sendTaskRequestλ₯Ό ν˜ΈμΆœν•˜κ³  있기 λ•Œλ¬Έμ΄λ‹€. 그리고 이 μš”μ²­μ€ μ»΄ν¬λ„ŒνŠΈκ°€ μž¬ν‰κ°€λ˜μ–΄λ„ μ „μ†‘λ˜μ§€ μ•ŠμœΌλ©°, 단지 물리적으둜 μš°λ¦¬κ°€ 폼을 μ œμΆœν–ˆμ„ λ•Œμ—λ§Œ ν•¨μˆ˜κ°€ μž¬μ‹€ν–‰λ  것이닀. μ΄λŠ” App μ»΄ν¬λ„ŒνŠΈμ™€ λΆ„λͺ… λ‹€λ₯Έ 점이닀.

taskText λ₯Ό μ „λ‹¬ν•˜λŠ” 방법

  • μš°λ¦¬λŠ” μ—¬κΈ°μ„œ μƒˆλ‘œμš΄ 이슈λ₯Ό λ§ˆμ£Όν•˜κ²Œ λœλ‹€. createTask ν•¨μˆ˜λ₯Ό 보면,
const createTask = (taskData) => {
  const generatedId = taskData.name;
  const createdTask = { id: generatedId, text: taskText };

  props.onAddTask(createdTask);
};
  • text 속성 κ°’μœΌλ‘œ 전달해야 ν•˜λŠ” taskText 뢀뢄이 μ œλŒ€λ‘œ μ „λ‹¬λ˜μ§€ μ•Šμ•˜μŒμ„ μ•Œ 수 μžˆλ‹€. νΌμ—μ„œ HTTP μš”μ²­μ„ 톡해 μ „λ‹¬λœ ν…μŠ€νŠΈ 뢀뢄이 λΉ μ ΈμžˆλŠ” 것이닀. 이 taskTextλŠ” enterTaskHandler ν•¨μˆ˜μ˜ λ§€κ°œλ³€μˆ˜λ₯Ό 톡해 λ°›μ•„μ˜€κ³  μžˆλ‹€. κ·Έλ ‡λ‹€λ©΄ μ–΄λ–»κ²Œ 이λ₯Ό 전달해야 ν•˜λŠ” 걸까? 이 taskTextλ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄μ„œλŠ” 두 개의 선택지가 μžˆλ‹€. 첫 λ²ˆμ§Έλ‘œλŠ”
const enterTaskHandler = async (taskText) => {
  const createTask = (taskData) => {
    const generatedId = taskData.name;
    const createdTask = { id: generatedId, text: taskText };

    props.onAddTask(createdTask);
  };

  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask.bind(null, taskText)
  );
};
  • createTask ν•¨μˆ˜λ₯Ό ν†΅μ§Έλ‘œ κ°€μ Έμ™€μ„œ enterTaskHandler ν•¨μˆ˜ μ•ˆμ—μ„œ μ •μ˜ν•˜λŠ” 방법이 μžˆλ‹€. μ΄λŠ” μžλ°”μŠ€ν¬λ¦½νŠΈ μŠ€μ½”ν”„μ— 따라 ν•¨κ»˜ μž‘λ™λ˜κ³ , taskTextλ₯Ό 직접 λ°›κΈ° λ•Œλ¬Έμ— λ”λŠ” λ¬Έμ œκ°€ λ˜μ§€ μ•Šμ„ 것이닀. 이처럼 λ³΅μž‘ν•œ 쀑첩 ꡬ쑰λ₯Ό ν”Όν•˜κΈ° μœ„ν•΄μ„œ λ‹€λ₯Έ 방법을 μ‚¬μš©ν•΄λ„ λœλ‹€.
const createTask = (taskData, taskText) => {
  const generatedId = taskData.name;
  const createdTask = { id: generatedId, text: taskText };

  props.onAddTask(createdTask);
};
  • μ΄μ „μ²˜λŸΌ 각 ν•¨μˆ˜λ“€μ„ λ”°λ‘œ λ‘” λ‹€μŒμ—, taskTextλ₯Ό createTask ν•¨μˆ˜μ˜ 두 번째 인자둜 μΆ”κ°€ν•΄μ£ΌλŠ” 것이닀. λ”°λΌμ„œ μ»€μŠ€ν…€ ν›…μ—λŠ” 2개의 λ§€κ°œλ³€μˆ˜κ°€ ν•„μš”ν•˜κ²Œ λœλ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask
  );
};
  • ν˜„μž¬ createTask ν•¨μˆ˜λ₯Ό ν˜ΈμΆœν•˜λŠ” μ»€μŠ€ν…€ 훅은 ν•˜λ‚˜μ˜ λ§€κ°œλ³€μˆ˜λ§Œμ„ μ „λ‹¬ν•œλ‹€.

use-fetch.js

applyData(data);
  • 이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„  μžλ°”μŠ€ν¬λ¦½νŠΈ 내뢀에 μžˆλŠ” λ©”μ†Œλ“œμΈ bind()λ₯Ό μ‚¬μš©ν•˜λ©΄ λœλ‹€.

bind() λ©”μ†Œλ“œ μ‚¬μš©ν•˜κΈ°

  • μš°λ¦¬κ°€ sendTaskRequest에 μ „λ‹¬ν•˜λ €λŠ” createTask에 bind() λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•œλ‹€. 그러면 bind() λ©”μ†Œλ“œλŠ” ν•¨μˆ˜λ₯Ό 사전에 ꡬ성할 수 μžˆλ„λ‘ ν•΄μ€€λ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask.bind()
  );
};
  • μš°λ¦¬κ°€ sendTaskRequest 에 μ „λ‹¬ν•˜λ €λŠ” creatTask 에 bind()λ₯Ό ν˜ΈμΆœν•΄μ€€λ‹€. 그러면 bind λ©”μ†Œλ“œλŠ” ν•¨μˆ˜λ₯Ό 사전에 ꡬ성해쀄 것이닀.

    호좜 μ¦‰μ‹œ ν•¨μˆ˜κ°€ μ‹€ν–‰λ˜μ§€λŠ” μ•ŠλŠ”λ‹€. 이것은 μžλ°”μŠ€ν¬λ¦½νŠΈμ˜ 기본적인 λ©”μ†Œλ“œμ΄λ―€λ‘œ μ–΄λ–€ ν•¨μˆ˜μ— λŒ€ν•΄μ„œλ„ 사전에 κ΅¬μ„±ν•˜κΈ° μœ„ν•΄μ„œλŠ” bind()λ₯Ό μ‚¬μš©ν•  수 μžˆλ‹€.

const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask.bind(null)
  );
};
  • bind() λ©”μ†Œλ“œμ— λ³΄λ‚΄λŠ” 첫 번째 μΈμžλŠ” 싀행이 μ˜ˆμ •λœ ν•¨μˆ˜μ—μ„œ this μ˜ˆμ•½μ–΄λ₯Ό μ‚¬μš©ν•˜κ²Œ λ§Œλ“œλŠ” κ²ƒμ΄μ§€λ§Œ μ—¬κΈ°μ„œλŠ” ν•„μš”κ°€ μ—†κΈ° λ•Œλ¬Έμ— null둜 μ„€μ •ν•œλ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask.bind(null, taskText)
  );
};
  • bind() λ©”μ†Œλ“œμ— λ³΄λ‚΄λŠ” 두 번째 μΈμžλŠ” 호좜 μ˜ˆμ •μΈ ν•¨μˆ˜(createTask)κ°€ μ „λ‹¬λ°›λŠ” 첫 번째 μΈμžμ—¬μ•Όλ§Œ ν•œλ‹€.
const createTask = (taskData, taskText) => {
  ...
};
  • 즉, μ΄λ ‡κ²Œ taskTextλ₯Ό μ „λ‹¬ν•˜λ €λ©΄ bind() λ©”μ†Œλ“œμ˜ 두 번째 μΈμžμ— taskTextλ₯Ό μ „λ‹¬ν•΄μ„œ 제좜된 폼(enterTaskHandler)μ—μ„œ taskTextλ₯Ό 찾도둝 λ§Œλ“€λ©΄ λœλ‹€.
const enterTaskHandler = async (taskText) => {
  sendTaskRequest(
    {
      url: "https://react-http-9914f-default-rtdb.firebaseio.com/tasks.json",
      method: "POST",
      body: { text: taskText },
      headers: {
        "Content-Type": "application/json",
      },
    },
    createTask.bind(null, taskText)
  );
};
  • ν•¨μˆ˜κ°€ μ‹€μ œλ‘œ ν˜ΈμΆœλ˜λŠ” useFetch μ—μ„œ μ „λ‹¬λ˜λŠ” λ‹€λ₯Έ 인자의 경우 κ°„λ‹¨ν•˜κ²Œ 이 λ§€κ°œλ³€μˆ˜μ˜ λͺ©λ‘ 끝에 μΆ”κ°€ν•˜μ—¬ μ²˜λ¦¬ν•˜λ©΄ λœλ‹€.

use-fetch.js

const sendRequest = useCallback(async (requestConfig, applyData) => {
  ...
    const data = await response.json();
    applyData(data);
  ...
}, []);
  • μ΄κ²ƒμœΌλ‘œ μ»€μŠ€ν…€ ν›…μ˜ applyData 의 μœ μΌν•œ 인자둜 μ „λ‹¬λ˜λŠ” λ°μ΄ν„°λŠ” createTask의 두 번째 인자둜 μΆ”κ°€λœλ‹€. 이제 호좜이 되면, μ΄λŠ” μš°λ¦¬κ°€ μ›ν•˜λŠ” λͺ¨λ“  데이터λ₯Ό λ°›κ²Œ λœλ‹€.

정리

  • μ§€κΈˆκΉŒμ§€ HTTP μš”μ²­μ„ μ „μ†‘ν•˜λŠ” μ»€μŠ€ν…€ ν›…μ˜ 생성 방법에 λŒ€ν•΄ ν•™μŠ΅ν–ˆλ‹€. ν™•μ‹€νžˆ λ³΅μž‘ν•˜κ³  μ–΄λ €μš΄ 뢀뢄이 μžˆμ—ˆμ§€λ§Œ, 이런 λ°©μ‹μœΌλ‘œ μ»€μŠ€ν…€ 훅을 μ‚¬μš©ν•˜κ²Œ λœλ‹€λ©΄ μ€‘λ³΅λ˜λŠ” 둜직 특히 μƒνƒœ(state) μ„€μ •κ³Ό 같은 λ‘œμ§λ“€μ„ μ»€μŠ€ν…€ ν›…μœΌλ‘œ μ•„μ›ƒμ†Œμ‹±ν•  수 μžˆλ‹€λŠ” μ μ—μ„œ ν•„μš”ν•œ μž‘μ—…μž„μ„ μ•Œ 수 μžˆμ—ˆλ‹€. λ˜ν•œ 이λ₯Ό μ„œλ‘œ λ‹€λ₯Έ μ»΄ν¬λ„ŒνŠΈμ— μ μš©ν•΄μ„œ λ‹€μ–‘ν•œ μ’…λ₯˜μ˜ μš”μ²­μ„ 보내고, 응닡 데이터에 λŒ€ν•΄ λ‹€μ–‘ν•œ μž‘μ—…μ„ μ²˜λ¦¬ν•˜λ©° λ‘œλ”© 및 였λ₯˜ μƒνƒœλ₯Ό 곡유 λ‘œμ§μ„ 톡해 μ΅œμ ν™”ν•  수 μžˆμ—ˆλ‹€.

✦ 좜처


🚨 ν•΄λ‹Ή ν¬μŠ€νŒ…μ€ Udemy의 ⌜React μ™„λ²½ κ°€μ΄λ“œβŒŸ κ°•μ˜λ₯Ό 베이슀둜 ν•œ κΈ°λ‘μž…λ‹ˆλ‹€.
✍🏻 κ°•μ˜ git repo λ°”λ‘œκ°€κΈ°

profile
일단 곡뢀가 '적성'에 λ§žλŠ” 개발자. κ·Όμ„±μžˆμŠ΅λ‹ˆλ‹€.
post-custom-banner

0개의 λŒ“κΈ€