[React JS] 3. ํ”„๋ก ํŠธ์—”๋“œ ๊ธฐ๋ณธ๊ตฌ์กฐ ์ƒ์„ฑํ•˜๊ธฐ

 

1. Vite๋ฅผ ์ด์šฉํ•œ ๋ฆฌ์•กํŠธ ์ƒ์„ฑ

- vite

npm init vite

project name: ./

react ์„ ํƒ

javascript ์„ ํƒ

 

-> ๋ฆฌ์•กํŠธ ์ƒ์„ฑ

npm install

-> node_module์•ˆ์— package.json dependencies๋“ค์ด ๋“ค์–ด๊ฐ

 

ํด๋”๊ตฌ์กฐ ์ƒ์„ฑ

components : ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ

hooks : ์ปค์Šคํ…€ ํ›…์Šค

layout : header(navBar)   , footer, side bar 

pages : ํŽ˜์ด์ง€๋“ค

store : redux ์ƒํƒœ๊ด€๋ฆฌ

utils : ์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ์—์„œ ์‚ฌ์šฉ๋˜๋Š” ํ•จ์ˆ˜๋“ค

 

ํŒŒ์ผ ์ƒ์„ฑ


2. ESlint

1) ํ•„์š”ํ•œ ๋ชจ๋“ˆ ์„ค์น˜

npm install -D vite-plugin-eslint eslint eslint-config-react-app

2) eslint plugin ์ ์šฉ

vite.config.js

import eslint from 'vite-plugin-eslint'

export default definedConfig({
  plugins: [react(), eslint()],
  })

3) eslint ์„ค์ •ํŒŒ์ผ ์ƒ์„ฑ

src > .eslintrc ํŒŒ์ผ (์›๋ž˜ ์žˆ๋„น)

 

4) eslint rules ์ƒ์„ฑ

extends์— react-app ๋„ฃ์–ด์ฃผ๊ธฐ


3. TailWindCSS

- HTM์•ˆ์—์„œ CSS๋ฅผ ๋งŒ๋“ค์ˆ˜์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” CSS ํ”„๋ ˆ์ž„์›Œํฌ

- Bootstrap๊ณผ ๋น„์Šทํ•˜๊ฒŒ m-1, flex์™€ ๊ฐ™์ด ๋ฏธ๋ฆฌ ์„ธํŒ…๋œ Utility Class๋ฅผ ํ™œ์šฉํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ HTML์—์„œ ์Šคํƒ€์ผ๋ง ํ•  ์ˆ˜ ์žˆ๋‹ค.

1) ๋น ๋ฅธ ์Šคํƒ€์ผ๋ง ๊ฐ€๋Šฅ

2) class ํ˜น์€ id๋ช…์„ ์ž‘์„ฑํ•˜๊ธฐ ์œ„ํ•œ ๊ณ ์ƒํ•˜์ง€ ์•Š์•„๋„ ๋จ

3) ์œ ํ‹ธ๋ฆฌํ‹ฐ ํด๋ž˜์Šค๊ฐ€ ์ต์ˆ™ํ•ด์ง€๋Š” ์‹œ๊ฐ„์ด ํ•„์š”ํ•˜์ง€๋งŒ IntelliSense ํ”Œ๋Ÿฌ๊ทธ์ธ์œผ๋กœ ์ต์ˆ™ํ•ด์งˆ ์ˆ˜ ์žˆ์Œ

 

https://tailwindcss.com/ 

 

install Tailwind CSS with Vite

npm install -D postcss autoprefixer tailwind
npx tailwindcss init -p

 


4. React Router Dom

- React Router Dom์„ ์‚ฌ์šฉํ•˜๋ฉด ์›น ์•ฑ์—์„œ ๋™์  ๋ผ์šฐํŒ…์„ ๊ตฌํ˜„ ๊ฐ€๋Šฅ

- ๋ผ์šฐํŒ…์ด ์‹คํ–‰ ์ค‘์ธ ์•ฑ ์™ธ๋ถ€์˜ ๊ตฌ์„ฑ์—์„œ ์ฒ˜๋ฆฌ๋˜๋Š” ๊ธฐ์กด ๋ผ์šฐํŒ… ์•„ํ‚คํ…์ฒ˜์™€ ๋‹ฌ๋ฆฌ, React Router Dom์€ ์•ฑ ๋ฐ ํ”Œ๋žซํผ์˜ ์š”๊ตฌ์‚ฌํ•ญ์— ๋”ฐ๋ผ ์ปดํฌ๋„ŒํŠธ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ… ์šฉ์ดํ•˜๊ฒŒํ•จ

 

