76일차 내용 정리

채공부·2025년 9월 9일

React

  • 전체 페이지를 새로 로드하지 않고 URL이 바뀌어도 필요한 컴포넌트만 렌더링

SPA

  • Single Page Application
  • 페이지 전체를 처음 한 번만 불러오고 이후에는 필요한 부분만 바꿔서 보여주는 방식 (페이지 전환 시 전체 페이지를 다시 로딩하지 않는다)

router/index.jsx 자동 인식

  • 파일명이 index 라서 해당 폴더명까지만 import 해도 자동으로 index 파일을 가져와 사용한다
import router from './router'

라우트 배열 분리하기

라우팅 설정을 컴포넌트별로 파일로 분리하면 코드가 더 깔끔하고 유지보수가 쉬워진다

  • member.jsx 파일에 따로 라우트 배열 작성
const paths = [
	{path:"/members", element:<Member/>},
    {path:"/members/:num", element:<MemberDetail/>}
]

export default paths;
  • index.jsx 에서 해당 배열 불러오기 및 라우트 통합
import paths from "./member"

const routes = [
	...paths
];

createBrowserRouter( )

# 없이 자연스러운 url 주소를 표시하고 싶으면 사용
단 클라이언트가 새로고침을 누르면 문제 발생
createHashRouter 사용 : 새로고침 대응 가능

  • router 폴더에 index.jsx 에 router2 추가
const router2 = createBrowserRouter([{
    path:"/",
    element:<App/>,
    children: routes.map((route)=>{
        return {
            index: route.path === "/", //자식의 path 가 "/" 면 index 페이지 역활을 하게 하기 
            path: route.path === "/" ? undefined : route.path, // path 에 "/" 두개가 표시되지 않게  
            element: route.element //어떤 컴포넌트를 활성화 할것인지 
        }
    })
}]);

export default router2;

동적 라우팅

라우트 경로에 :변수명 형태를 사용하면 클라이언트의 현재 URL에 따라 다른 값을 변수로 받을 수 있다

{path:"/members/:num", element:<MemberDetail/>}
/members/1
/members/2
/members/3

배열 & 객체 구조 분해 할당

C:\Users\USER>node

> const arr = [10, "kim"];
undefined
> const [a, b] = arr;
undefined
> const obj = {num:10, name:"park"};
undefined
> const {num} = obj;
undefined
> num
10
> const {name} = obj;
undefined
> name
'park'

useParams( )

URL 파라미터를 포함한 객체(object)를 반환하는 React Router hook

import { useParams } from 'react-router-dom';

const {num} = useParams();

회원 추가

  • pages 폴더 안에 MemberForm.jsx 생성
function MemberForm() {
    return (
        <>
            <h1>회원 추가 양식</h1>  
            <form action="">

            </form>
        </>
    );
}
  • index.jsx 에 MemberForm 라우트 경로 추가
{path:"/members/new", element:<MemberForm/>}
  • Member.jsx 에 회원 목록 페이지에 추가 링크 생성
<NavLink to="/members/new">회원 추가</NavLink>
  • MemberForm.jsx 에 method 추가 및 텍스트 박스 & 버튼 추가
    • form 안에 있는 submit 버튼을 누르면 form 에는 submit 이벤트가 발생
function MemberForm() {
    return (
        <>
            <h1>회원 추가 양식</h1>  
            <form action="/api/v1/members" method="post">
                <div>
                    <label htmlFor="name">이름</label>
                    <input type="text" name="name" id="name"/>
                </div>
                <div>
                    <label htmlFor="addr">주소</label>
                    <input type="text" name="addr" id="addr"/>
                </div>
                <button type="submit">저장</button>
            </form>
        </>
    );
}
  • form 요소에 onSubmit 이벤트 핸들러 추가
<form action="/api/v1/members" method="post" onSubmit={handleSubmit}>
  • form 에 submit 이벤트가 발생했을 때 실행할 함수 작성
    • e : 발생한 이벤트에 대한 정보가 들어 있다
      • e.preventDefault()
      • e.target
