본문 바로가기
학원수업/3월

03/22 국비학원 개발자과정-Spring @ annotation 정리, Flux아키텍쳐, Redux, React Hook

by 코딩마스터^^ 2023. 3. 22.

MVC아키텍쳐의 한계

https://velog.io/@alskt0419/FLUX-%EC%95%84%ED%82%A4%ED%85%8D%EC%B3%90%EB%9E%80

root다 모두 제어

거대 프로젝트가 되면 오류가 발생

장애가 너무 많다.

양방향 데이터 바인딩이다. 너무 복잡하다. 비효율적. 

 

 

 

Redux는 단방향 데이터 바인딩이다.

양방향이 안된다.

 

쿠키와 세션 화면에 반영이 되기 위해서는 브라우저가 Dom Tree를 그린 후 Render Tree를 그려야한다.

렌더 트리는 기존의 돔 트리에 CSS가 포함된 트리이다.

화면에 대한 깜박임, 기다림 없이 최적화된 상태로 보여주는것이 브라우저의 역할.

엔진들이 모여져서 서비스가 진행...

한 화면에 탑-바디-바텀 이 있다.

전체를 감싸는것은 App.jsx이다.

index.html->index.js->App.jsx

 

함수는 소문자

컴포넌트는 대문자

 

바디 안에도 서브 컴포넌트가 여러개 옴

 

데이터는 단방향으로만 흐르고 새로운 데이터를 넣으면

어떻게? useState, useEffect

처음부터 다시 시작되는 방식으로 설계 -> Flux 아키텍쳐

 

Flux아키텍쳐가 중앙에서 하위 컴포넌트에 바로 상태 즉, 데이터를 전달

Dispatcher : 일꾼. 허브.

Action에는 변화가 들어있다.

 

worker함수(객체)를 store에 전달한다.

 

허브-깔때기를 써야한다=>Switch문 case따라 다르게 보내야한다.

Dispatcher에는 타입을 줘야한다.

dispatch({ <= 디스패쳐
type: GET_POST, <= 액션 이름
    // GET_POST을 통해 스토어에 변화가 일어남
})

 

Action

안에 타입이 있어야한다.

상태변경을 위해서는 디스패쳐 통해서 보내야한다.

//상태는 createStore()안에 있다.
const createStore=()=>{ //배치 위치는 index.js배치-store생성
 let state;//상태를 담아두는 저장소 상태=데이터
 //함수를 담아두는 배열 선언
 let handlers=[] 

 //상태를 바꾸는 일을 send함수가 한다.-useSelector훅을 통해서
 const send=(action)=>{
  //새로운 객체가 만들어진다. 똑같은 state인데 대입연산자로 치환됐다. 새로운 state이다.
  /*Map m= new HahMap()
  m=new HashMap()*/
  console.log("send호출");
  state=worker(state, action)
  //나에게 구독 신청한 사람들에게 알려줌
  handlers.forEach(handler=>handler())//()붙어있다=>함수호출

 }

 const subscribe=((handler)=>{//여기서 핸들러 배열에 담아줌. 핸들러가 콜백함수 useDispatch훅
  handlers.push(handler);//push함수-Array내장함수 배열에 넣을때
 })

 const getState=()=>{
  return state
 }
 //함수안에서 함수를 리턴하도록 처리를 해야 바깥쪽에서 해당 함수를 요청 할 수 있다.
  return{
    /*state/* 이렇게 직접 주지 않는다. */
    send,//함수-객체 파라미터로 들어온 상태를 받아서 가공해서 새로운 객체로 내보냄
    getState,/*함수-상태정보를 담은  state를 반환해줌 어떻게 알고 얘를 호출하지...? 알아서 제공해줘야... */
    subscribe
  }
}//end of Store
//Store
//상태를 담을 변수를 선언
//콜백함수를 담믈 배열 선언
//send함수 구현-action이 파라미터로 들어옴
//구독-발행모델(상태변화있으면 알려줘)-subscribe-handler콜백함수-얘를 통해서 처리
//서브스크라이브를 통해서 들어온 콜백함수는 handler배열에 담긴다.
//getState함수를 통해서 state값을 반환 받을수있다.
//return{send,subscribe,getState}

