LogoSEO Jing
  • All Posts
  • SEO Jing
  • okayJing
  • KD Team
  • CLAB Coreteam
  • Study

Contact Me

© 2026 SEOJing. All rights reserved.

프론트엔드 스터디 대면 7주차: React 입문과 프로젝트 구조

2026년 6월 28일·12분 읽기
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
js
const button = document.querySelector("#submit");
const countEl = document.querySelector("#count");
let n = 0;

button.addEventListener("click", () => {
  n++;
  countEl.textContent = n;
  if (n >= 10) button.disabled = true;
});
Paragraph fallback
Paragraph component omitted

상태(n)가 바뀔 때마다 영향받는 DOM 요소를 <strong>직접 찾아서</strong> 바꿔야

합니다.

같은 UI 조각을 여러 곳에 쓰려면 코드를 복사해야 합니다. 상태가 어디서 어떻게 바뀌는지 코드를 전부 읽어야만 알 수 있습니다.

Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
js
// 명령형: 어떻게 바꿀지 직접 지시
countEl.textContent = n;
button.disabled = n >= 10;
badge.classList.toggle("hidden", n === 0);
Paragraph fallback
Paragraph component omitted
tsx
// 선언형: 상태에 따라 화면이 어떻게 보여야 하는지 작성
function Counter() {
  const [n, setN] = useState(0);

  return (
    <div>
      {n > 0 && <Badge count={n} />}
      <p>{n}</p>
      <button onClick={() => setN(n + 1)} disabled={n >= 10}>
        +1
      </button>
    </div>
  );
}
Paragraph fallback
Paragraph component omitted
명령형은 "어떻게 바꿀지"를 써야 하고, 선언형은 "어떻게 보여야 하는지"를 씁니다.

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
tsx
function Button({ label, onClick, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled}>
      {label}
    </button>
  );
}
Paragraph fallback
Paragraph component omitted
tsx
<Button label="신청하기" onClick={handleApply} disabled={!canApply} />
Paragraph fallback
Paragraph component omitted

<strong>재사용</strong> — 버튼, 카드, 입력창 같은 UI 조각을 여러 화면에서 꺼내

씁니다.

<strong>조합</strong> — 작은 컴포넌트를 쌓아서 더 큰 화면을 만듭니다. <strong>책임 분리</strong> — 버튼은 버튼처럼 생긴 것만 신경 씁니다. 나머지는

다른 곳에서 담당합니다.

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
tsx
function StudyPage() {
  return (
    <Layout>
      <Header title="스터디 목록" />
      <StudyList>
        <StudyCard title="React 스터디" status="OPEN" />
        <StudyCard title="알고리즘 스터디" status="CLOSED" />
      </StudyList>
    </Layout>
  );
}

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
tsx
function Counter() {
  const [count, setCount] = useState(0);

  return (
    <div>
      <p>{count}</p>
      <button onClick={() => setCount(count + 1)}>+1</button>
    </div>
  );
}
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted

전체를 다시 그리지 않고, 변경된 부분만 업데이트합니다. DOM 조작은 비용이 비쌉니다. 필요한 만큼만 건드리는 것이 빠릅니다. 개발자는 이 과정을 직접 제어하지 않아도 됩니다. React가 처리합니다.

Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Paragraph fallback
Paragraph component omitted
tsx
function ApplyButton() {
  // UI도 있고, 서버 요청도 있고, 권한 규칙도 있고, 상태 변경도 있음
  // 버튼 하나를 고치려 했는데 서비스 전체 규칙을 건드리게 됩니다.
}
Paragraph fallback
Paragraph component omitted
코드 양이 아니라, 책임이 섞일 때 프로젝트가 터집니다.

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted

<strong>UI</strong> — 무엇을 어떻게 보여줄 것인가 <strong>상태</strong> — 지금 화면이나 앱이 어떤 상태인가 <strong>서버 데이터</strong> — 서버에서 무엇을 가져오고, 어떻게 캐시하고, 언제

다시 가져올 것인가

<strong>비즈니스 규칙</strong> — 우리 서비스에서만 성립하는 판단 기준은

