본문 바로가기
국비학원/수업기록

국비 지원 개발자 과정_Day74

by 루팽 2023. 3. 15.

캡쳐링-부모에서 자식으로

버블링-자식에서 부모로

컴포넌트 설계 → 부모에서 자식으로 전해주는 건 가능하지만 반대는 안된다

 

렌더링 되는 경우 3가지 → state, props, 부모컴포넌트가 바뀔 때!

 

<해커뉴스 페이지처리 - HackerNewsPage.jsx>

import axios from "axios";
import React, { useEffect, useState } from "react";
import { useNavigate } from "react-router-dom";
import styled from "styled-components";
import HackerNewsList from "../hacker/HackerNewsList";
import Bottom from "../include/Bottom";
import Header from "../include/Header";

const NewsListUL = styled.ul`
  background-color: gray;
  padding-top: 10px;
  padding-left: 6px;
  padding-right: 6px;
  padding-bottom: 18px;
`

const HackerNewsPage = ({ authLogic }) => {
  const [newsList, setNewsList] = useState([]);
  // 페이징 처리에 필요한 코드 추가
  // 현재 바라보는 페이지
  const [currentPage, setCurrentPage] = useState(1)
  // 한 페이지에 출력될 로우의 수
  const [newsPerPage, setNewsPerPage] = useState(5)

  // 배열 객체에서 어디부터 어느 구간까지를 잘라낼 것인가를 위한 변수 선언 및 초기화
  const indexOfLast = currentPage * newsPerPage // 10 20 30 40... 같이 처리됨(1n, 10일경우)
  const indexOfFirst = indexOfLast - newsPerPage // 0 10 20 50... 같이 처리됨
  const currentNews = news => {
    let currentNews = 0
    // 파라미터로 받은 배열에서 잘라내기 - slice배열내장함수(begin부터 end 인덱스 전까지)
    currentNews = news.slice(indexOfFirst, indexOfLast) // 0~9, 10~19, 20~29... 같이 처리됨
    return currentNews
  }

  const NEWSURL = "https://api.hnpwa.com/v0/news/1.json";
  const navigate = useNavigate();

  const onLogout = () => {
    console.log("HackerNewsPage onLogout 호출");
    authLogic.logout();
  };

  useEffect(() => {
    axios.get(NEWSURL).then((response) => {
      console.log(response.date)
      console.log(response.data.length);
      setNewsList(response.data);
    });
  }, []);

  useEffect(() => {
    authLogic.onAuthChange((user) => {
      if (!user) {
        navigate("/");
      }
    });
  });

  return (
    <>
      <Header onLogout={onLogout} />
      <NewsListUL>
        <HackerNewsList newsList={currentNews(newsList)} newsPerPage={newsPerPage} totalNews={newsList.length} paginate={setCurrentPage} />
      </NewsListUL>
      <Bottom />
    </>
  );
};

export default HackerNewsPage;

 

<해커뉴스 페이지처리 - HackerNewsList.jsx>

import React from 'react'
import Pagination from './Pagination'

/*
  HackerNewsPage에서 조회된 결과를 가지고 페이징 처리에 필요한 연산을 수행하여
  필요한 정보를 HackerNewList에 props로 넘겨야함
  데이터셋이 배열 객체이므로 배열 내장 함수들을 활용할 수 있을 것
*/
const HackerNewsList = (props) => {
  const { newsList, newsPerPage, totalNews, paginate } = props
  console.log(newsList.length) // 10
  console.log(newsPerPage) // 10
  console.log(totalNews) // 30
  console.log(paginate) // 함수 출력

  return (
    <>
      <div>
        {newsList && Object.keys(newsList).map((key) => (
            <li key={key}>[id:{newsList[key].id}]
            &nbsp; {newsList[key].title}
            </li>
          ))}
      </div>
      <Pagination newsPerPage={newsPerPage} totalNews={totalNews} paginate={paginate} />
    </>
  )
}

export default HackerNewsList

 

<해커뉴스 페이지처리 - Pagination.jsx>

import React from 'react'
import styled from 'styled-components';

