일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- 정수형타입
- 한국건설관리시스템
- 환경설정
- exception
- 생성자오버로드
- 객체 비교
- 자바
- 메소드오버로딩
- 컬렉션 타입
- 예외미루기
- abstract
- 오라클
- 어윈 사용법
- GRANT VIEW
- 컬렉션프레임워크
- cursor문
- EnhancedFor
- 인터페이스
- 대덕인재개발원
- 사용자예외클래스생성
- 추상메서드
- 집합_SET
- 제네릭
- NestedFor
- 참조형변수
- 예외처리
- 다형성
- oracle
- Java
- 자동차수리시스템
- Today
- Total
거니의 velog
(4) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 4 본문
(3) API 연동하기
* 이제 API를 연동해 보자. axios를 사용하여 API를 연동해 보자. 그리고 리덕스에서 비동기 작업을 쉽게 관리하기 위해 redux-saga와 이전에 만들어서 사용했던 createRequestSaga 유틸 함수를 이용해 보겠다.
* 먼저 필요한 라이브러리를 설치해 주자.
$ yarn add axios redux-saga
{
"name": "blog-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.19.0", // axios 추가
"immer": "^3.1.3",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.1.0",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"redux": "^4.0.1",
"redux-actions": "^2.6.5",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^1.0.3", // redux-saga 추가
"styled-components": "^4.3.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
}
* 이번 프로젝트에서 사용되는 API의 수가 적은 편이므로 모든 API를 함수로 만들고 하나의 파일에 넣어서 관리해도 큰 지장은 없다. 하지만 유지 보수성을 높이기 위해 기능별로 파일을 나누어서 작성해 볼 것이다.
* src/lib/api 디렉터리를 만들고 그 안에 기능별로 파일을 따로따로 생성하겠다.
[1] axios 인스턴스 생성
* API 함수를 작성하기 전에 먼저 해야 할 작업이 있다. 바로 axios 인스턴스를 생성하는 것이다.
import axios from 'axios';
const client = axios.create();
/*
글로벌 설정 예시:
// API 주소를 다른 곳으로 사용함
client.defaults.baseURL = 'https://external-api-server.com/'
// 헤더 설정
client.defaults.headers.common['Authorization'] = 'Bearer a1b2c3d4';
// 인터셉터 설정
axios.intercepter.response.use(\
response => {
// 요청 성공 시 특정 작업 수행
return response;
},
error => {
// 요청 실패 시 특정 작업 수행
return Promise.reject(error);
}
})
*/
export default client;
* 이렇게 axios 인스턴스를 만들면 나중에 API 클라이언트에 공통된 설정을 쉽게 넣어 줄 수 있다. 사실 인스턴스를 만들지 않아도 이러한 작업을 할 수 있다. 하지만 인스턴스를 만들지 않으면 애플리케이션에서 발생하는 모든 요청에 대해 설정하게 되므로, 또 다른 API 서버를 사용하려 할 때 곤란해 질 수 있다. 따라서 처음 개발할 때부터 이렇게 인스턴스를 만들어서 작업하는 것을 권장한다.
* 추가로 나중에 axios를 사용하지 않는 상황이 왔을 때 쉽게 클라이언트를 교체할 수 있는 것 또한 장점이다.
[2] 프록시 설정
* 현재 백엔드 서버는 4000 포트, 리액트 개발 서버는 3000 포트로 열려 있기 때문에 별도의 설정 없이 API를 호출하려고 하면 오류가 발생한다. 이 오류를 CORS(Cross Origin Request) 오류라고 부르는데, 네트워크 요청을 할 때 주소가 다른 경우에 발생한다. 이 오류를 해결하려면 다른 주소에서도 API를 호출할 수 있도록 서버 쪽 코드를 수정해야 한다. 그런데 최종적으로 프로젝트를 다 완성하고 나면 결국 리액트 앱도 같은 호스트에서 제공할 것이기 때문에 이러한 설정을 하는 것은 불필요하다.
* 그 대신 프록시(proxy)라는 기능을 사용할 것이다. 웹팩 개발 서버에서 지원하는 기능인데, 개발 서버로 요청하는 API들을 우리가 프록시로 설정해 둔 서버로 그대로 전달해 주고 그 응답을 웹 애플리케이션에서 사용할 수 있게 해 준다.
* CRA로 만든 프로젝트에서 프록시를 설정할 때는 package.json 파일을 수정하면 된다. 해당 파일을 열어 다음 내용을 추가하자.
{
"name": "blog-frontend",
"version": "0.1.0",
"private": true,
"dependencies": {
"axios": "^0.19.0",
"immer": "^3.1.3",
"react": "^16.8.6",
"react-dom": "^16.8.6",
"react-redux": "^7.1.0",
"react-router-dom": "^5.0.1",
"react-scripts": "3.0.1",
"redux": "^4.0.1",
"redux-actions": "^2.6.5",
"redux-devtools-extension": "^2.13.8",
"redux-saga": "^1.0.3",
"styled-components": "^4.3.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"proxy": "http://localhost:4000/"
}
* 이제 리액트 애플리케이션에서 client.get('api/posts')를 하면, 웹팩 개발 서버가 프록시 역할을 해서 http://localhost:4000/api/posts에 대신 요청한 뒤 결과물을 응답해 준다.
[3] API 함수 작성
* 프록시 설정이 끝났으면 개발 서버를 껐다가 다시 실행해 주자. 이어서 회원 인증에 필요한 API를 사용하기 쉽도록 함수화하여 파일로 작성해 줄 것이다.
[lib/api/auth.js]
import client from './client';
// 로그인
export const login = ({ username, password }) =>
client.post('/api/auth/login', { username, password });
// 회원가입
export const register = ({ username, password }) =>
client.post('/api/auth/register', { username, password });
// 로그인 상태 확인
export const check = () => client.get('/api/auth/check');
[4] 더 쉬운 API 요청 상태 관리
* 다음으로 redux-saga를 통해 더 쉽게 API를 요청할 수 있도록 loading 리덕스 모듈과 createRequestSaga 유틸 함수를 설정하겠다.
* 먼저 loading 리덕스 모듈을 작성하자.
[modules/loading.js]
import { createAction, handleActions } from 'redux-actions';
const START_LOADING = 'loading/START_LOADING';
const FINISH_LOADING = 'loading/FINISH_LOADING';
/*
요청을 위한 액션 타입을 payload로 설정합니다 (예: "sample/GET_POST")
*/
export const startLoading = createAction(
START_LOADING,
(requestType) => requestType,
);
export const finishLoading = createAction(
FINISH_LOADING,
(requestType) => requestType,
);
const initialState = {};
const loading = handleActions(
{
[START_LOADING]: (state, action) => ({
...state,
[action.payload]: true,
}),
[FINISH_LOADING]: (state, action) => ({
...state,
[action.payload]: false,
}),
},
initialState,
);
export default loading;
* 이 리듀서를 만든 다음에는 루트 리듀서에도 등록해 주자.
[modules/index.js]
import { combineReducers } from 'redux';
import auth from './auth';
import loading from './loading';
const rootReducer = combineReducers({
auth,
loading,
});
export default rootReducer;
* 이어서 lib 디렉터리에 새 파일을 만들고 createRequestSaga 함수를 작성하자.
import { call, put } from 'redux-saga/effects';
import { startLoading, finishLoading } from '../modules/loading';
export default function createRequestSaga(type, request) {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return function* (action) {
yield put(startLoading(type)); // 로딩 시작
try {
const response = yield call(request, action.payload);
yield put({
type: SUCCESS,
payload: response.data,
});
} catch (e) {
yield put({
type: FAILURE,
payload: e,
error: true,
});
}
yield put(finishLoading(type)); // 로딩 끝
};
}
[5] auth 리덕스 모듈에서 API 사용하기
* 그럼 방금 만든 유틸 함수를 사용하여 auth 리덕스 모듈에서 API를 사용할 수 있도록 구현하겠다.
* 우선 다음과 같이 여섯 가지 액션 타입을 추가로 더 선언해야 한다.
[modules/auth.js]
import { createAction, handleActions } from 'redux-actions';
import produce from 'immer';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
const REGISTER = 'auth/REGISTER';
const REGISTER_SUCCESS = 'auth/REGISTER_SUCCESS';
const REGISTER_FAILURE = 'auth/REGISTER_FAILURE';
const LOGIN = 'auth/LOGIN';
const LOGIN_SUCCESS = 'auth/LOGIN_SUCCESS';
const LOGIN_FAILURE = 'auth/LOGIN_FAILURE';
(...)
* 각 요청마다 액션 타입을 세 개 선언해야 하는데, 같은 작업이 조금 반복된다. 코드를 반복해서 작성하는 것이 사람에 따라 조금 귀찮을 수 있다. 이와 같은 경우 액션 타입을 한꺼번에 만드는 함수를 선언하는 방법도 있다. 위 코드를 한번 리팩토링해 보자.
* 우선 createRequestSaga.js 파일을 열고 그 내부에 createRequestActionTypes 라는 함수를 선언하여 내보내자.
[lib/createRequestSaga.js]
import { call, put } from 'redux-saga/effects';
import { startLoading, finishLoading } from '../modules/loading';
export const createRequestActionTypes = (type) => {
const SUCCESS = `${type}_SUCCESS`;
const FAILURE = `${type}_FAILURE`;
return [type, SUCCESS, FAILURE];
};
export default function createRequestSaga(type, request) {
(...)
}
* 이 함수를 사용하면 요청에 관련된 액션 타입들을 선언할 때 다음과 같이 작성할 수 있다.
[modules/auth.js]
import { createAction, handleActions } from 'redux-actions';
import produce from 'immer';
import { createRequestActionTypes } from '../lib/createRequestSaga';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
const [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE] =
createRequestActionTypes('auth/REGISTER');
const [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE] =
createRequestActionTypes('auth/LOGIN');
* 어떤가? 중복되는 코드들이 많이 사라졌다. 취향에 따라 이렇게 리팩토링하여 사용해도 되고, const를 세 번 사용하여 선언해도 상관없다.
* 이제 createRequestSata를 통해 각 API를 위한 사가를 생성하고, 액션 생성 함수와 리듀서도 구현해 보겠다.
[modules/auth.js]
import { createAction, handleActions } from 'redux-actions';
import produce from 'immer';
import { takeLatest } from 'redux-saga/effects';
import createRequestSaga, {
createRequestActionTypes,
} from '../lib/createRequestSaga';
import * as authAPI from '../lib/api/auth';
const CHANGE_FIELD = 'auth/CHANGE_FIELD';
const INITIALIZE_FORM = 'auth/INITIALIZE_FORM';
const [REGISTER, REGISTER_SUCCESS, REGISTER_FAILURE] =
createRequestActionTypes('auth/REGISTER');
const [LOGIN, LOGIN_SUCCESS, LOGIN_FAILURE] =
createRequestActionTypes('auth/LOGIN');
export const changeField = createAction(
CHANGE_FIELD,
({ form, key, value }) => ({
form, // register , login
key, // username, password, passwordConfirm
value, // 실제 바꾸려는 값
}),
);
export const initializeForm = createAction(INITIALIZE_FORM, (form) => form); // register / login
export const register = createAction(REGISTER, ({ username, password }) => ({
username,
password,
}));
export const login = createAction(LOGIN, ({ username, password }) => ({
username,
password,
}));
// saga 생성
const registerSaga = createRequestSaga(REGISTER, authAPI.register);
const loginSaga = createRequestSaga(LOGIN, authAPI.login);
export function* authSaga() {
yield takeLatest(REGISTER, registerSaga);
yield takeLatest(LOGIN, loginSaga);
}
const initialState = {
register: {
username: '',
password: '',
passwordConfirm: '',
},
login: {
username: '',
password: '',
},
auth: null,
authError: null,
};
const auth = handleActions(
{
[CHANGE_FIELD]: (state, { payload: { form, key, value } }) =>
produce(state, (draft) => {
draft[form][key] = value; // 예: state.register.username을 바꾼다
}),
[INITIALIZE_FORM]: (state, { payload: form }) => ({
...state,
[form]: initialState[form],
authError: null, // 폼 전환 시 회원 인증 에러 초기화
}),
// 회원가입 성공
[REGISTER_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth,
}),
// 회원가입 실패
[REGISTER_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
// 로그인 성공
[LOGIN_SUCCESS]: (state, { payload: auth }) => ({
...state,
authError: null,
auth,
}),
// 로그인 실패
[LOGIN_FAILURE]: (state, { payload: error }) => ({
...state,
authError: error,
}),
},
initialState,
);
export default auth;
* 구현할 때 로딩에 관련된 상태는 이미 loading 리덕스 모듈에서 관리하므로, 성공했을 때와 실패했을 때의 상태에 대해서만 신경 쓰면 된다.
* 리덕스 모듈을 저장했으면 프로젝트의 rootSaga를 만들어 주자.
[modules/index.js]
import { combineReducers } from 'redux';
import { all } from 'redux-saga/effects';
import auth, { authSaga } from './auth';
import loading from './loading';
const rootReducer = combineReducers({
auth,
loading,
});
export function* rootSaga() {
yield all([authSaga()]);
}
export default rootReducer;
* 다음으로 스토어에 redux-saga 미들웨어를 적용하자.
[src/index.js]
import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import { BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import { composeWithDevTools } from 'redux-devtools-extension';
import createSagaMiddleware from 'redux-saga';
import rootReducer, { rootSaga } from './modules';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
rootReducer,
composeWithDevTools(applyMiddleware(sagaMiddleware)),
);
sagaMiddleware.run(rootSaga);
ReactDOM.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>,
document.getElementById('root'),
);
* 이제 회원가입 및 로그인 기능을 구현하는 데 필요한 리덕스 관련 코드의 준비를 모두 마쳤다.
'React > React_프론트엔드 프로젝트' 카테고리의 다른 글
(6) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 6 (0) | 2024.02.22 |
---|---|
(5) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 5 (0) | 2024.02.22 |
(3) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 3 (0) | 2024.02.22 |
(2) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 2 (0) | 2024.02.21 |
(1) 프론트엔드 프로젝트 : 시작 및 회원 인증 구현 1 (0) | 2024.02.21 |