[Front-end] 개발자 공부

[개발 공부 71일차] 비동기 DAY | React Query, Thunk, Promise

MOLLY_ 2024. 6. 12. 17:19
728x90

< 목차 >
1. React Thunk
2. Promise
3. React Query

4. 금일 소감

 

 

 

 

1. React Thunk

 

: Redux의 *미들웨어로, 비동기 작업(예: API 호출)을 쉽게 처리할 수 있도록 도와줌

 

Thunk는 액션 크리에이터가 단순히 액션 객체를 반환하는 대신 함수를 반환할 수 있게 해준다.

 

 

* 미들웨어?

  • 정의: 요청과 응답 사이에서 '중간 역할'을 하는 코드
  • 이유: 요청/응답 처리, 코드 재사용, 유지보수성 향상
  • 예시: 요청 로깅, 인증, 데이터 검증 등

 

 

TL;DR

  1. 정의: Redux 미들웨어로, 액션 크리에이터에서 함수를 반환할 수 있게 해줌
  2. 이유: 비동기 작업 처리, 복잡한 액션 로직 분리, 상태와 디스패치 접근 가능
  3. 사용법: Thunk 설치 및 미들웨어 적용, 비동기 액션 크리에이터 작성, 컴포넌트에서 디스패치

 

 

사용하는 이유

  • 비동기 로직 처리: API 호출 같은 비동기 작업을 Redux 액션에서 직접 처리할 수 있게 해줌
  • 복잡한 액션 로직 분리: 액션 크리에이터 안에서 여러 액션을 순차적으로 디스패치 가능
  • 상태와 디스패치에 접근: Thunk 함수 내부에서 현재 Redux 상태와 dispatch 함수에 접근할 수 있음

 

 

사용법

(1) 설치

npm install redux-thunk

 

 

(2) 미들웨어 적용

import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducers';

const store = createStore(rootReducer, applyMiddleware(thunk));

 

 

(3) 비동기 액션 크리에이터 작성

// actions.js

export const fetchData = () => {
  return async (dispatch) => {
    dispatch({ type: 'FETCH_DATA_REQUEST' });
    try {
      const response = await fetch('https://api.example.com/data');
      const data = await response.json();
      dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
    } catch (error) {
      dispatch({ type: 'FETCH_DATA_FAILURE', error });
    }
  };
};

 

 

(4) 컴포넌트에서 디스패치

// MyComponent.js

import React, { useEffect } from 'react';
import { useDispatch, useSelector } from 'react-redux';
import { fetchData } from './actions';

const MyComponent = () => {
  const dispatch = useDispatch();
  const data = useSelector(state => state.data);

  useEffect(() => {
    dispatch(fetchData());
  }, [dispatch]);

  return (
    <div>
      {data.loading ? 'Loading...' : data.items.map(item => <div key={item.id}>{item.name}</div>)}
    </div>
  );
};

export default MyComponent;


 

 

[Reference]

  1. [ExpressJS] Using middleware - https://expressjs.com/en/guide/using-middleware.html
  2. [Positronx.io] Handle API Calls with Thunk Middleware - https://www.positronx.io/react-redux-handle-api-calls-with-thunk-middleware-tutorial
  3. [Positronx.io] Handle API Calls with Thunk Middleware - https://www.positronx.io/react-redux-handle-api-calls-with-thunk-middleware-tutorial
  4. [FreeCodeCamp] How to Work with Redux-Thunk - https://www.freecodecamp.org/news/how-to-work-with-redux-thunk
  5. [FreeCodeCamp] Redux Thunk Explained with Examples - https://www.freecodecamp.org/news/redux-thunk-explained-with-examples
  6. [redux.js.org] Redux Fundamentals, Part 6: Async Logic and Data Fetching - https://redux.js.org/tutorials/fundamentals/part-6-async-logic
  7. [redux.js.org] Writing Logic with Thunks - https://redux.js.org/usage/writing-logic-thunks
  8. [DigitalOcean] Understanding Asynchronous Redux Actions with Redux Thunk - https://www.digitalocean.com/community/tutorials/redux-redux-thunk

 

 

2. Promise

: 비동기 작업의 완료 또는 실패를 처리하기 위한 JavaScript 객체

비동기 작업이 성공하면 결과 값을, 실패하면 오류를 제공한다.

 

 

TL;DR

  • 정의: 비동기 작업의 완료 또는 실패를 처리하는 객체
  • 이유: 비동기 코드의 가독성 향상, 작업 체이닝, 에러 처리
  • 종류: Pending, Fulfilled, Rejected
  • 사용법: then과 catch로 결과 처리, 체이닝, Promise.all과 Promise.race로 병렬 실행

Promise를 사용하면 비동기 작업을 효율적으로 관리하고, 가독성 높은 코드를 작성 가능

 


사용하는 이유

  • 비동기 코드의 가독성 향상: 콜백 지옥을 피하고, 코드의 가독성을 높여줌
  • 비동기 작업 체이닝: then과 catch를 사용하여 연속적인 비동기 작업을 처리할 수 있음
  • 에러 처리: 비동기 작업에서 발생하는 에러를 체계적으로 처리할 수 있음

 

종류

  • Pending: 초기 상태, 이행하거나 거부되지 않은 상태
  • Fulfilled: 작업이 성공적으로 완료된 상태
  • Rejected: 작업이 실패한 상태

 

 

사용법

(1) Promise 생성

const myPromise = new Promise((resolve, reject) => {
  // 비동기 작업 수행
  if (/* 성공 조건 */) {
    resolve('성공 결과');
  } else {
    reject('실패 이유');
  }
});

 

 

(2) then과 catch로 결과 처리

