국비학원/수업기록

국비 지원 개발자 과정_Day65

루팽 2023. 3. 2. 22:02

pojo step1

void → ActionForward(페이지 이동과 관련)

Action 인터페이스 설계

doGet, doPost → execute(req, res)

 

pojo step2 - 좀 더 스프링스럽게 바꿔보기

ActionForward → String

1. “redirect: XXX.jsp” or “redirect:XXX.sp2”(입력, 수정, 삭제 후 결과 페이지 처리 목적)

2. “forward:XXX.jsp”

    주의: XXX.sp2를 사용 불가(연속적으로 forward 사용 불가)

3. “업무폴더명/페이지이름”

    WEB-INF/view/업무폴더명/페이지이름.jsp

 

pojo step3 - 최대한 스프링과 비슷하게

String → ModelAndView객체(scope가 request, 화면이름 정하면 WEB-INF 아래에서 찾음)를 추가

spring4 버전까지 잘 사용되다 Model과 ModelMap이 지원되면서 사용하지 않게 됨

request scope를 가지고 오로지 UI지원을 위해 추가된 API

 

Model(인터페이스), ModelMap(클래스)과 ModelAndView 차이점

Model, ModelMap은 데이터만 저장

ModelAndView은 데이터와 이동하고자 하는 View Page를 같이 저장

 

<pojo step2 - Controller.java>

package com.pojo.step2;

import java.io.IOException;

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

public interface Controller {
	public String execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException;
}

 

<pojo step2 - ActionServlet.java>

package com.pojo.step2;

import java.io.IOException;

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

import org.apache.log4j.Logger;

/*
 * 서블릿이 되려면 HttpServlet을 상속받아야함
 * 사용자정의 메소드 doService -> 오버라이드한 doGet, doPost에서 호출하도록함
 * 메인메소드 없음 -> URL로 톰캣서버에 요청 전달(req, res)
 */
public class ActionServlet extends HttpServlet {
	private static final long serialVersionUID = 1L;
	Logger logger = Logger.getLogger(ActionServlet.class);

	protected void doService(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doService 호출");
		String uri = req.getRequestURI();
		logger.info(uri); // /board/boardList.st2가 찍힘

		String context = req.getContextPath();
		logger.info(context); // "/" -> server.xml에 들어있음

		String command = uri.substring(context.length() + 1);
		System.out.println(command); // board/boardList.st2

		int end = command.lastIndexOf(".");
		System.out.println(end); // 15(board의 경우)

		command = command.substring(0, end);
		System.out.println(command); // board/boardList

		String upmu[] = null; // upmu[0]=업무명(폴더명), upmu[1]=요청기능이름(메소드명)
		upmu = command.split("/"); // /기준으로 문자열 잘라서 upmu 배열에 담기
		logger.info(upmu[0] + ", " + upmu[1]);
		// upmu req에 담기
		req.setAttribute("upmu", upmu);

		Board2Controller boardController = new Board2Controller();
		Object obj = ""; // null이 맞지만 String이 들어온다는 전제로 ""
		obj = boardController.execute(req, res);

		// 페이지 이동처리 공통코드
		// obj 형식 예시 -> redirect:XXX.jsp or forward:XXX.jsp
		if (obj != null) {
			String pageMove[] = null;

			// obj가 String인 경우, pageMove에 값 넣기
			if (obj instanceof String) {
				// obj에 :이 포함된 경우
				if (((String) obj).contains(":")) {
					logger.info(": 포함되어 있음");
					pageMove = obj.toString().split(":");
				}
				// obj에 :이 포함되지 않은 경우
				else {
					logger.info(": 포함되어 있지 않음");
					pageMove = obj.toString().split("/");
				}
			}
			logger.info("페이지이동 => " + pageMove[0] + ", " + pageMove[1]);

			// pageMove가 null이 아닐 경우 각 방식으로 페이지 이동처리
			if (pageMove != null) {
				// pageMove[0] = redirect or forward
				// pageMove[1] = XXX.jsp
				String path = pageMove[1];
				// pageMove[0]이 redirect
				if ("redirect".equals(pageMove[0])) {
					res.sendRedirect(path);
				}
				// pageMove[0]이 forward
				else if ("forward".equals(pageMove[0])) {
					RequestDispatcher view = req.getRequestDispatcher("/" + path + ".jsp");
					view.forward(req, res);
				}
				// 그외 forward 처리
				else {
					path = pageMove[0] + "/" + pageMove[1];
					RequestDispatcher view = req.getRequestDispatcher("/WEB-INF/view" + path + ".jsp");
					view.forward(req, res);
				}
			}
		} // end of 페이지 이동처리 공통코드
	}

	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doGet호출");
		doService(req, res);
	}

	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("doPost호출");
		doService(req, res);
	}
}

 

