저번 시간에 헤더(header)를 만들었습니다
상대적으로 간단한 작업이었는데
이것이 단순한 컴포넌트였기 때문입니다
이번 시간부터는 좀더 복잡한 작업을 하겠습니다
오늘 강의에서는 음식 메뉴를 표시하는 작업을 하겠습니다
그것을 위해 컴포넌트를 하나 더하겠습니다
컴포넌트 폴더(components) 안에
Meals.jsx을 만들겠습니다
오늘자 작업이 끝난 뒤에는
임시 백엔드에서 음식 데이터를 불러와서
백엔드에는 음식(meals)이라는 엔드포인트(endpoint)가
get이라는 요청을 제어하고
그 엔드포인트가 이 임시 백엔드의
가상 음식 데이터를 반환합니다
그러므로 이를 위해 요청을 보내야 하는데
음식 컴포넌트 안에서 요청을 보내고
또 여기에서 음식 아이템(item)을 반환하겠습니다
Meals.jsx로 돌아와서
먼저 음식(Meals)라는 컴포넌트 함수를 내보내겠습니다
그리고 ul를 하나 반환하겠습니다
이 목록에는 음식(meals)라는 ID가 있습니다
다시 index.css로 가 보시면
여기에 만들어진 규칙이
meals라는 이름에 대응합니다
이 규칙은 음식 메뉴 목록을 위해
만들어진 것입니다
이제 목록을 만들었으니
목록 안에 아이템을 더하겠습니다
이 작업을 위해 데이터가 필요하기 때문에
백엔드로 HTTP 요청을 보내겠습니다
이미 언급했듯이
백엔드의 /meals로 GET이라는 요청을 보내야 합니다
그러므로 이 요청을 위한 코드를 작성하겠습니다
지난 강의에서 배우셨듯이
HTTP 요청을 보내기 위해서는
리액트에 포함되어 있는 fetch라는 함수를 사용합니다
fetch는 가져온다는 뜻으로
요청을 보낼 서버의 URL을 함수의 입력값으로 받습니다
이 경우의 주소는 http://localhost:3000입니다
그리고 /meals를 작성하는데
이 부분이 목표로 하는 엔드포인트입니다
이제 이 요청문을 구성할 때
요청 메소드를 바꿀 수도 있습니다
하지만 GET이 여기 설정된 기본값이므로
추가로 보낼 다른 정보는 필요하지 않습니다
그러므로 요청문 작성은 이 정도로 하겠습니다
이전 강의에서 배우셨듯이
fetch 함수는 프로미스(promise)를 반환합니다
여기에서 then 메소드로 함수를 정의하면
이 함수는 프로미스가 해결될 때 실행됩니다
즉 응답(response)을 받았을 때입니다
다릉 방법으로는 async/await 패턴을 사용할 수도 있지만
이 경우 컴포넌트 함수를 async 함수로 변환할 수 없습니다
리액트에서는 이런 변환이 불가능합니다
그러므로 이 요청을 처리하기 위해서
then을 사용하거나 이 부분을 새로운 함수로 감쌀 수 있습니다
이 새로운 함수는 fetchMeals라고 이름짓겠습니다
이제 이 함수가 async 함수가 됩니다
이것은 표준 함수로서
컴포넌트 함수에 포함되어 있기 때문입니다
이제 이 코드를 여기로 옮겨오면
await을 사용해서 응답(response)을 받아올 수 있습니다
이 때 응답에 오류가 있을 수도 있는데
이 경우는 요청이 실패했거나
서버 내에 어떤 문제가 생겼기 때문입니다
그러므로 만약 응답에 문제가 있다면(!response.ok)
즉 400이나 500쯤의 응답 상태 코드라면
어떤 조치를 취할 필요가 있습니다
또 따로 조치가 필요한 부분이 있습니다
만약 요청을 보내는 데에 실패한다면
즉 인터넷 연결이 안 되어 있는 등의 문제가 있다면
이 부분을 catch 등으로 감쌀 수 있습니다
이 작업은 조금 있다가 더 하겠습니다
일단은 이 부분을 최대한 간단하게 작성해서
요청을 보내도록 하고
응답에 문제가 없다고 가정하도록 하겠습니다
그러므로 이 코드도 조금 있다가 다시 작업하겠습니다
이제 응답으로 받았다고 가정한 데이터를 사용하겠습니다
이 작업을 위해 response.json을 호출하겠습니다
이 임시 백엔드가 데이터를 전송할 때
데이터 형식이 JSON이기 때문이기 때문입니다
그래서 이 JSON 데이터 형식을 사용해서
데이터를 추출한 뒤에
자바스크립트 객체 및 값으로 변환하려면
응답 객체에는 JSON 메소드를 사용해야 합니다
그리고 여기 보이듯이 이 메소드가 프로미스를 생산하기 때문에
여기에도 await을 사용해서
응답 데이터를 받을 수 있게 하겠습니다
여기서의 데이터 내용은 음식 메뉴입니다
그러므로 이름을 음식(meals)로 짓겠습니다
다시 임시 백엔드로 돌아가 보면
여기에서도 meals라는 항목을
이 파일에서 불러와서
프론트엔드로 보내고 있습니다
이렇게 음식(meals)를 가져왔습니다
다음으로는 이 음식 데이터가 포함된 UI를 업데이트하는 것입니다
여기서 응답과 데이터 추출에
await을 사용했기 때문에
컴포넌트 함수를 실행했을 때
음식 데이터를 즉각적으로
가져오지는 못할 것입니다
즉 조금의 지연이 있다는 의미인데
몇 밀리초밖에 되지 않지만 어쨌든 지연이 있습니다
그러므로 초반에는 음식이 여기 없을 것이므로
이것을 어떤 상태로 제어해야 할 필요가 생깁니다
이렇게 제어하면 초반에 음식 데이터가 없을 때도
UI 렌더링을 할 수 있게 됩니다
그리고 음식 데이터가 도착하면
UI를 그 새 데이터로 업데이트하는 것입니다
그러므로 여기에 useState 훅(hook)을 사용하겠습니다
위치는 아직 음식 컴포넌트입니다
그리고 리액트로부터 useState를 가져오겠습니다
그리고 여기에 빈 배열을 만들겠습니다
이 부분이 음식 상태여야 하는데
호출해왔다는 의미로 loadedMeals로 하겠습니다
그리고 이 음식 데이터는 초반에 바로 도착하지 않습니다
그러므로 시작 상태에서는 빈 배열입니다
하지만 13번째 줄에는 meals를 받아오므로
음식 데이터를 모두 가져온 뒤에는
다시 loadedMeals 상태를 업데이트합니다
이 작업을 위해 setLoadedMeals를 호출해서 loadedMeals를
백엔드에서 가져온 meals와 동일하도록 업데이트하겠습니다
상태 업데이트 작업이 완료되었으므로 이제
아래쪽에서 음식 데이터를 반환할 수 있습니다
일단은 간단하게 작업하기 위해서
loadedMeals 상태를 그대로 사용하고
각 메뉴 아이템을 목록 아이템으로 .map하겠습니다
목록의 키(key)는 meal.id입니다
이제 백엔드에서 제공하는 가상의 메뉴 각각에
이 id를 통해 접근할 수 있게 됩니다
그리고 각 메뉴에는 이름과
가격, 상세설명 그리고 이미지가 있습니다
지금은 일단 이름만 반환하겠습니다
Meals.jsx로 돌아와서
목록 아이템 태그 사이에
meals.name을 반환하겠습니다
이렇게 하면 이 코드가 작동하는지 확인할 수 있습니다
그리고 지금은 이 코드가 작동하지 않습니다
왜냐하면 지금은 이 음식 메뉴를 가져오고
상태를 업데이트하는 로직이
이 fetchMeals라는 함수에 포함되어 있는데
이 함수는 호출되지 않습니다
그러므로 지금 시점에서는
이 코드는 제대로 실행되지 않습니다
물론 이 컴포넌트 함수 속에서
해당 함수를 호출할 수는 있지만
이전에 이미 배우셨듯
이 방식을 추천드리지는 않습니다
이 방식이 부적절한 이유는
이 함수를 이 컴포넌트 함수 내부에서 호출한다면
물론 이 코드는 실행될 수 있겠지만
문제는 이 코드 내부에서
상태를 업데이트한다는 점입니다
상태를 업데이트하면 어떻게 됩니까?
이 상태를 포함하고 있는
컴포넌트 함수가 재실행됩니다
그러므로 Meals 컴포넌트 함수가
재실행됨으로써
이 코드 또한 재실행됩니다
이 코드가 재실행된다면
이 상태도 다시 설정됩니다
이렇게 되면 멈추지 않는 루프(loop)가 생깁니다
이 루프가 치명적인 이유는
결국에는 앱에 충돌이 생기기 때문입니다
그러므로 이렇게
예측하지 못한 변수으로 인해
무한 루프가 생긴다면
해결하기 위해서 useEffect 훅을 사용해야 합니다
이 훅에 대해서는 이전 강의에 다룬 적이 있습니다
이 훅을 통해 이 모든 부수적인 요소를
컴포넌트가 렌더링된 뒤에 실행할 수 있습니다
또 이렇게 하면 의존성(dependency) 배열을 정의하여
함수가 루프에 빠지는 그 시점에
그 함수를 제어할 수 있도록 합니다
그러므로 리액트에서 useEffect를 가져와서
여기 있는 Effect 함수를 정의하겠습니다
그리고 이 fetchMeals 함수와
이 함수에 대한 호출을
useEffect의 Effect 함수 안으로 옮기겠습니다
이 fetchMeals 함수를 옮길 때
물론 이 Effect 함수 바깥으로 옮겨도 되지만
여기에 의존성(dependency)으로서 추가하는 이유는
이 Effect 함수 속에서 사용하는 내용이
이 함수 바깥에서 정의되었기 때문입니다
이 경우에는 useCallback을 통해
컴포넌트 함수가 재실행될 때마다
fetchMeals가 재실행되지 않도록 할 수 있습니다만
이 작업은 사실 불필요합니다
fetchMeals 함수는 어차피
Effect 함수 내에서만 사용되기 때문입니다
그러므로 이 경우 가장 간단한 방식은
이 함수를 Effect 함수 안으로 옮기는 것입니다
이제 컴포넌트 함수가 실행된 이후에
이 함수 또한 실행됩니다
만약 의존성이 없거나
불변하는 의존성 뿐이라면
이 코드는 절대 재실행되지 않습니다
이 경우에 이전처럼 루프에 빠지지 앟을 수 있습니다
그러므로 여기에는 의존성을 더할 필요가 없습니다
이 Effect 함수는 이제
외부 속성이나 상태, 혹은
렌더링 도중 변화를 가져올 만한 값을 사용하지 않기 때문입니다
이 함수가 사용하는 외부의 것은
setLoadedMeals 함수 뿐입니다
이 함수는 useState 훅을 통해 제공되므로
리액트에서는 절대 변하지 않습니다
이 작업이 끝나면 이 부분은 정상적으로 작동하고
음식 메뉴 또한 모두 가져올 수 있게 됩니다
작동하는 것을 확인하기 위해
앱 컴포넌트로 넘어온 후에
음식 컴포넌트를 추가해 줍니다
이 때 음식 파일(Meals.jsx)에서 가져와야 합니다이대로 저장한 후 새로고침을 해 보면
이대로 저장한 후 새로고침을 해 보면
음식 데이터를 확인할 수 있습니다
현재로서는 메뉴 이름밖에 볼 수 없는데
메뉴 이름 말고 다른 것을 가져오지 않았기 때문입니다
어쨌든 이것으로 데이터를 가져올 수 있다는 것을 확인했습니다
즉 올바른 방향으로
필요한 작업을 완료했습니다