๋ฆฌ์กํธ์์ ์ํ๋ณํ๋ฅผ ์ ๋ฐ์ดํธ ํด์ฃผ๋ ํ (Hook) ํจ์์ค ํ๋๋ก useState์ ๋น์ทํ ์ญํ ์ด์ง๋ง useState๋ณด๋ค ๋ ๋ณต์กํ ์ํ๊ด๋ฆฌ๋ฅผ ํ ๋ ์ฌ์ฉ ๋๋ค. ์ด๋ป๊ฒ ๋ณต์กํ ์ํ๊ด๋ฆฌ๋ฅผ ํ๊ฒ ๋๋์ง๋ ์๋๋ฅผ ์ฐธ๊ณ ํ์.
reducer๋ ์ปดํฌ๋ํธ ๋ฐ์์ ์ ์ธ๋์ด ํ์ฌ ์ํ์ ์ก์ ์ ๋ฐ์์ ์๋ก์ด ์ํ๋ฅผ ๋ฐํํ๋ ํจ์์ด๋ค.
์๋ ์ฝ๋๋ก ๊ธฐ๋ณธ ๋ฌธ๋ฒ์ ๋ณด์๋ผ.
state: ํ์ฌ ์ํ
action : ์ํ๋ฅผ ๋ณ๊ฒฝํ๊ธฐ ์ํ ์ ๋ณด๋ฅผ ๋ด๊ณ ์๋ ๊ฐ์ฒด๋ก action ๊ฐ์ฒด๋ type ํ๋๋ฅผ ๋ฐ๋์ ๊ฐ์ง๊ณ ์์ด์ผ ํ๋ฉฐ, ๊ทธ ์ธ์ ํ๋๋ ์ํ ๋ณ๊ฒฝ์ ํ์ํ ๊ฐ๋ค์ ๋ด๊ณ ์๋ค.
const reducer = (state, action) => {
switch (action.type) {
case "INIT": {
return action.data;
}
case "CREATE": {
const created_date = new Date().getTime();
const newItem = {
...action.data,
created_date
};
return [newItem, ...state];
}
case "REMOVE": {
return state.filter((it) => it.id !== action.targetId);
}
case "EDIT": {
return state.map((it) =>
it.id === action.targetId
? {
...it,
content: action.newContent
}
: it
);
}
default:
return state;
}
};
.
.
.
const [state, dispatch] = useReducer(reducer, initialState);
state : ์ปดํฌ๋ํธ์์ ์ฌ์ฉํ ์ํ
dispatch: ๋ด๋ถ์ ๊ฐ์ฒด๋ก type๊ณผ ํ์ฌ state๊ฐ์ ๋ด๊ณ useReducer์ ํธ๋ฆฌ๊ฑฐ๊ฐ ๋๋ ํจ์
reducer: reducerํจ์๋ก ๋ก์ง์ด ๋ด๊ฒจ ์์
initialState: ์ด๊ธฐํ ์ํ
ํ๋ฆ์ ๋ณด๋ฉด
1. ํ๋ฉด ์์ฒญ๊ณผ ์ด๊ธฐ ๋ ๋๋ง.
2. initialState๊ฐ์ผ๋ก state ์ด๊ธฐํ ๊ทธ๋ฆฌ๊ณ reducer ํจ์ ๋ ๋๋ง.
3. dispatchํจ์๋ฅผ ํตํด useReducerํจ์๋ฅผ ๊ฑฐ์ณ reducerํจ์ ์ฌ์ฉ.
4. reducer ํจ์๋ฅผ ํตํด ๋ฐํ๋ ์ํ๋ก ๋ณ๊ฒฝ
์๋ ์ฝ๋๋ฅผ ํตํด์ ๊ธฐ๋ฅ์ ๋ํ ๋ก์ง์ด ๋ถ๋ฆฌ๋๊ณ ์ปดํฌ๋ํธ ๋ถ๋ถ์์๋ ์ด๋ค ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ง dispatch๋ฅผ ํตํด reducer์์ ์ฌ์ฉ๋ ์ํ์ ํจ์๋ฅผ ์ ํ๋ค.
{type: ์ด๋ค๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ณ data: ์ด๋ค ์ํ๋ฅผ ์ฃผ์
ํ๊ฒ ์ต๋๋ค.}
// ์์ ๊ฐ์ด ๋ก์ง์ด ์ปดํฌ๋ํธ์ ๋ถ๋ฆฌ๊ฐ ๋๋ค.
const App = () => {
const [data, dispatch] = useReducer(reducer, []);
.
.
.
const getData = async () => {
const res = await fetch(
"https://jsonplaceholder.typicode.com/comments"
).then((res) => res.json());
const initData = res.slice(0, 20).map((it) => {
return {
author: it.email,
content: it.body,
emotion: Math.floor(Math.random() * 5) + 1,
created_date: new Date().getTime(),
id: dataId.current++
};
});
dispatch({ type: "INIT", data: initData });
};
const onCreate = useCallback((author, content, emotion) => {
dispatch({
type: "CREATE",
data: { author, content, emotion, id: dataId.current }
});
dataId.current += 1;
}, []);
๋ณดํต์ useState๋ฅผ ์ฌ์ฉํ๋ฉด ์ํ์ ๊ด๋ จํ ํจ์๋ค์ด ๋ง์ ๊ฒฝ์ฐ ํผ์ฌ๋์ด ํด๋น ์ฝ๋๋ฅผ ์ฐพ์ผ๋ ค๋ฉด ์๊ฐ์ด ๊ฑธ๋ฆฌ๊ฒ ๋๋ค. ๊ฐ๋ ์ฑ์ด ์ ํ๋๊ณ ์ ์ง๋ณด์์ฑ๊น์ง ์ ํ๋๋ค.
useReducer๋ฅผ ํตํด ์ํ ์ ๋ฐ์ดํธ์ ๋ํ ๊ธฐ๋ฅ์ ์ปดํฌ๋ํธ์ ๋ฌด๊ดํ๊ฒ ๋ถ๋ฆฌ ์ํค๋ฉฐ ํด๋น ๊ธฐ๋ฅ๋ค๋ง ์ ์ธํ์ฌ ๊ด๋ฆฌํ ์ ์์ผ๋ฉฐ ์ ์ธ๊ณผ ์ฌ์ฉ์ ๋ถ๋ฆฌ ์์ผ์ ๊ฐ๋ ์ฑ๊ณผ ์ ์ง๋ณด์์์๋ ์ฅ์ ์ ๊ฐ์ง๋ค.
์ผ๋ฐ์ ์ผ๋ก ๋ค๋ฅธ ์ธ์ด์ ํ๋ ์์ํฌ๋ฅผ ๋ฐฐ์ธ ๋์ ๋ง์ฐฌ๊ฐ์ง๋ก context๋ผ๋ ๊ณต์ ์์ญ์ ํตํด ์ ์ญ์ ์ธ ๊ฐ์ด๋ ํ์์ ๋ํด์ ์ฌ์ฉํ๊ณ ๋ ํ๋ค.
React์์๋ Context๋ ์ปดํฌ๋ํธ ํธ๋ฆฌ ์ ์ฒด์์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ ์ ์๋๋ก ํด์ฃผ๋ ๋ฐฉ๋ฒ์ผ๋ก ์๋ ์์ ์ฝ๋์ ๊ฐ์ด ์ค๊ฐ์ ์๋ ์ปดํฌ๋ํธ๋ค์ด props๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ ํ๋ ๊ณผ์ ์ ์๊ฑฐ์ณ๋ ๋๋ค.
//App > DiaryList > DiaryItem๋ก ์ฐ๊ฒฐ๋๋ ์ปดํฌ๋ํธ.
// DiaryList์์๋ onRemove์ onEdit์ด ๊ฑฐ์ณ๊ฐ๊ธฐ๋ง ํ๋ค.
const App = () => {
return (
.
.
.
<DiaryList diaryList={data} onRemove={onRemove} onEdit={onEdit} />
)
}
const DiaryItem = ({
onRemove,
onEdit,
id,
author,
content,
emotion,
created_date
}) => {
.
.
.
}
Context๋ฅผ ์ฌ์ฉํ๋ฉด Provider์ Consumer๋ผ๋ ๋ ๊ฐ์ง ์ปดํฌ๋ํธ๊ฐ ์๋ค.
Provider๋ Context๋ฅผ ๊ตฌ๋ ํ๋ ์ปดํฌ๋ํธ๋ค์๊ฒ Context์ ๋ณํ๋ฅผ ์๋ฆฌ๋ ์ญํ ๋ก Provider ์ปดํฌ๋ํธ๋ value๋ผ๋ prop์ ๋ฐ์์ ์ด ๊ฐ์ ํ์์ ์๋ ์ปดํฌ๋ํธ๋ค์ด ์ฌ์ฉํ ์ ์๊ฒ ์ ๋ฌํ๋ค.
Consumer๋ Provider๋ก ๋ฐ์ ๊ฐ์ ๋ํด์ ์ฌ์ฉํ๋ ์ปดํฌ๋ํธ๋ฅผ ๋ง๋๋๋ฐ ์ด๋ useContext๋ผ๋ Hook ํจ์๋ฅผ ์ฌ์ฉํ๋ฉด ๋์ฒด ๊ฐ๋ฅํ๋ค.
์๋๋ ์ฌ์ฉ์์ ์ด๋ค.
// ์ํ๋ฅผ ๊ณต์ ํ context ์์ญ์ ๋ง๋ ๋ค.
// ์ํ๋ฅผ ๋ณํ์ํฌ ํจ์๋ฅผ ๊ณต์ ํ context ์์ญ์ ๋ง๋ ๋ค. ๋ถ๋ฆฌ ์ด์ ์๋์ ์ ์
export const DiaryStateContext = createContext(null);
export const DiaryDispatchContext = createContext(null);
const App = () => {
/*
context๊ฐ ๋๋๋ง ๋ ๋ ์์ ๋ค์ด๊ฐ ์์๋ค๋ ๋ชจ๋ ๋๋๋ง๋๊ธฐ๋๋ฌธ์ ์ต์ ํ๊ฐ ํ๋ฆฌ๊ฒ ๋๋ค.
๋ฐ๋ผ์ ์ํ์ ๋ํ context์ ๊ธฐ๋ฅ์ ๋ํ context๋ฅผ ๋ถ๋ฆฌํด์ค๋ค.
๊ฑฐ๊ธฐ์ ๊ธฐ๋ฅ์ ๋ํ ๊ฒ๋ค์ useMemo๋ก ํ๋ฒ ๋ ๊ฐ์ธ์ค์ context์ ๋ฑ๋กํด์ค๋ค.
(์ฌ์ฉํ ๋๋ ๊ฐ์ธ์ค๊ฒ์ด ์๋ ๊ฐ๊ฐ์ผ๋ก ๋ถ๋ฌ๋ ๊ด์ฐฎ์ ๋ฏ)
*/
const memoizedDispatch = useMemo(() => {
return { onCreate, onRemove, onEdit };
}, []);
.
.
.
// ๊ฐ์ฅ ๋ฐ๊นฅ์ Provider์ปดํฌ๋ํธ์ value๋ฅผ ์ค์ ๊ฐ์ธ์ฃผ๋ฉด ๋
return (
<DiaryStateContext.Provider value={data}>
<DiaryDispatchContext.Provider value={memoizedDispatch}>
<div className="App">
<DiaryEditor />
<div>์ ์ฒด ์ผ๊ธฐ : {data.length}</div>
<div>๊ธฐ๋ถ ์ข์ ์ผ๊ธฐ ๊ฐ์ : {goodCount}</div>
<div>๊ธฐ๋ถ ๋์ ์ผ๊ธฐ ๊ฐ์ : {badCount}</div>
<div>๊ธฐ๋ถ ์ข์ ์ผ๊ธฐ ๋น์จ : {goodRatio}%</div>
<DiaryList />
</div>
</DiaryDispatchContext.Provider>
</DiaryStateContext.Provider>
);
};
//์ฌ์ฉ์ ๋ํด์๋ ์๋์ ๊ฐ์ด useContext๋ฅผ ์ด์ฉํ์ฌ ์ฌ์ฉ
const DiaryList = () => {
const diaryList = useContext(DiaryStateContext);
.
.
.
.
}
const DiaryItem = ({ id, author, content, emotion, created_date }) => {
const { onRemove, onEdit } = useContext(DiaryDispatchContext);
.
.
.
}
์ง๊ธ๊น์ง ๋ด๊ฐ ๋ง๋ค์๋ ์๋ฐ ํ๋ก์ ํธ๋ฅผ ๋ณด๋ฉด ์๋์ ๊ฐ์ด ํ๋์ ํ๋ก์ ํธ์ ์ฌ๋ฌ๊ฐ์ ํ๋ฉด
์ฆ, html template๋ฅผ ๋ง๋ค๊ณ ์น์๋ฒ์ ์์ผ์ ์ด์ฉํด์ ์์ฒญ์ ๋ฐ๊ณ ๋ฐ์ธ๋ฉ ๋์ด์๋ ๊ฐ๋ค์ ๋ง์ถฐ ์ ํ๋ฆฌ์ผ์ด์
ํ๋ฉด์ ๋๊ฒจ์ฃผ์๋ค.
์๋์๋ MPA์ฆ ํ๋์ ์ ํ๋ฆฌ์ผ์ด์ ์ ์ฌ๋ฌ๊ฐ์ ํ ํ๋ฆฟ์ ์ฌ์ฉํ์ฌ ๊ตฌ์ฑ๋์ด์๋ ํ์ด์ง์ ์ด๋๊ฐ์ ์บก์ณํ ์ฌ์ง์ด๋ค.
ํ๋ฉด ์ด๋ ์ค ์๋ก๊ณ ์นจ ํ์๊ฐ ๋์๊ฐ๋ฉด์ ๋ด๋ถ์ ์ธ ์ฒ๋ฆฌ๊ฐ ์๋ฃ๋์ด ์๋ต์ ์ค ๋๊น์ง ๋๋ ์ด ๋๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
์ฅ์ ์ผ๋ก๋ ์๋ฒ์์ ์ด๋ฏธ ๋ ๋๋งํด ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ฒซ ๋ก๋ฉ์ด ๋งค์ฐ ์งง๋ค๋ ๊ฒ์ด ์๊ณ ๋ด๋ถ์ ์ผ๋ก ์๋ฒ๋จ์์ ์ฒ๋ฆฌํ๋ ์์ญ์ด ๋ง๊ธฐ ๋๋ฌธ์ ๋ณด์์ ์ผ๋ก๋ ์ฐ์ํ ์ ์๋ค.
๋จ์ ์ผ๋ก๋ SPA์ ๋นํด ์ฌ์ฉ์ ๊ฒฝํ์ด ์ข์ง ๋ชปํ๋ค๋ ๊ฒ์ด ์๋ค.
(์์ : ๋ฒค์ธ ํํ์ด์ง)
๋ธ๋ผ์ฐ์ ์์ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๋ฉฐ CSR์ ๊ฒฝ์ฐ HTML, CSS์ ๋ชจ๋ ์คํฌ๋ฆฝํธ๋ค์ ํ ๋ฒ์ ๋ถ๋ฌ์จ๋ค.
CSR์ ์ด๊ธฐ ๋ก๋ฉ ์๋๊ฐ ๋๋ฆฌ์ง๋ง, ์ฌ์ฉ์์ ์ํธ์์ฉ ํ ๋๋ง๋ค ํ์ํ ๋ฐ์ดํฐ๋ง ์๋ฒ์์ ๊ฐ์ ธ์ค๊ธฐ ๋๋ฌธ์ ์ดํ์๋ ๋น ๋ฅธ ๋ฐ์์ฑ์ ๋ณด์ธ๋ค.
์๋ฒ์์ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๋ฉฐ ํ์ํ ๋ถ๋ถ์ HTML๊ณผ ์คํฌ๋ฆฝํธ๋ง ๋ถ๋ฌ์จ๋ค.
SSR์ ์ด๊ธฐ ๋ก๋ฉ ์๋๊ฐ ๋น ๋ฅด์ง๋ง, ์ดํ์๋ ๋๋ฆฐ ๋ฐ์์ฑ์ ๋ณด์ธ๋ค.
๋๋ SSR์์ ํ์ด์ง ์ด๋๋ง๋ค ๋ก๋ฉ ๊ฑธ๋ฆฌ๋ ๊ฒ๋ง ๋๊ปด์ง๋ค.
๊ทธ๋งํผ CSR์ด ์ฌ์ฉ์ ๊ฒฝํ์ด ์ข๋ค๋ ์ด์ผ๊ธฐ
index.html (entry point) ์์ JS์ ํ๋ ์์ํฌ๋ฅผ ํตํด ์์ฒญ์ ๋ฐ๋ฅธ HTML์์๋ง์ ๋ณ๊ฒฝํ์ฌ ํ๋ฉด์ ๋ฐ๊ฟ์ฃผ๋ฉฐ ์ฌ์ฉ์ ๊ฒฝํ์ ์ฐ์ ์ ๋ ๋ฐฉ๋ฒ
๋ผ์ฐํ
์ด๋? ๊ฒฝ๋ก๋ฅผ ์ ํด์ฃผ๋ ํ์ ๊ทธ ์์ฒด๋ฅผ ์๋ฏธํ๋ค.
๋ฐ๋ผ์ ํ์ด์ง ๋ผ์ฐํ ์ด๋ ์์ฒญ์ ๋ฐ๋ผ ์ด๋ค ํ์ด์ง๋ก ๊ฐ์ผํ๋์ง ๊ฒฝ๋ก๋ฅผ ์๋ ค์ฃผ๋ ๊ณผ์ ์ ์๋ฏธํ๋ค.
๋ฆฌ์กํธ์์ ํ์ด์ง ๋ผ์ฐํ
์ ๊ตฌํํ๊ธฐ ์ํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค.
๋ผ์ฐํฐ api์ ๋ํด์ ์ถ๊ฐํ๊ณ ํญ์ ์ต์ ๋ฒ์ ์ธ์ง ํ์ธ!
๋ผ์ด๋ธ๋ฌ๋ฆฌ ์ฌ์ฉ ํ์
BrowserRouter๋ HTML5 history API๋ฅผ ์ฌ์ฉํ์ฌ ํด๋ผ์ด์ธํธ ์ธก์์ ๋ผ์ฐํ
์ ์ฒ๋ฆฌ.
Routes๋ ์ฌ๋ฌ ๊ฒฝ๋ก๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ ์ฌ์ฉ๋๋ฉฐ,
Route๋ ๊ฒฝ๋ก์ ํด๋น ๊ฒฝ๋ก์ ๋ํ ์ปดํฌ๋ํธ๋ฅผ ๋งคํ.
๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ์ฃผ์ ๊ธฐ๋ฅ
path variable ์ด๋ผ๋ ๊ธฐ๋ฅ -> useParams๋ฅผ ์ด์ฉํด์ url์ ๋ณ์๋ก ์ง์
Query String ์ฒ๋ฆฌ ๊ธฐ๋ฅ -> useSearchParams ํจ์๋ฅผ ์ด์ฉํด์ ์ฟผ๋ฆฌ์คํธ๋ง์ผ๋ก ์จ ๋ณ์๋ค์ ์ ์ฅ
page moving - > useNavigate()์ ์ด์ฉํด์ ์ง์ ํ ํ์ด์ง๋ก ๋ณด๋ด๋ ๊ฒ -1์ ๋ฃ์ผ๋ฉด ๋ค๋ก ๊ฐ๊ธฐ ๊ฐ๋ฅ
์๋๋ ์ฌ์ฉ์ ๋ํ ์์ ์ฝ๋
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "./pages/Home";
import New from "./pages/New";
import Edit from "./pages/Edit";
import Diary from "./pages/Diary";
function App() {
return (
<BrowserRouter>
<div className="App">
<Routes>
<Route path="/" element={<Home />} />
<Route path="/new" element={<New />} />
<Route path="/edit/:id" element={<Edit />} />
<Route path="/diary/:id" element={<Diary />} />
</Routes>
</div>
</BrowserRouter>
)
}
import { useNavigate, useParams } from "react-router-dom";
const Diary = () => {
const { id } = useParams();
.
.
.
return (
<div className="DiaryPage">
<MyHeader
headText={`${getStringDate(new Date(data.date))} ๊ธฐ๋ก`}
leftChild={
<MyButton text={"< ๋ค๋ก๊ฐ๊ธฐ"} onClick={() => navigate(-1)} />
}
rightChild={
<MyButton
text={"์์ ํ๊ธฐ"}
onClick={() => navigate(`/edit/${data.id}`)}
/>
}
/>
.
.
.
)
}