본문 바로가기

React/react 활용하기

react + redux를 활용한 로그인및 회원가입 만들기 - 회원가입만들기

https://github.com/loy124/express-mongodb-template

 

loy124/express-mongodb-template

express 와 mongodb를 연동해서 회원가입및 로그인을 구현해둔 예제입니다. Contribute to loy124/express-mongodb-template development by creating an account on GitHub.

github.com

 

해당 게시글을 통해 backend를 구축하고 해당 백엔드와 연동하는 프론트를 React로 구축을한다 

 

 

https://github.com/loy124/react-user-template

 

loy124/react-user-template

react + redux(redux thunk, redux promise) + axios를 활용한 회원가입및 로그인 기능 구현(+ 특정 접근 방지네비게이션 가드) - loy124/react-user-template

github.com

완성된 리액트 페이지

 

해당 게시글은 git에 올린 react-user-template을 기반으로 설명위주로 글을 쓸 예정이다 

 

먼저 를 사용해서 리액트 기본 상태를 구축해준다.

npx create-react-app frontend

 

해당 프로젝트에서는 react-router-dom, redux, react-redux(리덕스를 쉽게 사용할수 있게 도와주는 라이브러리)

, redux-promise(리덕스에서 promise 패턴을 사용할수 있게 해주는 라이브러리), redux-thunk(만약에 특정 액션이 몇초뒤에 실행되게 하거나, 현재 상태에 따라 아예 액션이 무시등 해당 기능을 사용하기 위해 사용), axios를 사용하기 때문에

 

터미널에서 해당 명령어를 실행해서 전부 다운받을 수 있게 한다.

npm i react-router-dom redux react-redux redux-promise redux-thunk axios --save

 

그리고 폴더구조를 생성해준다

 

_actions(redux의 action들 모음)

_reducers(redux의  reducers모음)

_components/views(화면을 그려주는 부분)

hoc(auth등 로그인시 접근제어를 위한 컴포넌트 폴더)

utils(라이브러리를 모아둔다, axios등)

 

 

먼저 redux에 대한 셋팅을 해준다.

 

1. Provider 로 react 감싸기 -> store생성하기(reduxThunk, reduxrnt) -> Provider에 생성한 store 바인딩하기 

 

index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Provider } from "react-redux";
import { applyMiddleware, createStore } from "redux";
import promiseMiddlerware from "redux-promise";
import reduxThunk from "redux-thunk";

const createStoreWidthMiddleware = applyMiddleware(
  promiseMiddlerware,
  reduxThunk
)(createStore);

ReactDOM.render(
  <React.StrictMode>
    <Provider
      store={createStoreWidthMiddleware(
        // 리듀서를 생성후 넣어준다
        // 
        //개발자 도구를 사용하기 위한 설정
        window.__REDUX_DEVTOOLS_EXTENSION__ &&
          window.__REDUX_DEVTOOLS_EXTENSION__()
      )}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

serviceWorker.unregister();

 

reducer관련 파일들을 모두 제작 한후 reducer 또한 넣어줄 예정이다.

 

먼저 axios는 전역적으로 활용하기 때문에 모듈화를 진행시키는게 좋다고 판단되어 모듈화를 진행시켰다.

 

axios.js

import axios from "axios";

const DOMAIN = "http://localhost:9000";
axios.defaults.withCredentials = true; // 쿠키 데이터를 전송받기 위해
export const request = (method, url, data) => {
  return axios({
    method,
    url: DOMAIN + url,
    data,
  })
    .then((res) => res.data)
    .catch((err) => console.log(err));
};

 

쿠키데이터를 주고받기 위해 axios.default.withCredentials를 true로 지정해주고 

request를 export 시켜서 어디서든 import해서 사용할수 있도록 모듈화를 진행하였다.

 

 

이제 각각 view들을 만들어준다.

 

기본적으로 LandingPage(기본화면), LoginPage(로그인 화면), RegisterPage(회원가입 화면) 세가지로 나뉘어있고

회원가입, 로그인, 로그아웃, auth(특정 라우터에 접근을 하지 못하게 만들기) 기능을 구현할 예정이다.

 

먼저 view들을 만들어준다

 

 

RandingPage.js

 

import React from "react";
import { withRouter } from "react-router-dom";

function LandingPage(props) {
  const onClickHandler = () => {};

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}>
      <h2>시작 페이지</h2>
      <button onClick={onClickHandler}>로그아웃</button>
    </div>
  );
}

export default withRouter(LandingPage);

 

RegisterPage.js

 

import React, { useState } from "react";

import { withRouter } from "react-router-dom";

