먼저 오늘 필요한 패키지들입니다
설치
npx create-react-app .
npm install react-router-dom redux redux-thunk react-redux axios styled-components
npm install -D redux-devtools-extension
간단한 백엔드 서버부터 먼저 만들어보겠습니다
Backend
CORS
처리에 특히 주의![server.js]
npm install express cors
const express = require("express");
const app = express();
const cors = require("cors");
app.use(cors({
origin: true,
credintials: true,
}));
app.use(express.json());
app.use((req, res, next) => {
console.log(req.method, req.path, req.body)
next()
});
// axios 요청에 대해 응답할 데이터
app.get("/categories", (req, res) => {
res.json([
{ path: "/", name: "Home" },
{ path: "/counter", name: "counter" },
]);
});
app.listen(3005, () => {
console.log(`backend server on 3005`);
});
서버 설정이 끝났으면 다음은 프론트 차례
리액트 프로젝트는 항상 라우터 설정부터 시작합니다
BrowserRouter
Routes
Route
NavLinkr
[./index.jsx]
import { BrowserRouter } from 'react-router-dom'
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
);
BrowserRouter
는 따로 분리해서 index의 최상단 컴포넌트로(App
컴포넌트를 감싸는 형태) 두는 것을 추천[./src/routes/AppRouter.jsx]
import { Routes, Route } from 'react-router-dom'
export const AppRouter = () => {
return (
<Routes>
<Route path="/" element={<Main />}></Route>
<Route path="/counter" element={<Counter />}></Route>
</Routes>
)
}
다음으로 Header 컴포넌트를 만들어서 <NavLink>
의 동작을 확인합니다
[./src/common/header/Header.jsx]
import { NavLink } from "react-router-dom"
export const Header = () => {
return <ul>
<li><NavLink to={"/"}>Main</NavLink></li>
<li><NavLink to={"/counter"}>Counter</NavLink></li>
</ul>
}
store
디렉토리를 생성하고 초기 세팅을 시작합니다Provider
, store
, rootReducer
[./src/store/Store.jsx]
createStore()
메서드는 redux
에서 불러옵니다import { createStore } from "redux";
const rootReducer = () => {
return { name: "web7722" }
}
export const store = createStore(rootReducer);
[./index.jsx]
Provider
는 react-redux
에서 불러옵니다import { Provider } from "react-redux";
import { store } from "./store"
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<Provider store={store}>
<BrowserRouter>
<App />
</BrowserRouter>
</Provider>
);
[./src/store/rootReducer.jsx]
combineReducers
의 인자로 빈객체를 넣은 것은 에러 방지를 위해서...import { combineReducers } from "redux"
export const rootReducer = combineReducers({})
redux devtools
를 설치합니다 (크롬 브라우저 익스텐션)npm install -D redux-devtools-extension
[./src/store/Store.jsx]
import { createStore } from "redux";
import { rootReducer } from "./rootReducer"
import { composeWithDevTools } from 'redux-devtools-extension'
export const store = createStore(rootReducer, composeWithDevTools());
먼저 category 디렉토리를 생성하겠습니다
[./src/store/category/reducer.jsx]
import {
CATEGORY_REQUEST_START,
CATEGORY_REQUEST_SUCCESS,
CATEGORY_REQUEST_ERROR,
} from "./types";
const initialState = {
loading: true,
error: null,
data: [],
};
export const category = (state = initialState, action) => {
switch (action.type) {
case CATEGORY_REQUEST_START:
return { ...state };
case CATEGORY_REQUEST_SUCCESS:
return { ...state, loading: false, error: null, data: action.payload };
case CATEGORY_REQUEST_ERROR:
return { ...state };
default:
return state;
}
};
[./src/store/rootReducer.jsx]
import { combineReducers } from "redux";
import { category } from "./category";
export const rootReducer = combineReducers({
category: category,
});
[./src/store/rootReducer.jsx]
import { createStore, applyMiddleware } from "redux";
import thunk from "redux-thunk";
import { rootReducer } from "./rootReducer";
import { composeWithDevTools } from "redux-devtools-extension";
export const store = createStore(
rootReducer,
// 불러온 thunk를 인자로 장착
composeWithDevTools(applyMiddleware(thunk))
);
미들웨어의 용도는 상태를 바꾸기 전에 요청 내용을 조작할 함수를 끼워넣기 위함
비동기요청으로 서버에 있는 카테고리 배열 데이터를 가져오는 것이 목표힙니다
먼저 axios 요청을 위한 사전세팅부터
[./src/utils/request.js]
import axios from "axios";
const request = axios.create({
baseURL: "http://localhost:3005",
withCredentials: true,
headers: {
"Content-type": "application/json",
},
});
export default request
액션과 리듀서는 다음과 같이 작성합니다
[./src/store/category/actions.jsx]
import {
CATEGORY_REQUEST_SUCCESS,
CATEGORY_REQUEST_ERROR,
CATEGORY_REQUEST_START,
} from "./types";
import request from "../../utils/request";
export const RequestSuccess = (payload) => ({
type: CATEGORY_REQUEST_SUCCESS,
payload,
});
export const RequestError = (payload) => ({
type: CATEGORY_REQUEST_ERROR,
payload,
});
// 고차함수의 첫번째 인자로는 props값을 전달받을 수 있습니다
// 지금 코드에서는 사용할 필요가 없지만 props를 전달받아야 할 상황을 대비
export const CategoryRequest = (props) => {
return async (dispatch) => {
// request start
dispatch({ type: CATEGORY_REQUEST_START });
try {
const response = await request.get("/categories");
console.log(response);
// request success
dispatch(RequestSuccess(response.data));
} catch (e) {
// request error
dispatch(RequestError(e));
}
};
};
// then() 함수를 사용해서 처리하는 방법. 위와 같은 코드입니다
// 둘 중 하나만 써야지 await, then 혼용하면 안돼요!!
// export const CategoryRequest = (props) => (dispatch) => {
// dispatch({ type: CATEGORY_REQUEST_START });
// request.get("/categories")
// .then(({data}) => dispatch(RequestSuccess(data)))
// .catch(error => dispatch(RequestError(error)))
// }
[./src/store/category/reducer.jsx]
import {
CATEGORY_REQUEST_START,
CATEGORY_REQUEST_SUCCESS,
CATEGORY_REQUEST_ERROR,
} from "./types";
const initialState = {
loading: true,
error: null,
data: [],
};
export const category = (state = initialState, action) => {
switch (action.type) {
case CATEGORY_REQUEST_START:
return { ...state, loading: true, error: null };
case CATEGORY_REQUEST_SUCCESS:
return { ...state, loading: false, error: null, data: action.payload };
case CATEGORY_REQUEST_ERROR:
return { ...state, loading: false, error: action.payload.message };
default:
return state;
}
};
[App.jsx]
import { useEffect } from "react";
import { AppRouter } from "./routes";
import { Header } from "./common";
import { useDispatch, useSelector } from "react-redux";
import { RequestSuccess } from "./store";
const App = () => {
// 고차함수 형태
const dispatch = useDispatch();
// 내가 원하는 상태값을 인자로 가져올 수 있습니다 (state는 전체 객체, 그 중에서 category만)
const { loading, error, data } = useSelector((state) => state.category);
useEffect(() => {
const payload = [
{ path: "/", name: "Home" },
{ path: "/counter", name: "Counter" },
];
// dispatch 액션 함수를 따로 만들 것인지는 선택...
dispatch(RequestSuccess(payload));
}, [dispatch]);
// 스켈레톤... 생명주기 메서드를 사용해서 false값으로 바꿔야 합니다
if (loading) return <>로딩중...</>;
if (error) return <>{error}</>;
return (
<>
<Header items={data} />
<AppRouter />
</>
);
};
export default App;