과거 프로그래밍 자료들/React

Next.js와 리덕스 설명(로그인, 로그아웃)

평부 2022. 10. 6. 15:33

 

출처 : https://www.inflearn.com/course/%EB%85%B8%EB%93%9C%EB%B2%84%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EB%A6%AC%EB%89%B4%EC%96%BC/dashboard

 

[리뉴얼] React로 NodeBird SNS 만들기 - 인프런 | 강의

리액트 & 넥스트 & 리덕스 & 리덕스사가 & 익스프레스 스택으로 트위터와 유사한 SNS 서비스를 만들어봅니다. 끝으로 검색엔진 최적화 후 AWS에 배포합니다., - 강의 소개 | 인프런...

www.inflearn.com

 

 

 

* 전통적인 웹사이트

- 브라우저가 요청 ↔ 프론트 서버 ↔ 백엔드 ↔ 데이터베이스로 동작

- 장점 : 모든 페이지가 화면에 그려짐

- 단점 : 모든 페이지가 화면에 그려지므로 로딩 속도가 느림

 

 

* 리액트, 뷰, 앵귤러 사용하는 이유

- 사용자에게 빠르게 인터렉션(단방향 서비스가 아니라 서로 교류가 잇는 양방향 통신이나 서비스)을 보여줄 때 

- 로딩창만 일단 먼저 보여줌(시간 단축)

- 서버사이드 렌더링 진행

(검색 엔진을 위함, 첫 방문일 때만 전통적인 웹사이트처럼 움직이다가 페이지 전환 시 방문한 페이지 코드만 보여줌)

 

 

* 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

 

[Next.js] Next.js에서 Prop `className` did not match 경고가 뜨는 이유

이 글은 Prop `props이름` did not match. 로 시작하는 Warning에 관한 내용입니다. 또한 Next.js에서 styled-components를 사용하면서 겪은 여러 가지 문제를 다룹니다. _document.tsx(jsx) Next.js에서 styled-..

tesseractjh.tistory.com

 

▶ 그 외 추가 설치할 것들

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";