
# 0 . 프로젝트 캠프 : React 2기 2주 차 후기
이번주를 마지막으로 총 2주간의 사전직무교육이 마무리되었습니다.
오프라인 9시부터 18시, 점심시간 1시간을 제외하고 풀타임으로 강의를 수강해 본 것이 너무 오랜만이라
빠른 진도를 따라가는 데에만 집중했던 것 같습니다.
3주 차부터는 프로젝트 주차가 시작됩니다.
이번 회고 포스팅에서는 2주 차에 학습했던 내용과 소감을 간단히 다뤄보려고 합니다.
# 1 . 프로젝트 캠프 2주 차 요일별 학습 회고
2주 차는 월~목, 총 4일간 사진직무교육을 진행하였으며,
금요일에는 3~6주 차에 예정되어 있는 프로젝트에 대비한 서류작성 및 기업별 킥오프 미팅으로 진행되었습니다.
useEffect
useEffect는 컴포넌트가 마운트 될 때, 혹은 의존성배열에 명시한 값의 변동이 있을 때 실행할 코드를 정의할 수 있는 함수입니다.
기본적인 형태는 다음과 같습니다.
// 1. 컴포넌트 마운트(렌더링)시 실행될 코드
// 2. cleanup 함수 : 컴포넌트가 언마운트될때 작성한 코드실행
// 3. 의존성함수,
// - []내부를 비워두면 마운팅, 렌더링될때마다 useEffect 내부코드 실행
// - []에 상태값을 넣어두면 해당 상태값이 변경될때마다 useEffect 내부코드 실행
// 기본 형태
useEffect( () => {
// (1)
return () => {
// (2)
}
}, [(3)] );
// 컴포넌트 생성 시점
const compExample = () => {
useEffect(() => {
console.log("Component Created");
}, []);
return <h1>compExample Component</h1>
}
// 컴포넌트 수정 시점
const compExample = () => {
const [count, setCount] = useState(0);
useEffect(() => {
console.log("Component Count Updated!" + count);
}, [count]);
return (
<h1>{count}</h1>
<button onClick={() => setCount((count) => count + 1)}>클릭</button>
)
}
// 컴포넌트 삭제 시점
const compExample = () => {
useEffect(() => {
console.log('Component Mounted');
return () => {
console.log('Component Unmounted');
};
}, []);
}
useLayoutEffect
useEffect와 동일한 개념이지만 실행되는 방식이 다릅니다.
useEffect는 컴포넌트가 렌더링 된 이후 비동기적으로 실행됩니다.
따라서 DOM을 조작하는 경우에 화면이 깜빡이는 것을 볼 수 있습니다.
하지만 useLayoutEffect는 컴포넌트를 렌더링 하기 직전에 동기적으로 실행되기 때문에
DOM을 조작하는 경우 화면이 깜빡이는 것을 볼 수 없습니다.
import { useEffect, useLayoutEffect, useState } from "react";
const UseLayoutEffect = () => {
const [count, setCount] = useState(0);
const now = performance.now();
// .. 1초정도 딜레이를 유발함
while (performance.now() - now < 1000) {}
// .. 0 -> 10 -> 0 의 변화과정이 눈에 보임 ( useEffect )
// useEffect(() => {
// if (count === 10) setCount(0); // 0 -> 10 -> 0
// }, [count]);
// .. 0 -> 10 -> 0 의 변화과정이 눈에 보이지 않음 ( useLayoutEffect )
// .. 그래서 언제 사용하는가?
// .. -> 컴포넌트가 눈에 보여지기 전에 레이아웃의 변경작업을 하고 싶을 때.
// .. -> DOM을 조작할때 깜박거리는 현상을 보여주고 싶지 않을 때.
// .. 어떤문제가 있는가? -> useLayoutEffect에 부하가 많이걸리는 작업을 넣으면 이 작업이 동작하는 동안에는 다른작업을 수행할 수 없다.
useLayoutEffect(() => {
if (count === 10) setCount(0); // 0 -> 10 -> 0
}, [count]);
// 사용처 정리
// useEffect() : 데이터 통신
// useLayoutEffect : 레이아웃 변경
return (
<>
<h1>Count : {count}</h1>
<button onClick={() => setCount(10)}>클릭</button>
</>
);
};
export default UseLayoutEffect;
useTransition
리액트 18 버전에 추가된 동시성 모드를 제공하는 훅입니다.
동시성 모드 : 리액트가 각 작업의 우선순위를 지정하여 동시 다발적으로 처리하는 개념
무거운 작업을 useTransition과 함께 사용하게 되면 부하를 줄이는 효과를 얻을 수 있습니다.
const [pending, startTransition] = useTransition();
useDeferredValue
const deferredDummy = useDeferredValue(value);
리액트 18 버전에 추가된 동시성 모드를 제공하는 훅입니다.
useTransition과 비슷하게 무거운 작업과 함께 사용하였을 때 부하를 줄여 렉을 감소시킬 수 있습니다.
useTransition vs useDeferredValue
두 개의 훅 모두 작업의 우선순위를 낮추어 처리함으로써 렉을 감소시킨다는 효과가 있습니다.
하지만 useTransition은 함수를, useDeferredValue는 값을 처리한다는 점이 다릅니다. ( 대상이 다름 )
그러므로 처리해야 하는 작업이 어떠한 형태인지 파악하고 알맞게 사용하는 것이 좋습니다.
useCallBack
함수형 컴포넌트를 메모이제이션하는데 사용되는 훅입니다.
메모이제이션이란 컴퓨터 프로그램이 동일한 계산을 반복해야 할 때,
이전에 계산한 값을 메모리에 저장하여 사용함으로써 동일한 계산의 반복수행을 제거하여,
프로그램의 실행속도를 향상시키고 성능을 개선하는 기술을 말합니다.
// 기본 형태
const fnExample = useCallback(함수, [의존성배열])
// 상세 내용
// increment, decrement는 참조자료형
// -> 리렌더링 될 때마다 새로운 값이 되어버린다.
// -> 그래서 메모이제이션이 되지 않는 것
// -> 참조자료형을 매개변수로 받는 컴포넌트는 React.memo를 통해서 메모이제이션 되지 않는다.
// 따라서 useCallBack으로 메모이제이션 해줘야한다.
// useCallBack이 메모이제이션의 풀어지는 조건은
// 의존성 배열에 있는 값이 변하는 것.
// -> 의존성 배열을 비워둔다면 절대 풀어지지 않는다.
const increment = useCallback(() => {
setCount((count) => count + 1);
}, []);
const decrement = useCallback(() => {
setCount((count) => count - 1);
}, []);
// 함수는 콜백형태로 작성해야만 한다
// 콜백형태로 작성하지않으면 메모이제이션 되었을때의 값을 기억하기때문에
// .. 정상적으로 동작하지 않는다.
useMemo
값을 메모이제이션하는데 사용하는 훅입니다.
// 기본 형태
const valueExample = useMemo(값, [의존성배열])
// 상세 내용
import { useMemo, useState } from "react";
import { initialItems } from "../../utils/utils";
const UseMemo = () => {
// 의도적으로 부하를 줄 수 있는 값을 정의합니다.
const [items] = useState(initialItems);
const selectedItems = useMemo(() => items.find((item) => item.selected), []);
const [count, setCount] = useState(0);
return (
<>
<h1>UseMemo Component</h1>
<pre>{JSON.stringify(selectedItems, null, 2)}</pre>
<h1>count : {count}</h1>
<button onClick={() => setCount((count) => count + 1)}>증가</button>
</>
);
};
export default UseMemo;
React.memo
컴포넌트를 메모이제이션하는데 사용하는 훅입니다.
export default React.memo(대상컴포넌트);
useReducer
조금 더 복잡한 상태관리를 할 때 사용하는 리액트 훅입니다
넓은 범위에서 useState 훅과 비슷합니다.
// 기본 형태
const [state, dispatch] = useReducer(reducer, initialState);
// 예제 코드
import { useReducer } from "react";
import "./Counter.css";
const reducer = (state: number, action: { type: string }) => {
switch (action.type) {
case "increament":
return state + 1;
case "decreament":
return state - 1;
default:
return state;
}
};
const Counter = () => {
const [counter, dispatch] = useReducer(reducer, 0);
return (
<div className="counter">
<h1>{counter}</h1>
<div>
<button onClick={() => dispatch({ type: "decreament" })}>-</button>
<button onClick={() => dispatch({ type: "increament" })}>+</button>
</div>
</div>
);
};
export default Counter;
ContextAPI
데이터가 필요한 컴포넌트가 직접 데이터를 요청하고 받는 방식
context 사용방법
- context 공급자 생성 - createContext( );
- 공급자 범위를 지정한다 → 생성한 공급자 컴포넌트의 객체의 몬텐츠로 공급할 다른 컴포넌트를 감싼다
- 공급할 데이터를 value 속성으로 지정
- 공급할 데이터를 useContext(공급자 컴포넌트 계획)로 받아서 사용한다.
Zustand
전역 상태 관리의 대표적인 라이브러리입니다.
카운터 상태를 관리하는 store를 정의합니다.
store에서 원하는 데이터를 받아서 사용할 수 있습니다.
import { create } from 'zustand';
const useCounterStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
}));
export default useCounterStore;
// ------------------------------------------------------------
// App.js
import React from 'react';
import useCounterStore from './counterStore';
function App() {
const { count, increase, decrease } = useCounterStore();
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increase}>Increase</button>
<button onClick={decrease}>Decrease</button>
</div>
);
}
export default App;
공식사이트 첫 화면 예제에서는 구조분해할당하는 것으로 사용되었습니다.
하지만 documentation에 명시된 방법이 더 권장되는 방법입니다.
자세한 코드는 다음과 같습니다.
// 공식 홈페이지 첫화면 예제
// - 구조분해할당
import { create } from 'zustand'
const useStore = create((set) => ({
count: 1,
inc: () => set((state) => ({ count: state.count + 1 })),
}))
function Counter() {
const { count, inc } = useStore()
return (
<div>
<span>{count}</span>
<button onClick={inc}>one up</button>
</div>
)
}
//-------------------------------------------------------------------------
// 공식 홈페이지 documentation 예제
// - create a store
import { create } from 'zustand'
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
updateBears: (newBears) => set({ bears: newBears }),
}))
// - binding components
function BearCounter() {
const bears = useStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation)
return <button onClick={increasePopulation}>one up</button>
}
Data Fetching
일반적인 fetch API를 사용하여 데이터 통신하는 방법에 대해 다뤄봤습니다.
// 리액트에서 API를 통해 데이터를 fetching하는 가장 기본적인 방법
useEffect(() => {
fetch("https://jsonplaceholder.typicode.com/todos/1")
.then((response) => response.json())
.then((json) => setTodo(json));
}, []);
data fetching + loading 상태에 따른 조건부 렌더링 1
// 1. 데이터를 불러오는 방법
// 2. 로딩상태를 핸들링하는 방법
import { useEffect, useState } from "react";
const FetchBasic = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// 리액트에서 API를 통해 데이터를 fetching하는 가장 기본적인 방법
useEffect(() => {
setIsLoading(true); // 로딩상태 true
fetch("http://localhost:5000?t=5000")
.then((response) => response.json())
.then((json) => {
setData(json);
setIsLoading(false);
});
}, []);
if (isLoading) {
// 로딩중일때 수행할 작업
return <h1>Loading</h1>;
}
return (
<>
<h1>FetchBasic Component</h1>
<pre>{JSON.stringify(data, null, 2)}</pre>
</>
);
};
export default FetchBasic;
data fetching + loading 상태에 따른 조건부 렌더링 2
// 위 예시와 다른점
// : 아래코드는 특정부분만 조건부렌더링이 가능하다.
// : 하지만 위 코드는 loading상태라면 화면전체에 Loading만 렌더링된다.
import { useEffect, useState } from 'react'
const FetchBasic = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
// 처음 화면 렌더링시에만 데이터를 fetching한다.
useEffect(() => {
setIsLoading(true);
// fetch('https://jsonplaceholder.typicode.com/todos')
fetch('http://localhost:3000?t=3000')
.then(response => response.json())
.then((json) => {
setData(json);
setIsLoading(false);
})
}, [])
return (
<>
<h1>FetchBasic</h1>
{/* isLoading이 true면 Loading 출력
false면 fetching된 데이터 출력 */}
{
isLoading
? <h1>Loading...</h1>
: <pre>{JSON.stringify(data, null, 2)}</pre>
}
</>
)
}
export default FetchBasic
data fetching + 에러 핸들링
import React, { useEffect, useState } from 'react'
const FetchBasic = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("")
const [isError, setIsError] = useState(false);
// 처음 화면 렌더링시에만 데이터를 fetching한다.
useEffect(() => {
setIsLoading(true);
fetch('http://localhost:3000?t=3000')
.then(response => response.json())
.then((json) => {
setData(json);
setIsLoading(false);
}).catch((e) => {
setIsLoading(false);
setIsError(true);
setError(e instanceof Error ? e.message : "알 수 없는 에러");
})
}, [])
if (isError) {
return <h1>{error}</h1>
}
return (
<>
<h1>FetchBasic</h1>
{/* isLoading이 true면 Loading 출력
false면 fetching된 데이터 출력 */}
{
isLoading
? <h1>Loading...</h1>
: <pre>{JSON.stringify(data, null, 2)}</pre>
}
</>
)
}
export default FetchBasic
data fetching + API요청이 끝나기 전 사용자가 이탈하는 경우 핸들링
import React, { useEffect, useState } from 'react'
const FetchBasic = () => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(false);
const [error, setError] = useState("")
const [isError, setIsError] = useState(false);
// 처음 화면 렌더링시에만 데이터를 fetching한다.
useEffect(() => {
const controller = new AbortController();
const signal = controller.signal;
setIsLoading(true);
setIsError(false);
// fetch('https://jsonplaceholder.typicode.com/todos')
fetch('http://localhost:3000?t=3000', { signal })
.then(response => response.json())
.then((json) => {
setData(json);
setIsLoading(false);
})
.catch((e) => {
if (e.name === "AbortError") return; // API요청이 취소된것일뿐 에러가 아니다라는 처리
setIsLoading(false);
setIsError(true);
setError(e instanceof Error ? e.message : "알 수 없는 에러");
})
.finally(() => {
// 성공, 실패와 관계없이 무조건 실행
setIsLoading(false);
})
return () => {
controller.abort();
}
}, [])
if (isError) {
return <h1>{error}</h1>
}
return (
<>
<h1>FetchBasic</h1>
{/* isLoading이 true면 Loading 출력
false면 fetching된 데이터 출력 */}
{
isError && <h1>{error}</h1>
}
{
isLoading
? <h1>Loading...</h1>
: <pre>{JSON.stringify(data, null, 2)}</pre>
}
</>
)
}
export default FetchBasic
# 2 . 사전직무교육 2주 차 수강 후기
2주 차는 본격적인 리액트 훅과 데이터 통신에 대한 개념을 학습하고 관련된 실습을 여러 차례 진행하였습니다.
이전에 리액트를 학습하며 어려웠거나 몰랐던 부분들을 한번 더 공부함으로써 정리할 수 있는 시간이었고
강의자체가 속성으로 중요내용만 빠르게 짚고 실습하는 방식이라 학습하는 데에도 도움이 많이 되었습니다.
강사님께서 최근에 수강했던 인터넷강의들에 비해 훨씬 잘 가르쳐주셔서 꽤나 만족했었던 2주였습니다.
강의자료나 퍼블리싱자료에도 많은 공을 들였다고 하셨는데,
그런 부분이 느껴질 정도여서 더 수월하게 이해하고 학습했던 것 같습니다.
2주안에 많은 개념을 다뤘고 많은 실습을 진행하였습니다.
실습 분량이나 많고 난이도가 쉽지만은 않은 만큼 한 번만 공부하고 넘기기에는 무리가 있을 것 같습니다.
추가적인 공부 및 실습을 통해 여러 번 정리해서 능숙하게 다룰 수 있도록 해야겠습니다.
# 3. 프로젝트 주차를 준비하며..
다음주, 9월 2일부터 프로젝트 주차가 시작되어 사전에 정해진 팀원분들과 협업하여 프로젝트를 진행하게 됩니다.
킥오프미팅 때 기업에서 언급해 주신 주요 구현사항에 집중하여 어떤 기술을 프로젝트에 녹여낼지와 같은 상세 내용들을 팀원분들과 미팅을 진행하여 우선적으로 결정하고 프로젝트를 세팅하는 한 주가 될 것 같습니다.
프로젝트 협업은 경험이 있으나 그때는 git이나 notion을 잘 다루는 분들이 세팅해 주셔서 따라가기만 했었는데,
이번기회에는 찾아서 공부해 보고 직접 적용해보려고 합니다.
팀원들끼리 서로서로 부족한 부분을 채워주면서 4주 동안의 프로젝트가 문제없이 잘 진행되었으면 좋겠습니다.
본 후기는 본 후기는 [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 과정(B-log) 리뷰로 작성 되었습니다.
#udemy #웅진씽크빅 #스나이퍼팩토리 #인사이드아웃 #미래내일일경험 #프로젝트캠프 #부트캠프
#React #리액트프로젝트 #프론트엔드개발자양성과정 #개발자교육과정
반응형
'교육 > 프로젝트 캠프 : React 2기' 카테고리의 다른 글
| [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 프로젝트 코스 3주차 후기 (2) | 2024.09.22 |
|---|---|
| [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 프로젝트 코스 2주차 후기 (6) | 2024.09.16 |
| [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 프로젝트 코스 1주차 후기 (1) | 2024.09.08 |
| [유데미x스나이퍼팩토리] 프로젝트 캠프 : React 2기 - 사전직무교육 1주차 후기 (5) | 2024.08.25 |