만일 오류가 발생한다면 ExceptionController 의 문제오 @Hidden 어노테이션을 붙여주면 된다
@Hidden // swagger ui 에서 무시하도록 @Hidden
@RestControllerAdvice
public class ExceptionController {
}
value 로 넣어 주어야 날짜가 표기 된다<input type="date" value={ }/>

getFormattedBirthday 메소드 생성@Data 어노테이션이 존재하지만 getter 메소드를 직접 추가하면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)

<td>{dto.formattedBirthday}</td>

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>

<td>{item.formattedBirthday}</td>
src 폴더를 Step02SPA2 폴더명으로 변경 후 src_templates 폴더를 복사해 새로운 src 폴더로 만든다
src 폴더 안에 components 폴더 생성 후 Counter.jsx 생성
function Counter() {
return<>
<button>-</button>
<button>0</button>
<button>+</button>
</>
}
Counter import 해서 사용function App() {
return (
<div className="container">
<h1>인덱스 페이지</h1>
<div><Counter/></div>
</div>
);
}
const [state, setState] = useState({
userName:"유재석",
email:"yu1234@naver.com",
count:0
});
onClick 이벤트 추가return<>
<button onClick={()=>{
setState({
...state,
count:state.count-1
});
}}>-</button>
<button>{state.count}</button>
<button onClick={()=>{
setState({
...state,
count:state.count+1
});
}}>+</button>
</>
reducer 함수 추가state : 현재 상태action : 동작을 담고 있는 objectnewState : 새로운 상태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;
};
userState 함수를 지우고 userReducer 함수 추가useReducer(리듀서 함수, 초기 state) 형식으로 호출하면 배열이 리턴되는데state 값, 1 번 방에는 상태값을 변경할 때 호출할 함수가 들어 있다{ 동작에 필요한 여러 가지 값을 전달할 수 있다 }const [state, dispatch] = useReducer(reducer, {
userName:"유재석",
email:"yu1234@naver.com",
count:0
});
setState 를 dispatch 로 변경return<>
<button onClick={()=>{
dispatch({
...state,
count:state.count-1
});
}}>-</button>
<button>{state.count}</button>
<button onClick={()=>{
dispatch({
...state,
count:state.count+1
});
}}>+</button>
</>
➜ 전의 코드와 동일한 동작을 한다
특정 요소의 참조값을 관리하기 위한 hook
document.querySelector("#one").value 는inputRef 는 object 인데 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 코드 추가object 로 만든다type 과 payload (추가할 이름) 를 담는다const action = {type:"add", payload:name};
//action 발행하기
dispatch(action);
// 입력창 초기화
inputRef.current.value = "";
if문 추가const reducer = (state, action) => {
let newState;
if(action.type === "add"){
// 기존의 상태값 friends 에 새로운 name 을 추가한 새로운 상태값을 대입한다
newState={
...state,
friends:[...state.friends, action.payload]
}
}
return newState;
}
map 함수를 이용한 li 요소 추가<ul>
{state.friends.map(item => <li>{item}</li>)}
</ul>
Friends import 해 사용<div><Friends/></div>