const worker = (state={count:0},action)=>{//state가 undefined되는것을 방지위해 (count)객체선언
  //무엇을 해야하나요? 참조무결성이 깨지는것 방지로 새로운 상태를 반환해라. 예상치못한 side-effect때문에 반드시 새로운 상태를 반환해줘라
  //상태를 바꾸면 createStore안에 state의 참조무결성이 깨짐
  //불변성을 지켜주기위해서 리덕스에서는 상태를 바꾸는 함수는 반드시 새로운 상태를 반환하라
  //새로운 상태=화면에 입력(Action)으로 상태의 객체를 줄테니까 이 객체를 Deep Copy해서 기존의 링크를 끊어라
switch(action.type){
  case "increase" :
  return{...state, count:state.count+1}
  case "decrease" :
  return{...state, count:state.count-1}
  default:
    return {...state}
}
  return {...state, count:state.count+1}//Deep Copy. 새로운 객체. 
}
//자바스크립트에서는 함수도 파라미터 넘길 수 있다.
const store=createStore(worker)//index.js에서 생성해서 props대신 redux를 쓸 것
//subscribe함수 호출시 파라미터로 콜백 한수를 넘김
store.subscribe(function(){
  console.log(store.getState())
})

//action의 내용은 send 에서만듦 {}안에값을 action으로 넘긴다.
//사용자가 버튼을 클릭 했을때 시그널 발생함-type정해서value를 store에 전달한다.
//store가 받아서 전변으로 관리가됨-G컴포넌트에서 즉시 사용 가능하다.
store.send({type : 'increase'})//시그널 주기 - action
store.send({type : 'increase'})//Action을 dispatcher 가 전달한다.
store.send({type : 'decrease'})

/*
Rule
1. UI한테는 직접적인 상태를 주지않는다. 리턴으로 준다.


함수는 객체다.
소문자로 선언하면 함수이고
대문자로 선언하면 화면을 렌더링 하는 컴포넌트-외우세요

코드 전개 시나리오
return에서는 상태 값을 직접 넘겨주지 않는다.
상태는 createStore함수에 있지만
변경하거나 읽거나 하는 코드들은 UI에 Component들이다.
이 컴포넌트들은 createStore의 함수의 바깥쪽에 위치한다.
제공을 한다는것은 createStore가 내용을 알고있다는 전제이다.
이런걸 줄테니까 이런걸 가지고 니가 바꿔....?
구조를 알아야한다.
그 수많은 어플리케이션의 상태구조를 다 알아야한다? 불가능. 개발자가 상태구조를 알고 있을것이다.
상태를 변경하는 로직을 알고있어야 한다.
createStore안에 let store로 선언되어있다.
worker를 createStore에 넘겨줬다?
언제 상태를 어떤 상태를 바꿀것인가?
타이밍에 대한 문제까지 발생하기때문에 함수호출을 잘 해야한다.
변경해줘라는 시그널을 밖에서 준다. 이미지버튼에 a태그에 url넣은것처럼 사용자가 눌렀을때 서버에 요청이 들어간다.(바깥쪽)
개발자들이 어플리케이션을 만들면서 적절한 시기에 시그널 넘겨주는것이 중요하다.
어떤 상태를 바꿔야한다? 함수가 필요하다.

문제제기
컴포넌트(HomePage.jsx, LoginPage.jsx)가 여러개 있는 상황에서 
어떤 컴포넌트의 데이터가 변경 되었는지 어떻게 알고서 getState함수를 호출 할까요?
구독발행모델-Pub and Subscibe
어떤 함수를 줄테니(구독할테니) 데이터가 변경되면 그 함수를 호출해줄래?
그때 그 함수를 호출해줘
이벤트 처리를 해줘
*/

 

 

출처 : https://velopert.com/3528

 

리덕스(Redux)를 왜 쓸까? 그리고 리덕스를 편하게 사용하기 위한 발악 (i) | VELOPERT.LOG

이 포스트는 리덕스의 리도 모르는 독자들을 대상으로 작성된 글입니다. 리덕스가 왜 필요한지 알아보고, 리덕스를 편리하게 사용하기 위한 발악을 한번 해보겠습니다. 리덕스 왜 쓸까? 리액트

velopert.com

내가준 데이터=액션

액션에는 타입이 있어야한다.

증가면 increse

감소면 decrease

 

컴포넌트 태그이다. 화면에 종속된다.

index.js가 루트이다.

어느 위치에 있든 상관없이 단 한번에 상태 준다...

 

rootReducer

state값을 불러올때 useState훅을 사용한다.

 

부서등록 페이지 만들기

post방식 연습해보기

포스트 방식은 바디에 들어간다.

리액트와 연동하려면 row로 한다.

 