<pojo step2 - web.xml에 st2 추가>

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xmlns="http://xmlns.jcp.org/xml/ns/javaee"
	xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
	id="WebApp_ID" version="4.0">
	<!-- 
		web.xml(배치서술자 Deployment Descriptor)은 톰캣에서 제공해준다 -> 위치는 WEB-INF 아래
		서버가 처음 로딩 시 web.xml 파일을 읽어들여 환경설정에 대해 어플리케이션을 배치하게 됨
	 -->
	<!-- context-param에 등록한 정보는 톰캣서버가 기동할 때 생성되서 서버가 죽을때까지 쭉 기억한다 -->
	<context-param>
		<param-name>log4jConfigLocation</param-name>
		<param-value>/WEB-INF/classes/log4.properties</param-value>
	</context-param>
	<!--
	자바로 웹서비스X / 서블릿은 가능 -> URL을 적을 수 있어서 -> xml에 등록가능(URL사용위해 서블릿을 배치)
	서블릿과 서블릿매핑은 항상 같이있어야한다
	URL을 등록(url-pattern) - URL이 있어야 요청할 수 있음
	servlet보다 servlet-mapping을 먼저 읽는다
	 -->
	<servlet>
		<servlet-name>FrontController1</servlet-name>
		<servlet-class>com.pojo.step1.FrontMVC1</servlet-class>
	</servlet>
		<!--
			*.st1하면 어떤 요청이름이 오든 확장자가 st1으로 끝나면 FrontMVC1 서블릿이 인터셉트함
			web.xml을 수정하면 무조건 서버를 재기동해야 수정된 내용이 반영됨
		 -->
	<servlet-mapping>
		<servlet-name>FrontController1</servlet-name>
		<url-pattern>*.st1</url-pattern>
	</servlet-mapping>
	<servlet>
		<servlet-name>FrontController2</servlet-name>
		<servlet-class>com.pojo.step2.ActionServlet</servlet-class>
	</servlet>
	<servlet-mapping>
		<servlet-name>FrontController2</servlet-name>
		<url-pattern>*.st2</url-pattern>
	</servlet-mapping>
	<session-config>
	  <session-timeout>10</session-timeout>
	</session-config>
</web-app>

<!--
	context-param과 init-param 차이
		context-param은 서버가 기동될 때 한 번 읽으면 서버가 종료될때까지 계속 기억함
		init-param은 서블릿이 요청될때마다 새로 읽는다

	Deployment Descriptor 배치서술자 spring maven방식 빌드 -> pom.xml파일에 등록
	web.xml문서를 수정하면 반드시 서버를 내렸다가 다시 기동할 것(주의!)
	
	톰캣서버를 기동하면 가장 먼저 읽는다
	이 문서를 읽으면 st1확장자에대해 알고있다
	.st1으로 들어오는 모든 요청은 FrontMVC1.java가 인터셉트한다
	이 요청은 브라우저의 주소창에서 하게되고
	이 요청의 URL을 읽어서 upmu[] 배열에 담는다
	배열에 담기는 정보는 아래와 같이 업무명/메소드명으로 이뤄진다(URL의 일부분)
	dept/getDeptList -> select처리 - 화면유지필요 - forward페이지 이동 - 조회결과는 request에 담는다
	dept/deptInsert -> insert처리 - 유지필요없음 - res.sendRedirect(페이지이름) -> 페이지이름은 path에 담겨있음
	dept/deptUpdate -> update처리 - 유지필요없음 - res.sendRedirect(페이지이름) -> 페이지이름은 path에 담겨있음
	dept/deptDelete -> delete처리 - 유지필요없음 - res.sendRedirect(페이지이름) -> 페이지이름은 path에 담겨있음
	path를 관리하는 클래스명은 ActionForward.java이다
	이 클래스에는 전역변수가 두 개 있다
	String path - 페이지 이름
	boolean isRedirect - true: res.sendRedirect();
											 false: RequestDispatcher view = req.getRequestDispatcher() ;
															view.forward(req, res);
	위 내용에 대한 설정은 어디서? -> FrontMVC1 doService()에서 했음