Single Page Application (SPA)

- ํ•˜๋‚˜์˜ index.html ํ…œํ”Œ๋ฆฟ ํŒŒ์ผ

- ํ•˜๋‚˜์˜ ํ…œํ”Œ๋ฆฟ์— ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ด์šฉํ•ด์„œ ๋‹ค๋ฅธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ด index.html์— ๋„ฃ์œผ๋ฏ€๋กœ ํŽ˜์ด์ง€๋ฅผ ๋ณ€๊ฒฝํ•ด์ฃผ๊ฒŒ๋จ

- ์ด๋•Œ React Router Dom ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๊ฐ€ ์ƒˆ ์ปดํฌ๋„ŒํŠธ๋กœ ๋ผ์šฐํŒ…/ํƒ์ƒ‰์„ ํ•˜๊ณ  ๋ Œ๋”๋งํ•˜๋Š”๋ฐ ๋„์›€์„ ์ฃผ๊ฒŒ ๋จ

 

์„ค์น˜

npm install react-router-dom --save

- src > index.js > react-router-dom์—์„œ BrowserRouter๊ฐ€์ ธ์˜จ ํ›„ ๋‹ค์Œ ๋ฃจํŠธ ๊ตฌ์„ฑ์š”์†Œ(App ๊ตฌ์„ฑ ์š”์†Œ)๋ฅผ ๊ทธ ์•ˆ์— ๋žฉํ•‘

- BrowserRouter : HTML5 history api(pushState, replaceState ๋ฐ popstate ์ด๋ฒคํŠธ)๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ UI๋ฅผ URL๊ณผ ๋™๊ธฐํ™”๋œ ์ƒํƒœ๋กœ ์œ ์ง€ํ•ด์คŒ 

 

์—ฌ๋Ÿฌ ์ปดํฌ๋„ŒํŠธ ์ƒ์„ฑ ๋ฐ ๋ผ์šฐํŠธ ์ •์˜ํ•˜๊ธฐ

- Routes ์•ˆ์— Route๋“ค์ด ๋“ค์–ด์žˆ์Œ

- ๋งŒ์•ฝ ๊ฒฝ๋กœ๋ฅผ ์คฌ์„๋•Œ path ๊ฒฝ๋กœ๋ฅผ ํ•˜๋‚˜์”ฉ ์—ฌ๊ธฐ์„œ ์ฐพ์•„๋ณด๊ณ  element์— ์žˆ๋Š” ์ปดํฌ๋„ŒํŠธ๋ฅผ ๋ณด์—ฌ์คŒ

 

Routes

- ์•ฑ์—์„œ ์ƒ์„ฑ๋  ๋ชจ๋“  ๊ฐœ๋ณ„ ๊ฒฝ๋กœ์— ๋Œ€ํ•œ ์ปจํ…Œ์ด๋„ˆ / ์ƒ์œ„ ์—ญํ• 

- Route๋กœ ์ƒ์„ฑ๋œ ์ž์‹ ์ปดํฌ๋„ŒํŠธ ์ค‘์—์„œ ๋งค์นญ๋˜๋Š” ์ฒซ๋ฒˆ์งธ Route๋ฅผ ๋ Œ๋”๋ง

 

Route

- ๋‹จ์ผ ๊ฒฝ๋กœ ๋งŒ๋“œ๋Š”๋ฐ ์‚ฌ์šฉ

- 1) path : ์›ํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ์˜ URL๊ฒฝ๋กœ

- 2) element : ๊ฒฝ๋กœ์— ๋งž๊ฒŒ ๋ Œ๋”๋ง๋˜์–ด์•ผํ•˜๋Š” ์ปดํฌ๋„ŒํŠธ ์ง€์ •

 

<Link/>๋ฅผ ์ด์šฉํ•ด ๊ฒฝ๋กœ ์ด๋™

