홈에서 원하는 노선번호 입력 시 해당 버스 페이지로 이동
const handleNavigateBusPage = useCallback(
async (e) => {
e.preventDefault();
// form에서 받은 busNumber로 busId 받아오기
let busId;
await axios
.get(`/api/info/${busNumber}`)
.then((res) => {
console.log(res.data);
busId = res.data.response.msgBody[0].busRouteList[0].routeId[0];
// 해당 노선페이지로 이동하면서 busNumber와 busId 전달하기
navigate(`/${busNumber}`, { state: { busNumber, busId } });
})
.catch((error) => console.log(error));
},
[busNumber, navigate],
);
홈페이지의 Form에서 사용자가 원하는 노선을 입력 후 '이동' 버튼을 클릭 시 공공데이터포털에서 해당 노선의 busId를 가져온다.
가져오기를 성공 하면 해당 노선페이지로 useNavigate를 사용하여 이동하면서 busNumber와 busId를 전달한다.
const BusPage = () => {
const location = useLocation();
const { busNumber, busId } = location.state;
const { data: timeTables } = useGetBusTimeTable();
const { data: busLocation } = useGetBusLocation(busNumber, busId);
return (
<div>
<h2>{busNumber}번 출발 시간표</h2>
{timeTables?.map((timeTable, i) => (
<li key={i}>{timeTable.departure}</li>
))}
</div>
);
};
BusPage에서는 해당 노선의 출발을 감지하면서 시간표를 업데이트하고 업데이트된 시간표를 렌더링해준다.
React Query로 버스 출발 감지하기
홈페이지에서 유저가 원하는 노선의 busNumber와 busId를 location의 state로 받아 함수를 실행한다.
const location = useLocation();
const { busNumber, busId } = location.state;
const { data: busLocation } = useGetBusLocation(busNumber, busId);
버스 실시간 위치를 가져오는 함수는 따로 모듈화를 해줬다.
export const useGetBusLocation = (busId, busNumber) => {
const { data, isLoading, error, refetch } = useQuery(
['busLocation'],
async () => {
// 실시간 버스 가져오는 쿼리 함수
return await axios.get(`/api/location/${busId}`).then((res) => {
// 성공 시 실시간 위치 반환
return res.data;
});
},
{
// useQuery 옵션
cacheTime: Infinity,
retry: 3,
retryDelay: 10 * 60 * 1000,
refetchInterval: 30 * 60 * 1000,
refetchIntervalInBackground: true,
},
);
// useQuery의 반환 데이터
return { data, isLoading, error, refetch };
};
여기서 이제 필요한 옵션들을 추가적으로 작성해줬다.
- 30초마다 refetch해서 버스의 실시간 위치를 가져오기
- 버스가 기점에서 출발했다면 DB에 시간을 추가해주는 POST요청 보내기
- 배차간격동안에는 계속 refetch 할 필요가 없으므로 refetchInterval 시간 조정하기
수정한 코드는 아래와 같다.
export const useGetBusLocation = (busId, busNumber) => {
// refetchIntervalTime을 state로 관리하기
const [refetchIntervalTime, setRefetchIntervalTime] = useState(30 * 1000);
const { data, isLoading, error, refetch } = useQuery(
['busLocation'],
async () => {
return await axios.get(`/api/location/${busId}`).then((res) => {
// 현재 시간 dayjs 라이브러리 사용
const now = dayjs();
res.data.response.msgBody[0].busLocationList.forEach((location) => {
// 운행 중인 모든 버스 중에 현재 정류장이 기점인 경우
if (parseInt(location.stationSeq[0]) <= 1) {
console.log(now.format('동탄국제고등학교 : HH시 mm분'));
// 서버로 시간표에 시간 추가 POST 요청
axios
.post(`/api/timetable/departure`, { busId, busNumber })
.then((res) => {
// refetch 시간 10분으로 설정
setRefetchIntervalTime(10 * 60 * 1000);
// 10분 후에 다시 30초로 설정
setTimeout(() => {
setRefetchIntervalTime(30 * 1000);
}, 10 * 60 * 1000);
})
.catch((error) => {
console.log(error);
});
} else {
console.log(now.format('HH:mm:ss 기점 버스 없음'));
}
});
// useQuery의 data값으로 리턴
return res.data;
});
},
{
cacheTime: Infinity,
retry: 3,
retryDelay: 10000,
refetchInterval: refetchIntervalTime, // state로 시간 관리
refetchIntervalInBackground: true,
},
);
return { data, isLoading, error, refetch };
};
일단 refetchIntervalTime을 상황에 따라 30초,10분로 쉽게 관리할 수 있도록 state를 사용했다.
운행 중인 모든 버스 중에 현재 정류장이 기점인 경우에 서버로 시간표에 시간을 추가해달라는 POST 요청을 보낸다.
보내고 성공 시, 다음 버스의 배차간격을 생각해 refetch 시간을 10분으로 설정해 의미없는 요청을 줄였다.
10분 후에는 다시 30초로 설정되어 버스 출발을 감지한다.
기점에서 버스 출발 시 시간표 등록
기점에서 버스가 출발하면 서버로 POST 요청을 하게 된다.
요청과 함께 받은 busNumber로 DB에서 기존에 시간표가 있는지 검색한다.
await TimeTables.findOne({ busNumber: req.body.busNumber }).then((timeTable) => {
DB에 시간표가 없을 경우 DB를 생성하고 요일과 시간을 추가해준다.
if (!timeTable) {
console.log(
`DB에 ${req.body.busNumber}버스 시간표가 없습니다. 새로운 시간표를 생성합니다.`,
);
TimeTables.create({
busNumber: req.body.busNumber,
busId: req.body.busId,
table: [
{
// 요일,시간 (표 UI 작성 시 요일로 구분해서 사용)
departure: now.format('ddd,HH:mm'),
},
],
})
.then((timeTable) => {
timeTable
.save()
.then(() =>
res.status(201).send(`${req.body.busNumber}번 시간표를 업데이트 했습니다.`),
);
})
.catch((error) => console.error(error));
요일은 나중에 표 형식으로 UI를 작성 할 때, 요일 별로 구분해주기 위해서 추가해줬다.
DB에 시간표가 있을 경우 요일과 시간만 등록한다.
else {
timeTable.table.push({
departure: now.format('ddd,HH:mm'),
});
timeTable
.save()
.then(() => res.status(201).send(`${req.body.busNumber}번 시간표를 업데이트 했습니다.`));
}
아래는 전체 코드다.
router.post(`/timetable/departure`, async (req, res, next) => {
try {
console.log('요청 버스 정보:: ', req.body);
// DB에서 버스번호로 시간표 찾기
await TimeTables.findOne({ busNumber: req.body.busNumber }).then((timeTable) => {
const now = dayjs(); // 요청 받았을 때의 시간을 저장해야하므로 스코프 주의
// DB에 시간표가 없을 경우
// DB 생성 후 요일과 시간 등록
if (!timeTable) {
console.log(
`DB에 ${req.body.busNumber}버스 시간표가 없습니다. 새로운 시간표를 생성합니다.`,
);
TimeTables.create({
busNumber: req.body.busNumber,
busId: req.body.busId,
table: [
{
// 요일,시간 (표 UI 작성 시 요일로 구분해서 사용)
departure: now.format('ddd,HH:mm'),
},
],
})
.then((timeTable) => {
timeTable
.save()
.then(() =>
res.status(201).send(`${req.body.busNumber}번 시간표를 업데이트 했습니다.`),
);
})
.catch((error) => console.error(error));
} else {
// DB에 시간표가 있을 경우
// 요일과 시간만 등록
timeTable.table.push({
departure: now.format('ddd,HH:mm'),
});
timeTable
.save()
.then(() => res.status(201).send(`${req.body.busNumber}번 시간표를 업데이트 했습니다.`));
}
});
} catch (error) {
next(error);
}
});
몇시간 감지해본 결과 DB에 잘 저장이 된 모습이다.