-->

 

<pojo step2 - Board2Controller.java>

package com.pojo.step2;

import java.io.IOException;
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;

public class Board2Controller implements Controller {
	Logger logger = Logger.getLogger(Board2Controller.class);
	Board2Logic boardLogic = new Board2Logic();
	
	@Override
	public String execute(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		logger.info("execute 호출");
		String upmu[] = (String[]) req.getAttribute("upmu");
		String page = null;
		
		if("boardList".equals(upmu[1])) {
			logger.info("게시글 목록 보기");
			List<Map<String, Object>> bList = null;
			bList = boardLogic.boardList();
			req.setAttribute("bList", bList);
			page = "forward:board2/boardList"; // 실제로 완성되면 /board2/boardList.jsp 이런형식
		}
		return page;
	}
}

 

<pojo step2 - Board2Logic.java>

package com.pojo.step2;

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

import org.apache.log4j.Logger;

public class Board2Logic {
	Logger logger = Logger.getLogger(Board2Logic.class);
	Board2Dao boardDao = new Board2Dao();
	
	public List<Map<String, Object>> boardList() {
		logger.info("boardList 호출");
		List<Map<String, Object>> bList = new ArrayList<>();
		bList = boardDao.boardList();
		return bList;
	}
}

 

<pojo step2 - Board2Dao.java>

package com.pojo.step2;

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

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.log4j.Logger;

import com.util.MyBatisCommonFactory;

public class Board2Dao {
	Logger logger = Logger.getLogger(Board2Dao.class);
	// 이종간인 MyBatis연동에 필요한 공통 클래스 객체주입 필요
	MyBatisCommonFactory mcf = new MyBatisCommonFactory();

	public List<Map<String, Object>> boardList() {
		logger.info("boardList 호출");
		List<Map<String, Object>> bList = null;
		SqlSessionFactory sqlSessionFactory = null;
		SqlSession sqlSession = null;
		try {
			sqlSessionFactory = mcf.getSqlSessionFactory();
			sqlSession = sqlSessionFactory.openSession();
			Map<String, Object> pMap = new HashMap<>();
			bList = sqlSession.selectList("boardList", pMap);
			logger.info(bList);
		} catch (Exception e) {
			e.printStackTrace();
		}
		return bList;
	}
}

/*
 * ActionSevlet(페이지이동) - XXXController - XXXLogic - XXXDao - MyBatis Layer
 */

 

<pojo step2 - board.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="com.mybatis.mapper.BoardMapper">
	<!-- 게시글목록 가져오는 쿼리문 -->
	<select id="boardList" parameterType="java.util.HashMap"
		resultType="map">
		SELECT bm.bm_no,
		bm.bm_title,
		bm.bm_writer,
		bs.bs_file,
		bm.bm_hit,
		bm.bm_pos,
		bm.bm_step
		FROM board_master_t bm, board_sub_t bs
		WHERE bm.bm_no =
		bs.bm_no(+)
		<!-- 글번호로 검색 -->
		<if test= 'bm_no  > 0' >
		 		AND bm_no = #{bm_no}
		 </if>
	  <!-- 글제목으로 검색 -->
		<if test= 'cb_search!=null  and cb_search.equals("bm_title")' >
		 		AND bm_title LIKE '%'||#{keyword}||'%'
		 </if>
	  <!-- 글내용으로 검색 -->
		<if test= 'cb_search!=null  and cb_search.equals("bm_content")' >
		 		AND bm_content LIKE '%'||#{keyword}||'%'
		 </if>
	  <!-- 작성자로 검색 -->
		<if test= 'cb_search!=null  and cb_search.equals("bm_writer")' >
		 		AND bm_writer LIKE '%'||#{keyword}||'%'
		 </if>
		ORDER BY bm.bm_group DESC, bm.bm_step ASC
	</select>