- aํƒœ๊ทธ์™€ ์œ ์‚ฌ

- ๋งํฌ๋ฅผ ํด๋ฆญํ•˜๋ฉด ๊ฒฝ๋กœ๋ฅผ ์‚ดํŽด๋ณด๊ณ  ํ•ด๋‹น ๊ฒฝ๋กœ ์ด๋ฆ„์œผ๋กœ ๊ตฌ์„ฑ๋œ ์š”์†Œ๋ฅด ๋ Œ๋”๋ง

 

์ค‘์ฒฉ ๋ผ์šฐํŒ… (Nested Routes)

- ๋ณต์žกํ•œ ๋ ˆ์ด์•„์›ƒ ์ฝ”๋“œ ์‚ฌ์šฉ ๊ฐ€๋Šฅ

- ๋Œ€๋ถ€๋ถ„์˜  ๋ ˆ์ด์•„์›ƒ์€ url ์„ธ๊ทธ๋จผํŠธ์— ์—ฐ๊ฒฐ๋˜๊ณ  react router๋Š” ์ด๋ฅผ ์™„์ „ํžˆ ์ˆ˜์šฉ

- ex) ํ—ค๋”, ํ‘ธํ„ฐ ๋ณด์—ฌ์ฃผ๊ธฐ๊ฐ€ ๊ฐ€๋Šฅ

 

Outlet

- ์ž์‹ ๊ฒฝ๋กœ ์š”์†Œ๋ฅผ ๋ Œ๋”๋งํ•˜๋ ค๋ฉด ๋ถ€๋ชจ ๊ฒฝ๋กœ ์š”์†Œ์—์„œ outlet ์‚ฌ์šฉ

- ํ•˜์œ„ ๊ฒฝ๋กœ๊ฐ€ ๋ Œ๋”๋ง ๋  ๋•Œ ์ค‘์ฒฉ๋œ UI ํ‘œ์‹œ๊ฐ€๋Šฅ

- ๋ถ€๋ชจ ๋ผ์šฐํŠธ๊ฐ€ ์ •ํ™•ํžˆ ์ผ์น˜ํ•˜๋ฉด ์ž์‹ ์ธ๋ฑ์Šค ๋ผ์šฐํŠธ๋ฅผ ๋ Œ๋”๋งํ•˜๊ฑฐ๋‚˜ ์ธ๋ฑ์Šค ๋ผ์šฐํŠธ๊ฐ€ ์—†์œผ๋ฉด ๋ Œ๋”๋ง ํ•˜์ง€ ์•Š์Œ

* ์•„์›ƒ๋ ›์„ ํฌํ•จํ•˜๋Š” ์ƒ์œ„ ์ปดํฌ๋„ŒํŠธ๋Š” ํ•˜์œ„ ์ปดํฌ๋„ŒํŠธ์— ๋ชจ๋‘ ํ‘œ์‹œ๋˜๋Š”๋“ฏ

 

useNavigate

- ๊ฒฝ๋กœ ๋ฐ”๊ฟ”์คŒ

 

useParams

- :style ๋ฌธ๋ฒ•์„ path ๊ฒฝ๋กœ์— ์‚ฌ์šฉํ–ˆ๋‹ค๋ฉด useParams()๋กœ ์ฝ์„ ์ˆ˜ ์žˆ์Œ

ex) path = "invoices/:invoiceId

- Invoice ์ปดํฌ๋„ŒํŠธ ์•ˆ์—์„œ ๊ฐ€์ ธ์˜ฌ๋•Œ useParams()๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๊ฐ€์ ธ์˜ฌ ์ˆ˜ ์žˆ์Œ

 

useLocation

- ์ด Hooks๋Š” ํ˜„์žฌ ์œ„์น˜ ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜

- ํ˜„์žฌ ์œ„์น˜๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค ์ผ๋ถ€ side effect๋ฅผ ์ˆ˜ํ–‰ํ•˜๋ ค๋Š” ๊ฒฝ์šฐ์— ์œ ์šฉ

 

useRoutes

- hooks๋ฅผ ์ด์šฉํ•ด์„œ ๊ฒฝ๋กœ ๋งŒ๋“ค๊ธฐ๋„ ๊ฐ€๋Šฅ 

 


