80일차 내용 정리

채공부·2025년 9월 15일

swagger ui

만일 오류가 발생한다면 ExceptionController 의 문제오 @Hidden 어노테이션을 붙여주면 된다

@Hidden // swagger ui 에서 무시하도록 @Hidden
@RestControllerAdvice
public class ExceptionController {

}

고객 수정 양식

  • iso 표준 형식의 문자열을 value 로 넣어 주어야 날짜가 표기 된다
<input type="date" value={ }/>

  • ClientDto 에 getFormattedBirthday 메소드 생성
    • 단순 출력용 생일 문자 형식
    • @Data 어노테이션이 존재하지만 getter 메소드를 직접 추가하면
      마치 formattedBirthday 라는 필드가 있는 것처럼 동작한다
public String getFormattedBirthday() {
	// 저장된 생일이 있으면 원하는 형식의 날짜 형식을 리턴하고 없으면 null 을 리턴한다
	String result = birthday != null ? birthday.format(DateTimeFormatter.ofPattern("yyyy년 MM월 dd일")) : null;
	return result;
}
  • input type="date" 에 맞는 형식
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE)

  • ClientDetail.jsx 에 생일 출력 변경
    • 생일을 단순히 출력할 때는 fomattedBirthday 를 출력
<td>{dto.formattedBirthday}</td>

  • ClientUpdateForm 에서는 birthday 그대로 사용
<div className="mb-3">
	<label className="form-label" htmlFor="birthday">생일 (선택)</label>
	<input className="form-control" onChange={handleChange} type="date" name="birthday" id="birthday"
			value={dto.birthday}/>
	<small className="form-text text-muted">나중에 입력 가능</small>
	{errors.birthday && <small className="form-text text-danger">{errors.birthday}</small>}
</div>

  • Client.jsx 에 생일 출력 코드 변경
<td>{item.formattedBirthday}</td>

상태값 관리

src 폴더를 Step02SPA2 폴더명으로 변경 후 src_templates 폴더를 복사해 새로운 src 폴더로 만든다

src 폴더 안에 components 폴더 생성 후 Counter.jsx 생성

function Counter() {
    return<>
        <button>-</button>
        <button>0</button>
        <button>+</button>
    </>
}
  • App.jsx 에 Counter import 해서 사용
function App() {
    return (
        <div className="container">
            <h1>인덱스 페이지</h1>
            <div><Counter/></div>
        </div>
    );
}
  • Counter.jsx 에 여러 개의 상태값으로 관리
const [state, setState] = useState({
	userName:"유재석",
	email:"yu1234@naver.com",
	count:0
});
  • 버튼에 onClick 이벤트 추가
    • 여기 저기서 필요할 때 상태값을 변경하는 로직이 필요
    • UI 가 복잡하거나 상태값을 변경할 일이 많으면 유지보수의 어려움이 존재
return<>
	<button onClick={()=>{
		setState({
			...state,
			count:state.count-1
		});
	}}>-</button>
	<button>{state.count}</button>
	<button onClick={()=>{
		setState({
			...state,
			count:state.count+1
		});
	}}>+</button>
</>
  • Counter.jsx 에 reducer 함수 추가
    • 차원을 감소 시켜서 새로운 상태값을 리턴하는 함수
    • state : 현재 상태
    • action : 동작을 담고 있는 object
      (지금은 string 이지만 object 로 변경할 예정)
    • newState : 새로운 상태
const reducer = (state, action) => {
    // 새로운 상태값을 담을 변수
    let newState;

    // action 값에 따라 분기해서
    if(action === "minus"){
        newState={
            ...state,
            count:state.count-1
        }
    }else if(action === "plus"){
        newState={
            ...state,
            count:state.count+1
        }
    }else{
        newState = state;
    }

    // 새로운 상태값을 리턴한다
    return newState;
};

useReducer()

  • 기존 userState 함수를 지우고 userReducer 함수 추가
    • useReducer(리듀서 함수, 초기 state) 형식으로 호출하면 배열이 리턴되는데
      배열의 0 번 방에 state 값, 1 번 방에는 상태값을 변경할 때 호출할 함수가 들어 있다
    • 상태값을 변경할 때 action 을 발행(dispatch) 하는 구조로 상태값을 변경한다
      • 지금은 string 이지만 object 로 변경할 예정
        { 동작에 필요한 여러 가지 값을 전달할 수 있다 }
