배포 URL: https://koreanhamster.github.io/delicious/
깃헙 링크: https://github.com/Koreanhamster/delicious
프로젝트 기록(노션): https://grape-curiosity-e94.notion.site/35b86a30c543458c9af716c50a331b77
프로젝트 목적과 의의
- (본인이) 영어와 요리를 둘 다 좋아하기에, 영어사용자들에게 아시안 레시피를 소개하는 서비스 구상
- 오픈API 사용하여 데이터를 가져오고 필요한 데이터를 적절히 사용하는 것에 의의
사용 스킬
- React
- Styled-components
기능구현(데모)
홈화면
- 검색창에 원하는 레시피 검색가능
- 이탈리안, 베트남, 타이, 한식 카테고리 클릭시 해당 페이지로 넘어감
- 베지테리언 레시피와 인기 레시피를 케로셀 형태로 제작
카테고리로 이동
- 베트남 카테고리로 이동한 모습
레시피 화면
- 각 카드 클릭시 Instructions와 Ingredients 확인 가능
검색기능
- 검색창에 원하는 요리 검색 가능
마주한 오류
API쓸거면 돈 내
진짜 진짜로. 방금 전까지 잘 되던 API fetch가 오류가 나네?
물론 이제까지 마주한 오류에서 억울했던 적은 단 한번도 없었지만(항상 나의 실수였으니) 이번만큼은, 진짜 억울했다.
그런데 알고보니 API사용량이 일정수준 이상이면 플랜을 업그레이드 해야만 사용할 수 있는거였다...^^
왜 공짜라고 생각했을까... 내자신
이 API를 대체할 Public API들을 뒤져보았지만 내가 딱 원하는 그런 데이터는 찾지 못해서 그냥 5불을 결제했다.
https://spoonacular.com/food-api/pricing
spoonacular recipe and food API
Let's Talk You want that latte with oat milk, a shot of pumpkin spice and without a straw? No problem, we're used to special requirements. Contact
spoonacular.com
음식에 관련된 어마어마한 데이터들을 제공해준다.
재료 하나하나의 정보와 이미지까지 다 제공한다. 대박. 돈값하네
렌더링이 될 때마다 새로 불러오는 API 해결하기
알고보니까 API를 안사도 됬었던게, 150points 까지는 불러오는게 무료였는데 새로고침을 할 때마다 많은 양의 데이터를 다시 가져오다보니 바로 그 사용량이 찼던거였다. 유투브에서 로컬스토리지에 데이터를 저장하고 데이터가 없는 경우에만 fetch를 할 수 있도록 하는 방법을 발견해서 따라해봤다. 이게 useEffect에서 의존성 배열로 빈 배열을 줘서 마운트시 한번만 렌더링 되게 하는거랑 뭐가 다른건지는 모르겠네.
const getPopular = async()=>{
// popular가 localstorage에 저장되어있는지 확인한다.
const check = localStorage.getItem('popular');
if(check){
// 저장되어있다면, fetching할 필요 없이 배열로 반환받는다.
setPopular(JSON.parse(check));
}else{
// 아무것도 없다면 fetching한다.
const api = await fetch(`https://api.spoonacular.com/recipes/random?apiKey=${process.env.REACT_APP_API_KEY}&number=12`);
const data = await api.json();
localStorage.setItem('popular', JSON.stringify(data.recipes))
setPopular(data.recipes);
console.log(data.recipes);
}
}
1. localStorage에는 오직 문자열만 저장할 수 있다. 그래서 data.recipes를 저장해주고싶으면 문자열로 반환해줘야 한다. JSON.stringfy(data.recipes)
2. 반대로 아이템을 가져올때는 문자열에서 배열로 파싱을 해줘야 한다. JSON.parse(check)
모든 페이지는 BrowserRouter 안에
카테고리에서 라우터를 사용하려는데 이런 오류가 나왔다. 검색해보니 내가 라우터를 사용한 곳이 BrowserRouter로 감싸져있지 않아서 생긴 문제였다.
import Pages from './pages/Pages'
import Category from './components/Category';
function App() {
return (
<div className="App">
<Category/>
<Pages/>
</div>
);
}
export default App;
난 Pages 안에 BrowserRouter를 넣었으니 당연히 Categroy에서 라우터가 될리가 없다.
BrowserRouter는 앞으로 웬만하면 App.js같은 상위컴포넌트에 넣어야겠다
리액트를 깊게 파보자! hook flow
콘솔창에 이것저것 출력을 해보다가, '얍'이 콘솔창에 두번 출력된다는걸 발견했다.
function Cuisine() {
const [cuisine, setCuisine] = useState([]);
let params = useParams();
console.log('얍'); // 1번째로 콘솔창 출력 예상
const getCuisine = async(name)=>{
const data = await fetch(`https://api.spoonacular.com/recipes/complexSearch?apiKey=
${process.env.REACT_APP_API_KEY}&cuisine=${name}`);
const recipes = await data.json();
setCuisine(recipes.results)
console.log(recipes.results); // 3번째로 콘솔창 출력 예상
};
useEffect(()=>{
getCuisine(params.type)
console.log(params.type); // 2번째로 콘솔창 출력 예상
},[params.type])
return (
<div>
</div>
)
}
export default Cuisine
잘 이해되지 않아 멘토님께 질문을 드렸고 리액트에서 꼭 알고있어야하는 Hook flow와, 렌더링을 일으키는 요소(혹은 메소드)와 밀접한 관련이 있다는 것을 알게되었다. useState에서 값 변경시 재랜더링이 일어난다는건 초반에 배워서 알고있었는데 이제 좀 플로우가 제대로 보이는 것 같다. 공부한 것을 토대로 이 코드의 플로우를 정리해보면,
// 0. 마운트 과정이 진행된다.
// 1. state초기화와 함께 '얍'이 실행된다.
// 2. 리턴까지 쭉 렌더링이 진행된다. (getCuisine은 그냥 함수선언이라 실행안됨)
// 3. useEffect가 실행된다.
// 4. 만들어둔 getCuisine함수를 실행한다.
// 5. setCuisine(recipes.results) -> stateUpdate가 진행된다.
// 6. 업데이트가 진행된다. -> 렌더링을 일으킨다
// 7. state초기화는 최초 한번만 진행되니 건너뛴다. 만들어둔 얍이 실행된다.
// 8. 리턴까지 렌더링이 진행된다.
+ 추가로 게으른 초기화(Lazy initialization)에 대해서 알고있으면 좋은데,
State초기화과정은 렌더링때마다 항상 진행되지만(그 안에 함수가 들어있다면 그 함수실행은 매번 된다) 실제로 리액트는 실제로 값을 초기화하는 과정이 필요한지 판단 후 필요없으면 진행하지 않기때문에 결국 *필요없는 연산*을 하게 된 꼴이 된다. 이때 만약 무거운 함수를 초기값으로 설정해주었다면 랜더링이 될 때마다 쓰지도 않을 함수를 계속해서 실행시켜야하니 비효율이다. 이 때 함수를 '콜백 함수형태'로 넣어주면 함수실행 자체도 브라우저 마운트시에만 일어나고 이외에는 일어나지 않는다!
아래는 마침 예전에 궁금해서 찍어본 영상
https://www.youtube.com/watch?v=Z_eWhLpnhbk&t=75s
It's a nice consistancy!
처음엔 useParams를 사용해서 뭔가 연결해주고 하는게 너무 어려웠다. 근데 페이지를 만들면서 같은 작업을 반복하다보니 뭔가 그 흐름이 있다는 걸 알게됬다.
- 페이지를 만들고
- 라우트에 페이지를 넣어주고
- URL로부터 뭔가를 받아와서
- fetch를 해주고
- 브라우저에 렌더링을 시켜줌
한번 해봤다고 되는게 아니라 눈감고도 휘리릭 만들 수 있을만큼 반복연습이 필요하다.
API안에 들어있는 HTML은 어떻게 랜더링하지?
summery를 가져오려 했는데 HTML형식이었다. 화면에는 태그내용이 그대로 출력됨.
해결 : innerHTML대신 쓰는 dangerouslysetInnerHTML…. (이름왜이래?)
이렇게 출력되는 이유는 HTML을 그대로 셋팅하는건 cross-site scripting (XSS) 공격에 노출될 수 이기 때문이다. 그래서 셋팅할 수 있지만 위험한 짓을 하고있다는걸 리마인드하기위해 이런 이름으로 지었다고 한다.
(그럼 왠만하면 이건 안해야겠군.)
많이 배웠다!
리액트로 프로젝트를 만든는건 이번이 처음이었는데, 개념으로만 배우던걸 실제로 사용해보니
1. 큰 기능이 아님에도 사용할 라이브러리가 많다!
2. 좋아하는걸 하니까 재밌다! 그리고 끝내니까 뿌듯하다!
3. Open API를 사용해서 더 재미난걸 만들어보고싶다!
라는 생각이 들었다. 특히 카카오 Developers에서 제공하는 API들을 가지고 요리저리 만들어보고싶다.
또 카테고리별로 쓸 돈을 셋팅해놓고 쓴만큼 Progress bar를 보여주는? 그런 시각화된 돈관리 앱도 만들고싶다.
우선 이 프로젝트는 유투브의 도움을 많이 받았기에 스스로 처음부터 다시한번 구현해봐야겠다.
'Front-End Developer > Project' 카테고리의 다른 글
회원가입페이지 코드리뷰 받은내용 고쳐보기 (0) | 2022.07.19 |
---|---|
깃헙 협업프로젝트시 Issue / PR 템플릿 생성하기 (+ 유투브) (0) | 2022.06.28 |
flex & grid 책을 출간하기까지(총괄은 쉽지 않다! 하지만 많이 배웠다.) (0) | 2022.06.06 |
Sass를 사용한 영화목록 페이지 클론코딩 (0) | 2022.05.04 |
Pure CSS로 포토샵의 역사 페이지 구현하기 (2) | 2022.04.27 |