</mapper>

 

<pojo step2 - MyBatisCommonFactory.java>

package com.util;

import java.io.Reader;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import org.apache.log4j.Logger;

public class MyBatisCommonFactory {
	Logger logger = Logger.getLogger(MyBatisCommonFactory.class);
//	MyBatis는 자바와 오라클 서버 사이에 위치하면서 DB연계를 지원하는 매핑서비스이다.
//	물리적으로 떨어져있는 서버와의 통신을위해 드라이버 클래스 로딩, 연결 통로 확보 코드가 반복적으로 작성됨
//	이것을 전담하는 SqlSessionFactory를 지원하고있음(MyBatis가)
//	스프링에선 SqlSessionFactoryBean
	public SqlSessionFactory sqlSessionFactory = null;

	// SqlSessionFactory객체를 생성해 주는 메소드 입니다.
	public void init() {
		try {
//			xml문서에 드라이버 클래스명, URL주소, 계정정보 담음
//			더하여 쿼리문을 xml문서에 따로 등록해서 관리하므로
//			쿼리문을 담고있는 xml문서의 물리적인 위치를 설정문서에 등록함
			String resource = "com\\util\\MyBatisConfig.xml";
			Reader reader = Resources.getResourceAsReader(resource);
			logger.info("before sqlSessionFactory : " + sqlSessionFactory);
//			싱글톤패턴에서 객체 생성하기
			if (sqlSessionFactory == null) {
				sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader, "development");
			}
			logger.info("after sqlSessionFactory : " + sqlSessionFactory);
		} catch (Exception e) {
			logger.info("[[ Exception ]] " + e.toString());
		}
	}// end of init

	public SqlSessionFactory getSqlSessionFactory() {
		init();
		return sqlSessionFactory;
	}

	public static void main(String[] args) {
		MyBatisCommonFactory mcf = new MyBatisCommonFactory();
		mcf.getSqlSessionFactory();
	}
}

 

<pojo step2 - boardList.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>

 

Spring 의존성 주입(DI)

객체 간 의존성을 개발자가 객체 내부에서 직접 호출(new연산자)하는 대신, 외부(스프링 컨테이너)에서 객체를 생성해서 넣어주는 방식(스프링이 객체의 라이프사이클 관리해 줌)

외부에서 두 객체 간의 관계설정을 해주는 디자인 패턴

인터페이스를 사이에 두어 클래스 레벨에서는 의존관계가 고정되지 않도록 하고, 런타임 시 관계를 동적으로 주입하여 유연성을 확보하고 결합도를 낮춤

 

DI(Dependency Injection)

스프링이 다른 프레임워크와 차별화되어 제공하는 의존 관계 주입 기능으로, 객체를 직접 생성하는 게 아니라 외부에서 생성한 후 주입 시켜주는 방식

DI(의존성 주입)를 통해서 모듈 간의 결합도가 낮아지고 유연성이 높아짐

 

IoC(Inversion of Control)

제어의 역전

스프링 애플리케이션에서는 오브젝트(빈)의 생성과 의존 관계 설정, 사용, 제거 등의 작업을 애플리케이션 코드 대신 스프링 컨테이너가 담당함

스프링 컨테이너(IoC 컨테이너)가 코드 대신 오브젝트에 대한 제어권을 갖고 있다고 해서 IoC라고 부름

 

IoC 컨테이너

스프링에서는 IoC를 담당하는 컨테이너를 빈 팩토리, DI 컨테이너, 애플리케이션 컨텍스트라고 부름

오브젝트의 생성과 오브젝트 사이의 런타임 관계를 설정하는 DI 관점으로 보면, 컨테이너를 빈 팩토리 또는 DI 컨테이너라고 부름

스프링 컨테이너는 단순한 DI 작업보다 더 많은 일을 하는데, DI를 위한 빈 팩토리에 여러 가지 기능을 추가한 것을 애플리케이션 컨텍스트라고 함