const [state, dispatch] = useReducer(reducer, {
	userName:"유재석",
	email:"yu1234@naver.com",
	count:0
	});
  • 버튼의 이벤트 요소에서 setStatedispatch 로 변경
return<>
	<button onClick={()=>{
		dispatch({
			...state,
			count:state.count-1
		});
	}}>-</button>
	<button>{state.count}</button>
	<button onClick={()=>{
		dispatch({
			...state,
			count:state.count+1
		});
	}}>+</button>
</>

➜ 전의 코드와 동일한 동작을 한다

useRef( )

특정 요소의 참조값을 관리하기 위한 hook

  • Friends.jsx 생성
    • input 요소에 입력한 value 값을 직접 읽어오고 싶다면
    • document.querySelector("#one").value
      react 에서는 추천하지 않는 구조
    • inputRefobject 인데 object 의 current 라는 방에 input 요소의 참조값이 들어있다
function Friends() {

    const inputRef = useRef(); // {current:x} 구조의 object
    
    return<>
        <p>로그인된 userName : <strong>{state.userName}</strong></p>
        <input ref={inputRef} type="text" placeholder="친구 이름 입력..."/>
        <button onClick={()=>{
            const name = inputRef.current.value;

        }}>추가</button>
        <ul>
        
        </ul>
    </>
}
  • reducer 함수 추가
const reducer = (state, action) => {
    let newState;

    return newState;
}
  • 초기 상태값 추가
const initState = {
	userName:"유재석",
	friends:[]
};
  • useReducer 함수 추가
    • useReducer (reduce 함수, 초기 상태값)
const [state, dispatch] = useReducer(reducer, initState);
  • 버튼의 이벤트 요소에 action 코드 추가
    • 발행할 action 을 object 로 만든다
    • object 에는 동작의 typepayload (추가할 이름) 를 담는다
const action = {type:"add", payload:name};

//action 발행하기
dispatch(action);
// 입력창 초기화
inputRef.current.value = "";
  • reducer 함수에 if문 추가
const reducer = (state, action) => {
    let newState;
    if(action.type === "add"){
        // 기존의 상태값 friends 에 새로운 name 을 추가한 새로운 상태값을 대입한다
        newState={
            ...state,
            friends:[...state.friends, action.payload]
        }
    }

    return newState;
}
  • ul 요소 안에 map 함수를 이용한 li 요소 추가
<ul>
	{state.friends.map(item => <li>{item}</li>)}
</ul>
  • index.jsx 에 Friends import 해 사용
<div><Friends/></div>

  • Reset 버튼 추가
<button onClick={()=>{
	const action = {type:"reset"};
	dispatch(action);
}}>Reset</button>
  • reducer 함수 안에 else if문 추가
else if(action.type === "reset"){
	newState = {
		...state,
		friends:[]
	}
}

Redux

  • 여러 상태값을 중앙에서 한 번에 관리하고 싶을 때 사용
  • 전역 상태 저장소 (store) 가 있고, 거기서 값을 끌어다 쓰거나 바꿀 수 있다
  • 컴포넌트마다 각각 useState 로 관리하면 나중에 복잡해지기에 공통으로 써야 되는 상태는 redux에서 통합 관리

  • 상태를 바꾸려면 reducer 라는 함수에서 바꿔야 되며 그걸 직접 바꾸는 게 아니라 action 이라는 걸 만들어서 dispatch 로 요청

  • action : typepayload 로 구성된 객체
  • dispatch(action) 하면 reducer 가 그걸 보고 state 를 새로 만든다

react-redux 설치

npm install redux react-redux

			    현재상태   동작
const reducer = (state, action) => {
	let newState;
	로직을 통해서
    
    return newState;
    			새로운 상태값을 리턴하는 함수가 reducer 함수
}
-----------------------------------------------------------------
const [state, dispatch] = userReducer(reducer, 초기 state)
	   이 상태값을 component 에서 사용하고
       상태값을 변경하고 싶을 때는 dispatch() 함수를 이용해서
       						    action 을 발행해서 상태값을 변경한다
  • Friends.jsx 에 reducer 함수에 else 문 추가
    • 일 처리할 수 없는 action type 이라면 원래 state 값을 리턴