무엇인가

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function StudyApplyButton({ study, user }) {
  const disabled =
    !user ||
    user.role !== "member" ||
    study.status !== "OPEN" ||
    study.appliedUserIds.includes(user.id) ||
    study.currentCount >= study.maxCount;

  return (
    <button
      disabled={disabled}
      className={disabled ? "bg-gray-300" : "bg-blue-500"}
    >
      신청하기
    </button>
  );
}
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function StudyApplyButton({ disabled }: { disabled: boolean }) {
  return (
    <button
      disabled={disabled}
      className={disabled ? "bg-gray-300" : "bg-blue-500"}
    >
      신청하기
    </button>
  );
}

const disabled = !canApplyStudy({ study, user });

<StudyApplyButton disabled={disabled} />;
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function MemberPage() {
  const [members, setMembers] = useState([]);
  const [selectedId, setSelectedId] = useState(null);

  useEffect(() => {
    fetch("/api/members")
      .then((res) => res.json())
      .then(setMembers);
  }, []);

  const selectedMember = members.find((member) => member.id === selectedId);

  return <MemberProfile member={selectedMember} />;
}
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function MemberPage() {
  const [selectedId, setSelectedId] = useState<string | null>(null);
  const { data: members = [] } = useMembersQuery();

  const selectedMember = members.find((member) => member.id === selectedId);

  return <MemberProfile member={selectedMember} onSelect={setSelectedId} />;
}
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function NoticeList() {
  const [notices, setNotices] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);

  useEffect(() => {
    setLoading(true);
    fetch("/api/notices")
      .then((res) => res.json())
      .then(setNotices)
      .catch(setError)
      .finally(() => setLoading(false));
  }, []);

  // ...
}
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function NoticeList() {
  const { data: notices = [], isLoading, error } = useNoticesQuery();

  if (isLoading) return <NoticeSkeleton />;
  if (error) return <ErrorMessage />;

  return <NoticeCards notices={notices} />;
}
tsx
function useNoticesQuery() {
  return useQuery({
    queryKey: ["notices"],
    queryFn: () => noticeApi.getNotices(),
  });
}
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
tsx
function RecruitmentCard({ recruitment, user }) {
  return (
    <Card>
      {recruitment.status === "OPEN" &&
        user?.role === "member" &&
        !recruitment.applicants.includes(user.id) && <button>지원하기</button>}
    </Card>
  );
}
Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted
ts
export function canApplyRecruitment({ recruitment, user }) {
  if (!user) return false;
  if (user.role !== "member") return false;
  if (recruitment.status !== "OPEN") return false;
  if (recruitment.applicants.includes(user.id)) return false;

  return true;
}
tsx
function RecruitmentCard({ recruitment, user }) {
  const canApply = canApplyRecruitment({ recruitment, user });

  return <Card>{canApply && <button>지원하기</button>}</Card>;
}
Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
구조한 줄 정의어울리는 상황
flat파일을 얕게 나열하는 구조작은 과제, 페이지 수가 적은 토이 프로젝트
feature기능 단위로 관련 파일을 묶는 구조도메인/기능이 분명한 서비스형 프로젝트
layeredcomponents, hooks, api, utils처럼 기술 계층별로 나누는 구조초반 학습용, 팀원이 구조를 빨리 이해해야 하는 프로젝트
atomicatoms/molecules/organisms처럼 UI 조립 단위로 나누는 구조디자인 시스템, 공통 UI 컴포넌트가 중요한 프로젝트
FSDapp/pages/widgets/features/entities/shared로 책임 레벨을 나누는 구조큰 규모, 여러 기능 팀이 같이 만지는 프로젝트
Paragraph fallback
Paragraph component omitted
txt
apps/member/src
├─ api
│  ├─ member
│  ├─ community
│  ├─ recruitment
│  └─ auth
├─ components
│  ├─ home
│  ├─ community
│  ├─ activity
│  ├─ library
│  └─ common
├─ pages
│  ├─ home
│  ├─ community
│  ├─ activity
│  └─ my
├─ app
│  ├─ layout
│  └─ route
├─ hooks
├─ model
├─ types
└─ utils

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
json
{
  "dependencies": {
    "@tanstack/react-query": "^5.90.21",
    "jotai": "^2.18.0",
    "ky": "^1.7.3",
    "react": "^19.2.0",
    "react-router": "^7.1.5",
    "tailwindcss": "^4.1.18",
    "zustand": "^5.0.11"
  }
}
영역왜 필요하나CLAB member app
패키지 매니저의존성 설치와 버전 고정pnpm
빌드개발 코드를 브라우저용 결과물로 변환Vite
모노레포여러 앱/패키지를 한 저장소에서 관리Turbo + workspace
언어타입으로 실수를 미리 잡음TypeScript
스타일링UI를 일관되게 표현Tailwind CSS v4
HTTP서버와 통신ky
서버 상태서버 데이터 캐싱·로딩·에러 관리React Query
전역 상태여러 화면이 공유하는 클라이언트 상태 관리Jotai + Zustand
라우팅URL과 화면을 연결React Router
린트/포맷코드 스타일과 실수를 자동 점검ESLint + Prettier
테스트변경 후 기존 동작 확인Vitest + Playwright
Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted

