Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Redux] Infinite Scroll #12

Open
yjkwon07 opened this issue Aug 11, 2021 · 0 comments
Open

[Redux] Infinite Scroll #12

yjkwon07 opened this issue Aug 11, 2021 · 0 comments
Labels
redux 리덕스 관련 문제

Comments

@yjkwon07
Copy link
Owner

yjkwon07 commented Aug 11, 2021

Infinite Scroll

주요 Action

  • nextLoad(다음페이지) => isLoadingMore[check] => load
  • refresh(새로고침) => data[empty], query[reset] => load
  • parameterChange(페이지 필터 변경) => parameter[pageSize, keyword....] => load

고려해야할 사항

  • SSR

    • 이미 초기 데이터가 보존되어 있다면, 어떤식으로 데이터를 불러와야 할까?
      • 여기서 STATE === 'INIT' 상황이면 로드 하면된다고 생각했지만,
      • parameterChange할 때 useEffectload하도록 하기 때문에, 해당 STATE 비교로는 부족해 보인다.
      useEffect(() => {
        if(STATE === 'INIT') {
          load();
        } 
      }, [load])
      
      // 초기 렌더링에서도 불러오게 됨, => isInitLoad가 되어있던 상황이면, 중복된 데이터가 들어감
      useEffect(() => {
        load();
      }, [load])
  • cache

    • 캐시를 통해 페이지를 다시 불러올지 판단을 할까?
    • redux에서 캐시 (key[parameter]-value[listData]) 형태로 저장을 할까 생각해 봄
    • 근데 막상 mutate 처럼 데이터를 다시 로드 해야하거나, 추가된 데이터를 어디 key로 보관 되어야 할지에대해 고민이 커짐
    • 결론은 현재 redux에서 캐시 까지 더 하는건 너무 오버 스택 같다고 생각
    • 향후, swr, react-query를 적용해보는게 좋을거 같다고 판단.
  • refresh

    • 만약 초기값에서 refresh 할 때, 개별 useState를 관리하게 되면, load가 반응이 일어나지 않는다.

isLoadingMore

  • front => result.length === pageSize
  • back => get parameter(ex. nextPage)

load

  • observer => parameter[change] => isInitLoad?(중복된 api call 막기) => load
  • fetch => parameter 바뀔때마다, fetch
    • load해야하는 함수마다 query를 이용하는 제어 컴포넌트의 state와 싱크를 맞춰주는것을 신경써야 한다.
    • 부분적으로 observer형태가 나올 수 있다.(ex. nextLoad)

parameterChange

  • page => page[n] => load
  • another page(parameter) => resetData, page[1], parameter[pageSize, keyword....] => load

refresh

  • queryReset => resetData, parameter[INIT] => load

resetData

  • 페이지를 불러올 때, 메타 데이터isLoadMore 판단하여 list를 이어 붙일지 판단한다.
  • 하지만, 여기서 load를 할때만, 고정적으로 적용하여 만약, 데이터를 새롭게 추가한다면, resetData를 호출하고 load가 호출되도록 하면 된다.

페이지 네이션 전략

  • 이슈(페이지 네이션 전략)
  • 결론적으로 pageFilter사용하는 컴포넌트가 하나면 state 전략을 사용해도 되지만, 그 이상으로 사용하게 되면, state 전략은 지양하는게 좋을거 같다.

1. state 전략

useHooks

export default function useListReadPost({ page, pageSize }: ListReadPostUrlQuery) {
  const dispatch = useDispatch();
  const { status, data: result } = useFetchStatus(listReadPost.TYPE);
  const data = useAppSelector(postSelector.listData);

  const isInitFetch = useRef(!!data.length);
  const isLoadingMore = useMemo(() => result?.length, [result?.length]);

  const resetData = useCallback(() => {
    dispatch(listReadReset());
  }, [dispatch]);

  const load = useCallback(() => {
    dispatch(listReadPost.request({ page, pageSize }, { isLoadMore: true }));
  }, [dispatch, page, pageSize]);

  useEffect(() => {
    if (!isInitFetch.current) {
      load();
    } else {
      isInitFetch.current = false;
    }
  }, [load]);

  return { status, data, isLoadingMore, resetData, load };
}

