Context와 API 연동하는 방법 !

단셰·2023년 3월 26일
0

Front-End (Basic Study)

목록 보기
16/16
post-thumbnail

컴포넌트에서 필요한 외부 데이터들은 컴포넌트 내부에서 useAsync 같은 Hook을 사용해서 작업하면 충분하지만, 가끔 특정 데이터들은 다양한 컴포넌트에서 필요하게 될 때도 있는데 (예: 현재 로그인 된 사용자의 정보 등,,) 그럴 때 Context를 사용하면 개발이 편해진다.

Context 를 여러 번 이용해봤지만 자꾸 잊어먹어서 기억 되살리기 겸 다시 공부해보는 겸 기록해 봅니다.,,
어쩜 볼 때 마다 새로운지 ㅎㅎ 눈물 ,,,,,,, 😇

Context 준비하기

src 폴더에 UsersContext.js 파일을 만들고, 다음 코드의 주석을 꼼꼼히 읽어가며 코드 따라 작성해보기!

UsersContext.js

import React, {CreateContext, useReducer, useContext} from 'react';

// UsersContext 에서 사용할 초기 상태
const initialState = {
	users: {
		loading: false,
		data: null,
		error: null
	},
	user: {
		loading: false,
		data: null,
		error: null
	}
};

// 성공 시, 상태 만들어주는 함수
const success = data => ({
		loading: false,
		data,
		error: null
})

// 실패 시, 상태 만들어주는 함수
const error = data => ({
		loading: false,
		data: null,
		error: error
}) 

// 위에서 만든 객체 / 유틸 함수들을 사용하여 리듀서 작성
function usersReducer(state, action){
	switch (action.type){
		case 'GET_USERS':
			return {
			...state,
			users: loadingState
			};
		case 'GET_USERS_SUCCESS':
			return {
			...state,
			users: success(action.data)
			};
		case 'GET_USERS_ERROR':
			return {
			...state,
			users: error(action.error)
			};
		case 'GET_USER':
			return {
			...state,
			user: loadingState
			};
		case 'GET_USER_SUCCESS':
			return {
			...state,
			user: success(action.data)
			};
		case 'GET_USER_ERROR':
			return {
			...state,
			user: error(action.error)
			};
		default:
			throw new Error(`Unhanded action type: ${action.type}`);
	}
}

// State 용 Context와 Dispatch용 Context 따로 만들기
const UsersStateContext = createContext(null);  /* Context용 Context */
const UserDispatchContext = createContext(null);  /* Dispatch용 Context */

// 위에 선언한 두가지 Context 들의 Provider로 감싸는 컴포넌트
export function UsersProvider({children}) {
	const [state, dispatch] = useReducer(usersReducer, initalState);
	return (
		<UsersStateContext.Provider value={state}>
			<UsersDispatch.Provider value={dispatch}>
				{children}
			</UsersDispatch.Provider>
		</UsersStateContext.Provider>
	);
}

// State를 쉽게 조회할 수 있게 해주는 커스텀 훅(Hook)
export function useUsersState(){
	const state = useContext(UsersStateContext);
	if(!state) {
		throw new Error('Cannot find UsersProvider');
	}
	return state;
}

// Dispatch를 쉽게 사용할 수 있게 해주는 커스텀 훅(Hook)
export function useUserDispatch() {
	const dispatch = useContext(UsersDispatchContext);
	if(!dispatch) {
		throw new Error('Cannot find UsersProvider');
	}
	return dispatch;
}

만약 id를 가지고 특정 사용자의 정보를 가져오는 API를 호출하고 싶다면 아래와 같은 형식으로 작성해야 한다.

dispatch({ type: 'GET_USER' });
try {
	const response = await getUser();
	dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
} catch (e) {
	dispatch({ type: 'GET_USER_ERROR', error: e });
}

위의 코드 설명 : 요청이 시작했을 때 action을 dispatch 해주고, 요청이 성공하거나 실패했을 때 또 다시 dispatch 해주는 것임 !

💡 여기서 reducer란 ?
⇒ 현재 상태(state)액션 객체(action)를 파라미터로 받아와서 새로운 상태를 반환해주는 함수
⇒ reducer 에서 반환하는 상태(state)는 곧 컴포넌트가 지닐 새로운 상태
⇒ reducer 에서 action은 업데이트를 위한 정보를 가지고 있음 (주로 type 값을 지닌 객체 형태로 사용하나, 꼭 따라야 할 규칙은 없음. 대부분 대문자와 _ 로 구성하는 관습)

useReducer 사용법 ?

const [state, dispatch] = useReducer(reducer, initalState); 

state ⇒ 앞으로 컴포넌트에서 사용할 수 있는 상태
dispatch ⇒ 액션을 발생시키는 함수

API 처리 함수 만들기

위의 작업을 처리하는 함수를 만들어 주자.
UsersContext.js 를 열어서 상단 axios를 import 해오고, 코드의 하단 부분에 getUersgetUser 함수를 작성한다.
이 함수들은 dispatch를 parameter 로 받아오고, API에 필요한 parameter 도 받아오게 된다.

/* UsersContext.js */

import React, { createContext, useReducer, useContext } from 'react';
import axios from 'axios';

export async function getUsers(dispatch) {
	dispatch({ type: 'GET_USERS'});
	try {
		const response = await axios.get(
		'https://jsonplaceholder.typicode.com/users'
		)
		dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
	} catch (e) {
		dispatch( { type: 'GET_USERS_ERROR', error: e } )
	}
}

export async function getUser(dispatch, id) {
	dispatch({type: 'GET_USER'});
	try {
		const response = await axios.get(
		'https://jsonplaceholder.typicode.com/users/${id}'
		)
		dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
	} catch (e) {
		dispatch( { type: 'GET_USERS_ERROR', error: e } )
	}
}