// form에 submit 이벤트가 발생했을 때 실행할 함수
const handleSubmit = (e) => {
    // 기본 동작 막기 (form 제출 막기)
    e.preventDefault();

    // 이벤트가 발생한 요소의 참조값 (form 요소의 참조값)
    const form = e.target;

    // 폼에 입력한 내용을 담고 있는 FormData 객체를 얻어낸다
    const formData = new FormData(form);

    // 폼에 입력한 내용을 object로 변환
    const obj = Object.fromEntries(formData);

    // object를 JSON 문자열로 변환
    const json = JSON.stringify(obj);

    // fetch() 함수를 이용해서 전송하기
    fetch(form.action, {
        method: form.method, // 요청 method 
        headers: { "Content-Type": "application/json" }, // 요청 header
        body: json // 요청 body
    })
    .then(res => res.json())
    .then(data => {
    	// 방금 추가한 회원의 정보가 data 에 들어 있다
        
    });
};

  • useNavigate() 함수 추가
    • javascript 로 라우팅할 수 있는 함수 얻어내기
    • 여기서 navigate 함수를 이용해서 이동할 수 있다
const navigate = useNavigate();
  • navigate() 코드 추가
    • "/members/x" 위치로 이동
    • <NavLink to="/members/x">이동</NavLink> 이런 링크를 눌른 효과
navigate(`/members/${data.num}?message=success`);
http://localhost:5173/#/members/6?message=success
  • MemberDetail.jsx 에 useSearchParams() 함수 추가
    • useSearchParams() : query 파라미터를 추출할 때 사용하는 hook
const [params] = useSearchParams();
console.log(params);
// success or null
console.log(params.get("message"));
  • React BootStrap 으로 저장 메세지 출력
 return (
	<>
		{params.get("message") && <Alert className="m-4" variant="success">저장 완료</Alert>}
	</>
);

BreadCrumb UI

src 폴더 안에 componenets 폴더 생성

  • components 폴더 안에 location.jsx 생성
import { NavLink } from 'react-router-dom';

function Location() {
    return<>
        <nav>
            <ol className="breadcrumb">
                <li className="breadcrumb-item">
                    <NavLink to="/">Home</NavLink>
                </li>
                
            </ol>
        </nav>
    </>
}
  • MemberDetail.jsx 에 Location 컴포넌트 사용
<Location/>
  • Location.jsx 에 list 파라미터 추가
    • Location({list}) : props 로 전달되는 값 중에서 list 만 구조 분해 할당을 해서 담겠다는 의미
    • <Location list={["aaa", "bbb", "ccc"]}/> 형식으로 사용한다면
      list 에는 ["aaa", "bbb", "ccc"] 배열이 들어 있다
function Location(list) {
    return<>
        <nav>
            <ol className="breadcrumb">
                <li className="breadcrumb-item">
                    <NavLink to="/">Home</NavLink>
                </li>
                
            </ol>
        </nav>
    </>
}
  • MemberDetail.jsx 에 Location 에 list 전달
<Location list={["회원 목록", "자세히 보기"]}/>
  • MemberDetail.jsx 에 bread 경로 정보 배열 추가
const bread = [
        {name:"Members", to:"/members"},
        {name:"Detail"}
    ];
  • Location 에 경로 배열 넘기기
<Location list={bread}/>
  • Location.jsx 에 배열 map 으로 breadcrumb 렌더링
    • 삼항연산자를 사용해 조건부 사용
      • to 값이 있으면 링크로 표시
      • to 값이 없으면 현재 위치(active) 로 표시
function Location({list}) {
    return<>
        <nav>
            <ol className="breadcrumb">
                <li className="breadcrumb-item">
                    <NavLink to="/">Home</NavLink>
                </li>
                {list.map(item=>
                    <li className={`breadcrumb-item ${item.to ? '':'active'}`}>
                        {item.to ? <NavLink to={item.to}>{item.name}</NavLink> : item.name }
                    </li>
                )}
            </ol>
        </nav>
    </>
}

  • MemberForm.jsx 에 bread 경로 정보 배열 추가
const bread = [
	{name:"Members", to:"/members"},
	{name:"New Form"}
];
  • Location 에 경로 배열 넘기기
<Location list={bread}/>

Axios

axios 설치

  1. npm 에서 axios 검색 후 클릭

  1. install 코드 복사

  1. VS CODE 에 Terminal 에 해당 코드 붙여넣기
npm i axios

회원 정보 수정

MemberDetail.jsx 에 수정, 삭제 버튼을 만들고 동작하도록 만들어 보세요
수정은 MemberUpdateForm.jsx 컴포넌트를 만들어서 동작하도록 하고
삭제는 정말로 삭제 하시겠습니까? 라는 bootstrap Modal 을 띄워서 동작하도록 해 보세요
"/members/1/edit" ➜ 수정 routing 경로

Location.jsx 파일명을 Bread.jsx 로 수정

  • index.jsx 에 MemberUpdateForm 라우트 경로 추가
{path:"/members/:num/edit", element:<MemberUpdateForm/>}
  • MemberDetail.jsx 에 수정 버튼 추가
