진짜진짜진짜로다가 숙련주차 강의정리 끝내는 날! (몇일... 걸렸지)
내일은.. 챌린지 과제 정리할 거 같고...
과제 요약하면 끝나지 않을까.. 싶네요
와 이정도면 너무 알차게 사는 림졍이 아닐까... ^-^)b...
오늘도 시작해볼까요?
React (리액트) 강의 정리 - 숙련 ver. (이이이어이이서)
드디어 강의정리 끝! 그리고 알다가도 모르겠는 수-빠 베이수.
React Router Dom
리액트 프로젝트에서 라우팅을 가능하게 하는 라이브러리.
리액트의 특징인 SPA 에서도 여러 페이지를 오가며 보이게 해줌.
- 주요 기능 및 개념
- 싱글 페이지 애플리케이션(SPA) 지원
- 하나의 HTML 파일로 구성된 SPA에서 여러 경로(route) 설정, 경로에 따른 컴포넌트 동적 렌더링이 가능함
- 브라우저 라우팅과 해시 라우팅
- BrowserRouter: HTML5의 히스토리 API를 사용해 URL을 관리, 주로 사용되는 라우터.
- HashRouter: URL에 #을 포함해 경로를 관리, 서버 설정이 불가능할 때 사용.
- 경로 설정과 컴포넌트 매칭
- <Route> : 컴포넌트를 사용, URL 경로 및 해당 경로에서 렌더링할 컴포넌트 지정.
- <Switch> : Route 목록 순서대로 검사 후, 가장 먼저 일치하는 하나의 경로만 렌더링함.
- Link와 NavLink
- <Link> : 페이지 간 이동을 위한 컴포넌트, HTML의 <a> 태그와 유사하게 동작하나, 새로고침 없이 경로를 변경
- <NavLink>: <Link>와 유사, but 현재 활성화된 경로를 표시하는 추가 스타일을 제공.
- useNavigate와 useParams
- useNavigate: 프로그래밍적으로 경로 변경 시 사용. 함수 호출 시 특정 경로로 이동 가능.
- useParams: URL 파라미터 받아올 수 있음, 동적 라우팅 구현할 때 유용.
- 패키지 설치하기
VsCode 터미널에서 패키지를 설치합시당
yarn add react-router-dom
- 사용방법 순서
- 페이지 컴포넌트 생성
- Router.js에 생성 및 router 설정 코드 작성
- App.js 에 import 및 적용
- 페이지 이동 테스트
1) 페이지 컴포넌트 생성
예시로 Home, About, Contact, Works 4개의 컴포넌트 만들어보기
src/pages/(컴포넌트.jsx, 이곳에 생성)
2) Router.js 생성 및 설정 코드 작성 (중요!)
→ 브라우저에 우리가 URL을 입력하고 이동했을 때 우리가 원하는 페이지 컴포넌트로 이동하게끔 만드는 부분
- src/shared/Router.js 생성 후 아래 코드 작성
import React from "react";
// 1. react-router-dom을 사용하기 위해서 아래 API들을 import
import { BrowserRouter, Route, Routes } from "react-router-dom";
// 2. Router 라는 함수를 만들고 아래와 같이 작성
// BrowserRouter를 Router로 감싸는 이유?
// SPA의 장점인 브라우저가 깜빡이지 않고 다른 페이지로 이동할 수 있게 만들어주기 위함!
const Router = () => {
return (
<BrowserRouter>
<Routes>
</Routes>
</BrowserRouter>
);
};
export default Router;
- 4개의 컴포넌트에 각각의 Route 설정하기 (아래 코드 참고)
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
const Router = () => {
return (
<BrowserRouter>
<Routes>
{/*
Routes안에 이렇게 작성합니다.
Route에는 react-router-dom에서 지원하는 props들이 있습니다.
path는 우리가 흔히 말하는 사용하고싶은 "주소"를 넣어주면 됩니다.
element는 해당 주소로 이동했을 때 보여주고자 하는 컴포넌트를 넣어줍니다.
*/}
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
3) Router.js, App.js 에 import 및 적용
// App 컴포넌트에 넣어주는 이유? 가장 최상위에 존재하는 컴포넌트 = App.js 라서
import React from "react";
import Router from "./shared/Router";
function App() {
return <Router />;
}
export default App;
App.js → Router.js 거치도록 코드를 구현한 이유?
우리가 어떤 컴포넌트를 화면에 띄우던, 항상 App.js를 거쳐야 함 → ∴ Router.js, App.js 내부에 위치
React Router Dom Hooks
react-router-dom에서 유용하게 사용할 수 있도록 제공하는 hook
1) useNavigate
버튼 클릭 or 컴포넌트 입력 시 페이지로 이동하게 만들어줄 때 사용되는 훅
예시) 버튼 클릭 시, 보내고자 하는 path로 페이지를 이동시킬 수 있도록 도와줌.
→ navigate 를 생성, navigate(’보내고자 하는 url’) 을 통해 페이지를 이동이 가능하다!
// src/pages/home.js
import { useNavigate } from "react-router-dom";
const Home = () => {
const navigate = useNavigate();
return (
<button
onClick={() => {
navigate("/works");
}}
>
works로 이동
</button>
);
};
export default Home;
2) useLocation
현재 위치하고 있는 페이지의 여러 정보를 추가적으로 얻을 수 있는 훅.
정보를 통해 페이지 안에서 조건부 렌더링에 사용하는 등, 여러가지 용도로 활용이 가능
// src/pages/works.js
import { useLocation } from "react-router-dom";
const Works = () => {
const location = useLocation();
console.log("location :>> ", location);
return (
<div>
<div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
</div>
);
};
export default Works;
3) Link
Link 는 훅 X , but 꼭 알아야 할 API
Link 는 html 태그 중 <a> 기능을 대체하는 API ( 페이지 간 이동을 위한 컴포넌트 )
+) JSX에서 <a> 사용해야 한다? 반드시 Link 사용해서 구현하자.
why? a 태그 사용 시, 페이지 이동되면 브라우저가 새로고침되기 때문.
If... 브라우저가 새로고침 된다? 모든 컴포넌트가 다시 렌더링되야 함 + 리덕스나 useState로 구축해놓은 모든 상태값이 초기화
→ 불필요한 움직임이 되어 성능에 악역향을 줄 수 있음. (SPA 그럼 왜써..)
- 예시코드
import { Link, useLocation } from 'react-router-dom';
const Works = () => {
const location = useLocation();
console.log('location :>> ', location);
return (
<div>
<div>{`현재 페이지 : ${location.pathname.slice(1)}`}</div>
<Link to="/contact">contact 페이지로 이동하기</Link>
</div>
);
};
export default Works;
Children
props와 chilren을 활용한 공통 Layout
Children이 쓰이는 이유
컴포넌트에 어떤 자식 엘리먼트가 들어올지 예상할 수 없을 때, 범용적인 ‘박스’ 역할을 하는 컴포넌트 자주 볼 수 있음 (Sidebar or Dialog)
여기서 말하는 범용적인 “박스” 역할을 하는 컴포넌트 = Layout 역할을 하는 컴포넌트.
children props를 가지고 페이지 레이아웃 생성 가능 개발자가 의도하는 UI도 구현 가능.
(무려 개별적으로 존재하는 Header, Footer, Page 합성도 가능하다!)
- 개발자가 의도하는 UI(개별적으로 존재하는 Header, Footer, Page 합성) 만들어주는 layout 컴포넌트 생성
예시코드 - [ src/shared/Layout.js ]
// src/shared/Layout.js
import React from 'react';
const HeaderStyles = {
width: '100%',
background: 'black',
height: '50px',
display: 'flex',
alignItems: 'center',
paddingLeft: '20px',
color: 'white',
fontWeight: '600',
};
const FooterStyles = {
width: '100%',
height: '50px',
display: 'flex',
background: 'black',
color: 'white',
alignItems: 'center',
justifyContent: 'center',
fontSize: '12px',
};
const layoutStyles = {
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
minHeight: '90vh',
}
function Header() {
return (
<div style={{ ...HeaderStyles }}>
<span>Sparta Coding Club - Let's learn React</span>
</div>
);
}
function Footer() {
return (
<div style={{ ...FooterStyles }}>
<span>copyright @SCC</span>
</div>
);
}
function Layout({ children }) {
return (
<div>
<Header />
<div style={{...layoutStyles}}>
{children}
</div>
<Footer />
</div>
);
}
export default Layout;
[ src/shared/Router.js ]
import React from 'react';
import { BrowserRouter, Route, Routes } from 'react-router-dom';
import Home from '../pages/Home';
import About from '../pages/About';
import Contact from '../pages/Contact';
import Works from '../pages/Works';
import Layout from './Layout';
const Router = () => {
return (
<BrowserRouter>
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
</Routes>
</Layout>
</BrowserRouter>
);
};
export default Router;
Dynamic Route
path에 유동적 값 주입 → 특정 페이지로 이동하게끔 구현하는 방법 (= 동적 라우팅)
URL 경로의 일부 변수로 처리, 다른 콘텐츠나 페이지 동적으로 로드하는 방식 → 간결하게 동적 변화가 있는 페이지 처리 가능!
- /user/:id에서 :id 부분은 동적으로 변할 수 있는 값, 사용자의 ID에 따라 다른 페이지 보여줌
- /product/:productId에서 :productId는 각 상품에 해당하는 ID로 바뀌어, 상품 상세 페이지를 동적으로 생성
- 설정하기
:id = 동적인 값을 받겠다
(그래서 works/1 로 이동 시, <Work /> 로 이동하고, works/2, works/3 …. works/100 모두 <Work /> 로 이동..한다)
+) :id = useParams 훅에서 조회할 수 있는 값 (나중에 자세하게 다룬다!)
import React from "react";
import { BrowserRouter, Route, Routes } from "react-router-dom";
import Home from "../pages/Home";
import About from "../pages/About";
import Contact from "../pages/Contact";
import Works from "../pages/Works";
const Router = () => {
return (
<BrowserRouter>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
<Route path="contact" element={<Contact />} />
<Route path="works" element={<Works />} />
{/* 아래 코드를 추가해주세요. 👇 */}
<Route path="works/:id" element={<Works />} />
</Routes>
</BrowserRouter>
);
};
export default Router;
Dynamic Routes와 useParams
- path parameter 조회하기
useParams 사용하면 조회 가능
useParams : URL에서 동적으로 지정된 파라미터 값 쉽게 조회가 가능하도록 도와줌
사용 시, element에 설정된 같은 컴포넌트 렌더링하게 됨.
<Route path="works/:id" element={<Work />} />
- 예시코드)
- 같은 컴포넌트를 렌더링하더라도 각각의 고유한 id 값, 조회 가능
// src/pages/Works.js
import React from 'react';
import { Link } from 'react-router-dom';
const data = [
{ id: 1, todo: '리액트 배우기' },
{ id: 2, todo: '노드 배우기' },
{ id: 3, todo: '자바스크립트 배우기' },
{ id: 4, todo: '파이어 베이스 배우기' },
{ id: 5, todo: '넥스트 배우기' },
{ id: 6, todo: 'HTTP 프로토콜 배우기' },
];
function Works() {
return (
<div>
{data.map((work) => {
return (
<div key={work.id}>
<div>할일: {work.id}</div>
<Link to={`/works/${work.id}`}>
<span style={{ cursor: 'pointer' }}>➡️ Go to: {work.todo}</span>
</Link>
</div>
);
})}
</div>
);
}
export default Works;
- work 컴포넌트 생성
// src/pages/Work.js
import React from 'react';
import { useParams } from 'react-router-dom';
const data = [
{ id: 1, todo: '리액트 배우기' },
{ id: 2, todo: '노드 배우기' },
{ id: 3, todo: '자바스크립트 배우기' },
{ id: 4, todo: '파이어 베이스 배우기' },
{ id: 5, todo: '넥스트 배우기' },
{ id: 6, todo: 'HTTP 프로토콜 배우기' },
];
function Work() {
const param = useParams();
const work = data.find((work) => work.id === parseInt(param.id));
return <div>{work.todo}</div>;
}
export default Work;
중첩된 라우트(Outlet)
ex) 사용자 대시보드에 여러 섹션이 있는 경우 각 섹션 별 다른 경로 설정 가능
import { BrowserRouter, Routes, Route } from 'react-router-dom';
import DashboardLayout from './DashboardLayout';
import Profile from './Profile';
import Settings from './Settings';
import Reports from './Reports';
function App() {
return (
<BrowserRouter>
<Routes>
<Route path="/dashboard" element={<DashboardLayout />}>
<Route index element={<Profile />} />
<Route path="settings" element={<Settings />} />
<Route path="reports" element={<Reports />} />
</Route>
</Routes>
</BrowserRouter>
);
}
/dashboard 경로, DashboardLayout 컴포넌트 사용.
레이아웃 내 /dashboard/settings, /dashboard/reports 등의 경로로 추가적인 페이지, 중첩하여 설정 가능.
∴ 대시보드 내 각 섹션의 독립적인 라우트 관리 및 UX(사용자 경험) 개선 가능
- Outlet 컴포넌트
중첩 라우팅 환경에서 부모 라우트 내에 렌더링되어야 할 자식 라우트를 표시할 위치를 정의하여
자식 경로의 렌더링 결과를 부모 컴포넌트가 어디에 표시할지 결정할 때 사용
→ 복잡한 애플리케이션의 라우트 구조, 더 명확하고 관리하기 쉽게 제작 가능
예시코드
import { Outlet } from 'react-router-dom';
function Layout() {
return (
<div>
<header>Header Section</header>
<main>
<Outlet /> {/* 여기에 자식 라우트의 컴포넌트가 렌더링됩니다. */}
</main>
<footer>Footer Section</footer>
</div>
);
}
중첩라우팅 vs children을 사용한 공통 레이아웃
각 라우트마다 고유의 Layout을 적용가능
but, ' 공통 Layout = 자식 '으로 사용하는 방식 → 모든 라우트에 동일한 레이아웃 적용할 때 유용.
∴ Layout 컴포넌트 = Routes에 포함X, 항상 Routes를 감싸는 상위 레벨에 위치해야 한다!
- 중첩 라우팅
<Routes>
<Route path="/" element={<Layout />}>
<Route index element={<Home />} />
<Route path="about" element={<About />} />
</Route>
</Routes>
- 공통 레이아웃
<Layout>
<Routes>
<Route path="/" element={<Home />} />
<Route path="about" element={<About />} />
</Routes>
</Layout>
- 중첩 라우팅 : 더 동적이고 유연한 라우트 관리를 제공
- 공통 레이아웃 방식 : 구조가 단순하고 일관되어 관리가 용이해요.
∴ 각각의 특징을 잘 고려해서 사용하는 것이 중요. ( react-router-dom 사용 시, Outlet 컴포넌트 이용 권장)
Supabase
BaaS(Backend as a Service)
웹과 모바일 앱 개발을 쉽고 빠르게 할 수 있도록 도와주는 클라우드 기반 백엔드 서비스
( 복잡한 백엔드 시스템을 직접 관리할 필요 X, 프론트엔드에 더 집중 가능 )
- 웹 애플리케이션의 필수 구성 요소
프론트엔드: 사용자가 보고 상호작용 부분(시각적인 부분 담당)
백엔드: 서버 측에서 데이터 처리, 사용자 관리, 로직 처리 담당
데이터베이스: 사용자 정보, 게시글 등 모든 데이터를 저장하고 필요할 때 불러오는 저장소
- BaaS의 필요성
서버나 보안 문제 신경 쓸 필요 없이 only 프론트 엔드만 해도 상관없이 나머지 문제들을 알아서 도와주기 때문!
( 서버 걱정 X, 개발에 필요한 기능 → 클라우드에서 바로 이용 )
- 인기 있는 BaaS 플랫폼
- Firebase: Google이 운영하는 플랫폼, 실시간 데이터베이스, 사용자 인증, 애널리틱스 등 쉽게 이용 가능.
- Parse: 자유도가 높은 오픈 소스 BaaS, 커스터마이징 가능.
- AWS Amplify: 강력한 AWS 클라우드 기반으로, 복잡한 백엔드 작업을 손쉽게 할 수 있도록 도와줌.
- BaaS 사용 시 장/단점
- 장점
- 개발 속도 향상: 백엔드 직접 만들 필요 X. 이미 준비된 기능 바로 사용이 가능하므로, 개발 시간 크게 단축시킬 수 있음.
- 유지보수 간편: 서버 운영이나 백엔드 시스템 관리에 대한 걱정 감소, 유지보수가 훨씬 편해짐.
- 자동 확장: 사용자가 늘어나도 서비스가 자동으로 스케일 업, 서버 부하 걱정 없이 안정적인 서비스를 제공 가능.
- 단점
- 유연성 부족: 표준화된 기능 외 특별한 요구사항 충족엔 한계 존재.
- 비용 예측 어려움: 사용량이 많아질수록 비용도 늘어남. 때로는 예상비용보다 비싸질수도..?
- 플랫폼 의존성: 너무 하나에만 의존하게 되면 나중에 다른 서비스로 이전하기 어려워짐. (마치 사람처럼요..)
Supabase
PostgreSQL을 기반으로 하는 오픈 소스 BaaS 플랫폼. (Firebase의 대안)
관계형 데이터베이스를 사용하면서도 실시간 기능을 원한다? Supabase 사용 권장!
추가) Supabase는 단순한 BaaS가 아니다!
PostgreSQL을 사용한 관계형 데이터 관리 + 실시간 데이터 변화를 감지 및 반응할 수 있는 기능 제공.
즉, DB 업데이트가 필요할 때마다 사용자 인터페이스가 자동으로 반응하여 변경사항 보여줌.
∴ 관계형 DB 사용 시, 복잡한 데이터 관계도 쉽게 표현 + 강력한 쿼리 기능 활용 가능!
- Supabase 사용 이유
1) 관계형 데이터 모델과 실무적인 역량
실무에서는 데이터의 일관성과 정확성이 매우 중요 → 이를 위해 대부분의 기업 시스템은 관계형 데이터베이스를 사용!
SQL 쿼리 작성, 데이터 테이블 간의 관계 설정, 트랜잭션 관리 등이 가능
→ 주식 거래 플랫폼, 실시간 대시보드, 협업 도구 등 실시간 데이터 처리가 중요한 어플리케이션 개발에 매우 유용!
-- 주문과 연관된 고객 정보를 함께 조회
SELECT orders.order_id, customers.name
FROM orders
JOIN customers ON orders.customer_id = customers.id
WHERE customers.region = 'Asia';
2) 실시간 기능의 실무적 적용
Firebase도 실시간 데이터베이스 기능을 제공, Supabase → 관계형 데이터베이스의 장점과 결합하여 제공함!
// 제품 가격 변경을 실시간으로 구독하는 코드
import { supabase } from './supabaseClient';
function subscribeToPriceChanges() {
supabase
.from('products')
.on('UPDATE', payload => {
alert(`Price updated! New price: ${payload.new.price}`);
})
.subscribe();
}
3) 기타
이 외에도 확장성, 보안, 오픈소스 커뮤니티 등 장점이 상당히 많으니. 적극적으로 Supabase를 사용해보자!
React에서 사용하기
1) 설치 및 세팅하기
VsCode 터미널에서 패키지를 설치합시당
yarn add @supabase/supabase-js
# 또는 npm install @supabase/supabase-js
설치가 완료된 후, Supabase 클라이언트를 초기화하는 코드를 작성
(이를 위해 Supabase 프로젝트의 URL과 공개 API 키가 필요.)
import { createClient } from "@supabase/supabase-js";
// 1) project url
const SUPABASE_PROJECT_URL = "YOUR_SUPABASE_URL";
// 2) anon key
const SUPABASE_ANON_KEY = "YOUT_SUPABASE_KEY";
const supabase = createClient(SUPABASE_PROJECT_URL, SUPABASE_ANON_KEY);
export default supabase;
2) 데이터베이스 입력하기 (console)
웹사이트 프로젝트 대시보드로 이동 후, ' Table Editor ' 사용하여 새로운 행 직접 추가 가능
3) 데이터베이스 읽기 (예시 코드)
// src > components > FetchData.jsx
import { useEffect, useState } from "react";
import supabase from "../supabaseClient";
const FetchData = () => {
const [users, setUsers] = useState([]);
useEffect(() => {
const fetchData = async () => {
const { data, error } = await supabase.from("NACAMP_SAMPLE").select("*");
if (error) {
console.log("error => ", error);
} else {
console.log("data => ", data);
setUsers(data);
}
};
fetchData();
}, []);
return (
<div>
<h3>유저정보</h3>
{users.map((user) => {
return (
<div
key={user.id}
style={{
border: "1px solid black",
}}
>
<h5>아이디 : {user.id}</h5>
<h5>이름 : {user.name}</h5>
<h5>나이 : {user.age}</h5>
<h5>주소 : {user.address}</h5>
</div>
);
})}
</div>
);
};
export default FetchData;
4) 데이터베이스 쓰기
// src > components > AddData.jsx
import React, { useState } from "react";
import supabase from "../supabaseClient";
const AddData = () => {
const [name, setName] = useState("");
const [age, setAge] = useState(0);
const [address, setAddress] = useState("");
const handleAdd = async () => {
const { data, error } = await supabase.from("NACAMP_SAMPLE").insert({
name,
age,
address,
});
if (error) {
console.log("error => ", error);
} else {
alert("데이터가 정상적으로 입력됐습니다.");
console.log("data => ", data);
}
};
return (
<div
style={{
border: "1px solid red",
}}
>
<h2>데이터 추가 로직</h2>
<div>
이름 :{" "}
<input
type="text"
value={name}
onChange={(e) => {
setName(e.target.value);
}}
/>
</div>
<div>
나이 :{" "}
<input
type="number"
value={age}
onChange={(e) => {
setAge(e.target.value);
}}
/>
</div>
<div>
주소 :{" "}
<input
type="text"
value={address}
onChange={(e) => {
setAddress(e.target.value);
}}
/>
</div>
<button onClick={handleAdd}>등록</button>
</div>
);
};
export default AddData;
5) 데이터베이스 수정
// src > components > UpdateData.jsx
import React, { useState } from "react";
import supabase from "../supabaseClient";
const UpdateData = () => {
const [targetId, setTargetId] = useState(0);
const [address, setAddress] = useState("");
const handleChange = async () => {
const { error } = await supabase
.from("NACAMP_SAMPLE")
.update({
address,
})
.eq("id", targetId);
if (error) {
console.log("error => ", error);
}
};
return (
<div
style={{
border: "1px solid blue",
}}
>
<h2>데이터 수정 로직</h2>
아이디 :{" "}
<input
type="number"
value={targetId}
onChange={(e) => setTargetId(e.target.value)}
/>
<br />
수정주소 :{" "}
<input
type="text"
value={address}
onChange={(e) => setAddress(e.target.value)}
/>
<button onClick={handleChange}>변경</button>
</div>
);
};
export default UpdateData;
6) 데이터베이스 삭제
// src > components > DeleteData.jsx
import React, { useState } from "react";
import supabase from "../supabaseClient";
const DeleteData = () => {
const [targetId, setTargetId] = useState(0);
const handleDelete = async () => {
const { error } = await supabase
.from("NACAMP_SAMPLE")
.delete()
.eq("id", targetId);
if (error) {
console.log("error => ", error);
}
};
return (
<div
style={{
border: "1px solid blue",
}}
>
<h2>데이터 삭제 로직</h2>
아이디 :{" "}
<input
type="number"
value={targetId}
onChange={(e) => setTargetId(e.target.value)}
/>
<button onClick={handleDelete}>삭제</button>
</div>
);
};
export default DeleteData;
마무리 - 이제 챌린지 과제 시작해야징...
분명 저번주까지는 게으른 J였던거 같은데..
이제는 그래도 나름 열심히 사는(?) J가 되어
하루하루 할 일정 다 끝내버리고 사는 중인 림졍... 대단하다!
챌린지 과제도 후딱 하고
미루고 미룬 이덕수... 오늘 해치우고
내일 배포하면 이번 주 주요일정은 끗-!
그런의미로 일단 밥먹으러 가야겠당
그럼 20000... 히히
오 늘 의 KPT 회고
- Keep : TIL 업로드 시간이 빨라졌어용
- Problem : 대신에 과제를 미뤘어용(?)
- Try : 이덕수를 진행해야해요 어서!
ㄲ
ㅜ
ㄷ.
'React TIL' 카테고리의 다른 글
[React] Day_41 데일리 정리 (3) | 2024.11.14 |
---|---|
[React] Day_40 데일리 정리 (0) | 2024.11.13 |
[React] Day_38 데일리 정리 (4) | 2024.11.11 |
[React] Day_37 데일리 정리 (2) | 2024.11.10 |
쉬어가는 TIME - 개인과제 중간점검(?) (feat. 꿀팁) (1) | 2024.11.09 |