@RequestMapping(/dept/*)

 

데이터베이스에 파일이름과 파일URL컬럼을 추가해준다.

포스트맨에서 post로 보낼때 꼭 JSON방식으로 보내준다.

 

dept.xml에 insert문을 넣어준다.

<?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.example.demo">
	<insert id="deptInsert" parameterType="com.example.demo.vo.DeptVO">
			INSERT INTO dept(deptno, dname, loc 
										<if test="filename !=null">
											,filename
										</if>
										<if test="fileurl !=null">
											,fileurl
										</if>										
											) 
								VALUES(#{deptno}, #{dname},#{loc}
								          <if test="filename!=null">
								          ,#{filename}
								          </if>
								          <if test="fileurl!=null">								          
								          ,#{fileurl}
								          </if>
								          )
	</insert>
</mapper>

 

import React, { useCallback, useState } from 'react'
import '../css/style.css'
import BlogHeader from '../include/BlogHeader'

import { Modal, Form, Button, Table } from 'react-bootstrap'
import DeptRow from '../dept/DeptRow'
import { deptInsertDB } from '../../service/dbLogic'


const DeptPage = ({imageUploader}) => {
  const [deptList, setDeptList]=useState([])//리렌더링이 계속 되는부분이면 배열로한다. 값이 계속바뀌니까
  const [show, setShow]=useState(false)
  const handleClose=()=>setShow(false)
  const handleShow=()=>setShow(true)
  //받아올 값 선언
  const [deptno,setDeptno]=useState(0)
  const [dname,setDname]=useState("")
  const [loc,setLoc]=useState("") //값을 입력하게 되면 onChange속성이 변화부분을 감지한다.
  //filename하나 fileurl 둘이니 객체로 선언할것
  const [file, setFiles]=useState({filename:null,fileurl:null})
  const handleDeptno=useCallback((value)=>{
    console.log(value);
    setDeptno(value)
  },[])//화면에서는 계속 기억을 할 필요없다. 화면 다 그려져 있으니까. 그래서 의존성 배열 비워둔다.
  const handleDname = useCallback((value) => {
    console.log(value);
    setDname(value);
  }, [])
  const handleLoc = useCallback((value) => {
    console.log(value);
    setLoc(value);
  }, [])
  //조건 검색 구현
  const reactSearch=()=>{

  }
  //부서 목록 가져오기
  // const deptList=()=>{

  // }
  //부서목록 JSON포맷 가져오기
  const jsonDeptList=()=>{

  }
  //이미지 파일 첨부 구현
  const imgChange=async(event)=>{
    // console.log(event.target.files[0]);
    const  uploaded=await imageUploader.upload(event.target.files[0])
    setFiles({
      filename:uploaded.public_id+"."+uploaded.format,
      fileurl:uploaded.url
    })
    //input의 이미지 객체 얻어오기
    const upload=document.querySelector("#dimg")
    //이미지를 넣을곳의 부모태그
    const holder=document.querySelector("#uploadImg")
    const file=upload.files[0]
    const reader=new FileReader()
    reader.onload=(event)=>{
    const img=new Image()
    img.src=event.target.result
    if(img.width>150){
      img.width=150
    }
    holder.innerHTML="";
    holder.appendChild(img)
    }
    reader.readAsDataURL(file)
    return false
  }
  //부서 등록 구현
  //Spring과 리액트연계하기-@RequestBody를 사용해서 JSon포맷으로 넘기는 컨셉
  const deptInsert=async()=>{
    const dept={
      deptno,
      dname,
      loc,
      filename:file.filename,
      fileurl:file.fileurl
    }
    const res=await deptInsertDB(dept)
    if(!res.date){//js에서는 0외(6가지)아니면 참
      console.log("부서등록에 실패 했습니다.");
    }
    else{
      console.log('부서등록 성공');
      //성공시 부서목록 새로고침 처리 할것-window.location.reload()쓰지말것-SPA컨벤션 
      //새로매번 고침하는거는 리액트아니다. 후지다
      //useEffect-의존성 배열을 연습할 수 있음
    }
  }
  
  return (
    <>
      <BlogHeader/>
      <div className="container">
      <div className="page-header">
      <h2>부서관리&nbsp;<i className="fa-solid fa-angles-right"></i>&nbsp;<small>부서목록</small></h2>
        <hr />
	    </div>      
      <div className="row">
        <div className="col-3">
          <select id="gubun" className="form-select" aria-label="분류선택">
            <option defaultValue>분류선택</option>
            <option value="deptno">부서번호</option>
            <option value="dname">부서명</option>
            <option value="loc">지역</option>
          </select>			
        </div>
		    <div className="col-6">
			    <input type="text" id="keyword" className="form-control" placeholder="검색어를 입력하세요" 
                 aria-label="검색어를 입력하세요" aria-describedby="btn_search" />
		    </div>
		    <div className="col-3">
			    <Button variant='danger' id="btn_search" onClick={reactSearch}>검색</Button>
		    </div>
	     </div> 
      <div className='book-list'>
        <Table striped bordered hover>
          <thead>
            <tr>
              <th>#</th>
              <th>부서번호</th>
              <th>부서명</th>
              <th>지역</th>
            </tr>
          </thead>
          <tbody>
          {deptList.map(dept => (
            <DeptRow key={dept.DEPTNO} dept={dept} />
          ))}
          </tbody>
        </Table> 
        <hr />    
        <div className='booklist-footer'>
          <Button variant="warning" onClick={jsonDeptList}>
            전체조회
          </Button>&nbsp; 
          <Button variant="success" onClick={handleShow}>
            부서등록
          </Button> 
        </div>
      </div>
    </div>
    {/* ========================== [[ 도서등록 Modal ]] ========================== */}
    <Modal show={show} onHide={handleClose} animation={false}>
        <Modal.Header closeButton>
          <Modal.Title>부서등록</Modal.Title>
        </Modal.Header>
        <Modal.Body>
        <Form id="f_dept" method="get">
          <input type="hidden" id="fileName" name="fileName"/>
          <input type="hidden" id="fileURL" name="fileURL"/>
          <Form.Group className="mb-3" controlId="formBasicDname">
            <Form.Label>부서번호</Form.Label>
            <Form.Control type="text" id="deptno" placeholder="Enter 부서번호" onChange={(e)=>{handleDeptno(e.target.value)}}/>
          </Form.Group>
          <Form.Group className="mb-3" controlId="formBasicDname">
            <Form.Label>부서명</Form.Label>
            <Form.Control type="text" id="dname" placeholder="Enter 부서명" onChange={(e)=>{handleDname(e.target.value)}} />
          </Form.Group>
          <Form.Group className="mb-3" controlId="formBasicLoc">
            <Form.Label>지역</Form.Label>
            <Form.Control type="text" id="loc" placeholder="Enter 지역" onChange={(e)=>{handleLoc(e.target.value)}}/>
          </Form.Group>
          <Form.Group className="mb-3" controlId="formBasicOffice">
            <Form.Label>건물이미지</Form.Label>
              <input className="form-control" type="file" accept='image/*' id="dimg" name="dimg" onChange={imgChange}/>
          </Form.Group>
          <div id="uploadImg">
            <img className='thumbNail' src="http://via.placeholder.com/200X250" alt="미리보기" />
          </div>
        </Form>

        </Modal.Body>
        <Modal.Footer>
          <Button variant="secondary" onClick={handleClose}>
            닫기
          </Button>
          <Button variant="primary" onClick={deptInsert}>
            저장
          </Button>
        </Modal.Footer>
      </Modal>     
    {/* ========================== [[ 부서등록 Modal ]] ========================== */}  
    </>
  )
}

