사용자 경험 개선 - 리액트의 Suspense

Urther·2022년 4월 19일
0

React , Redux

목록 보기
3/4

React 공식문서 - 데이터를 가져오기 위한 Suspense (실험 단계)

Suspense란 ?

const ProfilePage = React.lazy(() => import('./ProfilePage')); // 지연 로딩

// 프로필을 불러오는 동안 스피너를 표시합니다
<Suspense fallback={<Spinner />}>
  <ProfilePage />
</Suspense>

ProfilePage 에서는 비동기 처리가 진행되고 있다. 비동기 처리가 이루어지는 동안, Spinner 컴포넌트가 랜더된다. 사용자는 ProfilePage 컴포넌트에서 데이터가 없는 상태에서 기다리는 것이 아닌, spinner가 도는 것 같은 효과를 이용하기 때문에 친절한 웹을 만들 수 있도록 도와준다.

비동기 처리가 끝나면 리랜더링을 진행하여 ProfilePage를 사용자에게 보여준다.

주의 ) Suspense가 아닌 것은 무엇일까?

  • suspense는 데이터 불러오기에 대한 구현이 아니다.

    Suspense는 그저 컴포넌트가 읽어들이고 있는 데이터가 아직 준비되지 않았다고 사용자에게, 그리고 리액트에게 알려줄 수 있는 매커니즘이다.

  • 바로 사용할 수 있는 클라이언트가 아니다.

    fetch와 같은 것으로 Suspense를 대체할 수 없다. Relay API, SWR 와 같은 data fetching 라이브러리와 함께 사용할 수 있다.

Suspense로 가능한 것

  • 데이터 불러오는 라이브러리(ex. axios)들이 리액트와 결합할 수 있도록 해준다.
  • 경쟁 상태(Race condition)을 피할 수 있도록 도와준다.

    Suspense는 데이터를 동기적으로 읽어오는 것처럼 느껴지게 해준다.
    자바스크립트에서 경쟁상태란? 여러 개의 비동기 작업의 결과가 하나의 Dom 객체에 반영되는 상황이 있는 경우다.

    예제)
    1. 미키, 주모, 심바, 파노, 카일 5개의 버튼이 있고 각 버튼을 누르면 크루에 대한 프로필을 서버에 요청한다.
    2. 프로필 요청 응답을 앱이 받으면 해당 정보를 컴포넌트에 업데이트한다.
    3. 빠른 속도로 누른다고 생각하면 마지막에 누른 버튼과 컴포넌트의 정보가 일치할 수 있을까?

    "NO"
    이유는 버튼을 누른 순서대로 프로필 요청에 대한 응답이 도착하고 순서대로 동작하길 바란다는 것은 개발자의 생각이라는 것이다.

    그래서 Suspense는 이를 어떻게 해결했을까?

    • 이전 : A 프로필 요청 ➡️ 로딩 UI랜더 ➡️ A프로필 응답 ➡️ Profile 응답
    • Suspense 이용 : A 프로필 요청 ➡️ Profile 컴포넌트에 A프로필 요청 리소스 반영 ➡️ Suspense에 의해 A 요청에 대한 로딩 UI 변경 ➡️ 요청 리소스로 A 프로필에 대한 응답이 들어옴 ➡️ 반영 ⏤ 동기적으로 처리하는 것처럼 보인다.

    Reference | 사용자 경험 개선 1편 - react suspense

기존 접근 방식과 Suspense의 차이

기존 접근 방식- fetch-on-render

  • 랜더링한 직후에 불러오기

우리가 자주 사용하는 리액트 생명주기에 라이프 사이클 중에 ComponentDidMount를 호출할 수 있도록 해주는 UseEffect()라는 훅을 이용한다

function ProfilePage() {
  const [user, setUser] = useState(null);

  useEffect(() => {
    fetchUser().then(u => setUser(u));
  }, []);

  if (user === null) {
    return <p>Loading profile...</p>;
  }
  return (
    <>
      <h1>{user.name}</h1>
      <ProfileTimeline />
    </>
  );
}

function ProfileTimeline() {
  const [posts, setPosts] = useState(null);

  useEffect(() => {
    fetchPosts().then(p => setPosts(p));
  }, []);

  if (posts === null) {
    return <h2>Loading posts...</h2>;
  }
  return (
    <ul>
      {posts.map(post => (
        <li key={post.id}>{post.text}</li>
      ))}
    </ul>
  );
}

코드 해석

  1. ProfilePage 호출이 된다.
  2. user 에는 null 값이 등록된다
  3. user가 null 이기 때문에 loading <p>Loading profile...</p>이 호출된다.
  4. user 정보가 비동기 처리를 이용하여 받아오게 되면서 setState 함수를 통해 user에 데이터가 저장된다.
  5. 상태가 수정되었기 때문에 리랜더링이 일어난다.
  6. 그제서야 ProfileTimeline이 호출되고 1번부터 똑같은 작업이 실행된다.

문제점

비동기 처리의 장점이 무엇이었을까? 비동기 처리 작업을 하는 동안 다른 작업도 병렬적으로 처리할 수 있음이다. 그럼에도 위와 같은 코드는 코드상의 한계로 waterfall현상으로 병렬적으로 처리하지 못한다.

Data fetching 라이브러리와 Suspense

Relay API

  • GraphQL 에 의존적
  • 리액트 공식사이트에서는 Relay API를 소개해주고 있다. 그에 대한 까닭은 페이스북이 Relay를 사용하고 있기 때문이다.

개인적으로 GraphQL을 사용하고 있지 않기 때문에 패스했다.

SWR

SWR을 사용한 useRequest, 리엑트 Data Fetching
데이터를 가져오기 위한 리액트 훅, SWR
공식 문서에서 Suspense와 관련된 내용만 읽었습니다.

  • 아직 실험적인 기능이다.
  • 조건부 가져오기 기능

Suspense 기능 활성화하면 랜더링시 data 가 항상 준비된다.

function Profile () {
  const { data } = useSWR('/api/user', fetcher, { suspense: true })
}

여기서 data는 절대 undefined가 들어올 수 없다. 만약 조건부 가져오기를사용한다면 요청이 일시 중단 된 경우에만 undefined가 될 수 있다.

 const { data } = useSWR(isReady ? '/api/user' : null, fetcher, { suspense: true })

React Query

리액트 - react query 도입과 suspense error boundary 적용

const test=()=>{
  const {data}=useQuery("testQueryKey",fenchFn {suspense:true});

return(
  <Suspense fallback={<LoadingPage/>}>
 	 <TargetWidget data={data}>
  </Suspense>  
profile
이전해요 ☘️ https://mei-zy.tistory.com

0개의 댓글