payload : 어떤 action 과 같이 실려가는 data
action 객체는 최소한 type 을 가지고 있어야 한다payload 에 담는 것이 일반적dispatch({type:"UPDATE", payload:{name:"kim"}})

reducer 함수가 호출되는 시점
dispatch(action) 호출 시// BComponenet action 상태값을 변경
dispatch({type:"UPDATE"})
⭣
// reducer 함수 호출
const reducer = (state = initState, action) => {
let newState;
// action.type 에 따라 state 계산
return newState;
}
⭣
AComponent 에서 새로운 상태로 UI 렌더링
state = initState 는 어떻게 동작할까?
function showName(name){
console.log(name);
}
showName("kim"); ⭢ console 에 kim 출력
showName(); ⭢ console 에 undefined 출력
function showName(name = "park"){
console.log(name);
}
showName("kim") ⭢ console 에 kim 출력
showName(); ⭢ console 에 park 출력
Node.js 콘솔 실습
C:\Users\USER>node
> function showName(name = "park"){
... console.log(name);
... }
undefined
> showName("kim");
kim
undefined
> showName();
park
undefined
StudyChild.jsx 확인
state.fortuneToday
1. "동쪽으로 가면 귀인을 만나요"
2. "수정된 운세"
props 로 부모의 상태값을 전달 받는다
<p>오늘의 운세 : <strong>{props.fortune}</strong></p>
function StudyChild(props) {
const fortuneToday = useSelector(state => {
return state.fortuneToday;
});
return <>
<p>오늘의 운세 : <strong>{props.fortune}</strong></p>
<p>오늘의 운세 : <strong>{fortuneToday}</strong></p>
</>
}
App & Study & StudyChild 의 상태 전달 흐름
최상위 컴포넌트(App) 에서 상태값을 관리
부모의 컴포넌트가 자식 컴포넌트에게 props 로 상태값 전달
자식 컴포넌트는 전달받은 props 를 활용해 UI 에 출력
부모가 상태를 업데이트할 때 자식 컴포넌트는 자동으로 최신 상태를 반영
// App.jsx
<Study fortune={fortune}/>
⭣
// Study.jsx
<StudyChild fortune={props.fortune}/>
⭣
// StudyChild.jsx
<p>오늘의 운세 : <strong>{props.fortune}</strong></p>
Form.Control : input 요소 (input type="text")
function LoginModal() {
// store 로부터 loginModal 의 상태값을 읽어온다
// {title:"Modal의 제목", show:Modal을 띄울지 여부(true or false)} 형식의 object
const loginModal = useSelector(state => state.loginModal);
// 아이디 비밀번호를 입력했을 때 호출될 함수
const handleChange = (e) => {
}
return <>
{/* store 에서 관리되는 loginModal 의 show 값을 이용해서 띄울지 말지를 결정 */}
<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>
</>
}
useState 함수 추가const [state, setState] = useState();
handleChange 함수 완성const handleChange = (e) => {
// 입력한 내용을 상태값에 반영한다
setState({
...state,
[e.target.name]:e.target.value
});
}
onClick 이벤트 요소 추가<Button onClick={handleLogin}>로그인</Button>
handleLogin 함수 추가async 로 알려준다const handleLogin = async () => {
try{
// api 서버에 로그인 요청을 보내고
const res = await axios.post("/api/v1/login", state)
// userName 과 password 가 유효한 정보이면 정상 응답이 되고
}catch(err){
// userName 과 password 가 틀리면 401 UNAUTHORIZED 에러 응답을 할 예정
console.log(err);
}
}
@RequestMapping("/v1")
@RestController
public class RestUserController {
}
@Data
public class LoginRequest {
private String userName;
private String password;
}
ResponseEntity 메소드 생성@PostMapping("/login")
public ResponseEntity<String> login(@RequestBody LoginRequest request){
// 입력한 userName 과 password 가 유효한 정보인지 여부
boolean isValid = request.getUserName().equals("kimgura") && request.getPassword().equals("1234");
// 만일 유효하지 않는 정보라면
if(!isValid) {
// 401 을 응답
ResponseEntity<String> entity =
ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("아이디 혹은 비밀번호가 틀려요");
return entity;
}
// 가상의 토큰
String token = "api 자유 이용권, 유효기간:2025.09.16, userName:"+request.getUserName();
// 정상 응답을 하면서 token 을 발급
ResponseEntity<String> entity = ResponseEntity.ok(token);
return entity;
}
Swagger UI 로 가서 rest-user-controller 에 POST 메소드 실행
{
"userName": "kimgura",
"password": "1234"
}