export default DeptPage

 

부서관리 조건검색을 위해서 select문을 dept.xml에 추가해준다.

위의 deptno는 처음 조회시에 조건문이고

두번째 deptno는 조건검색시의 select문이다.

 

유효성 검사는 자바스크립트의 역할이다.

 

map으로 데이터 넘길때는 @RequestParam을 쓴다.

@어노테이션 정리-외우기!!!

@Configuration-클래스 선언 앞에

@Bean-메소드 선언 앞에

 

@ComponentScan

@Controller

-클래스 선언 앞에

@RestController -> @ResponseBody+@Controller합쳐진것이다. mime타입은 text/plian이다. 문자열로 출력한다.

화면 출력이 아니다.

바이너리와 answkdufdlfkd rnqnsgkf tn dlTdjdi...

-클래스 선언 앞에온다

 

@Autowired : 컨트롤러와 로직/ 로직과 다오/ 다오와 sqlSessionTemplate을 연결해준다. setter객체주입법을 대체하였다.

-클래스 선언 앞에온다

@RequestParam Map<String, Object> pMap

-메소드에 파라미터 자리

 

@Service

-모델계층에서 사용

-클래스 선언앞에사용

 

@Repository

-마이바티스 레이어를 객체주입 받을때 사용하는 어노테이션. 외부라이브러리를 주입받음.

sqlSessionTemplate에 @Autowired붙인것처럼

 

@pathVariable

-해쉬값을 받아올때에

-메소드의 파라미터 자리에 온다.

]

React Hook정리

 

useState-리렌터링의 책임이 있다.( Dom tree Render Tree)

useEffect-의존성 배열 중요

useRef 사용자 입력 컴포넌트를 기억시킬때

useNavigate 화면전환

useCallback 메모이제이션-함수를 기억함

useMemo 메모이제이션 - 값을 기억함

 

 

댓글