function RegisterPage(props) {
  const [Email, setEmail] = useState("");
  const [Password, setPassword] = useState("");
  const [Name, setName] = useState("");
  const [ConfirmPasword, setConfirmPasword] = useState("");

  const onEmailHandler = (e) => {
    setEmail(e.currentTarget.value);
  };

  const onNameHandler = (e) => {
    setName(e.currentTarget.value);
  };

  const onPasswordHanlder = (e) => {
    setPassword(e.currentTarget.value);
  };

  const onConfirmPasswordHandler = (e) => {
    setConfirmPasword(e.currentTarget.value);
  };

  const onSubmitHandler = (e) => {
    e.preventDefault();
  };
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}>
      <form
        onSubmit={onSubmitHandler}
        style={{ display: "flex", flexDirection: "column" }}>
        <label>Email</label>
        <input type="email" value={Email} onChange={onEmailHandler} />

        <label>Name</label>
        <input type="test" value={Name} onChange={onNameHandler} />

        <label>Password</label>
        <input type="password" value={Password} onChange={onPasswordHanlder} />

        <label>ConfirmPasword</label>
        <input
          type="password"
          value={ConfirmPasword}
          onChange={onConfirmPasswordHandler}
        />
        <br />
        <button type="submit">회원 가입</button>
      </form>
    </div>
  );
}

export default withRouter(RegisterPage);

 

LoginPage.js

 

import React, { useState } from "react";
import { withRouter } from "react-router-dom";
function LoginPage(props) {


  const [Email, setEmail] = useState("");
  const [Password, setPassword] = useState("");

  const onEmailHandler = (e) => {
    setEmail(e.currentTarget.value);
  };
  const onPasswordHanlder = (e) => {
    setPassword(e.currentTarget.value);
  };

  const onSubmitHandler = (e) => {
    e.preventDefault();
  };

  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}>
      <form
        onSubmit={onSubmitHandler}
        style={{ display: "flex", flexDirection: "column" }}>
        <label>Email</label>
        <input type="email" value={Email} onChange={onEmailHandler} />
        <label>Password</label>
        <input type="password" value={Password} onChange={onPasswordHanlder} />
        <br />
        <button type="submit">Login</button>
      </form>
    </div>
  );
}

export default withRouter(LoginPage);

 

