[WIL] 4주차 ⎮ Redux ➕ Docker ➕ Outlet ➕ Suspense ➕ custom hook
1. redux
리액트에서 많이 사용하는 상태 관리 라이브러리입니다. 중앙에서 상태들을 관리하여 props가 너무 깊어지는 것을 방지할 수 있습니다. 주요 개념 키워드는 store, action, dispatch, reducer
입니다.
- 스토어는 상태를 보관합니다. 리덕스에는 하나의 스토어가 있고 여기에서 state=상태를 관리합니다.
- 액션은 스토어의 상태를 변경하는 명령입니다. type, payload를 인자로 받습니다.
- 상태=스토어의 값을 업데이트하기 위해서는 반드시 action을 사용합니다.
- action을 사용해 스토어의 값을 변경하려면 반드시 dispatch를 거쳐야 합니다.
- 디스패치는 액션을 스토어에 전달합니다. 스토어에 있는 리듀서가 액션을 받아 상태를 변경하도록 전달합니다. 즉 액션은 반드시 디스패치로 전달되어야 스토어의 상태가 변경될 수 있습니다.
- 리듀서는 상태를 변경합니다. 디스패치를 통해 액션이 리듀서로 전달됩니다. 리듀서는 액션의 타입에 따라 상태를 변경하는 로직을 수행하고 스토어를 업데이트합니다.
- 이 때 불변성을 유지하기 위해 기존 상태를 기반으로 새로운 상태를 만들어 리턴합니다.
스토어에서 상태를 관리 « 스토어 변경을 위해서는 액션 필요 « 액션을 스토어에 사용하려면 디스패치 필요 « 디스패치는 리듀서를 통해 상태를 업데이트
a. store : 전체 상태 관리
// store/index.js
import { createStore } from "redux";
import reducer from "../redux/reducer";
// 스토어는 상태를 관리하며, 리듀서와 연결되어 상태를 업데이트합니다.
const store = createStore(reducer);
export default store;
b. reducer
액션에 따라 어떻게 상태를 업데이트할지 결정
// redux/reducer.js
const init = {
value: 100,
title: "test",
};
const reducer = (state = init, action) => {
console.log(state);
console.log(action.type);
if (action.type == "UP") {
return { ...state, value: state.value + action.payload };
}
if (action.type == "DOWN") {
return { ...state, value: state.value - action.payload };
}
return state;
};
export default reducer;
// redux/action.js
export const increment = (amount) => {
return {
type: 'UP',
payload: amount,
};
};
...
c. 상위 컴포넌트
Provider로 애플리케이션 전체에 Redux 스토어를 주입!
import { Provider } from "react-redux";
import store from "./store/index.js";
...
// Provider로 전체 컴포넌트를 감싸면 스토어를 하위 컴포넌트들이 사용할 수 있게 됩니다.
return(
<Provider store={store}> 로 감싸기
...
)
d. 하위 컴포넌트
useSelector로 스토어 상태 정보 얻기, useDispatch로 액션을 디스패치해서 스토어 상태 변경 요청
useSelector로 스토어의 state를 세팅
import { useDispatch, useSelector } from "react-redux";
...
const counterValue = useSelector((state) => state.value);
const titleValue = useSelector((state) => state.title);
const dispatch = useDispatch();
useDispatch로 불러와서
const dispatch = useDispatch();
...
dispatch({ type: "UP", payload: 2 });
dispatch로 스토어의 액션을 사용 가능
2. 도커 : Mysql 서버 올리기
a. 기본 명령어
- 이미지 목록 확인 : docker images
- 컨테이너 목록 확인 : docker ps / docker ps -a (중단된 것도 포함)
- 이미지 제거 : docker image rm 이미지 이름이나 ID
- 컨테이너 제거 : docker rm -f 컨테이너 이름이나 ID
b. 이미지 생성하기
이미지를 생성하기 위해서는 Dockerfile을 작성한 후 해당 파일이 있는 경로로 이동한다.
docker build -t imageMysql . // 이미지 생성
docker run -d --name containerName -e MYSQL_ROOT_PASSWORD=1234 -p 3306:3306 mysql // 실행
이제 이미지, 서버의 환경세팅 정보가 포함된 이미지가 생겼으므로 해당 이미지를 사용해 컨테이너를 실행할 수 있다. 아래 명령어로 컨테이너를 실행한다.
- 환경변수 비밀번호를 전달하고
- 호스트의 포트를 컨테이너의 포트와 연결해 외부에서 mysql에 접근할 수 있도록 설정
- 도커 허브에 있는 mysql 기본 이미지를 사용해 컨테이너를 실행한다.
mysql 서버가 도커 컨테이너 안에서 실행, localhost:3306에서 mysql 클라이언트를 통해 접속 가능
3. 도커
docker start mydb : 자고 있던 컨테이너 깨우기, docker ps -a로 죽거나 멈춘 컨테이너들 확인 가능. 여기 있는 컨테이너를 깨운다.
- 서버를 올리려면 먼저 이미지를 만들고, 이걸 컨테이너에 올려서 실행시키면 서버가 올라감
- 이미지를 만들기 위해서 Dockerfile을 사용한다.
- 도커 파일은 이미지 빌드를 위한 설정 파일로 이미지 생성에 필요한 명령어들이 포함되어 있다.
- 도커파일 위치에서 docker build 명령어를 사용해 도커 이미지를 빌드할 수 있음
a. Dbeaver로 데이터베이스 맛보기
SQL 단축키 :
- “ctrl+enter” 하면 해당 구문 실행
- 여러줄 실행은 “alt + x”
4. [react-router-dom] router
a. Outlet
- react-router-dom의 outlet은 중첩된 경로에서 자식 컴포넌트를 렌더링한다.
- vue의
와 유사하게 부모 경로에서 자식 경로가 렌더링될 위치를 지정.
예제
// App.js
import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom";
...
function App() {
return (
<Router>
<nav>
<Link to="/">Home</Link>
<Link to="/dashboard">Dashboard</Link>
</nav>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
{/* 부모 컴포넌트에서 Outlet 사용, 즉 대시보드 컴포넌트가 부모 */}
<Route path="/dashboard" element={<Dashboard />}>
{/* 중첩된 자식 라우트 */}
<Route path="profile" element={<Profile />} />
<Route path="settings" element={<Settings />} />
</Route>
</Routes>
</Router>
);
}
export default App;
// components/Dashboard.js
import { Outlet, Link } from "react-router-dom";
function Dashboard() {
return (
<div>
<h2>Dashboard</h2>
<nav>
<Link to="profile">Profile</Link>
<Link to="settings">Settings</Link>
</nav>
{/* 자식 경로가 렌더링될 위치, profile와 settings */}
<Outlet />
</div>
);
}
export default Dashboard;
대시보드의 자식 라우트 컴포넌트들은 대시보드 컴포넌트의 아웃렛 컴포넌트 위치에 자리한다.
b. [React] Lazy와 Suspense를 사용해서 필요할 때만 불러오기
React.lazy
와 Suspense
는 리액트에서 코드 스플리팅을 위해 사용됩니다. 즉, 필요할 때만 해당 컴포넌트를 불러오도록 하여 초기 로딩 시간을 줄입니다.
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<div className="App">
<h1>Lazy와 Suspense 예제</h1>
{/* Suspense를 사용하여 LazyComponent가 로드될 때까지 대기 */}
<Suspense fallback={<div>로딩 중...</div>}>
<LazyComponent />
</Suspense>
...
- lazy는 동적으로 컴포넌트를 불러온다. lazy() 함수를 사용해 요청이 있을 때 로드한다.
- suspense는 lazy로 로드되는 컴포넌트가 로드되기 전까지 보여줄 “로딩 UI”를 정의한다.
fallback
속성에서 불러올 때까지 보여줄 내용, 로딩 중 화면을 설정한다.
- 스켈레톤 코드나 로딩 스피너 애니메이션 등을 사용할 수 있다. 로딩 UI를 설정하지 않으면 영역이 잡혀있지 않아 해당 섹션이 빈 자리가 되어 갑자기 화면이 올라갔다가 내려가는 등의 문제가 생긴다.
c. [React] 커스텀 훅
여러 컴포넌트에서 반복적으로 사용되는 로직을 재사용할 수 있게 해준다. 기본적으로 리액트의 내장 훅에는 “useState, useEffect”
등이 있는데 이를 활용해 원하는 기능을 추가한다. 즉 내장 훅 저 두 개를 활용해서 조금 더 구체적인 로직을 설정해서 이곳저곳에서 사용하도록 하기 위한 것.
// hooks/useTimer.js 커스텀 훅: 타이머 기능
function useTimer(limit) {
const [count, setCount] = useState(0);
useEffect(() => {
if (count >= limit) return;
// 1초마다 count 값을 1씩 증가시키는 타이머
const interval = setInterval(() => {
setCount((prevCount) => prevCount + 1);
}, 1000);
// 클린업 함수, 컴포넌트가 언마운트되면 타이머 정리
return () => clearInterval(interval);
}, [limit]);
...
import useTimer from './useTimer';
function Component() {
const count = useTimer(10);
return (
<div>
<h1>타이머: {count}초 타이머는 10초에 도달하면 중지된다.</h1>
...
- 컴포넌트에서 커스텀 훅을 호출하면 커스텀 훅 컴포넌트가 마운트되며 이 때부터 타이머가 동작한다.
- 커스텀 훅 컴포넌트가 언마운트되기 전까지는 타이머가 계속해서 실행된다.
댓글남기기