SPA와 회원 가입 기능 구현 (23.09.11)

·2023년 9월 11일
0

React

목록 보기
6/30
post-thumbnail

💡 SPA(Single Page Application)

하나의 페이지를 사용하는 애플리케이션
-> 서버로부터 새로운 페이지를 가져오지 않고, 하나의 페이지에서 내용을 동적으로 변경함

전통적인 웹 사이트는 페이지 하나에 전달되는 데이터의 용량이 적었기 때문에, 새로운 페이지로 이동할 때 완전히 새로운 페이지를 서버에서 전송해 주었다. 이렇게 해도 파일의 크기가 크지 않아 큰 문제가 없었다.

그러나 점차 웹 사이트가 발전하면서 한 페이지가 가지고 있는 데이터의 용량이 커져 갔고, 매번 새로운 페이지를 전달하는 것이 버거워지게 되었다. 이에 따라 SPA를 사용하게 된 것이다.

SPA 구현 방식의 대표적인 예시로는 Ajax가 있다. 페이지를 새로 고침 하지 않아도 데이터가 업데이트 되는 것이다. 또한 SPA는 컴포넌트 개념으로 작성된 페이지에 적합한데, React를 사용하여 페이지가 컴포넌트들로 구성된 페이지일 경우 컴포넌트의 내용 또는 컴포넌트 자체를 교체하면 되므로 SPA가 특히 유용하다.

이 개념을 인지한 상태에서 이번 포스팅에서는 React와 Spring을 사용하여 각각 프론트엔드, 백엔드 코드를 작성해 볼 것이다. 이를 활용하여 회원 가입 기능을 구현해 보자!


👀 코드로 살펴보기

📁 Oracle DBMS

-- 계정 생성
ALTER SESSION SET "_ORACLE_SCRIPT" = TRUE;
CREATE USER todolist IDENTIFIED BY todolist1234;
GRANT CONNECT, RESOURCE TO todolist;
ALTER USER todolist DEFAULT TABLESPACE SYSTEM QUOTA UNLIMITED ON SYSTEM;

CREATE TABLE TODO_MEMBER(
   TODO_MEMBER_NO NUMBER PRIMARY KEY,
   ID VARCHAR2(30) NOT NULL,
   PW VARCHAR2(100) NOT NULL,
   NAME NVARCHAR2(10) NOT NULL
);

DROP TABLE TODO_LIST;

-- Todo-List용 테이블
CREATE TABLE TODO_LIST(
   TODO_NO NUMBER PRIMARY KEY,
   TITLE NVARCHAR2(50) NOT NULL,
   IS_DONE CHAR(1) DEFAULT 'X' CHECK (IS_DONE IN ('O','X')),
   TODO_MEMBER_NO NUMBER REFERENCES TODO_MEMBER 
);

CREATE SEQUENCE SEQ_TODO_MEMBER_NO NOCACHE;
CREATE SEQUENCE SEQ_TODO_NO NOCACHE;

위와 같이 TODO_LIST, TODO_MEMBER 테이블을 생성했다.


📁 VS Code

🔎 App.js

import React, { useState, createContext } from 'react';
import './App.css';

import SignupContainer from './Signup';

function App() {
  // 회원가입, 로그인, 회원의 Todo List 출력/추가/제거
  const [signupView, setSignupView] = useState(false);

  return (
    <>
      <button onClick={ () => {setSignupView(!signupView)} }>
        { signupView ? ('회원 가입 닫기') : ('회원 가입 열기') }
      </button>

      <div className='signup-wrapper'>
        {/* signupView가 true인 경우에만 회원 가입 컴포넌트 렌더링 */}
        {/* 조건식 && (true인 경우) */}
        {signupView === true && (<SignupContainer/>)}
      </div>
    </>
  );
}

export default App;

🔎 App.css

#root{
  padding: 15px;
}

.signup-wrapper{
  position: relative;
}

.signup-container{
  width: 300px;
  padding: 10px;
  position: absolute;

  background-color: white;
  border: 3px solid #ccc;
}

.signup-container > label{
  display: flex;
  justify-content: space-between;
  padding: 5px 0;
}

.signup-container > label > input{
  flex-basis: 65%;
}

.signup-container button{
  width: 100%;
}

🔎 Signup.js

회원 가입 Component 안에 회원 가입 요청 코드를 작성한다. (비동기, POST)

import React, { useState } from 'react';

