전역상태 관리 라이브러리이며, 중앙 state관리소라고 생각하면 편하다. 모든 state를 여기서 생성한다.
useState(Local State) <-> Redex(Global state)
yarn add redux react-redux
Redux에서는 폴더구조가 굉장히 중요하다
1. src 안에 redux폴더
생성
2. redux폴더
-> config폴더
랑 modules폴더
3. config폴더
-> configStore.js
redux폴더
: 리덕스와 관련된 코드를 모두 모아 놓을 폴더.config폴더
: 리덕스 설정과 관련된 파일들을 놓을 폴더.configStore.js
: 중앙 state 관리소" 인 Store를 만드는 설정 코드.modules
: 우리가 만들 State들의 그룹이 들어감.// configStore.js
import { createStore } from "redux";
import { combineReducers } from "redux";
//createStore()이랑 combineReducers() 이 뭔지 알려하지마!!!
//생각하지말고 그냥 무지성으로 쓰기.
const rootReducer = combineReducers({/* 여기 안에 리듀서 함수 넣는다.*/});
const store = createStore(rootReducer);
export default store;
//index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import reportWebVitals from "./reportWebVitals";
//추가할 코드
import store from "./redux/config/configStore";
import { Provider } from "react-redux";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
//App을 Provider로 감싸주고, configStore에서 export default 한 store를 넣는다.
<Provider store={store}>
<App />
</Provider>
);
reportWebVitals();
initialState : 초기 상태 값 설정
-> 이 부분은 reducer
함수의 return 부분에 순수함수
로서 형태를 맞춰야하기 때문에, 초기 설정값이 매우 중요하다고 볼 수 있겠다.
Reducer : 함수 모음집
// counter.js
// counter 리듀서
const counter = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default counter;
//configStore.js
import counter from "../modules/counter";
const rootReducer = combineReducers({
counter: counter, // <-- 새롭게 추가한 부분
});
const store = createStore(rootReducer);
export default store;
만든 모듈을 스토어에 연결시켜야 한다.
store
를 조회할 때에는 useSelector라는 react-redux
에서 제공하는 useSelector라는 hook을 이용한다.//App.js
import React from "react";
import { useSelector } from "react-redux";
const App = () => {
const counterStore = useSelector((state) => state);
console.log(counterStore); // 스토어를 조회
return <div></div>;
}
export default App;
스토어에서 reducer 함수를 counter라고 store 연결시켰기 때문에, 콘솔에 찍히는 값은 리듀서 함수와 그 값들이 되겠다.
useDispatch
useDispatch
라는 훅을 이용해서 리듀서로 보내게 된다.
// src/App.js
import React from "react";
import { useDispatch } from "react-redux";
const App = () => {
const dispatch = useDispatch(); // dispatch 생성
return (
<div>
<button
onClick={() => {// ()안에 있는 액션객체가 리듀서로 전달된다.
dispatch({ type: "PLUS_ONE" });
}}
>
+ 1
</button>
</div>
);
};
export default App;
reducer
함수가 들어있는 파일로 이동하여, switch문을 잘 작성해주기만 하면 끝!
switch 문의 조건에는 action의 type값을 받아올 수 있도록 할당하며, case에는 type에 넣었던 string 문자열을 그대로 넣어준다. type값을 조회할 때, 이 문자열과 비교하여 실행할지 말지를 결정하기 때문에, human error가 나지 않도록 조심하자.
//counter.js
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
console.log(action);
switch (action.type) {
case "PLUS_ONE":
return {
number: state.number + 1,
};
default: // default값을 추가하는 버릇을 키워보자.
return state;
}
};
export default counter;
그럼 dispatch를 이용해서 state값을 변경했으니, store에서 바뀐 state값을 조회해보자.
조회는 뭐다? redux
의 기본 hook인 useSelector를 이용해서 가져온다.
const number = useSelector((state) => state.counter.number);
//counter.js
// Action value를 상수들로 만들어 한곳에 모은다.
const PLUS_ONE = "PLUS_ONE";
const MINUS_ONE = "MINUS_ONE";
// Action Creator를 만든다.
export const plusOne = () => {
return {
type: PLUS_ONE,
};
};
export const minusOne = () => {
return {
type: MINUS_ONE,
};
};
const initialState = {
number: 0,
};
const counter = (state = initialState, action) => {
switch (action.type) {
case PLUS_ONE: // case에서도 위에서 만든 상수를 넣어버려.
return {
number: state.number + 1,
};
case MINUS_ONE:
return {
number: state.number - 1,
};
default:
return state;
}
};
export default counter;
state를 변경하는데 있어 만약 리듀서에게 어떤 값을 같이 보내줘야 한다면 payload를 액션객체에 같이 담아 보내는 것이다.
//counter.js
// Action Value
const ADD_NUMBER = "ADD_NUMBER";
// Action Creator
export const addNumber = (payload) => {
return {
type: ADD_NUMBER, // type에는 위에서 만든 Action Value를 넣어준다.
payload: payload, // payload의 경우에는 인자로 받아와서 return 해줄 수 있다.
};
};
//reducer
const counter = (state = initialState, action) => {
switch (action.type) {
case ADD_NUMBER:
return {
number: state.number + action.payload,
};
default:
return state;
}
};
페이지 전환에 주로 쓰이는 패키지라고 생각하면 되겠다.
yarn add react-router-dom
react-redux와 마찬가지로, react-router-dom도 폴더 구조가 매우 중요한다.
pages폴더
생성 -> 안에 여러 페이지 만들기 ex) Home.jsx
, About.jsx
, Contact.jsx
shared 폴더
생성 -> 안에 Router.js
생성 -> router 설정 코드 작성App.js
에서 import 해버리기Router.js
-> 브라우저에 우리가 URL을 입력하고 이동했을 때 우리가 원하는 페이지 컴포넌트로 이동하게끔 만드는 부분이다.
//Router.js
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
Routes안에 있는 Route에는 react-router-dom에서 지원하는 props들이 있다. path는 원하는 "주소"를 넣어주면 된다.
element는 해당 주소로 이동했을 때 보여주고자 하는 page 컴포넌트를 작성해주면 되겠다. (import 꼭 해주기!)
//App.js
import React from "react";
import Router from "./shared/Router";
function App() {
return <Router />;
}
export default App;
Router.js를 App 컴포넌트에 넣어주는 이유?
생각하지 말고 걍 무조건 App.js를 거쳐야 한다. path 별로 분기가 되는 Router.js를 App.js에 위치시키고, 이 페이지를 이용하는 모든 사용자가 무조건 App.js → Router.js 이런 식으로 이동할 수 있겠금 해야만 한다!!!!
//Home.js
import { useNavigate } from "react-router-dom";
const Home = () => {
const navigate = useNavigate();
return (
<button
onClick={() => {
navigate("/works");
}}
>
works로 이동
</button>
);
};
export default Home;
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
import Work from "../pages/Work";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
<Route path = "works/:id" element ={<Work/>}/>
</Routes>
</BrowserRouter>
);
};
export default Router;
최적화를 위해 데이터는 다른 파일로 빼주는 연습을 해본다.
//data.js
export const data = [
{
id : 1,
todo : '리액트 배우기',
},
{
id : 2,
todo : '노드 배우기',
},
{
id : 3,
todo : '스프링 배우기',
},
{
id : 4,
todo : '파이어 베이스 배우기',
},
{
id : 5,
todo : '데이터 베이스 배우기',
},
{
id : 6,
todo : 'next.js 배우기',
}
];
data를 다른 파일로 빼준거를 이제 work와 works 두개에 모두 import를 해주어야 한다.
//Works.js
import React from 'react';
import { Link, useParams } from 'react-router-dom';
import data from "../shared/data/";
function Works() {
return (
<div>
할일목록
{data.map((work) => {
return (
<div key={work.id}>
<div> 할일: {work.id}</div>
<Link to={`/works/${work.id}`}>
<span style={{ cursor: 'pointer' }}>➡️ Go to: {work.todo}</span>
</Link>
</div>
);
})}
</div>
);
}
export default Works;
상위 컴포넌트인 Work.jsx에 data를 import 해주고, map함수를 이용해서 data배열을 순회하면서 불러오도록 해보자.
<Link>
태그를 이용해서 하위 컴포넌트로 이동하는 path를 지정해줄 수 있다.
"<Link to={/works/${work.id}
}>"로 경로를 설정해주면, 라우터에서 지정한 id값으로 지정되어 하위페이지를 불러온다.
function Work() {
const param = useParams();
const work = data.find((work) => work.id === parseInt(param.id));
return <div>{work들어가서 비교할 수 있는 조건으로 함수를 만들 수 있다.
```jsx
const work = data.find((work).todo}</div>;
}
export default Work;
useParams 훅을 이용해서 불러온 parameter를 가져온다.
-> Router.js에서 설정한 Workd/:id에서 id로 parameter를 설정해놨기 때문에 하위페이지로 넘어왔을때, 이게 무슨 페이지인지 useParams를 이용해서 확인할 수 있게 된 것이다.
리덕스 끝! 전역하고 와야징