else{
	 newState = state;
}

ref = {inputRef} 예시

  • inputRef{current:참조값} 구조의 object
  • 해당 컴포넌트가 다시 렌더링 되더라도 current 라는 방에는 항상 이 input 요소의 참조값이 들어있다
  • 따라서 어디에선가 input 요소의 참조값이 필요하면 inputRef.current 로 얻어낼 수 있다
<input ref={inputRef} type="text" placeholder="친구 이름 입력..."/>

Props 전달

src 폴더를 Step04Reducer 로 폴더명을 변경 후 src_spa 폴더를 복사 붙여넣기 src 로 생성

  • App.jsx 에 useState 추가
    • App Component 에서 관리하는 상태값
const [fortune, setFortune] = useState("동쪽으로 가면 귀인을 만나요!")
  • components 폴더 생성 후 그 안에 Study.jsx 생성
    • props.fortune 이 가능하려면 부모 component 가
      <Study fortune={운세}/> 와 같이 props 로 전달을 해 주어야 한다
function Study(props) {
    return <div>
        <h3>Study 컴포넌트</h3>
        <p>오늘의 운세 : <strong>{props.fortune}</strong></p>
    </div>
}
  • indes.jsx 에 Study 컴포넌트 사용
import Study from './components/Study';

<Study fortune={fortune}/>
  • StudyChild.jsx 생성
function StudyChild(props) {
    return <>
        <p>오늘의 운세 : <strong>{props.fortune}</strong></p>
    </>
}
  • Study.jsx 에 StudyChild 컴포넌트 사용
import StudyChild from './StudyChild';

<StudyChild fortune={props.fortune}/>
App  ⟶  Study  ⟶  StudyChild 
    props	   props

  • main.jsx 에 initState 함수 추가
    • 초기값 설정 = default 값
// redux store 에서 관리될 state 의 초기값
const initState = {
  fortuneToday : "동쪽으로 가면 귀인을 만나요"
};
  • main.jsx 에 reducer 함수 추가
const reducer = (state = initState, action) => {
	let newState;
	if(action.type === "hello"){
		newState = {};
	} else{
		newState = state;
	}
  	return newState;
}
test();

function test(a){
	a 는 undefiend
}

	test2()
function test2(a=0){
	a 는 0
}
  • main.jsx 에 redux import
    • legacy_createStorecreateStore 라는 이름으로 사용하기
      (store 를 만들 함수)
    • from 'redux' 가 가능 하려면 npm install redux react-redux 를 설치해야 한다
import { legacy_createStore as createStore } from 'redux';
  • store 코드 추가
    • 위에서 만든 reducer 함수를 사용하는 redux store (중앙 저장소) 만들기
const store = createStore(reducer);
  • Provider 컴포넌트 추가
    • redux store 가 동작하도록 Provider 컴포넌트로 감싸준다
<Provider store={store}>
	<RouterProvider router={router}/>
</Provider>
  • StudyChild 에 useSelector 함수 추가
    • redux store 로 부터 필요한 상태값을 useSelector() hook 을 이용해서 얻어낸다
      • stateredux store 에서 관리하는 상태값
      • 필요한 상태값만 선택적으로 리턴할 수 있다
      • 여기서 리턴한 값이 useSelector() 함수의 리턴 값이 된다
      • 해당 상태값에 변화가 생기면 이 컴포넌트가(StudyChild) 가
        다시 렌더링 되면서 새로운 상태값이 리턴된다
const fortuneToday = useSelector(state => {
	return state.fortuneToday;
});
  • 새로운 p 요소 생성 그 안에 FortuneToday 추가
<p>오늘의 운세 : <strong>{fortuneToday}</strong></p>

  • main.jsx 에 reducer 함수 안에 if 문 코드 수정 & 추가
    • 따라서 운세를 어디에선가 변경하려면
      {type:"UPDATE_FORTUNE" , payload:"새로운 운세"}
      형식의 actiondispatch(발행) 하면 된다