const PageUl = styled.ul`
  float: center;
  list-style: none;
  text-align: center;
  border-radius: 6px;
  color: white;
  padding: 1px;
  border-top: 3px solid #FFC444;
  border-left: 3px solid #FFC444;
  border-right: 3px solid #FFC444;
  border-bottom: 3px solid #FFC444;
  background-color: rgba(78, 77, 77, 0.4);
`;

const PageLi = styled.li`
  display: inline-block;
  font-size: 17px;
  font-weight: 600;
  padding: 5px;
  border-radius: 16px;
  width: 25px;
  &:hover {
    cursor: pointer;
    color: white;
    background-color: #3DB33D;
  }
  &:focus::after {
    color: white;
    background-color: #266C3B;
  }
`;

// css를 js문법을 사용하여 컴포넌트로 만들어주는 라이브러리 - styled-components
// 페이지 숫자에 마우스를 올리면 동그라미처리, 글자색은 흰색
const PageSpan = styled.span`
  &:hover::after,
  &:focus::after {
    border-radius: 100%;
    color: white;
    background-color: #263A6C;
  }
`;

const Pagination = (props) => {
  const { newsPerPage, totalNews, paginate} = props
  console.log(`newsPerPage: ${newsPerPage}, totalNews: ${totalNews}, paginate: ${paginate}`)
  
  // 페이지네이션에 들어갈 숫자를 담을 배열 선언
  const pageNumbers = []
  for(let i=1; i<=Math.ceil(totalNews/newsPerPage); i++){
    // 배열에 값 추가 - 배열 함수 push
    pageNumbers.push(i)
  }

  return (
    <div>
      <PageUl>
        {pageNumbers.map(number => (
            <PageLi key={number}>
              <PageSpan onClick={()=>paginate(number)}>
                {number}
              </PageSpan>
            </PageLi>
          ))}
      </PageUl>
    </div>
  )
}

export default Pagination

 

<우편번호 검색기 - CommonController.java>

package com.pojo.step3;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.log4j.Logger;

import com.util.HashMapBinder;

public class CommonController implements Controller3 {
	Logger logger = Logger.getLogger(CommonController.class);
	private CommonLogic commonLogic = new CommonLogic();
	
	@Override
	public ModelAndView zipcodeList(HttpServletRequest req, HttpServletResponse res) {
		logger.info("zipcodeList 호출");
		List<Map<String, Object>> zList = null;
		// 사용자가 조건 검색을 원하는 경우 - 조건 값을 전달할 객체 생성함
		// MyBatis에서는 동적쿼리를 지원하므로 하나로 2가지 경우 사용 가능함
		Map<String, Object> pMap = new HashMap<>();
		HashMapBinder hmb = new HashMapBinder(req);
		hmb.bind(pMap);
		zList = commonLogic.zipcodeList(pMap);
		ModelAndView mav = new ModelAndView(req);
		// WEB-INF/views/common/jsonZipcodeList.jsp
		mav.setViewName("common/jsonZipcodeList");
		mav.addObject("zList", zList);
		return mav; // 리턴이 mav면 webapp/WEB-INF/views/common 아래의 jsp
	}
	