<Button variant="success" as={NavLink} to={`/members/${dto.num}/edit`}>수정</Button>
  • MemberUpdateForm.jsx
function MemberUpdateForm() {
    return<>
    <Bread list={bread}/>
        <h1>회원 수정 양식</h1> 
        <form>
            <div>
                <label htmlFor="name">이름</label>
                <input type="text" name="name" id="name"/>
            </div>
            <div>
                <label htmlFor="addr">주소</label>
                <input type="text" name="addr" id="addr"/>
            </div>
            <button type="submit">수정</button>
        </form>
    </>
}
  • 수정할 회원의 번호 얻어내기
const {num} = useParams();

const [dto, setDto] = useState({});
  • 수정할 회원의 정보를 API 서버로부터 받아와서 상태값을 관리
useEffect(()=>{
	fetch(`/api/v1/members/${num}`)
    .then(res=>res.json())
    .then(data=>setDto(data));
}, []);
  • 원래 회원 정보 출력
return<>
    <Bread list={bread}/>
    <h1><i>{dto.num}</i>번 회원 수정 양식</h1> 
    <form>
        <div>
            <label htmlFor="name">이름</label>
            <input type="text" name="name" id="name" value={dto.name}/>
        </div>
        <div>
            <label htmlFor="addr">주소</label>
            <input type="text" name="addr" id="addr" value={dto.addr}/>
        </div>
        <button type="submit">수정</button>
    </form>
</>
  • input 요소에 onChage 이벤트 추가
<div>
	<label htmlFor="name">이름</label>
	<input onChange={handleChange} type="text" name="name" id="name" value={dto.name}/>
</div>
<div>
	<label htmlFor="addr">주소</label>
	<input onChange={handleChange} type="text" name="addr" id="addr" value={dto.addr}/>
</div>
  • input 요소에 change event 가 일어났을 때 실행할 handleChange 함수 추가
const handleChange = (e) => {
	// 입력한 value 값을 읽어온다
	const value = e.target.value;
	// 입력한 input 요소의 name 속성의 값을 읽어온다
	const name = e.target.name;
		// 입력한 내용을 이용해서 상태값을 변경한다
	setDto({
		...dto,
		[name]:value // 여기서 name 은 "name" 또는 "addr" 이 된다
	})
};
  • form 요소에 action & onSubmit 이벤트 추가
<form action={`/api/v1/members/${dto.num}`} onSubmit={handleSubmit}>
  • route 이동을 위한 navigate 함수를 hook 을 이용해서 얻어낸다
const navigate = useNavigate();
  • form 에 submit 이벤트가 일어났을 때 실행할 함수 handleSubmit 함수 추가
const handleSubmit = (e)=>{
        e.preventDefault();
        const form = e.target; // 수정 form 요소의 참조값 
        // put 방식 요청을 이용해서 회원 정보 수정 요청을 한다
        fetch(form.action, {
            method : "put", // put 방식
            headers : {"Content-Type":"application/json"}, // json 문자열을 보낸다고 알림
            body : JSON.stringify(dto) // 상태값으로 관리하는 member object 를 json 문자열로 변경
        })
        .then(res=>res.json())
        .then(data=>{
            navigate(`/members/${data.num}`)
        });
    };

⚠️ 기능에는 문제가 없지만 콘솔에 경고 메시지가 발생하는 문제 해결

  • Bread.jsx 에 key 값 추가
 <li key={item.name} className={`breadcrumb-item ${item.to ? '':'active'}`}>
  • MemberUpdateForm.jsx 에 useState 에 빈 값 삽입
const [dto, setDto] = useState({
	num:null,
	name:"",
	addr:""
});

회원 삭제

  • components 폴더 안에 ConfirmModal.jsx 생성
    • show : boolean type 모달을 띄울지 여부
    • message : string type 모달 메세지 내용
    • onYes : function type 확인 버튼을 눌렀을 때 실행할 함수
    • onCancle : function type 취소 버튼을 눌렀을 때 실행할 함수
import React from 'react';
import { Button, Modal, ModalBody } from 'react-bootstrap';

function ConfirmModal({show, message, onYes, onCancel}) {
    return (
        <Modal show={show}>
            <Modal.Header>알림</Modal.Header>
            <ModalBody>
                {message}
            </ModalBody>
            <Modal.Footer>
                <Button variant='success' onClick={onYes}>확인</Button>
                <Button variant='warning' onClick={onCancel}>취소</Button>
            </Modal.Footer>
        </Modal>
    );
}
  • MemberDetail 에 삭제 버튼 추가