뒤에서 위의 이 부분은 코드 리팩토링하여 작성해둠. 이해를 위해 여기서는 이대로 두겠슴다.

Context 사용하기

맨 위에 만들어 둔 Context.js 를 사용해보자. App.js 를 열어서 UsersProvider로 감싸준다.

App.js

import Users from './Users';
import { UsersProvider } from './UsersContext';

const App () => {
	return (
		<UsersProvider>
			<Users />
		</UsersProvider>
	);
}

export default App;

그 다음, Users 컴포넌트의 코드를 Context 를 사용하는 형태로 코드를 전환.

Users.js

import React, { useState } from 'react';
import { useUsersState, useUsersDispatch, getUsers } from './UsersContext';
import User from './User';

const Users = () => {
	const [userId, setUserId] = useState(null);
	const state = useUsersState();
	const dispatch = useUsersDispatch();

	const { data: users, loading, error} = state.users;
	const fetchData = () => {
		getUsers(dispatch);
	};

	if(loading) return <div>loading ...</div>;
	if(error) return <div>Error !</div>;
	if(!users) return <button onClick={fetchData}>불러오기</button>;

	
	return (
		<>
			<ul>
				{users && users.map(user => (
					<li key={user.id} onClick={() => setUserId(user.id)}>
						{user.username} ({user.name})
					</li>
				)}
			</ul>
				<button onClick={fetchData}>다시 불러오기</button>
				{userId && <User id={userId} />}
		</>
	);
}

export default Users;

useUsersState()useUsersDispatch() 를 사용해서 statedispatch 를 가져오고, 요청을 시작할 때는 getUsers() 함수 안에 dispatch를 넣어서 호출해주었음.,,

함수 명이 비슷하다보니 점점 헷갈리기 시작한다 …….^^
중도포기 하지말고 끝까지 해봅시다 …….!!!!!!!! 😇

User.js

import React, { useState } from 'react';
import { useUsersState, useUsersDispatch, getUser } from './UsersContext';

const User = ({id}) => {
	const state = useUsersState();
	const dispatch = useUsersDispatch();

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

	const { data: user, loading, error} = state.user;

	if(loading) return <div>loading ...</div>;
	if(error) return <div>Error !</div>;
	if(!user) return null;

	
	return (
		<>
			<h2>{user.username}</h2>
			<p>
				<h2>Email: {user.email}</h2>
			</p>
		</>
	);
}

export default User;

코드 리팩토링 !

반복되는 로직을 함수화하여 재사용성 있게 코드를 다시 작성해봅시당
아까 리팩토링 한다고 한 코드 일부를 가져와보겠습니다.

반복되는 코드들

/* UsersContext.js */

import React, { createContext, useReducer, useContext } from 'react';
import axios from 'axios';

export async function getUsers(dispatch) {
	dispatch({ type: 'GET_USERS'});
	try {
		const response = await axios.get(
		'https://jsonplaceholder.typicode.com/users'
		)
		dispatch({ type: 'GET_USERS_SUCCESS', data: response.data });
	} catch (e) {
		dispatch( { type: 'GET_USERS_ERROR', error: e } )
	}
}

export async function getUser(dispatch, id) {
	dispatch({type: 'GET_USER'});
	try {
		const response = await axios.get(
		'https://jsonplaceholder.typicode.com/users/${id}'
		)
		dispatch({ type: 'GET_USER_SUCCESS', data: response.data });
	} catch (e) {
		dispatch( { type: 'GET_USERS_ERROR', error: e } )
	}
}

위의 코드는 앞 부분에 작성했던 코드입니다. 이 부분을 리팩토링 해볼게요 !
먼저, api 들이 들어있는 파일들을 따로 분리해주겠습니다. src 폴더에 api.js 를 만듭니당.

api.js

import axios from 'axios';

export async function getUsers() {
  const response = await axios.get(
    'https://jsonplaceholder.typicode.com/users'
  );
  return response.data;
}

export async function getUser(id) {
  const response = await axios.get(
    `https://jsonplaceholder.typicode.com/users/${id}`
  );
  return response.data;
}

그 다음, scr 폴더에 asyncActionUtils.js 라는 파일을 만들고 아래와 같이 코드를 작성해줍니다.

asyncActionUtils.js

export async function createAsyncDispatcher(type, promiseFn) {
	// success, error에 대한 action 타입 문자열 작성
  const SUCCESS = `${type}_SUCCESS`;
  const ERROR = `${type}_ERROR`;

	// 새로운 함수 생성
	// ...rest 사용하여 나머지 parameter를 rest 배열에 담기
	async function actionHandler(dispatch, ...rest) {
		dispatch({ type });  // 요청 시작
		try {
			const data = await promisFn(...rest);  // rest 배열을 spread 로 넣어주기
			dispatch({
				type: SUCCESS,
				data
			});  // SUCCESS
		} catch (e) {
			dispatch({
				type: ERROR,
				error: e
		});  // ERROR
  }
}

return actionHandler;  // 만든 함수 리턴
}

UsersContext.js

import React, { createContext, useReducer, useContext } from 'react';
import createAsyncDispatcher from './createAsyncDispatcher';
import * as api from './api'; // api 파일에서 내보낸 모든 함수들을 불러옴

(...)

export const getUsers = createAsyncDispatcher('GET_USERS', api.getUsers);
export const getUser = createAsyncDispatcher('GET_USER', api.getUser);

Context API 이용하여 외부 API 와 연동하기 끝 !
(리팩토링 할 부분이나 덧붙일 부분이 있으면 추후에 추가하도록 하겠숨 ,,, 이만 할 일이 산더미여서 ,,,,)

profile
Happy Hacking!

0개의 댓글