6장의 Bad 코드 중 하나를 고릅니다. 이 코드 안에 섞여 있는 책임을 표시합니다.

UI 화면 상태 서버 데이터 비즈니스 규칙

최소 2개 이상의 책임을 분리합니다. 특히 <strong>서버 상태 vs 화면 상태</strong>를 구분해봅니다. 리팩터 후 "바뀌기 쉬운 지점"이 어디로 이동했는지 설명합니다.

Subtitle fallback
Subtitle component omitted
tsx
function StudyPage() {
  const [studies, setStudies] = useState([]);
  const [selectedCategory, setSelectedCategory] = useState("ALL");

  useEffect(() => {
    fetch("/api/studies")
      .then((res) => res.json())
      .then(setStudies);
  }, []);

  const visibleStudies = studies.filter((study) => {
    if (selectedCategory !== "ALL" && study.category !== selectedCategory)
      return false;
    if (study.status !== "OPEN") return false;
    return true;
  });

  return (
    <div>
      <CategoryTabs value={selectedCategory} onChange={setSelectedCategory} />
      {visibleStudies.map((study) => (
        <StudyCard key={study.id} study={study} />
      ))}
    </div>
  );
}
Paragraph fallback
Paragraph component omitted

useStudiesQuery() — 서버 데이터 책임 selectedCategory — 화면 상태 책임 getVisibleStudies() — 비즈니스/필터 규칙 책임 StudyCard, CategoryTabs — UI 책임

Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted

React를 기반으로, 라우팅·빌드·배포·렌더링 방식까지 미리 구성해줍니다. 페이지를 <strong>서버에서 렌더할지, 클라이언트에서 렌더할지</strong> 선택할 수

있습니다.

지금 이 사이트도 Next.js로 만들어져 있습니다.

Paragraph fallback
Paragraph component omitted

---

Subtitle fallback
Subtitle component omitted
Paragraph fallback
Paragraph component omitted
Paragraph fallback
Paragraph component omitted
Subtitle fallback
Subtitle component omitted

다음 주 학습 자료(week8)의

API/비동기 관련 구간 읽기

콘솔에서 fetch("https://jsonplaceholder.typicode.com/todos/1")를 실행해보고

Promise가 어떻게 보이는지 확인하기

오늘 자료에서 <strong>서버 상태와 화면 상태</strong>를 구분한 예시를 하나 다시

읽어오기

Subtitle fallback
Subtitle component omitted

이번 주 학습 자료(week7) — 불변성, 프로토타입, 타입 체크

다음 주 학습 자료(week8) — API와 통신

Post Q&A

오케이징에게 물어보기

프론트엔드 스터디 대면 7주차: React 입문과 프로젝트 구조 전체를 기준으로 질문과 피드백을 받아요.답을 본 뒤에는 이 내용을 댓글로 달아서 서징에게도 물어볼 수 있어요. 작성자가 직접 볼 수 있어요!

0/500

포스트 목록

/study/clab-26-1/in-person
파일 9개, 폴더 0개
프론트엔드 스터디 대면 0주차: 프론트엔드 개발자란? 그리고 우리가 배울 것들프론트엔드 스터디 대면 1주차: HTML 마크업과 폼, 그리고 CSS의 시작프론트엔드 스터디 대면 2주차: 폼(Form), CSS 선택자, 그리고 박스 모델프론트엔드 스터디 대면 3주차: 박스 모델 실전, Position과 Flexbox프론트엔드 스터디 대면 6주차(1): HTML/CSS/JS 리마인드와 브라우저 렌더링프론트엔드 스터디 대면 6주차(2): AI 시대의 개발 방식과 프론트엔드 개발자의 위치프론트엔드 스터디 대면 7주차: React 입문과 프로젝트 구조프론트엔드 스터디 대면 8주차: API와 통신 — 프론트엔드와 백엔드의 계약프론트엔드 스터디 대면 9주차: Next.js 렌더링 진화와 웹 퍼포먼스

