-
카카오클라우드 - 클라우드 개발자 교육 2주차 후기bootcamp 2025. 11. 21. 10:35
리엑트 마지막 주 .
리엑트를 1주일만에 훑었다. 엄청나게 빠른 진도였다.
독학할때 리엑트를 조금씩 긴시간동안 공부했는데 이유는 나에게 너무 긴 러닝커브 때문이다.
html 태그조차 모르던 시기에 다짜고짜 리엑트 문법을 사용하려니 말이 안돼는것이다.
그 중 첫번째, useState를 가장 먼저 접했다.
#7일차
useState === 변수선언… 은 false다 그나마 useState == 변수선언?
useState는 변수 선언해 값을 변경해주는 기능이 있다.React 개념을 위한 첫 걸음이라 생각하는 아주 중요한 기능이다.
class MyComponent extends React.Component { constructor(props) { super(props); this.state = { count: 0, text: "hello" }; } handleClick = () => { this.setState({ count: this.state.count + 1 }); }; render() { return ( <div> <p>{this.state.count}</p> <button onClick={this.handleClick}>증가</button> </div> ); } }
이코드는 구버전 useState 문법이다. 처음 리엑트를 공부할때 이 높은 관문에 여러번 좌절한 경험이 있다.import { useState } from 'react'; import Result from './Result'; function Login() { const [showResult, setShowResult] = useState(false); const [data, setData] = useState({ id: '', password: '', checkedPassword: false, gender: '', mobile: '', region: '', hobby: [] }); const handleSubmit = () => { console.log(data); setShowResult(true); } const handleReset = () => { setData({ id: '', password: '', checkedPassword: false, gender: '', mobile: '', region: '', hobby: [] }); setShowResult(false); console.log(data); } return ( <div> <div> <h1>Login</h1> ID : <input type="text" value={data.id} onChange={e => setData({...data, id: e.target.value})} /><br /> </div> <div> Password : <input type="password" value={data.password} onChange={e => setData({...data, password: e.target.value})} /><br /> </div> <div> checkPassword : <input type="password" value={data.checkedPassword} onChange={e => setData({...data, checkedPassword: e.target.value})} /><br /> </div> <div> 성별 : <input type="radio" name="gender" value="M" onChange={e => setData({...data, gender: e.target.value})} />남자 <input type="radio" name="gender" value="F" onChange={e => setData({...data, gender: e.target.value})} />여자<br /> </div> <div> 전화번호 : <input type="text" value={data.mobile} onChange={e => setData({...data, mobile: e.target.value})} /> <br /> </div> <div> 지역 : <select value={data.region} onChange={e => setData({...data, region: e.target.value})}><br /> <option value="서울">서울</option> <option value="부산">부산</option> <option value="경기">경기</option> <option value="강원">강원</option> <option value="충남">충남</option> <option value="충북">충북</option> <option value="전남">전남</option> <option value="전북">전북</option> <option value="경남">경남</option> <option value="경북">경북</option> <option value="제주">제주</option> </select><br /> </div> <div> 취미 : <input type="checkbox" name="hobby" value="게임" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />게임 <input type="checkbox" name="hobby" value="영화" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />영화 <input type="checkbox" name="hobby" value="여행" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />여행 <input type="checkbox" name="hobby" value="캠핑" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />캠핑<br /> <input type="checkbox" name="hobby" value="맛집" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />맛집 <input type="checkbox" name="hobby" value="운동" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />운동 <input type="checkbox" name="hobby" value="독서" onChange={e => setData({...data, hobby: [...data.hobby, e.target.value]})} />독서<br /> </div> <div> <button onClick={handleSubmit}>확인</button> <button onClick={handleReset}>초기화</button> </div> {showResult && <Result data={data} />} </div> ) } export default Login;이번 학습시간에 useState로 로그인 화면을 만들었다.
useState 배열사용법
주말동안 특정 모듈의 의존성이 업데이트 되었나보다.
- styled-components → postcss@8.4.49
- vite → postcss@8.5.6
해당 문제로 vite 개발서버가 실행되지 않았다.
event 사용법
#8일차useEffect
class MyComponent extends React.Component { componentDidUpdate(prevProps, prevState) { if (prevState.count !== this.state.count) { console.log("count 변경됨"); } } }구버전 문법이다. 가물가물한 문법인데 여기서 리엑트를 손놓았던 경험이 많다.
useEffect 안에선 state 변경하지말것.
react css
useMemoreact route
<BrouserRoute>리엑트는 SPA.
단일 페이지 애플리케이션이다.
이 장점이자 단점을 보완하기위한 라우트 기능이다.
쿼리 스트링(Query String)의 장단점.- 장점
1. 간단하고 빠르게 구현 가능
- 서버·클라이언트 모두 특별한 설정 없이 쉽게 파라미터를 전달할 수 있어.
- 기본적으로 HTTP GET 요청에 적합하여 캐시도 자연스럽게 활용 가능.
2. 가독성이 좋음
- URL만 봐도 어떤 조건으로 검색했는지 바로 이해됨.
- 예: /search?q=react&sort=date → 어떤 검색인지 즉시 파악 가능.
3. 북마크 공유 가능
- 검색 조건, 필터 상태 등을 그대로 URL로 저장해 두고 나중에 공유 가능.
- 예: React 개발 시 상태를 URL로 동기화하면 새로고침해도 유지됨.
4. 캐싱 용이
- GET + Query String 조합은 CDN, Reverse Proxy(Nginx), Browser 캐시가 자동 작동.
- 데이터 조회가 많은 서비스에서 성능 최적화 가능.
- 단점
1. 보안 취약점
- URL에 그대로 노출되므로 JWT, 비밀번호, 주민번호처럼 민감한 정보 절대 넣으면 안 됨.
- 서버 로그·브라우저 히스토리·프록시 서버에 그대로 기록됨.
2. 길이 제한
- 브라우저와 서버 환경에 따라 수천자~수만자 제한이 존재.
- JSON 데이터를 파라미터로 넣는 복잡한 요청엔 부적합.
3. 복잡한 데이터 표현 어려움
- 배열, 중첩 객체, JSON 구조를 표현하려면 변환이 필요.
- 예: tags=["A","B"] → tags=A&tags=B 형태로 강제 변형해야 함.
4. REST 설계 원칙을 망가뜨릴 위험
- 조회(READ)에는 적합하지만, POST/PUT 같은 상태 변경 요청에 사용하면 혼동 발생.
- 예: 잘못된 설계 → /api/register?name=홍길동&age=20
import {useSearchParams} from 'react-router-dom'; function Person() { const [searchParams, setSearchParams] = useSearchParams(); const name = searchParams.get('name'); const job = searchParams.get('job'); const age = searchParams.get('age'); const city = searchParams.get('city'); return ( <div> <h1>Person</h1> <p>Name : {name}</p> <p>job : {job}</p> <p>Age : {age}</p> <p>City : {city}</p> </div> ) } export default Person;
REST API의 장단점- 장점
1. 표준화된 방식으로 설계가 명확해짐
- URI는 명사, HTTP Method는 동사 역할을 하므로 API 문서 구조가 깔끔함.
- 예:
- 조회: GET /api/hospital/10
- 생성: POST /api/hospital
- 수정: PUT /api/hospital/10
- 삭제: DELETE /api/hospital/10
2. 클라이언트-서버 완전 분리
- Spring Boot, Nest.js, Django 등 백엔드와
React/React Native/Next.js 프론트가 독립적으로 개발 가능.
3. HTTP 구조 덕분에 확장성과 유지보수성 우수
- Stateless → 서버 확장(EKS, Docker Scale-out)이 쉬움.
- Load Balancer, Proxy, Cache 등 표준 인프라 활용 가능.
4. 언어·플랫폼 독립
- 어떤 앱이든 HTTP 요청만 가능하면 API 사용 가능 → iOS/Android/웹 모두 사용 가능.
5. 캐싱 전략 활용 가능
- GET 요청에 대해서는 CDN, Cache-Control을 통한 최적화가 용이.
- 단점
1. 복잡한 실시간 통신에 취약
- REST는 request/response 구조라 실시간 업데이트·푸시 알림·스트리밍엔 부적합.
- 그래서 SSE, WebSocket, gRPC를 사용하게 됨.
- 형주의 Garabu 프로젝트에서 SSE, Redis Pub/Sub, gRPC를 사용하는 이유가 이것.
2. 오버헤드가 크고 비효율적일 수 있음
- HTTP Header가 무거움 → 모바일 환경에서는 비효율.
- gRPC 대비 10%~60%까지 성능이 떨어지는 경우 있음.
3. 버전 관리가 번거로울 수 있음
- /api/v1, /api/v2 이런 식으로 URI가 복잡해짐.
- GraphQL은 이런 문제를 해결하려고 등장한 방식.
4. 복잡한 응답 구조 관리가 어려움
- 클라이언트가 필요한 데이터만 골라 받기 힘듦 → 오버페칭/언더페칭 문제 발생.
- GraphQL 도입 이유 중 하나.
5. 엄격한 스펙이 없음
- Roy Fielding 논문에 기반한 “아키텍처 스타일”일 뿐
공식 표준이 아니라서 팀마다 설계 방식이 달라질 수 있음.
#9일차
route path
Link
outlet
Spa에서 새로고침 없이 화면의 데이터를 갱신할수 있는 방법이다.
context provider, consumer
context
#10일차
redux, Zustand상태관리의 개념이다.
내 프로덕션 환경에서 특정 상태를 체크해 유지 할 수 있게 도와주는 라이브러리다.
리덕스 Zustand의 개념에 대해
두 라이브러리는 단순히 “글로벌 상태 저장을 한다”는 측면은 같지만, 내부 구조·패러다임·사용 난이도·코드 양·미들웨어 구성 방식까지 전혀 다르다.
Redux 개념 — 규칙 기반의 예측 가능한 중앙 상태 관리
Redux는 전역 상태를 단일 Store에서 관리하고, 액션 → 리듀서 → 새로운 상태라는 매우 엄격한 흐름을 가진다. 이 구조는 복잡한 애플리케이션(대규모 팀 개발, 상태 추적이 중요한 서비스)에 큰 장점이 있다.
- Single Store
- 전역 상태는 하나의 Store에서 관리.
- 어디에서 어떤 상태가 바뀌는지 명확하게 추적 가능.
- Immutable Update(불변성 유지)
- 상태는 직접 수정하지 않고 항상 새로운 객체를 반환.
- 디버깅, 타임트래블, undo/redo가 가능.
- Action
- 상태 변경을 요청하는 오브젝트.
- 예: { type: "LOGIN_SUCCESS", payload: {...} }
- Reducer
- 액션을 받아 새로운 상태를 반환하는 순수 함수.
- 외부 비동기나 부작용 없음.
- Middleware
- Redux Thunk, Saga, RTK Query 등으로 비동기 로직 처리 가능.
- 구조적 분리가 잘돼 있어 확장성 높음.
- Redux Toolkit(RTK)
- 현대 Redux 개발 방식.
- 보일러플레이트를 대폭 줄여 사용성이 좋아짐.
Zustand 개념 — 가볍고 빠른 미니멀리즘 상태관리
React Hooks 기반의 간단하고 유연한 상태 관리 라이브러리다. Redux처럼 엄격한 규칙이 없고, 단순히 create()로 store를 만들고 hook처럼 가져다 쓰면 된다.
- Store 하나 또는 여러 개 자유롭게 사용
- Redux처럼 강제된 구조 없음.
- 필요하면 여러 개 만들고, 모듈 형태로 분리.
- Mutable Update 허용(옵션)
- Immer를 내장하고 있어 불변성을 쉽게 유지할 수 있음.
- 필요 시 mutable 방식도 가능(퍼포먼스 유리).
- Selector 기반 최적화
- 특정 상태만 구독하게 해서 리렌더 최소화(성능 ↑).
- 미들웨어 지원
- persist(스토리지 저장), devtools, immer 등 플러그인 방식.
- Redux Devtools도 사용 가능.
- 가볍고 빠른 구조
- 보일러플레이트 최소화.
- Swift하게 개발할 때 적합.
용어 정리.
보일러 플레이트란?
보일러플레이트는 애플리케이션을 만들 때 반복적으로 등장하는 필수 코드 덩어리.
이 코드를 개발자가 직접 매번 작성해야 하며, 대부분 구조가 고정되어 있고 특별한 창의성이나 비즈니스 로직이 필요한 부분이 아니다.
Redux를 써야 하는 경우
- MSA 구조처럼 여러 모듈에서 공통 상태를 엄격하게 관리해야 할 때
- 상태가 복잡하고 의존성이 높아 추적이 중요한 경우
- 팀원 수가 많고 “표준화된 규칙”이 필요한 경우
- Devtools로 액션 흐름 분석이 필수인 서비스
- 로그인/인증/권한 시스템 등 변경이 복잡한 경우
Zustand를 써야 하는 경우
- 페이지 단위의 지역 상태 전역화 정도로 충분할 때
- 속도·간결함·가벼움이 중요할 때
- React Native 앱처럼 리렌더 비용을 줄여야 하는 환경
- 팀 규모가 작고 빠른 개발이 필요한 스타트업 상황
- “그냥 store 하나 만들고 값 관리만 하면 끝”
실제 사용 예)
- Zustand (주로 최신 기능)
Zustand는 대부분의 상태 관리에 사용함
- Redux 사용 (레거시 및 특정 기능)
Redux는 다음 영역에서 사용됩니다:
- Redux Store 설정
- @reduxjs/toolkit의 configureStore 사용
- 여러 슬라이스를 combineReducers로 통합
- redux-persist로 세션 스토리지에 일부 상태 저장
2. 로그인 상태 슬라이스
- 로그인 성공/실패, 토큰 갱신
한줄 :
- Zustand: 최신 기능의 주요 상태 관리
- Redux: 레거시 코드와 일부 특정 기능(로그인 상태, 거래 폼의 비동기 작업)
실제 사용의 예는 정말 실제로 사용하여 서비스를 운영중이라 자세한 내용을 담지 못했다.
실습은 "상태관리"의 맥락, 개념을 잡기 위해 context API로 진행.
#11일차
리엑트로 게시판 만들기.
지금 까지 배웠던거 원기옥.fast API로 백단 구현해서 붙일 예정.
// Login.jsx import { useState } from 'react'; import { useNavigate, Link } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import '../styles/Login.css'; function Login() { const [id, setId] = useState(''); const [password, setPassword] = useState(''); const [error, setError] = useState(''); const { login } = useAuth(); const navigate = useNavigate(); const handleSubmit = (e) => { e.preventDefault(); setError(''); if (!id || !password) { setError('ID와 비밀번호를 입력해주세요.'); return; } if (login(id, password)) { navigate('/'); } else { setError('ID 또는 비밀번호가 올바르지 않습니다.'); } }; return ( <div className="login-container"> <div className="login-box"> <h1>로그인</h1> <form onSubmit={handleSubmit}> <div className="form-group"> <label htmlFor="id">ID</label> <input type="text" id="id" value={id} onChange={(e) => setId(e.target.value)} placeholder="ID를 입력하세요" /> </div> <div className="form-group"> <label htmlFor="password">비밀번호</label> <input type="password" id="password" value={password} onChange={(e) => setPassword(e.target.value)} placeholder="비밀번호를 입력하세요" /> </div> {error && <div className="error-message">{error}</div>} <button type="submit" className="submit-btn">로그인</button> </form> <div className="signup-link"> 계정이 없으신가요? <Link to="/signup">회원가입</Link> </div> </div> </div> ); } export default Login;//Write.jsx import { useState } from 'react'; import { useNavigate } from 'react-router-dom'; import { useAuth } from '../context/AuthContext'; import '../styles/Write.css'; function Write() { const [title, setTitle] = useState(''); const [content, setContent] = useState(''); const { user, addPost } = useAuth(); const navigate = useNavigate(); const handleSubmit = (e) => { e.preventDefault(); if (!title.trim() || !content.trim()) { alert('제목과 내용을 입력해주세요.'); return; } addPost({ title, content }); alert('게시글이 작성되었습니다.'); navigate('/'); }; const handleCancel = () => { if (window.confirm('작성을 취소하시겠습니까?')) { navigate('/'); } }; if (!user) { return ( <div className="write-container"> <div className="not-logged-in"> <p>로그인이 필요합니다.</p> </div> </div> ); } return ( <div className="write-container"> <h1>글쓰기</h1> <form onSubmit={handleSubmit} className="write-form"> <div className="form-group"> <label htmlFor="title">제목</label> <input type="text" id="title" value={title} onChange={(e) => setTitle(e.target.value)} placeholder="제목을 입력하세요" /> </div> <div className="form-group"> <label htmlFor="content">내용</label> <textarea id="content" value={content} onChange={(e) => setContent(e.target.value)} placeholder="내용을 입력하세요" rows="15" /> </div> <div className="button-group"> <button type="submit" className="submit-btn">작성</button> <button type="button" onClick={handleCancel} className="cancel-btn">취소</button> </div> </form> </div> ); } export default Write;// AuthContext.jsx import { createContext, useContext, useState, useCallback, useRef } from 'react'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); // 기본 admin 계정 포함 const [users, setUsers] = useState([ { id: 'admin', password: 'wjsansrk', name: '관리자', email: '' } ]); // 회원 목록 const [posts, setPosts] = useState([]); // 게시글 목록 const postIdRef = useRef(1); // 게시글 ID 관리 (ref 사용) const login = (id, password) => { // 기본 admin 계정 체크 if (id === 'admin' && password === 'wjsansrk') { setUser({ id: 'admin', password: 'wjsansrk', name: '관리자', email: '' }); return true; } // 일반 사용자 체크 const foundUser = users.find(u => u.id === id && u.password === password); if (foundUser) { setUser(foundUser); return true; } return false; }; const logout = () => { setUser(null); }; const signup = (userData) => { const existingUser = users.find(u => u.id === userData.id); if (existingUser) { return false; // 이미 존재하는 ID } setUsers([...users, { ...userData }]); return true; }; const addPost = useCallback((postData) => { if (!user) { console.error('User is not logged in'); return null; } const newPostId = postIdRef.current; postIdRef.current += 1; const newPost = { id: newPostId, ...postData, author: user.name || user.id, viewCount: 0, likeCount: 0, createdAt: new Date().toISOString() }; setPosts(prevPosts => [newPost, ...prevPosts]); return newPost; }, [user]); const updatePost = (id, updatedData) => { setPosts(posts.map(post => post.id === id ? { ...post, ...updatedData } : post )); }; const deletePost = (id) => { setPosts(posts.filter(post => post.id !== id)); }; const incrementViewCount = (id) => { setPosts(posts.map(post => post.id === id ? { ...post, viewCount: post.viewCount + 1 } : post )); }; const incrementLikeCount = (id) => { setPosts(posts.map(post => post.id === id ? { ...post, likeCount: post.likeCount + 1 } : post )); }; return ( <AuthContext.Provider value={{ user, users, posts, login, logout, signup, addPost, updatePost, deletePost, incrementViewCount, incrementLikeCount }}> {children} </AuthContext.Provider> ); } export function useAuth() { return useContext(AuthContext); }간단한 코드를 작성해 게시판을 만들어보았다.
스타일은 채찍피티의 작품. 만족스러운 결과다.
하지만 어디서 많이 본디자인이다. 기존에 회사에서 쓰던 css스타일을 학습했나보다;;
——————————————————————————
본 후기는 [카카오엔터프라이즈x스나이퍼팩토리] 카카오클라우드로 배우는 AIaaS 마스터 클래스 3기(B-log) 리뷰로 작성 되었습니다.'bootcamp' 카테고리의 다른 글
카카오클라우드 - 클라우드 개발자 교육 7주차 후기 (0) 2025.12.31 카카오클라우드 - 클라우드 개발자 교육 6주차 후기 (0) 2025.12.19 카카오클라우드 - 클라우드 개발자 교육 5주차 후기 (0) 2025.12.12 카카오클라우드 - 클라우드 개발자 교육 3주차 후기 (0) 2025.11.28 카카오클라우드 - 클라우드 개발자 교육 1주차 후기 (0) 2025.11.14