<Button variant="danger" className="ms-1">삭제</Button>
  • ConfirmModal 을 띄울지 여부를 상태값으로 관리
const [showModal, setShowModal] = useState(false);
  • 버튼에 onClick 이벤트 추가 & ConfirmModal 사용
import ConfirmModal from '../components/ConfirmModal';
<Button variant="danger" className="ms-1" onClick={()=>setShowModal(true)}>삭제</Button>
<ConfirmModal show={showModal} message="삭제 하시겠습니까?"/>
  • 취소 버튼 클릭시 Modal 창 숨기는 onCancle 이벤트 추가
<ConfirmModal show={showModal}
	message="삭제 하시겠습니까?"
	onCancel={()=>setShowModal(false)}
</>
  • 삭제 버튼 클릭시 회원정보를 삭제하는 onYes 이벤트 추가
<ConfirmModal show={showModal}
	message="삭제 하시겠습니까?"
	onCancel={()=>setShowModal(false)}
	onYes={handleYes}
/>
  • useNavigate 함수 추가
const navigate = useNavigate();
  • 삭제 확인을 눌렀을 때 실행할 handleYes 함수 추가
const handleYes = () => {
	fetch(`/api/v1/members/${dto.num}`,{
		method : "delete"
	})
	.then(res=>res.json())
	.then(data=>{
		navigate("/members");
	});
};

Navbar

  • components 폴더 안에 BsNavbar.jsx 생성
import { Container, Nav, Navbar } from 'react-bootstrap';
import { NavLink } from 'react-router-dom';

function BsNavbar(props) {
    return<>
        <Navbar fixed="top" expand="md" className="bg-warning mb-2">
            <Container>
                <Navbar.Brand as={NavLink} to="/">Acorn</Navbar.Brand>
                <Navbar.Toggle aria-controls="collapseContent"/>
                <Navbar.Collapse id="collapseContent">
                    <Nav className='me-auto'>
                        <Nav.Link as={NavLink} to="/">Home</Nav.Link>
                        <Nav.Link as={NavLink} to="/study">Study</Nav.Link>
                        <Nav.Link as={NavLink} to="/play">Play</Nav.Link>
                        <Nav.Link as={NavLink} to="/members">Members</Nav.Link>
                    </Nav>
                </Navbar.Collapse>
            </Container>
        </Navbar>      
    </>
}
  • App.jsx 에 BsNavbar 사용
function App() {
    const currentOutlet = useOutlet();
    return<>
        <BsNavbar/>
        <div className="container pt-4" style={{marginTop:"50px"}}>
            {currentOutlet}
        </div>
    </>
}

페이징 처리

  • Spring09JpaApplication 에 반복문을 이용한 샘플 데이터 100개 저장
for(int i=0; i<100; i++) {
	Member tmp = Member.builder().name("이름"+i).addr("주소"+i).build();
	em.persist(tmp);
}         
  • MemberPageResponse 클래스 생성
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Data
public class MemberPageResponse {
	private List<MemberDto> list;
	private int startPageNum;
	private int endPageNum;
	private int totalPageNum;
	private int pageNum;
}
{
	"list":[{], {}, {}, ...]
	"startPageNum":1,
	"endPageNum":10,
	"totalPageNum" : 11,
	"pageNum" : 3
}
  • MemberService 인터페이스에 getPage 메소드 생성
public MemberPageResponse getPage(int pageNum);
  • MemberServiceImpl 에 상수값 추가 및 추상 메소드 오버라이드
// 한 페이지에 몇 개의 row 를 출력할 것인지에 대한 값
final int PAGE_ROW_COUNT = 10;
// 페이징 처리 UI 에 페이지 번호를 몇 개씩 출력할 지에 대한 값
final int PAGE_DISPLAY_COUNT = 5;
@Override
public MemberPageResponse getPage(int pageNum) {
	// num 에 대해서 내림차순 정렬하겠다는 Sort 객체
	Sort sort = Sort.by(Sort.Direction.DESC, "num");
		
	// pageNum 과 page row count 와 정렬 객체를 전달해서 원하는 PageRequest 를 만들어내고
	PageRequest pageRequest = PageRequest.of(pageNum-1, PAGE_ROW_COUNT, sort);
		
	// drg.springframework.data.domain 패키지의 Page type 을 import 해야 한다
	Page<Member> page = memberRepo.findAll(pageRequest);
		
	// Page 객체를 stream 으로 만들어서 dto 의 list 를 얻어낸다
	List<MemberDto> list = page.stream().map(MemberDto :: toDto).toList();
		
	return null;
}
profile
학원 공부 내용 정리

0개의 댓글