parameterChange

  const handleChangePageSize = useCallback(
    (value) => {
      resetData();
      setPage(1);
      setPageSize(value);
    },
    [resetData],
  );

refresh

  • observer형태라면, resetData만 될것이고, queryReset하더라도 load가 되지 않을것이다.
    • 조건문을 추가
  const handleRefreshPostListData = useCallback(() => {
    resetData();
    if (page === 1 && pageSize === DEFAULT_PAGE_SIZE) {
      load();
    } else {
      setPage(1);
      setPageSize(DEFAULT_PAGE_SIZE);
    }
  }, [load, page, pageSize, resetData]);
  • 여기서 페이지 필터 파라미터들 같은 경우, object 형태로 묶어서 관리하는게 좋아 보인다.
  • object로 관리하게 되면, 리렌더링이 자주 발생되지 않을까 고민 되었지만, 여기서 부분 리렌더링 아닌 이상, 비슷한 포퍼먼스라고 판단 되었다.

2. redux replace 전략

  • state 전략의 단점은 다른 컴포넌트에서 같은 페이지 필터를 사용할 때, 싱크가 안맞게 되는것이다.
  • 공통적으로 state를 관리하는 곳이 필요하게 되었다.
  • 결론적으로 redux store에서 필터를 관리하는 state를 추가하게 되었다.

useHooks

export default function useListReadPost({ mode }: IProps) {
  const dispatch = useDispatch();
  const { status, data: result } = useFetchStatus(listReadPost.TYPE);
  const filter = useAppSelector(postSelector.listReadFilter);
  const data = useAppSelector(postSelector.listData);

  const isInitFetch = useRef(!!data.length);
  const isMoreRead = useMemo(() => result?.length, [result?.length]);

  const resetData = useCallback(() => {
    dispatch(listReadReset());
  }, [dispatch]);

  const filterChange = useCallback(
    (filters: Partial<IPostState['listReadFilter']>) => {
      dispatch(listReadFilterChange(filters));
    },
    [dispatch],
  );

  const refresh = useCallback(() => {
    resetData();
    dispatch(listReadFilterReset());
  }, [dispatch, resetData]);

  const load = useCallback(() => {
    dispatch(
      listReadPost.request({ page: filter.page, pageSize: filter.pageSize }, { isLoadMore: mode === 'infinite' }),
    );
  }, [dispatch, filter.page, filter.pageSize, mode]);

  useEffect(() => {
    if (!isInitFetch.current) {
      load();
    } else {
      isInitFetch.current = false;
    }
  }, [load]);

  return { status, data, isMoreRead, filter, resetData, load, filterChange, refresh };
}

parameterChange

  const handleChangePageSize = useCallback(
    (value) => {
      resetData();
      filterChange({
        page: 1,
        pageSize: value,
      });
    },
    [filterChange, resetData],
  );

refresh

  • useHooks에서 관리하도록 위임함

component 전체 코드

const ListReadView = () => {
  const {status, data: postListData, resetData, isMoreRead, filter, filterChange, refresh} = useListReadPost({
    mode: 'infinite',
  });
  const {page, pageSize} = filter;

  const nextLoad = useCallback(() => {
    if (isMoreRead) {
      filterChange({
        page: page + 1,
      });
    }
  }, [filterChange, isMoreRead, page]);

  const handleChangePageSize = useCallback(
    (value) => {
      resetData();
      filterChange({
        page: 1,
        pageSize: value,
      });
    },
    [filterChange, resetData],
  );

  useEffect(() => {
    const onScroll = throttle(function () {
      if (window.scrollY + document.documentElement.clientHeight > document.documentElement.scrollHeight - 300) {
        nextLoad();
      }
    }, 300);

    window.addEventListener('scroll', onScroll);
    return () => {
      window.removeEventListener('scroll', onScroll);
    };
  }, [nextLoad]);

  return (
    <>
      {/* ... */}
    </>
  );
}
@yjkwon07 yjkwon07 added the redux 리덕스 관련 문제 label Aug 11, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
redux 리덕스 관련 문제
Projects
None yet
Development

No branches or pull requests

1 participant