애플리케이션 컨텍스트는 그 자체로 IoC와 DI 그 이상의 기능을 가짐

 

BeanFactory(빈 팩토리)

스프링 컨테이너의 최상위 인터페이스로 스프링 빈을 관리하고 조회하는 역할을 담당

대표적으로 getBean() 메소드를 제공함

 

ApplicationContext (애플리케이션 컨텍스트)

애플리케이션 컨텍스트는 빈 팩토리 기능을 모두 상속받아서 제공

빈 팩토리에게 없는 추가 기능을 가지고 있음(메시지 소스를 활용한 국제화, 환경변수, 애플리케이션 이벤트, 편리한 리소스 조회 등)

 

스프링  빈(Bean)

기존 자바에선 Class를 생성하고 new를 입력하여 원하는 객체를 직접 생성한 후에 사용했지만, Spring에서는 new로 생성한 객체가 아니라 Spring에 의하여 관리당하는 자바 객체를 사

이렇게 Spring에 의해 생성되고 스프링 컨테이너(IoC 컨테이너)가 관리하는 자바 객체를 Bean이라고 함

Spring Bean을 얻기 위하여 BeanFactory.getBean(), ApplicationContext.getBean()과 같은 메소드를 사용하여 Spring에서 직접 자바 객체를 얻어서 사용함

빈 자체가 클래스라고 생각하면 됨(bean을 보면 클래스로 인식해야 함)

 

스프링 엔진(스프링 컨테이너)

  1. BeanFactory

    spring-beans.jar에 살고 있음

    게으른 인스턴스화

 

  2. ApplicationContext

    spring-context.jar

    이른 인스턴스화

    영속적(기억 o)

 

아래와 같이 new 하면 스프링 누릴 수 없다(new 쓰면 객체의 라이프사이클을 자신이 관리하겠다는 것)

스프링 누리려면 클래스를 xml문서에 등록해야 하고, 식별 위해 id(인스턴스변수)가 필요함

com.di.HelloBean helloBean = new HelloBeanImpl();

 

<스프링4 연습 - helloBean.xml>

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.3.xsd">
	<bean id="helloBean" class="com.di.HelloBeanImpl" scope="singleton">
		<property name="msg">
			<value>Have a Good Time</value>
		</property>
	</bean>
</beans>

 

<스프링4 연습 - HelloBean.java>

package com.di;

public interface HelloBean {
	// 추상 메소드 선언만
	public String getGreeting(String msg);
}

 

<스프링4 연습 - HelloBeanImpl.java>

package com.di;

public class HelloBeanImpl implements HelloBean {
	String msg = null;

	public void setMsg(String msg) {
		this.msg = msg;
	}

	@Override
	public String getGreeting(String msg) {
		// this.msg는 helloBean.xml에 있는 value
		// this없이 msg하면 받아온 msg값이 출력
		return "spring " + msg;
	}
}

 

<스프링4 연습 - HelloMain.java>

package com.di;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.xml.XmlBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;

public class HelloMain {
	public static void main(String[] args) {
		Resource resource = new FileSystemResource("D:\\workspace_spring\\kh_spring4\\src\\main\\java\\com\\di\\helloBean.xml");
		// Spring Container는 두 가지 유형의 컨테이너 제공함(BeanFactory, ApplicationContext)
		
		// BeanFactory -> spring-beans.jar에 살고있음
		// 모든 빈을 늦게 로딩함(게으른 인스턴스화) - getBean()호출될 때까지 Bean의 생성을 미룸
		BeanFactory factory = new XmlBeanFactory(resource);
		// new대신 아래와같이 선언 -> 제어역전, 의존성 주입, scope
		HelloBean helloBean = (HelloBean)factory.getBean("helloBean");
		System.out.println(helloBean.getGreeting("안녕"));
		
		// ApplicationContext -> spring-context.jar에 살고있음
		// Context 시작시킬 때 모든 singleton빈을 미리 로딩함으로써(이른 인스턴스화)
		// 그 빈이 필요할 때 즉시 사용될 수 있도록 해줌
		// 어플리케이션 동작시 Bean생성되기를 기다릴 필요가 없게됨
		ApplicationContext context = new ClassPathXmlApplicationContext("com\\di\\helloBean.xml");
		HelloBean helloBean2 = (HelloBean)context.getBean("helloBean");
		System.out.println(helloBean2.getGreeting("잘가"));
	}
}

 

