ToyProject(더다주) PagiNation

노영완·2023년 7월 21일
0

ToyProject(더다주)

목록 보기
3/13

시연영상

FE : React

BE : node mongodb mongoose

Pagination을 구현하는데 있어 기존 offset 방식이 아닌 cursor 방식이 있다고 해서 학습차 cursor 방식으로 구현해 보기로 했다.

offset vs cursor

offset 방식은 쿼리가 복잡하지 않다는 면에서 아무래도 큰 장점이 있다. 하지만 뒤로 갈수록 쿼리의 속도가 늦어지면 가장 큰 문제는 sns 같은 업데이트 삭제같은 데이터의 수정이 많이 생기고 또한, 데이터의 양이 많을때에는 데이터가 누락 및 중복이 될 수 있다는 큰 단점이 있다. 그걸 보완한 방식이 cursor 방식이다.

예를 들어

offset

[1 2 3 4 5 6 7 8 9 10] 이렇게 10개의 데이터가 있다고 가정. 두 개의 데이터씩만 보여주기로 할 때, limit(2)가 될 것이고 첫번째 페이지에는 [1,2]가 담겨서 보내질 것이다. 그리고 두번재 페이지에는 skip(2)가 되어 앞에 1,2의 데이터는 skip 다음 3,4에 데이터가 보내지게 되는데 이 때, 데이터를 불러오는 도중 데이터가 새로 생성 되었다고 가정하자
[0 1 2 3 4 5 6 7 8 9 10] 이렇게 0이라는 데이터가 새로 생성되었고 앞선 세번째 페이지에 데이터는 [4 5]가 될 것이다.
[1 2][3 4][4 5] 중복이 되었다 이러한 문제가 offset이 가진 문제이다.

cursor

데이터의 변동이 없는 혹은 데이터의 양이 많지 않을때는 보기편한 간편한 offset 방식이 맞지만 반대의 경우는 cursor 방식이 맞다고 생각이 든다. cursor 방식은 skip을 사용하지 않는다 비교연산자를 사용해 skip을 한다. mongodb 기준으로 _id를 사용해 _id기준 그 다음 _id의 값에 데이터를 가져오게 하는 것이다. 이러한 방식은 뚜렷한 값이 있으니 그 값에 따른 다음 데이터를 불러올 것이며 이 결과는 누락 및 중복이 발생할 일이 없을 것이다.

Server Code

productRouter.get("/main", async (req, res) => {
  try {
    const { lastid } = req.query;
    const conditionQuery = lastid ? { _id: { $gt: lastid } } : {};
    const [newProducts, count] = await Promise.all([
      await NewProduct.find(conditionQuery).sort({ _id: 1 }).limit(8),
      await NewProduct.count(),
    ]);
    return res.json({
      newProducts,
      totalPage: Math.ceil(count / 8),
    });
  } catch (e) {
    return res.status(500).json({ err: e.message });
  }
});

일단 기존의 offset 방식과는 다르게 query로 _id에 값이 들어온다. 그리고 find({})로 전체를 불러오는게 아닌 gt( 이 메서드는 초과된 다음의 값들을 불러온다는 뜻이다.) find({gt})를 통해 다음의 값들을 불러왔다. sort를 사용하지 않고 limit만으로 사용해 앞선 데이터 skip 및 8개의 데이터만 불러왔다.

Client Code

  const [mainNewData, setMainNewData] = useState<Array<ProductType>>([]);
  const [totalPage, setTotalPage] = useState();
  const dispatch: AppDispatch = useDispatch();
  const navigate = useNavigate();
  const mainNewProductAxios = useCallback(async () => {
    const data = await mainNewProduct("");
    return data;
  }, []);
  useEffect(() => {
    mainNewProductAxios().then(({ newProducts, totalPage }) => {
      setMainNewData(newProducts);
      setTotalPage(totalPage);
    });
  }, [mainNewProductAxios]);
  const paginationOnClick = () => {
    if (mainNewData.length === 0) return;
    const lastImgArr: ProductType = mainNewData[mainNewData.length - 1];
    const lastImgId = lastImgArr._id;
    mainNewProduct(`?lastid=${lastImgId}`).then(({ newProducts }) => {
      setMainNewData((prevData) => [...prevData, ...newProducts]);
    });
  };

lastImgArr로 가장 마지막 index에 값을 가져오고 _id에 값을 변수에 저장

1개의 댓글

comment-user-thumbnail
2023년 7월 21일

가치 있는 정보 공유해주셔서 감사합니다.

답글 달기