$ npm init vite
# 별도로 패키지 설치 해줘야 함
$ npm install
$ npx tailwindcss init
위와 같은 설정 파일을 작성해줌.
index.css 내부 스타일링 초기화 후 아래와 같이 작성.
@tailwind base;
@tailwind components;
@tailwind utilities;
Javascript를 CSS로 변환하는 도구
preset-env-cssdb, autoprefixer, stylelinter 등 다양한 플러그인 사용 가능하게 해주는 도구
코드 가독성 향상 (접두사)
최신 CSS (폴리필 결정)
CSS 모듈 (클래스명 중복 X)
CSS 에러 방지 (console에 에러 나타남)
autoprefixer
- caniuse의 값을 이용하여 접두사 등을 적용해줌
$ npm install axios
const DataCard = ( name, url ) => {
const [data, setData] = useState([]);
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
const response = await axios.get(url);
const formatedData = formatData(response.data);
setData(formatedData);
} catch (error) {
console.error(error);
}
}
const formatData = (params) => {
const { id, name, category} = params;
const data = {
id,
name,
category: category[0].name
}
}
}
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Operators/Optional_chaining
const type = data?.type;
// data가 있는 경우에 data.type을 가져옴
// data가 없는 경우에는 undefined 에러가 뜸
<img
src={src}
loading="lazy"
onLoad={() => setLoads(false)}
/>
const LazyImage = ({ url, alt }) => {
const [loads, setLoads] = useState(true);
const [opacity, setOpacity] = useState(0);
useEffect(() => {
loads ? setOpacity(0) : setOpacity(100);
}, [loads]);
return (
<>
{
loads && <LoadingBar />
}
<img
src={url}
alt={alt}
loading="lazy"
onLoad={() => setLoads(false)}
/>
</>
)
예> 한 번에 20개씩 불러올 때
offset: 0 -> 20 -> 40
limit: 20
const [offset, setOffset] = useState(0);
const [limit, setLimit] = useState(20);
useEffect(() => {
fetchData(true); // 처음에 가져올 때에는 true 전달
}, []);
const fetchData = async(isFirstFetch) => {
try {
const offsetValue = isFirstFetch ? 0 : offset + limit;
const url = `~~~url/?limits=20&offset=${offsetValue}`;
const response = awiat axios.get(url);
setDatas([...datas, ...response.data]);
setOffset(offsetValue);
} catch (error) {
console.error(error);
}
}
return (
<>
<button
onClick={() => fetchDate(false)}
>더 보기</button>
</>
)
const [searhTerm, setSearchTerm] = useState('');
...
const handleSearchInput = async(e) => {
// onChange 때마다 get 요청 보냄
setSearchTerm(e.target.value);
if (e.target.value.length > 0) {
try {
const response = await axios.get(`~~url/${e.target.value}`);
const data = {
url: `~~url/${response.data.id}`,
name: searchTerm
}
setDatas([data]);
} catch (error) {
setDatas([]);
console.error(error);
}
} else {
// 검색 값이 ''인 경우, 모든 데이터를 다 보여줌
fetchData(true); // 초기 값 불러옴
}
}
return (
<>
<form
>
<input
type="text"
onChange={handleSearchInput}
value={searchTerm}
/>
<button type="submit">검색</button>
<form/>
</>
)
setTimeout
/src/hooks 폴더 생성 후 useDebounce.js
파일 생성
export const useDebounce = (value, delay) => {
const [debouncedValue, setDebouncedValue] = useState(value);
useEffect(() => {
const handler = setTimeout(() => {
setDebouncedValue(value);
}, delay);
return () => {
clearTimeout(handler); // 1초 지나기 전에 변경되면 clearTimeout을 통해 새롭게 1초를 기다림
}
}, [value, delay]);
return debouncedValue;
}
import { useDebounce } from '../hooks/useDebounce';
const [searchTerm, setSearchTerm] = useState('');
const debouncedSearchTerm = useDebounce(searchTerm, 500);
useEffect(() => {
fetchData(debouncedSearchTerm);
}, [debouncedSearchTerm]);
const fetchData = async(searchTerm) => {
try {
const response = await axios.get(`~~url/${searchTerm}`);
// setDatas
} catch (error) {
// setDatas([])
console.error(error);
}
}
const onChangeInput = (e) => {
setSearchTerm(e.target.value);
}
GET 요청 - 최초 1번만 (1000개 데이터 다 가져옴)
1000개 데이터를 저장해서
페이지네이션 or 더보기 실행할 때 마다 필터해서 보여줌
1000개의 데이터를 가지고 있음
그러나, 1000개의 데이터를 다 보여주지는 않음
두 가지 상태로 나눠서 관리하기.
const [allDatas, setAllDatas] = useState([]);
const [displayedDatas, setDisplayedDatas] = useState([]);
const limitNum = 20; // 한 번에 보여주는 데이터 수
const url = `~~url/?limit=1000&offset=0`
useEffect(() => {
fetchData();
}, []);
const fetchData = async () => {
try {
const response = awiat axios.get(url);
setAllDatas(response.data.results); // 전체 데이터 1000개 저장
setDisplayedDatas(filterDisplayedData(response.data.results);
} catch (error) {
console.error(error);
}
}
const filterDisplayedData = (allDatas, displayedDatas=[]) => {
const limit = displayedDatas.length + limitNum;
// displayedDatas가 0개 -> 20개 -> 40개 -> ...
const array = allDatas.filter((_, index) => index + 1 <= limit);
// allDatas는 1000개 데이터가 있고, 여기서 0~19 까지만 필터링함
return array;
}
return (
...
<button onClick={() => setDisplayedDatas(filterDisplayedData(allDatas, displayedDatas))>더보기</button>
);
return (
...
{(allDatas > displayedDatas) && (displayedDatas.length !== 1) && (<button onClick={() => setDisplayedDatas(filterDisplayedData(allDatas, displayedDatas))>더보기</button>)}
);
검색 기능을 이전에는 GET 요청으로 했지만,
이제는 모든 데이터를 가지고 있기 때문에 해당 name 필드에 검색값을 포함하는 것을 가져오면 된다.
const AutoComplete = ({allDatas, setDisplayedDatas, }) => {
const [searchTerm, setSearchTerm] = useState('');
const handleSubmit = (e) => {
// 폼 제출 시 (엔터 or 검색 버튼 클릭)
e.preventDefault();
let text = searchTerm.trim(); // 공백 제거
setDisplayedData(filterNames(text));
setSearchTerm('');
}
const filterNames = (Input) => {
const value = Input.toLowerCase();
retrun value
? allDatas.filter(e => e?.name.includes(value))
: []
}
return (
<form onSubmit={handleSubmit}>
<input
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
/>
<button>검색</button>
</form>
)
}
const checkEqualName = (input) => {
const filteredArray = filterNames(input);
// 완벽히 일치하는 이름이 있으면 자동완성에서 없애기 (빈 배열 반환)
return filteredArray[0]?.name === input ? [] : filteredArray
}
onClick={() => setSearchTerm(e.name)}
return (
...
{ checkEqualName(searchTerm).length > 0 && (
<ul>
{checkEqualName(searchTerm).map((e, i) => (
<li key={`button-${i}`} onClick={() => setSearchTerm(e.name)}>
{e.name}
</li>
))}
</ul>
)
)