Reset 버튼 추가<button onClick={()=>{
const action = {type:"reset"};
dispatch(action);
}}>Reset</button>
else if문 추가else if(action.type === "reset"){
newState = {
...state,
friends:[]
}
}
store) 가 있고, 거기서 값을 끌어다 쓰거나 바꿀 수 있다컴포넌트마다 각각 useState 로 관리하면 나중에 복잡해지기에 공통으로 써야 되는 상태는 redux에서 통합 관리
상태를 바꾸려면 reducer 라는 함수에서 바꿔야 되며 그걸 직접 바꾸는 게 아니라 action 이라는 걸 만들어서 dispatch 로 요청
action : type 과 payload 로 구성된 객체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 을 발행해서 상태값을 변경한다
reducer 함수에 else 문 추가else{
newState = state;
}
ref = {inputRef} 예시
inputRef 는 {current:참조값} 구조의 object<input ref={inputRef} type="text" placeholder="친구 이름 입력..."/>
src 폴더를 Step04Reducer 로 폴더명을 변경 후 src_spa 폴더를 복사 붙여넣기 src 로 생성
useState 추가const [fortune, setFortune] = useState("동쪽으로 가면 귀인을 만나요!")
props.fortune 이 가능하려면 부모 component 가<Study fortune={운세}/> 와 같이 props 로 전달을 해 주어야 한다function Study(props) {
return <div>
<h3>Study 컴포넌트</h3>
<p>오늘의 운세 : <strong>{props.fortune}</strong></p>
</div>
}
Study 컴포넌트 사용import Study from './components/Study';
<Study fortune={fortune}/>
function StudyChild(props) {
return <>
<p>오늘의 운세 : <strong>{props.fortune}</strong></p>
</>
}
StudyChild 컴포넌트 사용import StudyChild from './StudyChild';
<StudyChild fortune={props.fortune}/>
App ⟶ Study ⟶ StudyChild
props props

initState 함수 추가// redux store 에서 관리될 state 의 초기값
const initState = {
fortuneToday : "동쪽으로 가면 귀인을 만나요"
};
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
}
redux importlegacy_createStore 를 createStore 라는 이름으로 사용하기store 를 만들 함수)from 'redux' 가 가능 하려면 npm install redux react-redux 를 설치해야 한다 import { legacy_createStore as createStore } from 'redux';
store 코드 추가const store = createStore(reducer);
Provider 컴포넌트 추가<Provider store={store}>
<RouterProvider router={router}/>
</Provider>
useSelector 함수 추가useSelector() hook 을 이용해서 얻어낸다state 는 redux store 에서 관리하는 상태값useSelector() 함수의 리턴 값이 된다const fortuneToday = useSelector(state => {
return state.fortuneToday;
});
FortuneToday 추가<p>오늘의 운세 : <strong>{fortuneToday}</strong></p>

reducer 함수 안에 if 문 코드 수정 & 추가{type:"UPDATE_FORTUNE" , payload:"새로운 운세"}action 이 dispatch(발행) 하면 된다if(action.type === "UPDATE_FORTUNE"){ // 운세를 변경하는 action type
newState = {
...state, // 기존의 상태값을 펼쳐놓고
fortuneToday:action.payload // action 의 payload 로 전달된 운세로 변경
};
}
<input type="text" placeholder="변경할 운세"/>
<button>변경하기</button>
useDispatch & useRef 함수 추가// action 을 dispatch 할 함수 얻어내기
const dispatch = useDispatch();
// input 요소의 참조값을 사용하기 위해
const inputRef = useRef();
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);

initState 함수에 상태값 2개 추가const initState = {
fortuneToday : "동쪽으로 가면 귀인을 만나요",
userInfo:null,
loginModal:{
title:null,
show:false
}
};
function LoginModal() {
return <>
</>
}
handleChange 함수 추가const handleChange = (e) => {
}
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>
</>
show 를 true 상태로 변경const initState = {
fortuneToday : "동쪽으로 가면 귀인을 만나요",
userInfo:null,
loginModal:{
title:null,
show:true
}
};
reducer 함수에 else if 문 추가action typeelse if(action.type === "LOGIN_MODAL"){
newState = {
...state,
loginModal:action.payload
}
}
LoginModal 사용<LoginModal/>

show 를 false 상태로 변경show:true
return (
<>
<h1>인덱스 페이지</h1>
<button>로그인</button>
</>
);
dispatch 함수 추가const dispatch = useDispatch();
onClick 이벤트 추가button onClick={()=>{
const action = {
type:"LOGIN_MODAL",
payload:{
title:"로그인 폼입니다",
show:true
}
};
dispatch(action)
}}>로그인</button>
흐름

결과