<리액트 복습세팅 - index.js>

import React from "react";
import ReactDOM from "react-dom/client";
/* bootstrap import해놔야 제대로 적용됨 */
import "bootstrap/dist/css/bootstrap.min.css";
import App from "./App";
import UseStateApp1 from "./components/1_useState/UseStateApp1";
import UseEffectApp1 from "./components/2_useEffect/UseEffectApp1";

const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
  <React.StrictMode>
    {/* <App /> */}
    {/* <UseStateApp1 /> */}
    <UseEffectApp1 />
  </React.StrictMode>
);

 

<리액트 useState 연습 - UseStateApp1.jsx>

import React, { useState } from 'react'
import { Button } from 'react-bootstrap'

const UseStateApp1 = () => {
  let nums = [1, 2, 3]
  let result = 0
  const [result2, setResult2] = useState(0)

  const plus = () => {
    result++ // 그냥 result를 넣으면 값이 바뀌지 않음
    setResult2(result2 + 1) // useState훅을 사용한 result2를 넣으면 리렌더링됨
  }

  return (
    <>
      <h2>useState복습 - 1</h2>
      <Button onClick={plus}>더하기</Button>
      <h2>덧셈 결과 출력 {result}</h2>
      <h2>덧셈 결과 출력 {result2}</h2>
      <hr />
      {nums.map((num, index) => (
        <h3 key={index}>{num}</h3>
      ))}
      <hr />
    </>
  )
}

export default UseStateApp1

/* 
  상태값이 변할때마다 렌더링이 되도록 하고싶다면 useState훅을 사용할것
*/

 

<리액트 useState 연습 - UseStateApp2.jsx>

import React, { useState } from 'react'
import { Button } from 'react-bootstrap'

const UseStateApp1 = () => {
  console.log('UseStateApp1') // 무조건 한번 출력됨
  let nums = [1, 2, 3]
  let result = 0
  // 상태가 변하면 UI에 반영되는 것이 리액트임
  const [result2, setResult2] = useState(() => {
    console.log('useState') // 한 번은 출력됨 - 딱 한 번만 호출됨
    return 0
  })
  

  const plus = () => {
    result++ // 그냥 result를 넣으면 값이 바뀌지 않음
    console.log(`plus ${result}`)
    // setResult2가 실행될때 다시 return이 실행됨(다시 그려짐 - 1씩 증가되는 숫자 확인 가능)
    setResult2(result2 + 1) // useState훅을 사용한 result2를 넣으면 리렌더링됨
  }

  return (
    <>
      <h2>useState복습 - 1</h2>
      <Button onClick={plus}>더하기</Button>
      <h2>덧셈 결과 출력 {result}</h2>
      <h2>덧셈 결과 출력 {result2}</h2>
      <hr />
      {nums.map((num, index) => (
        <h3 key={index}>{num}</h3>
      ))}
      <hr />
    </>
  )
}

export default UseStateApp1

/* 
  상태값이 변할때마다 렌더링이 되도록 하고싶다면 useState훅을 사용할것
*/

 

<리액트 useState 연습 - UseStateApp3.jsx>

import React, { useState } from 'react'
import { Button } from 'react-bootstrap'

const UseStateApp1 = () => {
  console.log('UseStateApp1') // 무조건 한번 출력됨
  let sample = [
    {empno: 7566, ename:"김유신"},
    {empno: 7499, ename:"이성계"},
    {empno: 7600, ename:"강감찬"},
  ]

  const[emps, setEmps] = useState(sample)

  const getEmpList = () => {
    console.log("조회버튼 클릭")
    // concat을 사용하면 배열도 추가되고 실시간 리렌더링이 일어남
    const other = sample.concat({empno:7700, ename:"나신입"})
    console.log(other)
    setEmps(other)
    // push를 사용하면 배열은 추가되지만 실시간 리렌더링이 되지 않음
    // sample.push({empno:7700, ename:"나신입"})
    // console.log(sample)
    // setEmps(sample)
    // 바뀌지 않으면 리렌더링하지 않는다(기본값도, 세팅값도 sample일 경우)
  }

  return (
    <>
      <h2>useState복습 - 2</h2>
      <Button onClick={getEmpList}>조회</Button>
      <hr />
      {emps.map((item, index) => (
        <h3 key={index}>{item.empno}번, {item.ename}</h3>
      ))}
    </>
  )
}