if(action.type === "UPDATE_FORTUNE"){ // 운세를 변경하는 action type
	newState = {
		...state, // 기존의 상태값을 펼쳐놓고
		fortuneToday:action.payload // action 의 payload 로 전달된 운세로 변경
	};
}
  • App.jsx 에 텍스트 박스 & 버튼 추가
<input type="text" placeholder="변경할 운세"/>
<button>변경하기</button>
  • main.jsx 에 useDispatch & useRef 함수 추가
// action 을 dispatch 할 함수 얻어내기
const dispatch = useDispatch();

// input 요소의 참조값을 사용하기 위해
const inputRef = useRef();
  • input 요소에 ref 코드 추가
<input ref={inputRef} type="text" placeholder="변경할 운세"/>
  • 버튼에 onClick 이벤트 추가
<button onClick={()=>{
	// 입력한 문자열
	const inputFortune = inputRef.current.value;
	// action 객체
	const action = {type:"UPDATE_FORTUNE", payload:inputFortune};
	// action 발행하기
	dispatch(action);
}}>변경하기</button>

  • 버튼 이벤트 요소에 setFortune 코드 추가
    • 테스트로 자체 상태값도 변경해 보기
setFortune(inputFortune);

모달창

  • main.jsx 에 initState 함수에 상태값 2개 추가
const initState = {
  fortuneToday : "동쪽으로 가면 귀인을 만나요",
  userInfo:null,
  loginModal:{
    title:null,
    show:false
  }
};
  • LoginModal.jsx 생성
function LoginModal() {

    return <>
    
    </>
}
  • handleChange 함수 추가
    • 아이디 비밀번호를 입력했을 때 호출될 함수
const handleChange = (e) => {

}
  • loginModal 추가
    • store 로부터 loginModal 의 상태값을 읽어온다
const loginModal = useSelector(state => state.loginModal);
  • Modal 컴포넌트 추가
    • loginModal.show 값에 따라 표시 여부 제어
    • loginModal.title 을 타이틀로 출력
    • store 에서 관리되는 loginModal 의 show 값을 이용해서 띄울지 말지를 결정
  • FloatingLabel 코드 추가
    • userName , password 필드 구성
    • onChange={handleChange} 연결
import { Button, FloatingLabel, Form, Modal } from 'react-bootstrap';

return <>
    <Modal show={loginModal.show} size="lg" centered>
        <Modal.Header closeButton>
            <Modal.Title>{loginModal.title}</Modal.Title>
        </Modal.Header>
        <Modal.Body>
            <FloatingLabel controlId='userName' label="User Name" className='mb-3'>
                <Form.Control onChange={handleChange} name="userName" type="text" />
            </FloatingLabel>
            <FloatingLabel controlId='password' label="Password" className='mb-3'>
                <Form.Control onChange={handleChange} name="password" type="password" />
            </FloatingLabel>
        </Modal.Body>
        <Modal.Footer>
            <Button>로그인</Button>
        </Modal.Footer>
    </Modal>    
</>
  • showtrue 상태로 변경
const initState = {
  fortuneToday : "동쪽으로 가면 귀인을 만나요",
  userInfo:null,
  loginModal:{
    title:null,
    show:true
  }
};
  • main.jsx 에 reducer 함수에 else if 문 추가
    • 로그인 모달의 상태를 변경하는 action type
else if(action.type === "LOGIN_MODAL"){
	newState = {
		...state,
		loginModal:action.payload
	}
} 
  • App.jsx 에 LoginModal 사용
<LoginModal/>

  • 다시 showfalse 상태로 변경
show:true
  • Home.jsx 에 버튼 추가
return (
	<>
		<h1>인덱스 페이지</h1>
		<button>로그인</button>
	</>
);
  • Home.jsx 에 dispatch 함수 추가
const dispatch = useDispatch();
  • 버튼에 onClick 이벤트 추가
button onClick={()=>{
	const action = {
		type:"LOGIN_MODAL",
		payload:{
			title:"로그인 폼입니다",
			show:true
		}
	};
	dispatch(action)
}}>로그인</button>

흐름

결과

profile
학원 공부 내용 정리

0개의 댓글