useEffect를 사용하는 이유는 데이터를 가져오기 위해서가 아니라 리액트는 프라미스와 에러에 대응할 수 없기 때문이다.
비동기 함수와의 일관성: useEffect
는 일반적으로 부수 효과를 처리하기 위해 사용됩니다. 하지만 useEffect
가 반환하는 값은 주로 정리(clean-up) 작업을 위한 함수이어야 합니다. async
함수는 항상 Promise
를 반환하기 때문에, 반환 값이 기대하는 일반적인 정리 함수 형태와는 다릅니다.
오류 처리의 어려움: useEffect
안에서 직접 async
함수를 사용하면 내부에서 발생한 오류를 캐치하거나 처리하기가 어려울 수 있습니다. try-catch
블록 안에서 await를 사용하여 오류를 처리할 수 없습니다. 이로 인해 예기치 않은 동작이 발생할 수 있습니다.
올바른 패턴은 useEffect
안에서 비동기 코드를 호출하는데 사용되는 별도의 함수를 선언하고, 그 함수 내에서 async/await
패턴을 사용하는 것입니다. 예를 들면 다음과 같습니다
useEffect(() => {
const fetchData = async () => {
try {
// 비동기 작업 수행
} catch (error) {
// 오류 처리
}
};
fetchData();
}, []);
import { useEffect, useState } from "react";
// 리액트는 자식 컴포넌트가 프로미스를 반환하거나
// 에러를 던질 때는 전혀 대응하지 못한다.
function App() {
const [값, set값] = useState(0);
const 비동기데이터가져오기 = async () => {
const res = await fetch(
"https://pokeapi.co/api/v2/pokemon?limit=5&offset=0"
);
const data = await res.json();
set값(data.results.length);
};
// fetch를 react 컴포넌트와 격리하기 위해
// useEffect의 부수효과를 이용한다.
useEffect(() => {
// useEffect 안에서 async 함수를 선언하면
// 프로미스를 반환하므로 에러가 발생한다
비동기데이터가져오기();
}, []);
return <>{값}</>;
}
export default App;
function 메모이즈(함수) {
// 0. 캐시라는 변수는 클로저가 된다
const 캐시 = {};
return function (키) {
console.log("캐시 내부", 캐시);
// 1.캐시 안에서 이전에 계산한 값을 꺼내 온다
const 값 = 캐시[키];
// 2. 이전에 계산한 값이 있으면 그 값을 반환한다
if (값) {
console.log("히트다 히트!");
return 값;
}
// 3. 이전에 계산한 값이 없으면 새로 계산해서 캐시에 넣자
캐시[키] = 함수(키);
// 4. 그리고 계산한 값을 반환!
return 캐시[키];
};
}
VITE_PB_URL = http://127.0.0.1:8090 # 환경변수 설정
VITE_PB_API = $VITE_PB_URL/api #API 엔드포인트를 저장
DB_PASSWORD = qweiuickx2@zkjw # 비트와의 약속 : 변수명에 'VITE'를 명시하지 않으면 값을 불러 올 수 없음!
.env.local
에 있는 변수 불러오는 방법
<Route path="product/edit/:productId" element={<ProductEdit />}/>
{/* :productId - url 파라미터에 따라 동적으로 라우팅 해주겠다는 뜻 */}
// productId는 동적 경로 매개변수
// ProductEdit 컴포넌트에서 이 매개변수를 추출하려면 useParams 훅을 사용
${import.meta.env.VITE_PB_API}/collections/products/records/${productId}
// http://127.0.0.1:8090/api/collections/products/records/상품ID
defaultvalue - 폼 요소의 초기값을 설정하는 데 사용 / 리액트가 관리하지 않고 사용자에 의해서 값을 바꾸고 싶을 때 사용
<div className="flex gap-3">
<label htmlFor={titleId}>타이틀</label>
<input
type="text"
name="title"
id={titleId}
// 리액트가 제공하지 않는 함수를 사용할 때는 defaultValue 사용해야 한다.
defaultValue={formState.title}
onChange={handleDebounceChangeInput} // debounce 함수
/>
</div>
value - 폼요소의 현재 값을 나타냄 / 리액트가 관리할 때 사용
PocketBase SDK
NPM을 사용해 PocketBase SDK 패키지를 설치합니다.
npm i pocketbase
import PocketBase from 'pocketbase';
const pb = new PocketBase(import.meta.env.VITE_PB_URL);
// PocketBase SDK {}
export default pb;
<Route> 설정:
이 부분은 처음에 설정되며, 브라우저의 주소창에 특정 URL 경로가 입력되었을 때
일치하는 라우트가 있으면 해당 라우트의 컴포넌트를 렌더링합니다.
path 속성에 있는 경로 패턴을 가지고 매칭 여부를 결정합니다.
컴포넌트 내에서 const { productId } = useParams(); 실행:
이 부분은 ProductEdit 컴포넌트 내부에서 실행됩니다.
해당 컴포넌트가 라우트에 의해 렌더링될 때, 컴포넌트 내부의 코드가 실행됩니다.
따라서 useParams()를 호출하여 동적 매개변수 값을 추출하는 것은 ProductEdit 컴포넌트가 렌더링될 때 일어납니다.
따라서 순서는 <Route> 설정이 먼저 이루어진 다음, ProductEdit 컴포넌트가 렌더링되면서 내부에서 useParams()를 호출하여 동적 매개변수 값을 가져오게 됩니다.
이렇게 함으로써 동적으로 변화하는 경로와 해당 경로의 매개변수 값을 활용하여 원하는 정보를 렌더링하거나 네비게이션을 처리할 수 있습니다.
lifting
상태와 달리, 참조 객체의 현재 값은 변경되어도 리액트가 다시 렌더링을 하지 않는다.
상태는 시간의 흐름에 따라 변하고, 상태가 변경되면 리액트는 필연적으로 반응(리액션: 렌더링)한다.
함수 내부의 변수나 포함된 함수는 다음 번 실행(리-렌더링) 시, 초기화된다. (가비지 컬렉터에 의해서)
컨텍ㄷ스트 == 영역
전
후
Serialize(직렬화): 객체를 데이터스트림(연속적인 바이너리 형태나 텍스트 형태(예: JSON) )으로 만드는 것
즉, 객체에 저장된 데이터를 스트림에 쓰기위해 연속적인 데이터를 변환하는것. (JSON.stringify())
Deserialization(역직렬화): 반대로 스트림으로부터 데이터를 읽어 객체를 만드는 것 (JSON.parse())
useCallback 훅은 리액트 컴포넌트 내에서 함수를 최적화하여 생성하거나 업데이트할 때 사용됩니다. 함수 컴포넌트가 렌더링될 때마다 새로운 함수가 생성되는 경우, 불필요한 리렌더링이 발생할 수 있고, 성능 저하의 원인이 될 수 있습니다. 이러한 문제를 해결하고자 useCallback 훅을 사용하여 함수를 메모이제이션하고 최적화할 수 있습니다.
// useCallback : 함수 값만 기억 vs. useMemo : JS 모든 값(함수 포함)을 기억
// useMemo 훅: 모든 JS 값 유형 기억
// const cachedUpdate = useMemo(() => 함수 값, [])
// useCallback 훅: JS 함수 값만 기억
const update = useCallback(
(nextData) => {
setData(key, nextData);
setStorageData(nextData);
},
[key]
);
// const updateMemo = useMemo(
// () => (nextData) => {
// setData(key, nextData);
// setStorageData(nextData);
// },
// [key]
// );
const remove = useCallback(() => {
deleteData(key);
setStorageData();
}, [key]);
// const removeMemo = useMemo(
// () => () => { deleteData(key); },
// [key]
// );
토스트에서 문제 상황 발생
순수한 영역에서 상태를 바꾸려고 시도했기 때문에 에러가 발생한다
useEffect를 사용
const { isAuth } = useAuth();
useEffect(() => {
// if (!isAuth) {
toast("로그인 된 사용자만 이용 가능한 페이지입니다.", {
icon: "⚠️",
ariaProps: {
role: "alert",
"aria-live": "polite",
},
});
// }
return () => {};
}, [isAuth]);
if (!isAuth) {
return <Navigate to="/signin" />;
}
return children;
}