Upgrade to Pro — share decks privately, control downloads, hide ads and more …

눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유

kakao
December 08, 2022

눈에 보이지 않는 개선: My구독의 Redux에서 React-Query 전환 경험 공유

#React #React-Query #Tanstack-Query #Redux #리팩토링 #React16 #React18

My구독은 보이지 않은 개선을 진행하고 있는데요
그중에서 Redux에서 React-Query로 전환했던 그 이유와 과정을 소개하고 그 과정에서 만났던 이슈를 어떻게 해결했는지 공유합니다.
이번 발표를 통해서 React 16과 React 18에서 React-Query를 도입하거나 기존 Redux 구조를 개선하려고 시도하실 때 참고가 되었으면 좋겠습니다.

발표자 : fred.dy
카카오 FE플랫폼팀의 톡FE파트에서 프론트 앤드 개발자로 일하고 있는 프레드입니다. My구독과 이모티콘 스토어 프로젝트를 담당하고 있습니다.

kakao

December 08, 2022
Tweet

More Decks by kakao

Other Decks in Programming

Transcript

  1. My구독의 Redux에서 React - Query 전환 경험 공유 Copyright 2022.

    Kakao Corp. All rights reserved. Redistribution or public display is not permitted without written permission from Kakao. 눈에 보이지 않는 개선 이동건 fred.dy 카카오 if(kakao)2022
  2. 톡서랍 플러스 이모티콘 플러스 My 구독 카카오톡 데이터 백업 팀채팅

    연락처 패스워드 등 다양한 이모티콘 사용 이모티콘 자동 추천 랜덤 이모티콘 추천 검색 등
  3. React - Query 조금 알아보기 Redux에서 React - Query로 전환

    이유 React - Query로 전환 과정 정리
  4. React - Query 조금 알아보기 Redux에서 React - Query로 전환

    이유 React - Query로 전환 과정 정리
  5. React 동기화 캐싱 const CacheContext = React.createContext(null); const { Provider

    } = CacheContext; function CacheProvider({ children }) { const [cache, setCache] = useState({}); ... return <Provider value={value}>{children}</Provider>; } 데이터 패칭 const [data, setData] = useState(null); const fetch = useCallback(() => { ... }, []); useEffect(() => { fetch(); }, []); return { data, refetch: fetch }; useEffect(() => { const interval = setInterval(handleFocus, cacheTime) window.addEventListener('focus', handleFocus, false); ... return () => { clearInterval(interval); window.removeEventListener('focus', handleFocus, false); ... }; }, []);
  6. React 동기화 캐싱 const CacheContext = React.createContext(null); const { Provider

    } = CacheContext; function CacheProvider({ children }) { const [cache, setCache] = useState({}); ... return <Provider value={value}>{children}</Provider>; } 데이터 패칭 const [data, setData] = useState(null); const fetch = useCallback(() => { ... }, []); useEffect(() => { fetch(); }, []); return { data, refetch: fetch }; useEffect(() => { const interval = setInterval(handleFocus, cacheTime) window.addEventListener('focus', handleFocus, false); ... return () => { clearInterval(interval); window.removeEventListener('focus', handleFocus, false); ... }; }, []); 테스트 시간
  7. React - Query QueryClient (Context) useQuery useMutation React Context API

    useEffect useState useCallback 캐싱 에 러 핸 들 링
  8. - React에서 제공하는 기본 Hook으로도 충분히 멋진 비동기 처리 커스텀

    훅을 만들 수 있습니다. \😆/ - 필요한 기능만 구현할 수 있으며 별도의 라이브러리 설치가 필요 없습니다. 😁 - 기본 Hook으로 만들기 위해서는 시간과 노력이 필요합니다. 🤔 - React - Query 대비 안정성을 확보하기 어렵습니다. 😭 React - Query 없이 React 자체만으로는 불가능하나요? Releases 4 days ago Star 29.9K Download 6.3M / month * 2022.09 기준, 출처 : https:/ /github.com/tanstack/query
  9. React - Query 조금 알아보기 Redux에서 React - Query로 전환

    이유 React - Query로 전환 과정 정리
  10. Sagas 결제 정보 API 사용자 정보 API Sagas 구독 정보

    API Sagas 상품 정보 API Sagas API Sagas API Sagas API Sagas
  11. Sagas 결제 정보 API 사용자 정보 API Sagas 구독 정보

    API Sagas 상품 정보 API Sagas API Sagas API Sagas API Sagas
  12. Sagas 결제 정보 API 사용자 정보 API Sagas 구독 정보

    API Sagas 상품 정보 API Sagas API Sagas API Sagas API Sagas
  13. 사용자 정보 API Query 구독 정보 API Query 상품 정보

    API Query 결제 정보 API Query export function useData({ ...state }) { useEffect(() => { actions.fetchҳة੿ࠁAPI(); }, []); const { ҳة੿ࠁ } = state; return ҳة੿ࠁ; } export function useData() { const { data, isLoading } = useQuery(KEY1, fetch࢚ಿ੿ࠁAPI); const { data: ҳة੿ࠁ } = useQuery( KEY2, () => fetchҳة੿ࠁAPI(data), { enabled: !isLoading } ); return ҳة੿ࠁ; } Redux React - Query
  14. 클라이언트 서버 페이지 이름 모 달 loadStatus 사용자 정보 구독

    상태 상 품 정 보 결 제 정 보 구독 정보 배너 정보
  15. Redux 페이지 이름 모 loadStatus 구 독 상 태 상

    품 정 보 결 제 정 보 구독 정보 배너 정보
  16. 클라이언트 서버 페이지 이름 모 달 loadStatus 사용자 정보 구독

    상태 상 품 정 보 결 제 정 보 구독 정보 배너 정보
  17. Context API constate recoil redux … React - Query RTK

    - Query Apollo SWR … … … 페이지 이름 모 달 loadStatus 사용자 정보 구독 상태 상 품 정 보 결 제 정 보 구독 정보 배너 정보
  18. useEffect(() => { actions.fetchѾઁ੿ࠁ(); }, []); useEffect(() => { if

    (loadStatus?.fetchѾઁ੿ࠁ?.error) { actions.fetching('fetchѾઁ੿ࠁ'); history.replace("/"); } }, [loadStatus?.fetchѾઁ੿ࠁ?.error]); Redux에서의 에러 핸들링
  19. - Redux에서 API 상태에 따라 화면을 구성하기 위해서는 별도의 도구나

    상태가 필요합니다. 😭 - Redux - Saga는 의존성이 깊은 구조를 만들어 낼 수도 있습니다. 😢 - Redux는 간단한 API 추가에도 장황한 Boilerplate가 필요합니다. 😅 - Redux는 API 에러 핸들링 과정에서 다소 불필요한 작업이 발생할 수 있습니다. 🥲 다른 라이브러리 대신 React - Query를 사용하는 이유가 있나요? - 우수한 비동기 처리 라이브러리가 많이 있습니다. \😆/ - 사용하는 방식, 구조, 기능에서 React - Query가 더 적합하다고 판단하여 사용하게 되었습니다. 🤔
  20. React - Query 조금 알아보기 Redux에서 React - Query로 전환

    이유 React - Query로 전환 과정 정리
  21. React - Query로 전환 과정 Redux 고차 컴포넌트 -> Redux

    Hook Redux Hook -> React - Query React 16 -> React 18
  22. 고차 컴포넌트 전체적인 코드 구조 변경 API & 로직 누락

    발생할 수 있음 Redux 고차 컴포넌트 -> Redux Hook 불필요한 re - render 발생할 수 있음 Props drilling Production …
  23. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑);
  24. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑); Redux connect 함수
  25. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑); dispatch selector
  26. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑);
  27. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑); selector dispatch
  28. Redux 고차 컴포넌트 -> Redux Hook function ࢚ಿ࢚ࣁಕ੉૑({ ঘ࣌, ...࢚క

    }) { useEffect(() => { ঘ࣌.fetchҳةؘ੉ఠ({ productGroupId }); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const { ҳةؘ੉ఠ, loadStatus } = ࢚క; const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } export default connectStore(࢚ಿ࢚ࣁಕ੉૑);
  29. Redux 고차 컴포넌트 -> Redux Hook export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const dispatch = useDispatch(); useEffect(() => { dispatch(actions.fetchҳةؘ੉ఠ({ productGroupId })); }, [productGroupId]); useEffect(() => { // ী۞ ೩ٜ݂ }); const ҳةؘ੉ఠ = useSelector(({ ҳةؘ੉ఠ }) => ҳةؘ੉ఠ); const loadStatus = useSelector(({ loadStatus }) => loadStatus); const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; }
  30. Redux 고차 컴포넌트 -> Redux Hook export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const dispatch = useDispatch(); useEffect(() => { dispatch(actions.fetchҳةؘ੉ఠ({ productGroupId })); }, [productGroupId]); useEffect(() => { // ী۞ ೩ٜ݂ }); const ҳةؘ੉ఠ = useSelector(({ ҳةؘ੉ఠ }) => ҳةؘ੉ఠ); const loadStatus = useSelector(({ loadStatus }) => loadStatus); const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } 액션 , 상태 삭제
  31. Redux 고차 컴포넌트 -> Redux Hook export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const dispatch = useDispatch(); useEffect(() => { dispatch(actions.fetchҳةؘ੉ఠ({ productGroupId })); }, [productGroupId]); useEffect(() => { // ী۞ ೩ٜ݂ }); const ҳةؘ੉ఠ = useSelector(({ ҳةؘ੉ఠ }) => ҳةؘ੉ఠ); const loadStatus = useSelector(({ loadStatus }) => loadStatus); const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } useDispatch 추가 useSelector 추가
  32. Redux 고차 컴포넌트 -> Redux Hook export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const dispatch = useDispatch(); useEffect(() => { dispatch(actions.fetchҳةؘ੉ఠ({ productGroupId })); }, [productGroupId]); useEffect(() => { // ী۞ ೩ٜ݂ }); const ҳةؘ੉ఠ = useSelector(({ ҳةؘ੉ఠ }) => ҳةؘ੉ఠ); const loadStatus = useSelector(({ loadStatus }) => loadStatus); const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } API 요청
  33. loadStatus Header Component Content Component Footer Component 사용자 정보 API

    매대 정보 API 상품 정보 API Redux Hook 최적화
  34. Redux Hook 최적화 const fetchࢎਊ੗ؘ੉ఠ = useSelector(({ loadStatus }) =>

    loadStatus.fetchࢎਊ੗ؘ੉ఠ); const fetchݒ؀ؘ੉ఠ = useSelector(({ loadStatus }) => loadStatus.fetchݒ؀ؘ੉ఠ); const fetch࢚ಿؘ੉ఠ = useSelector(({ loadStatus }) => loadStatus.fetch࢚ಿؘ੉ఠ); Redux Hook을 최적화하는 이유 - 전체적인 구조가 변경됨에 따라 언제 작업이 마무리될지 모릅니다. - 작업 도중 다른 작업이 발생할 수 있습니다. - 해결할 수 있는 부분을 놔두기가 애매합니다.
  35. Redux Hook 최적화 const fetchࢎਊ੗ؘ੉ఠ = useSelector(({ loadStatus }) =>

    loadStatus.fetchࢎਊ੗ؘ੉ఠ); const fetchݒ؀ؘ੉ఠ = useSelector(({ loadStatus }) => loadStatus.fetchݒ؀ؘ੉ఠ); const fetch࢚ಿؘ੉ఠ = useSelector(({ loadStatus }) => loadStatus.fetch࢚ಿؘ੉ఠ); export default function useLoadStatus(deps = []) { return useSelector( ({ loadStatus }) => deps.reduce((status, key) => ({ ...status, [key]: loadStatus[key] }), {}), isEqual ); }
  36. Redux Hook 최적화 const loadStatus = useLoadStatus([‘ fetchࢎਊ੗ؘ੉ఠ ', ‘

    fetchݒ؀ؘ੉ఠ ‘,' fetch࢚ಿؘ੉ఠ ‘]); export default function useLoadStatus(deps = []) { return useSelector( ({ loadStatus }) => deps.reduce((status, key) => ({ ...status, [key]: loadStatus[key] }), {}), isEqual ); }
  37. Redux Hook 최적화 const loadStatus = useLoadStatus([‘ fetchࢎਊ੗ؘ੉ఠ ', ‘

    fetchݒ؀ؘ੉ఠ ‘,' fetch࢚ಿؘ੉ఠ ‘]); 0 19 38 기존 useSelector useLoadStatus
  38. Redux Hook 최적화 const loadStatus = useLoadStatus([‘ fetchࢎਊ੗ؘ੉ఠ ', ‘

    fetchݒ؀ؘ੉ఠ ‘,' fetch࢚ಿؘ੉ఠ ‘]); 0 19 38 기존 useSelector useLoadStatus
  39. Redux Hook -> React - Query React - Query로 전환

    과정 Redux 고차 컴포넌트 ->
  40. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const dispatch = useDispatch(); useEffect(() => { dispatch(actions.fetchҳةؘ੉ఠ({ productGroupId })); }, [productGroupId]); useEffect(() => { if (loadStatus?.fetchҳةؘ੉ఠ?.error) { error(); } }); const ҳةؘ੉ఠ = useSelector(({ ҳةؘ੉ఠ }) => ҳةؘ੉ఠ); const loadStatus = useSelector(({ loadStatus }) => loadStatus); const fetched = isFetched(deps, loadStatus); return fetched && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; }
  41. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; }
  42. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } loadStatus 대신 사용
  43. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } Dispatch & Selector 대신 사용
  44. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } 에러 핸들링
  45. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } with React 16
  46. Redux Hook -> React - Query export default function ࢚ಿ࢚ࣁಕ੉૑()

    { const { data: ҳةؘ੉ఠ, isLoading } = useQuery(['ҳةؘ੉ఠ'], () => fetchҳةؘ੉ఠ(productGroupId), { enabled: productGroupId, onError: () => { error(); }, }); return !isLoading && <Layout ҳةؘ੉ఠ={ҳةؘ੉ఠ} />; } with React 16 if (!ݒ؀Success || !࢚ಿSuccess || (!੉ਊӂIdle && !੉ਊӂSuccess) || ੌद઺૑࢚ಿLoading) { return null; }
  47. React - Query 개선 - useQueriesLoading export function useQueriesLoading() {

    const client = useQueryClient(); const queries = client.getQueryCache().findAll(); const [queryChangedCount, setQueryChangedCount] = useState(0); useEffect(() => { if (queries) { setQueryChangedCount(queries.length); } }, []); useEffect(() => { if (queries.every(({ state }) => state.status !== 'loading') && queryChangedCount >= 0) { setQueryChangedCount((prevState) => prevState - 1); } }); return queryChangedCount >= 0 }
  48. React - Query 개선 - useQueriesLoading export function useQueriesLoading() {

    const client = useQueryClient(); const queries = client.getQueryCache().findAll(); const [queryChangedCount, setQueryChangedCount] = useState(0); useEffect(() => { if (queries) { setQueryChangedCount(queries.length); } }, []); useEffect(() => { if (queries.every(({ state }) => state.status !== 'loading') && queryChangedCount >= 0) { setQueryChangedCount((prevState) => prevState - 1); } }); return queryChangedCount >= 0 } 등록된 쿼리 가져오기
  49. React - Query 개선 - useQueriesLoading export function useQueriesLoading() {

    const client = useQueryClient(); const queries = client.getQueryCache().findAll(); const [queryChangedCount, setQueryChangedCount] = useState(0); useEffect(() => { if (queries) { setQueryChangedCount(queries.length); } }, []); useEffect(() => { if (queries.every(({ state }) => state.status !== 'loading') && queryChangedCount >= 0) { setQueryChangedCount((prevState) => prevState - 1); } }); return queryChangedCount >= 0 } 전체 쿼리 개수를 state 저장
  50. React - Query 개선 - useQueriesLoading export function useQueriesLoading() {

    const client = useQueryClient(); const queries = client.getQueryCache().findAll(); const [queryChangedCount, setQueryChangedCount] = useState(0); useEffect(() => { if (queries) { setQueryChangedCount(queries.length); } }, []); useEffect(() => { if (queries.every(({ state }) => state.status !== 'loading') && queryChangedCount >= 0) { setQueryChangedCount((prevState) => prevState - 1); } }); return queryChangedCount >= 0 } loading 상태인지 확인
  51. React - Query 개선 - useQueriesLoading if (!ݒ؀Success || !࢚ಿSuccess

    || (!੉ਊӂIdle && !੉ਊӂSuccess) || ੌद઺૑࢚ಿLoading) { return null; } const isQueriesLoading = useQueriesLoading(); if(isQueriesLoading){ return null }
  52. Query 작성 방식 사용자 정보 API 매대 정보 API 상품

    정보 API Header Component Content Component Footer Component
  53. 기본 Query (기본 Mutation) Query 작성 방식 export function Component()

    { const { data: ࢚ಿ੿ࠁ, isLoading } = use࢚ಿ੿ࠁ(); const { data: ҳة੿ࠁ } = useҳة੿ࠁ({ ࢚ಿ੿ࠁ, options: { enabled: !isLoading }}); return <Page ҳة੿ࠁ={ҳة੿ࠁ} />; } export function useData() { const { data: ࢚ಿ੿ࠁ, isLoading } = use࢚ಿ੿ࠁ(); const { data: ҳة੿ࠁ } = useҳة੿ࠁ({ ࢚ಿ੿ࠁ, options: { enabled: !isLoading }}); return ҳة੿ࠁ; } 컴포넌트 커스텀 훅
  54. Query 작성 방식 - 페이지 단위 Key 값 추가 -

    Select에서 실질적인 데이터가 있는 값만 반환 - onError 옵션이 있어야 useErrorBoundary를 설정하도록 처리 import { useQuery as useQueryOrigin } from 'react-query'; export default function useQuery(queryKey, queryFn, options = {}) { const { … } = options; return useQueryOrigin(queryKey, queryFn, { … }); } -> 페이지 단위 쿼리 초기화 가능 -> 쿼리를 사용할 때 중복 Select 방지 -> 에러 핸들링 누락 방지
  55. Redux + Redux - Saga 폴더 구조 개선 - data

    - 매대데이터 - actions - actionTypes - reducers - sagas - services - 홈데이터 React - Query - hooks - services - queries - 매대데이터 - 홈데이터 - mutations
  56. 폴더 구조 개선 actions actionTypes reducers sagas services export function

    useݒ؀({ options } = {}) { return useQuery([queryKeys.ݒ؀], () => api.get(`/ݒ؀/API`), options); } action actionType reducer saga service
  57. 폴더 구조 개선 actions actionTypes reducers sagas services export function

    useݒ؀({ options } = {}) { return useQuery([queryKeys.ݒ؀], () => api.get(`/ݒ؀/API`), options); } action actionType reducer saga service
  58. 폴더 구조 개선 Redux + Redux - Saga - data

    - 매대데이터 - actions - actionTypes - reducers - sagas - services - 홈데이터
  59. 폴더 구조 개선 React - Query - hooks - services

    - queries - 매대데이터 - 홈데이터 - mutations
  60. 폴더 구조 개선 React - Query - hooks - services

    - queries - 매대데이터 - 홈데이터 - mutations POST, DELETE … GET
  61. 폴더 구조 개선 - hooks - services - queries -

    매대데이터 - 홈데이터 - mutations React - Query
  62. 폴더 구조 개선 - hooks - services - queries -

    매대데이터 - 홈데이터 - mutations Page Component Hook React - Query
  63. My구독 중복 API 및 로직 최소화 API 네이밍 import 경로에서

    목적 표현 import { useݒ؀ؘ੉ఠ } from '@/hooks/queries/ݒ؀ؘ੉ఠ'; 목적
  64. React 16 -> React 18 function ࢚ಿ࢚ࣁۨ੉ইਓ() { ... const

    { data } = useQuery(...); return <Component data={data} />; } function ࢚ಿ࢚ࣁಕ੉૑() { ... const { isSuccess } = useQuery(...); const { isSuccess: ҳة੿ࠁSuccess } = useQuery(...); const isRender = isSuccess && ҳة੿ࠁSuccess; return isRender && <࢚ಿ࢚ࣁۨ੉ইਓ></࢚ಿ࢚ࣁۨ੉ইਓ>; } Redux 데이터 바탕으로 화면 구성 데이터 요청 및 렌더링 여부 결정
  65. function ࢚ಿ࢚ࣁۨ੉ইਓ() { ... const { data } = useQuery(...);

    return <Component data={data} />; } function ࢚ಿ࢚ࣁಕ੉૑() { ... const { isSuccess } = useQuery(...); const { isSuccess: ҳة੿ࠁSuccess } = useQuery(...); const isRender = isSuccess && ҳة੿ࠁSuccess; return isRender && <࢚ಿ࢚ࣁۨ੉ইਓ></࢚ಿ࢚ࣁۨ੉ইਓ>; } 중복 요청 re - render React 16 -> React 18
  66. React 16 -> React 18 function ࢚ಿ࢚ࣁۨ੉ইਓ() { ... const

    { data } = useQuery(...); return <Component data={data} />; } function ࢚ಿ࢚ࣁಕ੉૑() { ... const { isSuccess } = useQuery(...); const { isSuccess: ҳة੿ࠁSuccess } = useQuery(...); const isRender = isSuccess && ҳة੿ࠁSuccess; return isRender && <࢚ಿ࢚ࣁۨ੉ইਓ></࢚ಿ࢚ࣁۨ੉ইਓ>; }
  67. React 16 -> React 18 function ࢚ಿ࢚ࣁಕ੉૑() { ... return

    <࢚ಿ࢚ࣁۨ੉ইਓ />; } function Route() { ... return <Suspense><࢚ಿ࢚ࣁಕ੉૑ /></Suspense>; } function ࢚ಿ࢚ࣁۨ੉ইਓ() { ... const { data } = useQuery(...); return <Component data={data} />; } isRender 분기 삭제
  68. React 16 -> React 18 function ࢚ಿ࢚ࣁಕ੉૑() { ... const

    { data } = useQuery(...); return <Component data={data} />; } function Route() { ... return <Suspense><࢚ಿ࢚ࣁಕ੉૑ /></Suspense>; }
  69. React 16 -> React 18 function ࢚ಿ࢚ࣁಕ੉૑() { ... const

    { data } = useQuery(...); return <Component data={data} />; } function Route() { ... return <Suspense><࢚ಿ࢚ࣁಕ੉૑ /></Suspense>; } function ࢚ಿ࢚ࣁۨ੉ইਓ() { ... const { data } = useQuery(...); return <Component data={data} />; } function ࢚ಿ࢚ࣁಕ੉૑() { ... const { isSuccess } = useQuery(...); const { isSuccess: ҳة੿ࠁSuccess } = useQuery(...); const isRender = isSuccess && ҳة੿ࠁSuccess; return isRender && <࢚ಿ࢚ࣁۨ੉ইਓ></࢚ಿ࢚ࣁۨ੉ইਓ>; } 이전 이후
  70. - My구독 특성과 상황 덕분에 총 3단계로 나누어 전환작업을 진행했습니다.

    - 과정마다 그 상황에 맞춰 최대한 더 좋은 방향으로 진행할 수 있도록 여러 도전을 진행했습니다. - Redux Hook 기반으로 전환할 때 select를 좀 더 최적화하는 방법에 대해 공유했습니다. - React - Query로 전환하는 과정에서 React 16에서 상태 지옥에서 벗어나는 방법에 대해 공유했습니다. - My구독에서 React - Query를 어떻게, 어떤 구조로 사용하는지 공유했습니다. - React 18로 전환하면서 어떤 이점이 발생하는지 공유했습니다. Redux에서 React - Query로 전환 과정
  71. React - Query 조금 알아보기 Redux에서 React - Query로 전환

    이유 React - Query로 전환 과정 정리
  72. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다.
  73. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다. 비동기 처리 상태 isLoading isFetching isSuccess … In fi nite Queries
  74. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다. In fi nite Queries My구독 화면 render 여부 Query enabled 처리 결제 내역 화면 비동기 처리 상태
  75. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다. - 백그라운드 패칭을 지원합니다. Background Fetching focus, mount, interval …
  76. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다. - 백그라운드 패칭을 지원합니다. My구독 이용권 등록 및 변경 페이지 구독 상세 페이지 … Background Fetching
  77. 이런 점에서는 좋았습니다. \😆/ - Hook 기반을 제공하고 캐싱을 지원합니다.

    - 비동기 처리를 위한 유용한 도구를 제공합니다. - 백그라운드 패칭을 지원합니다. 게시판 댓글 좋아요 / 싫어요 주식 목록
  78. 이런 상황에서는 고민을 해보세요. 🤔 - 서버 사이드 데이터가 거의

    없는 경우 -> 서버 사이드 데이터가 더 많아질 때 적용
  79. - React 18에서 비동기 처리 상태때문에 도입하는 경우 이런 상황에서는

    고민을 해보세요. 🤔 - 서버 사이드 데이터가 거의 없는 경우 -> Suspense 사용
  80. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. Redux - Saga의 takeLatest
  81. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. function useExecutionOnce(func, delay) { ... return async (...args) => { if (!completeRef.current) { return; } completeRef.current = false; try { await func(...args); await waitMutating(); } catch (e) { throw e; } }; }
  82. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. function useExecutionOnce(func, delay) { ... return async (...args) => { if (!completeRef.current) { return; } completeRef.current = false; try { await func(...args); await waitMutating(); } catch (e) { throw e; } }; } 비동기 작업 대기
  83. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. function useExecutionOnce(func, delay) { ... return async (...args) => { if (!completeRef.current) { return; } completeRef.current = false; try { await func(...args); await waitMutating(); } catch (e) { throw e; } }; } useIsMutaing 기반 mutation 작업 대기
  84. Storybook 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만

    호출하기 위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. My구독 E2E Test Snapshot Test
  85. Storybook 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만

    호출하기 위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. My구독 E2E Test Snapshot Test Mock Service Worker
  86. 구독 완료 페이지 이런 아쉬운 점이 있습니다. 😭 - Mutation은

    한 번만 호출하기 위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. 1회성 페이지 에러 재요청
  87. 구독 완료 페이지 이런 아쉬운 점이 있습니다. 😭 - Mutation은

    한 번만 호출하기 위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. 1회성 API refetchOnWindowFocus: false, retry: 0,
  88. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. - 전체적인 데이터 흐름을 파악하기 어렵다는 느낌을 받기도 합니다. Pure 컴포넌트 상태 컴포넌트
  89. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. - 전체적인 데이터 흐름을 파악하기 어렵다는 느낌을 받기도 합니다. Pure 컴포넌트 상태 컴포넌트 API 데이터 중 하나만 필요
  90. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. - 전체적인 데이터 흐름을 파악하기 어렵다는 느낌을 받기도 합니다. Pure 컴포넌트 상태 컴포넌트 Props drilling API 데이터 중 하나만 필요
  91. 이런 아쉬운 점이 있습니다. 😭 - Mutation은 한 번만 호출하기

    위해 Wrapper 함수가 필요합니다. - UI 테스트를 진행하게 될 때 Mock API가 필요할 수 있습니다. - 항상 서버 데이터와 같은 데이터를 바라보는 것이 좋은 것은 아닙니다. - 전체적인 데이터 흐름을 파악하기 어렵다는 느낌을 받기도 합니다.
  92. 참고 자료 & 이미지 출처 21) React-Query ܾܻૉ, Star, ׮਍۽٘

    ੿ࠁ https://github.com/tanstack/query 49, 50, 51, 52, 53) ࠺زӝ ࢚క ҙܻ ۄ੉࠳۞ܻ ࠺Ү https://react-query-v3.tanstack.com/comparison 51) RTK-Query ੿ࠁ https://redux-toolkit.js.org/rtk-query/overview 52) apollo ੿ࠁ https://www.apollographql.com/ 53) SWR ੿ࠁ https://swr.vercel.app/ko 90-94) useQueriesLoading ղਊ https://tech.kakao.com/2022/06/13/react-query/ 120, 121, 122) IE, Chrome, Safari, React ই੉௑ https://commons.wikimedia.org/ 122) React-Query ই੉௑ https://react-query-v3.tanstack.com/ 151) Mock Service Worker ই੉௑ https://mswjs.io/