const SignupContainer = () => {

    const [id, setId] = useState('');
    const [pw, setPw] = useState('');
    const [pwCheck, setPwCheck] = useState('');
    const [name, setName] = useState('');
    const [result, setResult] = useState('');

    // 회원 가입 함수
    const signup = () => {
        if(pw !== pwCheck){
            alert('비밀번호가 일치하지 않습니다.');
            return;
        }

        /* 회원 가입 요청(비동기, POST) */
        fetch("/signup", {
            method : "POST",
            headers : {
                "Content-Type" : "application/json"
            },
            // JS Object -> JSON
            body : JSON.stringify({
                id : id,
                pw : pw,
                name : name
            })            
        })
        .then(resp => resp.text())
        .then(result => {
            if(result > 0){
                setResult('회원 가입 성공');
                setId('');
                setPw('');
                setPwCheck('');
                setName('');
            }

            else{
                setResult('회원 가입 실패');
            }
        })
        .catch( e => console.log(e));

    };

    return(
        <div className='signup-container'>
            <label>
                ID : 
                <input type='text'
                    onChange={ e => {setId(e.target.value)} }
                    value={id}
                />
            </label>
            <label>
                PW : 
                <input type='password'
                    onChange={ e => {setPw(e.target.value)} }
                    value={pw}
                />
            </label>
            <label>
                PW CHECK : 
                <input type='password'
                    onChange={ e => {setPwCheck(e.target.value)} }
                    value={pwCheck}
                />
            </label>
            <label>
                NAME : 
                <input type='text'
                    onChange={ e => {setName(e.target.value)} }
                    value={name}
                />
            </label>

            <button onClick={signup}>가입하기</button>

            <hr/>

            <h3>{result}</h3>
        </div>
    );
};

export default SignupContainer;

🔎 .env

포트 번호를 바꿔 주기 위해 .env 파일을 만든 후, 포트 번호를 80번으로 지정해 주었다.

PORT=80

🔎 package.json

현재 Spring에서 설정한 서버의 포트 번호는 8080이다.
Spring과 React를 연동시켜 주기 위해 proxy 구문을 추가해 준다.

💭 Proxy란?

"대리"의 의미로, 클라이언트와 Web 서버 중간에 위치하고 있어 대신 통신을 받아 주는 역할

{
	...
  },
  "proxy" : "http://localhost:8080"
}

📁 Spring

Package Explorer로 본 구조는 이러하다.

🔎 Todo.java

package edu.kh.todo.model.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class Todo {
	private int todoNo;
	private String title;
	private String isDone;
	private int todoMemberNo;
}

🔎 TodoMember.java

package edu.kh.todo.model.dto;

import lombok.Getter;
import lombok.Setter;
import lombok.ToString;

@Getter
@Setter
@ToString
public class TodoMember {
	private int todoMemberNo;
	private String id;
	private String pw;
	private String name;
}

🔎 TodoController.java

package edu.kh.todo.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import edu.kh.todo.model.dto.TodoMember;
import edu.kh.todo.model.service.TodoService;

@RestController
public class TodoController {

	@Autowired
	private TodoService service;

	/** 회원 가입 */
	@PostMapping(value="/signup", produces="application/json; charset=UTF-8" )
	public int signup(@RequestBody TodoMember member) {

		return service.signup(member);
	}

	@GetMapping("/test")
	public int test() {
		return 100;
	}
}

🔎 TodoService.java

package edu.kh.todo.model.service;

import edu.kh.todo.model.dto.TodoMember;

public interface TodoService {

	int signup(TodoMember member);

}

🔎 TodoServiceImpl.java

package edu.kh.todo.model.service;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import edu.kh.todo.model.dao.TodoDao;
import edu.kh.todo.model.dto.TodoMember;

@Service
public class TodoServiceImpl implements TodoService{

	@Autowired
	private TodoDao dao;

	@Override
	public int signup(TodoMember member) {
		return dao.signup(member);
	}

}

🔎 TodoDao.java

package edu.kh.todo.model.dao;

import org.mybatis.spring.SqlSessionTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import edu.kh.todo.model.dto.TodoMember;

@Repository
public class TodoDao {

	@Autowired
	private SqlSessionTemplate sqlSession;

	public int signup(TodoMember member) {
		return sqlSession.insert("todoMapper.signup", member);
	}
	
}

🔎 todo-mapper.xml

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="todoMapper">

	<insert id="signup">
		INSERT INTO TODO_MEMBER
		VALUES(SEQ_TODO_MEMBER_NO.NEXTVAL, #{id}, #{pw}, #{name})
	</insert>
	
</mapper>

💻 구현 화면

구현한 화면에서 아이디, 비밀번호, 이름을 입력한 뒤 '가입하기' 버튼을 클릭해 보자. 회원 가입이 성공했을 경우 아래에 '회원 가입 성공' 문구가 출력된다.

DBMS를 확인해 보면 방금 입력한 데이터가 성공적으로 삽입된 것을 볼 수 있다!

profile
풀스택 개발자 기록집 📁

0개의 댓글