-
Notifications
You must be signed in to change notification settings - Fork 20
[노현지] Sprint5 #48
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[노현지] Sprint5 #48
The head ref may contain hidden characters: "React-\uB178\uD604\uC9C0-sprint5"
Conversation
[Fix] delete merged branch github action
…현지-sprint1 [노현지] Sprint1
…Sprint-Mission into Basic-노현지-sprint2
…노현지-sprint2 [노현지] Sprint2
…노현지-sprint3 [노현지] Sprint3
|
스프리트 미션 하시느라 수고 많으셨어요. |
| @@ -0,0 +1,11 @@ | |||
| const BASE_URL = "https://panda-market-api.vercel.app"; | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
base URL은 환경 변수에 저장하시는게 좋습니다!
환경 변수(Environment Variable):
process.env에 내장되며 앱이 실행될 때 적용할 수 있는 값입니다!
다음과 같이 적용할 수 있습니다:
// .env.development
REACT_APP_BASE_URL="http://localhost:3000"
// .env.production
REACT_APP_BASE_URL="http://myapi.com"
// 사용시
<a href={`${process.env.REACT_APP_BASE_URL}/myroute`}>URL</a>
왜 환경 변수에 저장해야 하나요?
개발(dev), 테스트(test), 실제 사용(prod) 등 다양한 환경에서 앱을 운영하게 되는 경우, 각 환경에 따라 다른 base URL을 사용해야 할 수 있습니다. 만약 코드 내에 하드코딩되어 있다면, 각 환경에 맞춰 앱을 배포할 때마다 코드를 변경해야 하며, 이는 매우 번거로운 작업이 됩니다. 하지만, 환경 변수를 .env.production, .env.development, .env.test와 같이 설정해두었다면, 코드에서는 단지 다음과 같이 적용하기만 하면 됩니다.
const apiUrl = `${process.env.REACT_APP_BASE_URL}/api`;
이러한 방식으로 환경 변수를 사용하면, 배포 환경에 따라 쉽게 URL을 변경할 수 있으며, 코드의 가독성과 유지보수성도 개선됩니다.
실제 코드 응용과 관련해서는 다음 한글 아티클을 참고해보세요! => 보러가기
| const query = `page=${page}&pageSize=${pageSize}&orderBy=${order}`; | ||
| const response = await fetch(`${BASE_URL}/products?${query}`); | ||
| if (!response.ok) { | ||
| throw new Error("데이터를 불러오는데 실패했습니다"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
쿼리는 URLSearchParams로 손쉽게 사용할 수 있어요 !
const params = new URLSearchParams({ limit: '100' });
const { data } = await instance.get(`/articles/${articleId}/comments`, { params });axios를 사용하실 경우 URLSearchParams와 함께 객체로 손쉽게 핸들링할 수 있습니다 !
객체로 구성할 수 있어 가독성이 좋고, URL 인코딩을 자동으로 처리하여 특수 문자나 공백이 포함된 값에서도 안전하게 동작합니다 !
URLSearchParams:
URLSearchParams인터페이스는 URL의 쿼리 문자열을 대상으로 작업할 수 있는 유틸리티 메서드를 정의합니다.
쿼리를 생성하실 때에 참고해서 사용해보세요 😊
|
|
||
| export async function getItems({ page = "", pageSize = "", order = "" }) { | ||
| const query = `page=${page}&pageSize=${pageSize}&orderBy=${order}`; | ||
| const response = await fetch(`${BASE_URL}/products?${query}`); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
axios를 사용해보는건 어떨까요?(제안/선택)
fetch 모듈을 잘 만든다는 것은 어렵습니다. 다음 사항들을 고려해볼 수 있어요:
- 만약
get이 아닌 메써드(post,patch,delete등)일 경우는 어떻게 처리할 수 있을까요? query와body가 필요할 때는 어떻게 처리 할 수 있을까요?- 로그인 인가를 위한 토큰을 request 전에 자동으로 삽입할 수는 없을까요? (인증/인가를 자동으로 할 수 없을까요?)
- 처음 한 번에 Base URL을 지정할 수는 없을까요?
- Base URL을 사용하다가 타 Domain에 보내야 될 때는 어떻게 할 수 있을까요?
이 모든 요구사항들을 '잘 만든다는 것'은 어려워요. 따라서 이 모든걸 만들어진fetch모듈을 사용해보고 후에fetch모듈을 만들어 보는 것도 좋은 학습 방법이 될 수 있어요.
- Base URL을 사용하다가 타 Domain에 보내야 될 때는 어떻게 할 수 있을까요?
어떻게 세팅하면 될까? 🤔
instance를 만들어서 export를 하고 사용해보는 것 정도로 시도해보면 좋을 것 같아요. axios-instance 파일을 만들어서 instance를 생성하고 export한 후 사용해보는건 어떨까요?
다음과 같이 만들어볼 수 있어요:
const baseURL = process.env.NEXT_PUBLIC_LINKBRARY_BaseURL;
const instance = axios.create({
baseURL: baseURL,
headers: {
'Content-Type': 'application/json',
},
});
export default instance인가에 필요한 accessToken을 localStorage가 있다면 axios의 인터셉터를 활용할 수 있습니다 !
인터셉터는 혼자 해결해보시는 것을 권장드립니다. 혹시 모르시겠으면 다음 위클리 미션에 질문해주세요. 😊
사용 방법 🚀
사용 방법은 정말 간단해요. 다음과 같이 사용할 수 있습니다:
instance.get(`/user/${userId}`)딱 보니. 마이그레이션도 정말 쉽게 할 수 있겠죠? 😊
| <input | ||
| name="search" | ||
| className="searchBar" | ||
| placeholder="검색할 상품을 입력해주세요" | ||
| /> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
input의 타입 중 search를 적용해볼 수 있을 것 같아요 😊
| <input | |
| name="search" | |
| className="searchBar" | |
| placeholder="검색할 상품을 입력해주세요" | |
| /> | |
| <input | |
| name="search" | |
| type="search" | |
| className="searchBar" | |
| placeholder="검색할 상품을 입력해주세요" | |
| /> |
| if (!response.ok) { | ||
| throw new Error("데이터를 불러오는데 실패했습니다"); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
API 통신부에서 에러가 났을 때 어떻게 대처하면 될까요?
현재 error를 모두 "데이터를 불러오는데 실패했습니다"에러를 throw 해주고 있기에 해당 함수를 사용하는 컴포넌트에서는 분기처리하기 힘들거예요 !
그렇다면 어떻게 할까요?
방법은 다양합니다만, 지금 바로 해볼 수 있는 방법은 throw를 해보는거예요:
| if (!response.ok) { | |
| throw new Error("데이터를 불러오는데 실패했습니다"); | |
| } | |
| try { | |
| } catch (error) { | |
| console.error(`Failed to fetch data: ${error}`); // 통신부에서 처리할 로직 및 로깅 | |
| throw(error); | |
| } |
위처럼 throw를 해준다면 서버에서 반환되는 에러 메시지를 사용자에게 toast든, 모달이든, 알러트든 보여줄 수 있겠죠?
다음과 같이요 !!:
// Component
useEffect(() => {
try {
getItemsComments();
// 이어서..
} catch (err) {
alert(err.message) // 만약 서버에서 적절한 `err.message`를 반환하지 않는다면 현지님께서 정의해둔 "데이터를 불러오는데 실패했습니다"를 출력해도 무방함.
}
}, [])| function getPageSize(width) { | ||
| if (width > 1200) return 10; // PC | ||
| else if (width > 768) return 6; // Tablet | ||
| else return 4; // Mobile | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
해당 함수는 컴포넌트 외부에 선언해볼 수 있습니다 !
해당 함수는 컴포넌트의 자원(상태, props)을 사용하고 있지 않으므로 컴포넌트 바깥에 선언해볼 수 있어요. 이는, 리렌더링에 의한 불필요한 함수 재선언을 방지해주며, 컴포넌트와 무관한 함수를 분리함으로서 컴포넌트 내부에는 컴포넌트의 자원을 사용하는 코드만 남게 되므로 가독성이 향상될 수 있어요 😊
| const [pageSize, setPageSize] = useState(10); | ||
| const [pageBound, setPageBound] = useState(0); | ||
| const [items, setItems] = useState([]); | ||
| const pageArr = [1, 2, 3, 4, 5]; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
임의로 페이지 배열을 만들어두신 듯 하군요 !
다음 미션에서는 totalCount와 offset을 계산하여 만들어봅시다 😊
| useEffect(() => { | ||
| function handleResize() { | ||
| const newPageSize = getPageSize(window.innerWidth); | ||
| if (newPageSize !== pageSize) { | ||
| setPageSize(newPageSize); | ||
| } | ||
| } | ||
| window.addEventListener("resize", handleResize); | ||
| return () => window.removeEventListener("resize", handleResize); | ||
| }, [pageSize]); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
(제안/선택)디바운싱/쓰로틀링을 통하여 성능 최적화를 시켜볼까요?
해당 이벤트에 console.log를 호출해보면 리사이징이 될 때마다 정말정말 많은 호출하는 것을 볼 수 있을거예요 !
그만큼 성능에 좋지 못하다는 이야기겠지요?
따라서, 프론트엔드 개발자들은 이렇게 잦은 이벤트가 발생할 때(리사이징, 스크롤, 타이핑 등) 디바운싱/쓰로틀링을 통하여 최적화를 시키곤 합니다.
쓰로틀링(Throttling): 일정 시간 동안 하나의 함수만 호출되도록 하는 기법입니다. 예를 들어, 사용자가 스크롤을 할 때, 매번 이벤트를 처리하지 않고 일정 간격으로 한 번만 처리하게 합니다. 이를 통해 성능을 향상시킬 수 있습니다.
디바운싱(Debouncing): 여러 번 발생하는 이벤트 중 마지막 이벤트가 발생한 후 일정 시간이 지난 다음에 한 번만 실행되도록 하는 기법입니다. 예를 들어, 사용자가 검색어를 입력할 때, 입력이 끝난 후 일정 시간 동안 추가 입력이 없으면 검색 요청을 보냅니다.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
다음과 같은 코드가 있습니다:
function Container() {
useEffect(() => {
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
}, []);
// ... Some code
}디바운싱으로 다음과 같이 최적화를 시킬 수 있습니다 !:
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function Container() {
const handleResize = () => {
console.log('Window resized');
};
useEffect(() => {
const debouncedHandleResize = debounce(handleResize, 300);
window.addEventListener('resize', debouncedHandleResize);
return () => {
window.removeEventListener('resize', debouncedHandleResize);
};
}, []);
// ... Some code
}이렇게 하면 연속된 이벤트가 끝난 후 0.3초마다 호출하게 되어 기존보다 훨씬 최적화된 기능이 될 수 있습니다 !
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
매번 작성하기 번거로운가요?
그렇죠... 번거롭죠.. (사실 저도 GPT한테 맡긴 코드입니다 하하하핳ㅎ 그래서 될지는 장담못함..)
디바운싱, 쓰로틀링은 정말 흔한 기법이어서 참조할 수 있는 문서가 많습니다 !
해당 라이브러리를 사용한 코드로 볼까요?
import { useRef, useState } from 'react';
import { useDebounceCallback } from 'usehooks-ts';
function useDebounceValue(initialValue, delay, options) {
const eq = options?.equalityFn ?? ((left, right) => left === right);
const unwrappedInitialValue = typeof initialValue === 'function' ? initialValue() : initialValue;
const [debouncedValue, setDebouncedValue] = useState(unwrappedInitialValue);
const previousValueRef = useRef(unwrappedInitialValue);
const updateDebouncedValue = useDebounceCallback(setDebouncedValue, delay, options);
// Update the debounced value if the initial value changes
if (!eq(previousValueRef.current, unwrappedInitialValue)) {
updateDebouncedValue(unwrappedInitialValue);
previousValueRef.current = unwrappedInitialValue;
}
return [debouncedValue, updateDebouncedValue];
}
export default useDebounceValue;그리고 사용 방법:
import React, { useState } from 'react';
import useDebounceValue from './useDebounceValue';
const ExampleComponent = () => {
const [inputValue, setInputValue] = useState('');
const [debouncedValue] = useDebounceValue(inputValue, 500);
const handleChange = (event) => {
setInputValue(event.target.value);
};
return (
<div>
<input type="text" value={inputValue} onChange={handleChange} placeholder="Type something..." />
<p>Debounced Value: {debouncedValue}</p>
</div>
);
};
export default ExampleComponent;|
현지님 정말정말 수고하셨습니다 ! 과제하시느라 수고 많으셨습니다 현지님 ! 😊😊 |
요구사항
기본
중고마켓
중고마켓 반응형
심화
주요 변경사항
const키워드로email_regex변수 선언togglePassword함수를 가독성 좋게 변경멘토에게