	@Override
	public Object jsonBoardList(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardList(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardDetail(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageUpload(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageDownload(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object imageGet(HttpServletRequest req, HttpServletResponse res) {
		return null;
	}

	@Override
	public Object boardInsert(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}

	@Override
	public Object boardUpdate(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}

	@Override
	public Object boardDelete(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		return null;
	}
}

 

<우편번호 검색기 - memberMgr.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
	pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원관리 시스템</title>
<%@ include file="../common/easyUI_common.jsp"%>
</head>
<body>
	<script>
		$(document).ready(function() {
			// 우편번호 찾기버튼
			$("#btn_zipcode").linkbutton({
				onClick : function() {
					// $('#dlg_zipcode').dialog('open');
					console.log('우편번호 찾기')
					$('#dlg_zipcode').dialog('open');
				}
			})
			
			// 찾기버튼
			$("#btn_search").linkbutton({
				onClick:function(){
					const u_dong = $("#_easyui_textbox_input1").val();
					if(u_dong == null || u_dong.length < 1) {
						alert("검색할 동을 입력하세요");
						// 동 정보가 없으면 처음부터 새로 시작해야하니 return함
						return;                                                             
					} else {
						console.log('사용자가 입력한 동이름 => ' + u_dong);
						// 오라클 서버를 경유해서 조회된 결과를 datagrid에 출력해보기
					// location.href = "/common/zipcodeList.st3?dong="+u_dong;
						$('#dg_zipcode').datagrid({
						    url:'/common/zipcodeList.st3?dong='+u_dong,
						    columns:[[
						        {field:'ZIPCODE',title:'우편번호',width:100},
						        {field:'ADDRESS',title:'주소',width:300,align:'left'}
						    ]]
						});
					}
				}
			}) // end of 찾기버튼
			
			// 엔터쳤을때
			$('#dong').textbox('textbox').bind('keydown', function(e){
				if(e.keyCode == 13){
					// alert('사용자가 입력한 동이름은 ' + u_dong);
					console.log('사용자가 입력한 동이름 => ' + $(this).val());
					$('#dg_zipcode').datagrid({
					    url:'/common/zipcodeList.st3?dong=' + $(this).val(),
					    columns:[[
					        {field:'ZIPCODE',title:'우편번호',width:100,align:'center'},
					        {field:'ADDRESS',title:'주소',width:300,align:'left'}
					    ]],
					    singleSelect: true,
					    onSelect: function(index, row){
					    	g_address = row.ADDRESS
					    	alert(g_address);
					    },
					    onDbClickCell: function(index, field, value) {
					    	if("ZIPCODE" === field) {
					    		$("#mem_zipcode").textbox('setValue', value)
					    		$("#mem_addr").textbox('setValue', g_address)
					    	}
					    }
					});
				}
			})
		}) // end of ready - DOM Tree가 다 그려지고 실행됨
	</script>
	<!--======================= 우편번호 검색기 =======================-->
	<div id="dlg_zipcode" class="easyui-dialog" title="우편번호찾기" style="width:500px;height:400px;"
	        data-options="iconCls:'icon-save',resizable:true,modal:true, closed:true">
		<input class="easyui-textbox" id="dong" name="dong" labelPosition="top" data-options="prompt:'동이름 이나 주소정보 입력...'" style="width:210px;">
		<a id="btn_search" href="#" class="easyui-linkbutton" data-options="iconCls:'icon-search'">찾기</a>
		<div  style="margin-bottom:10px;"></div>
		<table id="dg_zipcode"></table>
	</div>
	<!--======================= 우편번호 검색기 =======================-->
	<!--======================= 회원가입 =======================-->
	<div id="dlg_ins"
		style="width: 100%; max-width: 480px; padding: 30px 60px;">
		<form id="f_ins">
			<input type="hidden" id="work" name="work" value="member" />
			<div style="margin-bottom: 10px">
				<input class="easyui-textbox" id="mem_id" name="mem_id" label="아이디:"
					labelPosition="top" data-options="prompt:'Enter a ID'"
					style="width: 110px;">
			</div>
			<div style="margin-bottom: 10px">
				<input class="easyui-textbox" id="mem_name" name="mem_name"
					label="이름:" labelPosition="top"
					data-options="prompt:'Enter a Name'" style="width: 150px;">
			</div>
			<div style="margin-bottom: 10px">
				<input class="easyui-textbox" id="mem_zipcode" name="mem_zipcode"
					label="우편번호:" labelPosition="top"
					data-options="prompt:'Enter a ZipCode'" style="width: 100px;">
				<a id="btn_zipcode" href="#" class="easyui-linkbutton">우편번호찾기</a>
			</div>
			<div style="margin-bottom: 10px">
				<input class="easyui-textbox" id="mem_addr" name="mem_addr"
					label="주소:" labelPosition="top"
					data-options="prompt:'Enter a ADDRESS'" style="width: 300px;">
			</div>
			<div style="margin-bottom: 10px">
				<input class="easyui-textbox" id="mem_pw" name="mem_pw" label="비번:"
					labelPosition="top" data-options="prompt:'Enter a PASSWORD'"
					style="width: 110px;">
			</div>
		</form>
	</div>
	<div id="tb_ins" style="padding: 5px;">
		<a id="btn_save" href="#" class="easyui-linkbutton"
			data-options="iconCls:'icon-save'">저장</a> <a id="btn_close" href="#"
			class="easyui-linkbutton" data-options="iconCls:'icon-cancel'">닫기</a>
	</div>
	<!--======================= 회원가입 =======================-->

</body>
</html>

 

<우편번호 검색기 - jsonZipcodeList.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@page import="java.util.*, com.google.gson.Gson"%>
<%
	List<Map<String,Object>> zList = 
	(List<Map<String,Object>>)request.getAttribute("zList");
	Gson g = new Gson();
	String temp = g.toJson(zList);
	out.print(temp);
%>

 

쿠키와 세션

  쿠키 → Client CSR(정적) - text에 저장 → 주소번지를 담을 수 없다, 한글처리 해야 함, 보안에 취약(로컬에 저장되니까)

  세션 → Server SSR(동기화됨, 동적) - 캐시메모리에 저장(용량작음) → 주소번지를 담을 수 있다, 한글처리 안 해도 됨, 보안에 유리(서버에 저장되니까),

  브라우저 안에도 세션값이 있음(쿠키에 세션 ID가 저장됨)

  쿠키는 응답 헤더를 통해서 넘어간다 → flush후에 쿠키를 추가하면 안 됨

  도메인마다 쿠키값이 다를 수 있다 → 그래서 getDomain() 사용

 

#쿠키 관련 메서드

메서드  리턴 타입  설명
getName() String 쿠키 이름을 구한다.
getValue() String 쿠키 값을 구한다.
setValue(String value) void 쿠키 값을 지정한다.
setDomain(String pattern) void 이 쿠키가 전송될 서버의 도메인을 지정한다.
getDomain() String 쿠키의 도메인을 구한다.
setPath(String uri) void 쿠키를 전송할 경로를 지정한다.
getPath() String 쿠키의 전송 경로를 구한다.
setMaxAge(int expiry) void 쿠키의 유효시간을 초 단위로 지정한다.
음수를 입력할 경우 웹 브라우저를 닫을 떄 쿠키가 함께 삭제된다.
getMaxAge() int 쿠키의 유효시간을 구한다.

 

<쿠키 - cookieCreate.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키 생성하기</title>
</head>
<body>
<%
	// 아래 코드는 서버가 바라보는 물리적인 위치에 들어있음
	// 서버측에서 실행됨 -> html -> 다운로드(SSR) -> 동적처리(실시간)
	Cookie c1 = new Cookie("notebook", "삼성갤럭시북");
	c1.setMaxAge(60);
	// 아래 코드를 생략하면 현재 페이지가 바라보는 물리적인 위치를 갖는다
	// 여기서는 /cookie임
	// c1.setPath("/");
	Cookie c2 = new Cookie("hp", "아이폰14");
	c2.setMaxAge(60*2);
	Cookie c3 = new Cookie("coffee", "아메리카노");
	c3.setMaxAge(60*3);
	response.addCookie(c1);
	response.addCookie(c2);
	response.addCookie(c3);
%>
</body>
</html>


<쿠키 - cookieRead.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키정보 읽기</title>
</head>
<body>
<%
	Cookie cs[] = request.getCookies();
	int size = 0;
	size = cs.length;
	for(int i=0; i<size; i++){
		out.print(cs[i].getName() + ", " + cs[i].getValue());
		out.print("<br />");		
	}
%>
</body>
</html>

 

<쿠키 - cookieDelete.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>쿠키 삭제하기</title>
</head>
<body>
<%
	Cookie cookie = new Cookie("notebook", "");
	cookie.setMaxAge(0);
	response.addCookie(cookie);
%>
</body>
</html>

 

<sts 멤버 - MemberController.java>

package com.example.demo.controller;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import com.example.demo.logic.MemberLogic;

@Controller
@RequestMapping("/member/*")
public class MemberController {
	Logger logger = LoggerFactory.getLogger(MemberController.class);
	
	@Autowired
	private MemberLogic memberLogic = null;
	
	@GetMapping("memberInsert")
	public String memberInsert(@RequestParam Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap.toString());
		int result = 0; // 1이면 회원가입 성공, 0이면 실패
		result = memberLogic.memberInsert(pMap);
		return "redirect:memberList";
	}
	
	@GetMapping("memberList")
	public String memberList(Model model) {
		logger.info("memberList 호출");
		return "member/memberList";
	}
}

 

<sts 멤버 - MemberLogic.java>

package com.example.demo.logic;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.example.demo.dao.MemberDao;

@Service
public class MemberLogic {
	Logger logger = LoggerFactory.getLogger(MemberLogic.class);

	@Autowired
	private MemberDao memberDao = null;
	
	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap.toString());
		int result = 0;
		result = memberDao.memberInsert(pMap);
		return result;
	}
}

 

<sts 멤버 - MemberDao.java>

package com.example.demo.dao;

import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

// @Repository -> MVC패턴 공부를위해 Service를 사용함
@Service
public class MemberDao {
	Logger logger = LoggerFactory.getLogger(MemberDao.class);

	public int memberInsert(Map<String, Object> pMap) {
		logger.info("memberInsert 호출");
		logger.info(pMap.toString());
		int result = 0;
		return result;
	}
	
}

 

<sts 멤버 - memberList.jsp>

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>회원목록 페이지</title>
</head>
<body>
<h3>회원목록 페이지</h3>
</body>
</html>

 

<스프링부트와 리액트 연동 - index.js>

import React from "react";
import ReactDOM from "react-dom/client";
import "./index.css";
import App from "./App";
import { BrowserRouter } from "react-router-dom";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </>
);

 

<스프링부트와 리액트 연동 - App.jsx>

import { Route, Routes } from 'react-router-dom';
import './App.css';
import MemberPage from './components/page/MemberPage';

function App() {
  return (
    <>
      <Routes>
        <Route path="/member" exact={true} element={<MemberPage />} />
      </Routes>
    </>
  );
}

export default App;

 

<스프링부트와 리액트 연동 - MemberPage.jsx>

import React, { useEffect, useState } from 'react'
import { jsonMemberListDB } from '../../service/dbLogic'

const MemberPage = () => {
  const [member, setMember] = useState({})

  useEffect(()=>{
    const memberList = async() => {
      const res = await jsonMemberListDB(member)
      console.log(res.data)
    }
    memberList()
  },[])  
  return (
    <div>
      회원관리 페이지입니다
    </div>
  )
}

export default MemberPage

 

<스프링부트와 리액트 연동 - dbLogic.js>

import axios from "axios";

export const jsonMemberListDB = (member) => {
  return new Promise((resolve, reject) => {
    try {
      const response = axios({
        method: "get",
        url: process.env.REACT_APP_SPRING_IP + "member/jsonMemberList",
        params: member,
      });
      resolve(response);
    } catch (error) {
      reject(error);
    }
  });
};

 

<스프링부트와 리액트 연동 - Mblog1Application.java>

package com.example.demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@SpringBootApplication
public class Mblog1Application {

	public static void main(String[] args) {
		SpringApplication.run(Mblog1Application.class, args);
	}

	@Bean
	public WebMvcConfigurer corsConfigurer() {
		return new WebMvcConfigurer() {
			@Override
			public void addCorsMappings(CorsRegistry registry) {
				registry.addMapping("/**").allowedOrigins("http://localhost:3000");
			}
		};
	}
}

 

<스프링부트와 리액트 연동 - RestMemberController.java>

package com.example.demo.controller;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import com.google.gson.Gson;

@RestController
@RequestMapping("/member/*")
public class RestMemberController {
	Logger logger = LoggerFactory.getLogger(RestMemberController.class);
	
	@GetMapping("jsonMemberList")
	public String jsonMemberList(Model model) {
		logger.info("jsonMemberList 호출");
		String temp = null;
		List<Map<String, Object>> mList = new ArrayList<>();
		Map<String, Object> rmap = new HashMap<>();
		rmap.put("mem_id", "tomato");
		rmap.put("mem_pw", "123");
		rmap.put("mem_name", "토마토");
		mList.add(rmap);
		Gson g = new Gson();
		temp = g.toJson(mList);
		return temp;
	}
}

댓글