export default UseStateApp1

/* 
  상태값이 변할때마다 렌더링이 되도록 하고싶다면 useState훅을 사용할것

  깊은복사는 객체의 실제 값을 복사하는 것 - 원본을 바꿔도 복사본은 안바뀐다
  얕은복사는 객체의 주소값을 복사하는 것 - 원본을 바꾸면 복사본도 바뀐다

  sample에 push함수를 통해서 원소를 추가하면
  버튼을 누를때마다 sample은 증가하지만, 렌더링은 새로되지 않는다
  sample의 주소번지가 바뀌지 않았기 때문에
  실제 배열의 원소는 계속 증가하지만 return이 호출되지 않는다
*/

 

<리액트 useState 연습 - UseStateApp4.jsx>

import React, { useState } from 'react'
import { Button } from 'react-bootstrap'

const UseStateApp1 = () => {
  console.log('UseStateApp1') // 무조건 한번 출력됨
  const[emps, setEmps] = useState([{empno:7900, ename:"나초보"}])

  const getEmpList = () => {
    console.log("조회버튼 클릭")
    let sample = [
      {empno: 7566, ename:"김유신"},
      {empno: 7499, ename:"이성계"},
      {empno: 7600, ename:"강감찬"},
    ]
    setEmps([...emps, ...sample])
  }

  return (
    <>
      <h2>useState복습 - 2</h2>
      <Button onClick={getEmpList}>조회</Button>
      {emps.map((item, index) => (
        <h3 key={index}>{item.empno}번, {item.ename}</h3>
      ))}
    </>
  )
}

export default UseStateApp1

/* 
  상태값이 변할때마다 렌더링이 되도록 하고싶다면 useState훅을 사용할것

  깊은복사는 객체의 실제 값을 복사하는 것 - 원본을 바꿔도 복사본은 안바뀐다
  얕은복사는 객체의 주소값을 복사하는 것 - 원본을 바꾸면 복사본도 바뀐다

  sample에 push함수를 통해서 원소를 추가하면
  버튼을 누를때마다 sample은 증가하지만, 렌더링은 새로되지 않는다
  sample의 주소번지가 바뀌지 않았기 때문에
  실제 배열의 원소는 계속 증가하지만 return이 호출되지 않는다
*/

 

<리액트 useEffect연습 - UseEffectApp1.jsx>

import React, { useEffect, useState } from 'react'
import { Button } from 'react-bootstrap'
import Sub1 from './Sub1'

const UseEffectApp1 = () => {
  console.log('UseEffectApp1')
  const [data, setData] = useState(0)

  const plus = () => {
    console.log('plus => ' + data)
    let resData = 5
    setData(data + 1)
  }

  useEffect(() => {
    console.log('effect => ' + data)
  } ,[data])
  // 빈 []을 넣으면 최초 1번만 실행됨
  // data를 넣으면 해당 값이 바뀔때마다 실행됨

  return (
    <>
      <h3>데이터: {data}</h3>
      <Button onClick={plus}>더하기</Button>
      <hr />
      {/* export default일때는 중괄호{}를 붙이지 않음 */}
      {/* 더하기 버튼 누를때마다 Sub1도 호출됨 -> 리렌더링될때마다 전부 새로 그리기에 */}
      <Sub1 />
    </>
  )
}

export default UseEffectApp1

 

<리액트 useEffect연습 - Sub1.jsx>

import React from 'react'

const Sub1 = () => {
  console.log('Sub1')
  
  return (
    <>
      <h3>Sub1</h3>
    </>
  )
}

export default Sub1