Next.js와 리덕스 설명(로그인, 로그아웃)
* 전통적인 웹사이트
- 브라우저가 요청 ↔ 프론트 서버 ↔ 백엔드 ↔ 데이터베이스로 동작
- 장점 : 모든 페이지가 화면에 그려짐
- 단점 : 모든 페이지가 화면에 그려지므로 로딩 속도가 느림
* 리액트, 뷰, 앵귤러 사용하는 이유
- 사용자에게 빠르게 인터렉션(단방향 서비스가 아니라 서로 교류가 잇는 양방향 통신이나 서비스)을 보여줄 때
- 로딩창만 일단 먼저 보여줌(시간 단축)
- 서버사이드 렌더링 진행
(검색 엔진을 위함, 첫 방문일 때만 전통적인 웹사이트처럼 움직이다가 페이지 전환 시 방문한 페이지 코드만 보여줌)
* Next.js
- 새로고침 시 로딩창이 없음(서버사이드 렌더링 적용)
- 페이지 이동 시 점점 빨라짐(캐싱)
- (개인적인 의견) javascript에서만 만든 프로젝트와 달리 router가 필요없다는 느낌 받음
→ pages 항목 만드니 router가 없어도 주소따라 페이지 이동 가능
* 리덕스
- 컴포넌트가 분리되어 있는 경우(state) 데이터가 따로따로 되어 있음
- 사용하려면 부모 컴포넌트 -> 자식 컴포넌트로 전달해야 함
- 중앙에서 관리할 수 없나? = contextAPI, Redux
- 아래의 예시는 id, password 데이터가 AppLayout, LoginForm, UserProfile에 사용됨
[리덕스 사용 전]
* 기존 AppLayout.js
const AppLayout = ({ children }) => {
const [isLoggedIn, setIsLoggedIn] = useState(false);
return (
<div>
<Menu items={items} mode="horizontal" />
{children}
<Row gutter={8}>
<Col xs={24} md={6}>
{isLoggedIn ? ( //로그인이 true면 UserProfile false면 LoginForm
<UserProfile setIsLoggedIn={setIsLoggedIn} />
) : (
<LoginForm setIsLoggedIn={setIsLoggedIn} />
)}
</Col>
</Row>
</div>
);
};
* 기존 LoginForm.js
▶ props인 setIsLoggedIn을 받아옴
▶ setIsLoggedIn(true) 시 LoginForm에서 UserProfile로 넘어감
const LoginForm = ({ setIsLoggedIn }) => {
const [id, onChangeId] = useInput("");
const [password, onChangePassword] = useInput("");
const onSubmitForm = useCallback(() => {
console.log(id, password);
setIsLoggedIn(true);
}, [id, password]);
* 기존 UserProfile.js
▶ props인 setIsLoggedIn을 받아옴
import React, { useCallback } from "react";
const UserProfile = ({ setIsLoggedIn }) => {
const onLogOut = useCallback(() => {
setIsLoggedIn(false); //로그아웃 시 false
}, []);
return (
<>
</>
);
};
[리덕스 사용 후]
* 리덕스 적용 시 생긴 문제 해결 ([Next.js] Next.js에서 Prop `className` did not match 경고가 뜨는 이유)
npm i babel-plugin-styled-components
출처 : https://tesseractjh.tistory.com/164
▶ 그 외 추가 설치할 것들
npm i react-redux
npm i redux-devtools-extension
중앙에서 데이터 관리하는 js 파일
* 리덕스 기본 세팅
export const initialState = {
};
const reducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default reducer;
* 리덕스 세팅 - 1
- /store/configureStore.js
import { applyMiddleware, createStore, compose } from "redux";
import { createWrapper } from "next-redux-wrapper";
import { composeWithDevTools } from "redux-devtools-extension";
import reducer from "../reducers";
const configureStore = (context) => {
console.log(context);
const middlewares = [];
const enhancer =
process.env.NODE_ENV === "production"
? compose(applyMiddleware(...middlewares))
: composeWithDevTools(applyMiddleware(...middlewares));
const store = createStore(reducer, enhancer);
return store;
};
const wrapper = createWrapper(configureStore, {
debug: process.env.NODE_ENV === "development",
});
export default wrapper;
* reducer 파일
- index.js
import { HYDRATE } from "next-redux-wrapper";
const initialState = {
user: {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
},
};
export const loginAction = (data) => {
return {
type: "LOG_IN",
data,
};
};
export const logoutAction = () => {
return {
type: "LOG_OUT",
};
};
//switch문으로 한 번에 관리
const rootReducer = (state = initialState, action) => {
switch (action.type) {
case HYDRATE:
console.log("HYDRATE", action);
return { ...state, ...action.payload };
case "LOG_IN":
return {
...state,
user: {
...state.user,
isLoggedIn: true,
user: action.data,
},
};
case "LOG_OUT":
return {
...state,
user: {
...state.user,
isLoggedIn: false,
user: null,
},
};
default:
return state;
}
};
export default rootReducer;
* 변경된 AppLayout.js
▶ useState 대신 useSelector 사용됨
import { useSelector } from "react-redux";
const AppLayout = ({ children }) => {
const { isLoggedIn } = useSelector((state) => state.user);
console.log("isLoggedIn", isLoggedIn);
return (
<div>
<Menu items={items} mode="horizontal" />
{children}
<Row gutter={8}>
<Col xs={24} md={6}>
{isLoggedIn ? <UserProfile /> : <LoginForm />}
</Col>
</Row>
</div>
);
};
* 변경된 LoginForm.js
▶ useDispatch 사용됨
▶ reducers 폴더에서 loginAction 가져옴
import { useDispatch } from "react-redux";
import { loginAction } from "../reducers";
const LoginForm = () => {
const dispatch = useDispatch();
const [id, onChangeId] = useInput("");
const [password, onChangePassword] = useInput("");
const onSubmitForm = useCallback(() => {
console.log(id, password);
dispatch(
loginAction({
id,
password,
})
);
}, [id, password]);
return (
<FormWrapper onFinish={onSubmitForm} style={{ padding: "10px" }}>
<Button type="primary" htmlType="submit" loading={false}>
로그인
</Button>
</FormWrapper>
);
};
* 변경된 UserProfile.js
import { useDispatch } from "react-redux";
import { logoutAction } from "../reducers";
const UserProfile = () => {
const dispatch = useDispatch();
const onLogout = useCallback(() => {
dispatch(logoutAction());
}, []);
return (
<>
<Button onClick={onLogout}>로그아웃</Button>
</>
);
};
* 리덕스 세팅 - 2
▶ 만약 reducers/index.js에서 switch가 늘어날 경우
▶ 분리할 필요성이 있음
▶ 각각 user, post로 분리 후 combineReducer로 합침
reducers/index.js
import { HYDRATE } from "next-redux-wrapper";
import { combineReducers } from "redux";
import user from "./user";
import post from "./post";
const rootReducer = combineReducers({
//hydrate를 위해 indexReducer 추가(서버사이드 렌더링 위함)
index: (state = {}, action) => {
switch (action.type) {
case HYDRATE:
console.log("HYDRATE", action);
return { ...state, ...action.payload };
default:
return state;
}
},
user,
post,
});
export default rootReducer;
reducers/post, user.js
//post
export const initialState = {
mainPosts: [],
};
const reducer = (state = initialState, action) => {
switch (action.type) {
default:
return state;
}
};
export default reducer;
//user
export const initialState = {
isLoggedIn: false,
user: null,
signUpData: {},
loginData: {},
};
export const loginAction = (data) => {
return {
type: "LOG_IN",
data,
};
};
export const logoutAction = () => {
return {
type: "LOG_OUT",
};
};
const reducer = (state = initialState, action) => {
switch (action.type) {
case "LOG_IN":
return {
...state,
isLoggedIn: true,
user: action.data,
};
case "LOG_OUT":
return {
...state,
isLoggedIn: false,
user: null,
};
default:
return state;
}
};
export default reducer;
* 이후 components에서 action을 가져오는 경로 수정
- LoginForm, UserProfile.js
//기존
import { loginAction } from "../reducers;
//수정
import { loginAction } from "../reducers/user";