myPromise
  .then(result => {
    console.log('성공: ', result);
  })
  .catch(error => {
    console.log('실패: ', error);
  });

 

 

(3) 비동기 작업 체이닝

myPromise
  .then(result => {
    console.log('첫 번째 성공: ', result);
    return anotherAsyncFunction(); // 또 다른 Promise 반환
  })
  .then(anotherResult => {
    console.log('두 번째 성공: ', anotherResult);
  })
  .catch(error => {
    console.log('실패: ', error);
  });

 

 

(4) Promise.all 사용

여러 개의 Promise를 병렬로 실행하고, 모두 완료될 때까지 기다림

const promise1 = Promise.resolve(3);
const promise2 = 42;
const promise3 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'foo');
});

Promise.all([promise1, promise2, promise3]).then(values => {
  console.log(values); // [3, 42, "foo"]
});

 

 

(5) Promise.race 사용

여러 개의 Promise 중 가장 빨리 완료된 것의 결과를 반환

const promise1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 500, 'one');
});
const promise2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 100, 'two');
});

Promise.race([promise1, promise2]).then(value => {
  console.log(value); // "two"
});

 

 

[Reference]

 

 

3. React Query

 

서버 상태를 쉽게 관리하고, 데이터를 페칭, 캐싱, 동기화 및 업데이트하는 데 도움을 주는 React 라이브러리

 

이를 통해 '비동기 작업을 간단하고 효율적으로 처리'할 수 있다.

 

 

TL;DR

  1. 정의: 버 상태를 관리하고 비동기 작업을 처리하는 React 라이브러리
  2. 이유: 데이터 페칭 관리, 자동 리프레시, 복잡한 비동기 로직 간소화, 최적화된 성능
  3. 종류: Queries (데이터 가져오기), Mutations (데이터 보내기), Query Invalidation (캐시 무효화)
  4. 사용법: 설치 및 설정, useQuery로 데이터 페칭, useMutation으로 데이터 업데이트

React Query를 사용하면 서버와의 데이터 동기화 작업이 단순해지고, 코드의 가독성 및 유지보수성이 향상된다.

 

 

사용하는 이유

  • 데이터 페칭 관리: API 요청을 쉽게 관리하고 자동으로 데이터를 캐싱
  • 자동 리프레시: 데이터 변경 시 자동으로 업데이트되어 최신 상태를 유지
  • 복잡한 비동기 로직 간소화: 복잡한 비동기 로직을 간단하게 처리할 수 있음
  • 최적화된 성능: 중복된 네트워크 요청을 방지하고, 필요한 시점에만 데이터를 불러옴

 

종류

  • Queries: 서버에서 데이터를 가져오고 캐싱하는 데 사용
  • Mutations: 서버에 데이터를 보내고 업데이트하는 데 사용
  • Query Invalidation: 특정 조건에 따라 캐시를 무효화하고 데이터를 다시 가져옴

 

 

사용법

(1) 설치

npm install react-query

 

 

(2) React Query 설정

// index.js 또는 App.js

import { QueryClient, QueryClientProvider } from 'react-query';

const queryClient = new QueryClient();

function App() {
  return (
    <QueryClientProvider client={queryClient}>
      <YourComponent />
    </QueryClientProvider>
  );
}

export default App;

 


(3) Queries 사용

// YourComponent.js

import { useQuery } from 'react-query';
import axios from 'axios';

const fetchTodos = async () => {
  const { data } = await axios.get('https://jsonplaceholder.typicode.com/todos');
  return data;
};

function YourComponent() {
  const { data, error, isLoading } = useQuery('todos', fetchTodos);

  if (isLoading) return <div>Loading...</div>;
  if (error) return <div>Error: {error.message}</div>;

  return (
    <div>
      {data.map(todo => (
        <div key={todo.id}>{todo.title}</div>
      ))}
    </div>
  );
}

export default YourComponent;

 

 

(4) Mutations 사용

import { useMutation, useQueryClient } from 'react-query';
import axios from 'axios';

const addTodo = async (newTodo) => {
  const { data } = await axios.post('https://jsonplaceholder.typicode.com/todos', newTodo);
  return data;
};

function AddTodoComponent() {
  const queryClient = useQueryClient();
  const mutation = useMutation(addTodo, {
    onSuccess: () => {
      queryClient.invalidateQueries('todos');
    },
  });

  const handleAddTodo = () => {
    mutation.mutate({ title: 'New Todo', completed: false });
  };

  return (
    <button onClick={handleAddTodo}>
      Add Todo
    </button>
  );
}

export default AddTodoComponent;

 

 

[Reference]

  1. [TkDodo's blog] Inside React Query - https://tkdodo.eu/blog/inside-react-query
  2. [Tanstack] Quick Start - https://tanstack.com/query/latest/docs/framework/react/quick-start

 

 

4. 금일 소감

ㅋㅋㅋㅋㅋㅋㅋ 나참,, 아직도 비동기가 어렵다니.. 다른 강의 내용은 수월하게 들었는데 비동기 통신 내용은 보고도

 

'어..?'

 

'어..?' 이러고 있다. 결국 어려운 부분만 다시 정리... 레퍼런스 내용도 어렵다 ㅋㅋㅋㅋㅋㅋ 하지만 어려운 내용 이해했을 때의 쾌감은 이루 말할 수 없으니... 열심히 여러 번 보는 수밖에..

 

제발 이해 좀 되달라구..! 제길...

 

어떤 내용은 조금 봐도 이해가 바로 되는 반면, 어떤 내용은 비동기 통신처럼 여러 번 봐도 어렵게 느껴지는 게 있는 듯하다. 반복학습만이 답... 예시 코드도 자주 보고, 개념도 여러 번 봐야겠다.

 

728x90