5. CSS ์ „์ฒด ๊ตฌ์กฐ & React Icons

<div class="flex flex-col h-screen justify-between">
<header class="h-10 bg-red-500">Header</header>
<main class="mb-auto h-10 bg-green-500">Content</main>
<footer class="h-10 bg-blue-500">Footer</footer>
</div>

* flex-col : main ์ถ•์ด ์œ„์—์„œ ์•„๋ž˜๋กœ, ์ˆ˜์ง ์ถ•์ด ์™ผ์ชฝ์—์„œ ์˜ค๋ฅธ์ชฝ์œผ๋กœ ๋ฐ”๋€œ (์›๋ž˜๋ž‘ ๋ฐ˜๋Œ€)

* justify between : ๊ฐ€์šด๋ฐ

* mb-auto (margin bottom auto) : ์ค‘๊ฐ„์—์„œ ์œ„๋กœ ์˜ฌ๋ ค์คŒ

 

React Icons ๋‹ค์šด

npm install react-icons

 

App.jsx

import { Outlet, Route, Routes } from "react-router-dom";
import "./App.css";
import LandingPage from "./pages/LandingPage";
import LoginPage from "./pages/LoginPage";
import RegisterPage from "./pages/RegisterPage";
import Navbar from "./layout/Navbar";
import Footer from "./layout/Footer";

function Layout() {
  return (
    <div className="flex flex-col h-screen justify-between">
      <Navbar />
      <main className="mb-auto w-10/12 max-w-4xl mx-auto">
        <Outlet />
      </main>
      <Footer />
    </div>
  );
}

function App() {
  return (
    <Routes>
      <Route path="/" element={<Layout />}>
        <Route index element={<LandingPage />} />
        <Route path="/login" element={<LoginPage />} />
        <Route path="/register" element={<RegisterPage />} />
      </Route>
    </Routes>
  );
}

export default App;

Footer > index.jsx

import React from "react";
import { AiOutlineSmile } from "react-icons/ai";

const Footer = () => {
  return (
    <div className="flex h-20 text-lg justify-center items-center">
      All rights reserved.
      <AiOutlineSmile />
    </div>
  );
};

export default Footer;

6. Redux ์ƒ์„ฑ

userSlice.js

cd frontend
npm install @reduxjs/toolkit react-redux
import { createSlice } from "@reduxjs/toolkit";

const initialState = {
  userData: {
    id: "",
    email: "",
    name: "",
    role: 0,
    image: "",
  },
  isAuth: false,
  isLoading: false,
  error: "",
};

const userSlice = createSlice({
  name: "user",
  initialState,
  reducers: {},
  extraReducers: (builder) => {},
});

export default userSlice.reducer;

store ์ƒ์„ฑ

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";

export const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

store ์‚ฌ์šฉํ•  App์— Provider ๊ฐ์‹ธ์ฃผ๊ธฐ (redux store ์ €์žฅ์†Œ์— ์—‘์„ธ์Šคํ•ด์•ผํ•˜๋Š” ๋ชจ๋“  ์ค‘์ฒฉ ๊ตฌ์„ฑ์š”์†Œ์—์„œ store ์‚ฌ์šฉ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•จ)

import { configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";

export const store = configureStore({
  reducer: {
    user: userReducer,
  },
});

7. Redux-Persist

๋ฆฌ๋•์Šค ์Šคํ† ์–ด์—์žˆ๋Š” State๋“ค์€ ํŽ˜์ด์ง€๋ฅผ ์ƒˆ๋กœ๊ณ ์นจํ•˜๋ฉด ์ดˆ๊ธฐํ™”๋จ

ํ•˜์ง€๋งŒ Redux Persist๋ฅผ ์ด์šฉํ•˜๋ฉด ํŽ˜์ด์ง€ ์ƒˆ๋กœ๊ณ ์นจ ํ›„์—๋„ ์ƒํƒœ ์œ ์ง€ ๊ฐ€๋Šฅ

cd frontend
npm i redux-persist

 

์ง๋ ฌํ™”(serialize) : object ๊ฐ’์„ string ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ (JSON.stringify)

์—ญ์ง๋ ฌํ™”(deserialize) : string๊ฐ’์„ object ๊ฐ’์œผ๋กœ ๋ณ€ํ™˜ (JSON.parse)

 

* reduxmiddleware์—์„œ๋Š” ๋“ค์–ด์˜ค๋Š” action ํ˜น์€ ๊ฐ’์ด serializableํ•œ ๊ฐ’์ธ์ง€ ์ฒดํฌํ•จ (์•„๋‹ˆ๋ฉด ๊ฒฝ๊ณ )

* redux persist ์ด์šฉํ•˜๋ฉด serializable ํ•˜์ง€ ์•Š์€ function์ด ๋“ค์–ด์žˆ๊ธฐ ๋•Œ๋ฌธ์— option์œผ๋กœ serializableCheck: false๋กœ ๋‘ 

--> but, redux persist ์‚ฌ์šฉํ• ๋•Œ๋งŒ ์ฒดํฌ ignoreํ•˜๋Š” ์˜ต์…˜ ๊ถŒ์žฅํ•จ (FLUSH ๊ฐ™์€๊ฒŒ action์˜ ํƒ€์ž…๋“ค์ž„)

import { combineReducers, configureStore } from "@reduxjs/toolkit";
import userReducer from "./userSlice";
import storage from "redux-persist/lib/storage";
import {
  FLUSH,
  PAUSE,
  PERSIST,
  PURGE,
  REGISTER,
  REHYDRATE,
  persistReducer,
  persistStore,
} from "redux-persist";

const rootReducer = combineReducers({
  user: userReducer,
});

const persistConfig = {
  key: "root",
  storage,
};

const persistedReducer = persistReducer(persistConfig, rootReducer);

export const store = configureStore({
  reducer: persistedReducer,
  middleware: (getDefaultMiddleware) =>
    getDefaultMiddleware({
      serializableCheck: {
        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
      },
    }),
});

export const persistor = persistStore(store);

* serializableํ•œ ๊ฒƒ๋“ค์€ store์— ๋ณด๊ด€์•ˆํ•˜๋Š”๊ฒŒ ์ข‹์Œ ์›๋ž˜๋Š”

 

<PersistGate>

persist gate๋กœ provider์ฒ˜๋Ÿผ ๊ฐ์‹ธ์ค˜์•ผํ•จ

- redux store์—์„œ ์ง€์† ๋ฐ์ดํ„ฐ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๋•Œ๊นŒ์ง€ ์•ฑ์˜ UI ๋žœ๋”๋ง ์ง€์—ฐ ๊ฐ€๋Šฅ

- ํŽ˜์ด์ง€ ๋ฆฌํ”„๋ ˆ์‹œ ๋˜๋ฉด store ๊ฐ’ ์ดˆ๊ธฐํ™”๋จ -> ๊ทธ๊ฑธ rehydrateํ•ด์„œ local storage๊ฐ’์„ ๊ฐ€์ ธ์™€์„œ store์— ๋„ฃ์–ด์คŒ

๊ทธ๋Ÿฐ๋ฐ ์ด๋•Œ ๊ทธ ์‚ฌ์ด์— UI ๋žœ๋”๋ง์ด ๋˜๋ฉด ๊ฐ’์ด ์ €์žฅ์ด ์•ˆ๋˜์žˆ๋Š” ์ƒํƒœ๋กœ ๋‚˜์˜ด --> ์ด๊ฑธ ์ง€์—ฐ์‹œํ‚ค๊ธฐ ์œ„ํ•œ ์žฅ์น˜

ReactDOM.createRoot(document.getElementById("root")).render(
  <BrowserRouter>
    <Provider store={store}>
      <PersistGate loading={null} persistor={persistor}>
        <App />
      </PersistGate>
    </Provider>
  </BrowserRouter>
);
  • ๋„ค์ด๋ฒ„ ๋ธ”๋Ÿฌ๊ทธ ๊ณต์œ ํ•˜๊ธฐ
  • ๋„ค์ด๋ฒ„ ๋ฐด๋“œ์— ๊ณต์œ ํ•˜๊ธฐ
  • ํŽ˜์ด์Šค๋ถ ๊ณต์œ ํ•˜๊ธฐ
  • ์นด์นด์˜ค์Šคํ† ๋ฆฌ ๊ณต์œ ํ•˜๊ธฐ