{
"userName": "kimgura2",
"password": "1234"
}

console.log(err);

api 자유 이용권, 유효기간:2025.09.16, userName:kimgura


useState 코드 추가const [errorMsg, setErrorMsg] = useState(null);
catch 문 안에 setErrorMsg 코드 추가setErrorMsg(err.response.data);
Alert 코드 추가{errorMsg && <Alert variant="danger">{errorMsg}</Alert>}

handleLogin 함수 안 try 문 안에 코드 추가localStorage 에 저장localStorageSession StoragelocalStorage.fakeToken = res.data;

handleLogin 함수 안 try 문 안에 payload & action 코드 추가const payload = {userName:state.userName, role:"ADMIN"};
const action = {type:"USER_INFO", payload};
➜ payload:payload 대신 payload 만 써도 된다
useDispatch 함수 추가const dispatch = useDispatch();
handleLogin 함수 안 try 문 안에 dispatch 관련 코드 추가dispatch(action);
// 로그인 폼을 초기화 하고 숨긴다
setErrorMsg(null);
dispatch({
type:"LOGIN_MODAL",
payload:{
title:"",
show:false
}
});
reducer 함수 안에 else if 문 추가else if(action.type === "USER_INFO"){
newState = {
...state,
userInfo:action.payload
}
}
<p><strong></strong>님 로그인중</p>
<button>로그아웃</button>
useSelector 함수 추가const userInfo = useSelector(state => state.userInfo);
userInfo 를 사용해 조건부로 출력 관리{ userInfo && <p>
<strong>{userInfo.userName}</strong>님 로그인중
<button>로그아웃</button>
</p>
}
handleLogin & handleLogout 함수 생성하고 그 안으로 코드 이동const handleLogin = () => {
const action = {
type:"LOGIN_MODAL",
payload:{
title:"로그인 폼입니다",
show:true
}
};
// action 발행
dispatch(action)
};
const handleLogout = () => {
// 토큰 삭제
delete localStorage.fakeToken;
// store 에서 관리되는 userInfo 를 null 로 변경한다
const action = {type:"USER_INFO", payload:null};
dispatch(action);
};
localStorage
1. 웹 브라우저가 기본 제공해주는 object
2. 문자열만 저장할 수 있다
3. 한 번 저장하면 지우지 않는 이상 영구 저장된다
onClick 이벤트 요소 수정 및 추가<button onClick={handleLogin}>로그인</button>
{ userInfo && <p>
<strong>{userInfo.userName}</strong>님 로그인중
<button onClick={handleLogout}>로그아웃</button>
</p>
}
첫번째 방법
{!userInfo && <button onClick={handleLogin}>로그인</button>}
{userInfo && <p>
<strong>{userInfo.userName}</strong>님 로그인중
<button onClick={handleLogout}>로그아웃</button>
</p>
}
in javascript
해당 if 문 의 의미는 만일 값이 존재한다면 이다
null , undefined , "" , 0 , false 는
값이 존재하지 않는다 라는 취급을 받는다
if(값){
}
> !true
false
> !null
true
> !undefined
true
> !""
true
> !false
true
> !0
true
두번째 방법
{userInfo ?
<p>
<strong>{userInfo.userName}</strong>님 로그인중
<button onClick={handleLogout}>로그아웃</button>
</p>
:
<button onClick={handleLogin}>로그인</button>
}
Modal 요소에 onHide 이벤트 요소 추가<Modal onHide={handleHide} show={loginModal.show} size="lg" centered>
handleHide 함수 추가hide 이벤트가 발생const handleHide = () => {
dispatch({
type:"LOGIN_MODAL",
payload:{title:"", show:false}
});
};
handler 객체 생성const handlers = {};
reducer2 함수 생성const reducer2 = (state = initState, action) => {
// 전달된 action 을 수행할 handler 함수를 얻어낸다
const handler = handlers[action.type];
// handler 함수가 존재한다면 handler 함수가 리턴하는 state 를 리턴하고 아니면
// 원래 state 를 리턴한다
return handler ? handler(state, action) : state;
};
handlers 안에 key 값 추가UPDATE_FORTUNE 이라는 KEY 값으로 function type 이 들어 있다{ } 를 리턴하는 코드만 존재() => 10 : 10 을 리턴하는 함수() => "kim" : "kim" 을 리턴하는 함수() => {} :화살표 함수() => ({}) : object 를 리턴하는 화살표 함수 (람다 함수)const handlers = {
UPDATE_FORTUNE : (state, action) => ({
...state,
fortuneToday:action.payload
}),
LOGIN_MODAL : (state, action) => ({
...state,
loginModal:action.payload
}),
USER_INFO : (state, action) => ({
...state,
userInfo:action.payload
})
};
➜ 해당 object 들을 파일을 생성해 따로 빼두어 object 를 해당 함수 안에 import 하여 사용 가능 = 확장성 고려
createStore 안에 reducer2 로 변경const store = createStore(reducer2);
function MemberOnlyPage() {
const userInfo = useSelector(state => state.userInfo);
return<>
<h3>로그인 된 회원 전용 페이지</h3>
<p>
<strong>{userInfo.userName}</strong>님 반갑습니다
</p>
<p>내용1</p>
</>
}
MemberOnlyPage 라우트 경로 추가import MemberOnlyPage from "../pages/MemberOnlyPage";
{path:"/member", element:<MemberOnlyPage/>}
<h3>회원 전용 링크</h3>
<NavLink to="/member">이동</NavLink>