useState(https://loy124.tistory.com/232?category=784079 ) 를 활용하여 현재 state와 state를 변경하는 함수를 각각 지정해주었다.

 

view를 만들었으니 해당 요청들을 모두 라우터들로 만들어줘야한다.

 

App.js

 

import React from "react";
import "./App.css";
import { BrowserRouter as Router, Switch, Route } from "react-router-dom";
import LandingPage from "./components/views/LandingPage/LandingPage";
import LoginPage from "./components/views/LoginPage/LoginPage";
import RegisterPage from "./components/views/RegisterPage/RegisterPage";
function App() {
  return (
    <Router>
      <div>
        <Switch>
          <Route exact path="/" component={LandingPage} />
          <Route exact path="/login" component={LoginPage} />
          <Route exact path="/register" component={RegisterPage} />
        </Switch>
      </div>
    </Router>
  );
}

export default App;

Router 내에서 Switch를 사용해서 다른 라우터들을 보여준다

 

 

view를 만들었으니 이제 회원가입 기능을 redux를 활용해서 구현해 보겠다.

 

 

 

 

먼저 types.js다 types.js에서는 각각 type들의 형태를 변수로 관리한다 (오타 방지및 관리를 편하게 하기위함)

회원가입 기능이니 REGISTER_USER로 정의해주었다.

 

type.js

export const REGISTER_USER = "REGISTER_USER";

 

이를 바탕으로 action 함수를 생성해준다 

 

userAction.js

import { REGISTER_USER } from "./types";
import { request } from "../utils/axios";

const USER_URL = "/api/user";

export function registerUser(dataToSubmit) {
  const data = request("post", USER_URL + "/register", dataToSubmit);

  return {
    type: REGISTER_USER,
    payload: data,
  };
}

 

 

모듈화된 axios로 제작된 request를 사용해서 액션 함수를 생성해준다.

post 요청을 보내고 받은 값을 payload에 실어서 

 

reducer에서 해당 타입을 읽을수 있도록 해야한다.

 

userReducer.js

import { REGISTER_USER } from "../_actions/types";

export default function (state = {}, action) {
  switch (action.type) {
    case REGISTER_USER:
      return { ...state, loginSuccess: action.payload };
    default:
      return state;
  }
}

 

 REGISTER_USER 일때 loginSuccess에 action의 payload값 을 받아온다 (userAction.js)의 값을 받아온다고 보면된다

 

해당 reducer을 한곳에서 묶어서 하나의 리듀서처럼 보여지게 하는 작업이 필요한데 

_reducers/index.js에서 combinReducers를 사용해서 하나의 리듀서로 만들어준다.

 

import { combineReducers } from "redux";
import user from "./userReducer";

const rootReducer = combineReducers({
  user,
});

export default rootReducer;

 

reducer가 생성되었으니 해당 reducer를 store에 넣어줘야한다.

 

 

index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import * as serviceWorker from "./serviceWorker";
import { Provider } from "react-redux";
import { applyMiddleware, createStore } from "redux";
import promiseMiddlerware from "redux-promise";
import reduxThunk from "redux-thunk";
import reducer from "./_reducers";

const createStoreWidthMiddleware = applyMiddleware(
  promiseMiddlerware,
  reduxThunk
)(createStore);

ReactDOM.render(
  <React.StrictMode>
    <Provider
      store={createStoreWidthMiddleware(
        reducer,
        //개발자 도구를 사용하기 위한 설정
        window.__REDUX_DEVTOOLS_EXTENSION__ &&
          window.__REDUX_DEVTOOLS_EXTENSION__()
      )}>
      <App />
    </Provider>
  </React.StrictMode>,
  document.getElementById("root")
);

// If you want your app to work offline and load faster, you can change
// unregister() to register() below. Note this comes with some pitfalls.

serviceWorker.unregister();

 

 

해당 기능까지 완료하였다면 정상적으로 회원가입 기능이 완성이 되었다.

 

RegisterPage.js에서 사용할 차례이다 

 

useDispatch(useDispatch는 redux store에 설정된 action에 대한 dispatch를 연결하는 훅이다.)와 registerUser를 import 해서 

useDispatch 안에 액션 함수를 넣어준다.

import React, { useState } from "react";
import { withRouter } from "react-router-dom";
import { useDispatch } from "react-redux";
import { registerUser } from "../../../_actions/userAction";

function RegisterPage(props) {
  const [Email, setEmail] = useState("");
  const [Password, setPassword] = useState("");
  const [Name, setName] = useState("");
  const [ConfirmPasword, setConfirmPasword] = useState("");
  const dispatch = useDispatch();

  const onEmailHandler = (e) => {
    setEmail(e.currentTarget.value);
  };

  const onNameHandler = (e) => {
    setName(e.currentTarget.value);
  };

  const onPasswordHanlder = (e) => {
    setPassword(e.currentTarget.value);
  };

  const onConfirmPasswordHandler = (e) => {
    setConfirmPasword(e.currentTarget.value);
  };

  const onSubmitHandler = (e) => {
    e.preventDefault();
    if (Password === ConfirmPasword) {
      let body = {
        email: Email,
        name: Name,
        password: Password,
      };
      dispatch(registerUser(body)).then((res) => {
        alert("가입이 정상적으로 완료되었습니다");
        props.history.push("/login");
      });
    } else {
      alert("비밀번호가 일치하지 않습니다");
    }
  };
  return (
    <div
      style={{
        display: "flex",
        justifyContent: "center",
        alignItems: "center",
        width: "100%",
        height: "100vh",
      }}>
      <form
        onSubmit={onSubmitHandler}
        style={{ display: "flex", flexDirection: "column" }}>
        <label>Email</label>
        <input type="email" value={Email} onChange={onEmailHandler} />

        <label>Name</label>
        <input type="test" value={Name} onChange={onNameHandler} />

        <label>Password</label>
        <input type="password" value={Password} onChange={onPasswordHanlder} />

        <label>ConfirmPasword</label>
        <input
          type="password"
          value={ConfirmPasword}
          onChange={onConfirmPasswordHandler}
        />
        <br />
        <button type="submit">회원 가입</button>
      </form>
    </div>
  );
}

export default withRouter(RegisterPage);

 

 

test5를 기반으로 회원가입을 진행하였다.

 

 

 

정상적으로 회원가입이 되어 /login 으로 넘어간것을 확인할 수 있다.

 

 

db에도 정상적으로 들어간것을 확인하였다.

 

 

www.inflearn.com/course/%EB%94%B0%EB%9D%BC%ED%95%98%EB%A9%B0-%EB%B0%B0%EC%9A%B0%EB%8A%94-%EB%85%B8%EB%93%9C-%EB%A6%AC%EC%95%A1%ED%8A%B8-%EA%B8%B0%EB%B3%B8#

 

따라하며 배우는 노드, 리액트 시리즈 - 기본 강의 - 인프런

이 강의를 통해서 리액트와 노드를 어떻게 사용하는지 기본적인 내용들을 배울 수 있습니다. 초급 웹 개발 프레임워크 및 라이브러리 React Node.js 웹 개발 온라인 강의 실무에서 가장 많이 활용되

www.inflearn.com

해당 글은 위 강의를 바탕으로 작성되었습니다.