같은 섹션의 대표 이미지

9 posts · latest first
Study26. 05. 25.

프론트엔드 스터디 대면 9주차: Next.js 렌더링 진화와 웹.

Next.js가 무엇이고 왜 필요한지 React와 비교해 이해한 뒤, CSR부터 SSR, SSG, ISR, PPR까지 렌더링 전략과 Web Vitals를 정리하며 스터디를 마무리합니다.

26. 05. 25.SEOJing
Study26. 05. 18.

프론트엔드 스터디 대면 8주차: API와 통신 — 프론트엔드와.

비동기와 async/await를 배운 뒤, 대면에서는 API를 단순 호출 방법이 아니라 프론트엔드와 백엔드 사이의 계약으로 바라봅니다. HTTP, REST, CORS, 프록시, API 클라이언트 구조, TanStack Query와 파일 배치까지 연결합니다.

26. 05. 18.SEOJing
Study26. 05. 11.

프론트엔드 스터디 대면 7주차: React 입문과 프로젝트 구조.

바닐라 JS의 한계에서 React가 등장한 이유까지, 컴포넌트와 렌더링 방식을 이해하고 프로젝트 구조 감각을 만들어가는 대면입니다.

26. 05. 11.SEOJing
Study26. 05. 04.

프론트엔드 스터디 대면 6주차(1): HTML/CSS/JS.

4~6주차 공백 이후 진행하는 첫 번째 대면입니다. HTML/CSS/JS 핵심을 가볍게 다시 연결하고, 브라우저가 코드를 실제 화면으로 바꾸는 과정을 통해 렌더링 파이프라인과 성능 감각을 잡습니다.

26. 05. 04.SEOJing
Study26. 05. 04.

프론트엔드 스터디 대면 6주차(2): AI 시대의 개발 방식과.

길어진 6주차 대면의 두 번째 차시입니다. AI 시대에 개발자가 실제로 어떻게 일하는지, 그리고 프론트엔드 개발자가 디자이너·백엔드·PM 사이에서 어떤 연결과 리딩 역할을 하는지 다룹니다.

26. 05. 04.SEOJing
Study26. 04. 10.

프론트엔드 스터디 대면 3주차: 박스 모델 실전,.

3주차 박스 모델과 스타일링 복습, 과제 풀이, 4주차 Position과 Flexbox 레이아웃까지 다룬 대면 스터디 정리입니다. 면접에서 자주 나오는 마진 겹침, box-sizing, Flexbox 관련 질문도 함께 준비합니다.

26. 04. 10.SEOJing
Study26. 04. 03.

프론트엔드 스터디 대면 2주차: 폼(Form), CSS.

2주차 폼과 CSS 선택자 복습, 과제 풀이, 3주차 박스 모델과 스타일링까지 다룬 대면 스터디 정리입니다. 면접에서 자주 나오는 GET vs POST, CSS 우선순위, 박스 모델 관련 질문도 함께 준비합니다.

26. 04. 03.SEOJing
Study26. 03. 27.

프론트엔드 스터디 대면 1주차: HTML 마크업과 폼, 그리고.

1주차 HTML 핵심 복습과 과제 풀이, 2주차 폼(Form)과 CSS 선택자까지 다룬 대면 스터디 정리입니다. 면접에서 자주 나오는 시맨틱 HTML, img alt 속성 관련 질문도 함께 준비합니다.

26. 03. 27.SEOJing
Study26. 03. 20.

프론트엔드 스터디 대면 0주차: 프론트엔드 개발자란? 그리고.

프론트엔드 개발자가 실무에서 하는 일, 2025-2026 기술 스택, 현실적인 연봉과 채용 트렌드를 정리했습니다. 스터디 자료 구조와 앞으로의 방향도 함께 안내합니다.

26. 03. 20.SEOJing