function ProtectedRoute({children}) {
//로그인 여부를 알기위해 userInfo 를 얻어낸다.
const userInfo = useSelector(state=>state.userInfo);
//현재 경로를 알아내기 위해
const location=useLocation();
//action 을 발행하기 위해
const dispatch=useDispatch();
//만일 로그인 상태가 아니라면
if(!userInfo){
//원래 가려던 목적지 정보와 query 파라미터 정보를 읽어내서
const url = location.pathname + location.search;
//테스트로 출력해보기
console.log(url);
const payload={
show:true,
title:"해당 페이지는 로그인이 필요 합니다!"
}
//로그인창을 띄우는 action 을 발행하면서 payload 를 전달한다.
dispatch({type:"LOGIN_MODAL", payload});
// return null 하면 currentRoute 에 빈 페이지가 출력된다.
return null;
}
// 여기서 children 은 <ProtectedRoute> 컴포넌트의 자식 컴포넌트를 가리킨다
return children;
}
<MemberOnlyPage/> 를 <ProtectedRoute> 로 감싸주기<MemberOnlyPage/>) 는LoginModal 이 출력된다{path:"/member", element: <ProtectedRoute><MemberOnlyPage/></ProtectedRoute>}

try 문 안에 코드 추가 const url = loginModal.url;
navigate 함수 추가const navigate = useNavigate();
try 문 안에 if 문 추가 if(url){
navigate(url);
}
function Game() {
return <>
<h1>Game 페이지</h1>
<p>로그인된 회원만 게임 가능</p>
<NavLink to="/">인덱스로</NavLink>
</>
}
Game 라우트 경로 추가{path:"/game", element:<ProtectedRoute><Game/></ProtectedRoute>}
Game 링크 추가<NavLink to="/game">Game</NavLink>

spring09.filter 패키지 생성
@Component 어노테이션을 붙여서 bean 으로 만든다@Component
public class AuthFilter extends OncePerRequestFilter{
// 필터가 수행될 때 호출되는 메소드
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
// 요청의 흐름 걔속 이어가기
filterChain.doFilter(request, response);
}
}
onClick 이벤트 부여<h3>Authorization 을 추가한 요청</h3>
<button onClick={()=>{
axios.get("/api/v1/members", {
headers:{
Authorization:"token..."
}
})
.then(res => console.log(res.data))
.catch(err => console.log(err))
}}>요청 보내기</button>
Header 코드 추가String authHeader = request.getHeader("Authorization");
System.out.println(authHeader);

token...