React | Suspense의 사용 방법 작동원리 사용하는 이유를 알아보자

이동욱·2024년 1월 9일
0

💡 사용할 때는 그저 편하게만 썼는데 알아볼수록 어렵고 방대하고 정리하기가 어려웠습니다.
잘못된 부분이 있다면 댓글 부탁드리겠습니다 :)

Suspense란?

Suspense는 React에서 비동기 작업을 관리하기 위한 기능이다. Suspense는 하위 컴포넌트들가 비동기 처리로 인해 화면을 렌더링 하지 못하는 경우 fallback UI로 대체된 화면을 보여준다.

function App() {
  return (
      <Suspense fallback={<Loading></Loading>}>
				<User resource={fetchUser("1")}/>   
      </Suspense>
  );
}

유저 정보를 가져오는동안 User는 일시 중단되고 렌더링 준비가 될때까지 React는 User에서 가장 가까운 Suspense는 대체 요소인 ‘Loading...’을 렌더링 한다.

다음 로드가 완료되면 fallback을 숨기고 User를 렌더링한다.


작동 원리

그러면 suspense는 어떻게 가 데이터를 가지고 있다는 중인걸 알고 fallback UI를 보여주는 걸까?

기존에 하던 hook을 쓰는 아래와 같은 방법을 사용하면 Suspense는 이를 감지하지 못한다.

const [user, setUser] = useState(null)

useEffect(()=>{
	fetch('유저 정보 가져오기')
		.then()
		...
},[])


suspense의 구현 컨셉 코드를 보면 쉽게 이해할 수 있다.

async function runPureTask(task) {
  for (;;) {
    try {
      return task(); 
    } catch (x) {
      if (x instanceof Promise) {
        await x; // pending promise가 throw된 경우 await으로 resolve 시도 => suspense
      } else {
        throw x; 
      }
    }
  }
}

for문은 무한으로 돌면서 task() 시도하고 Promisethrow된 경우를 캐치한다.

이를 Suspense에 맞추어 생각해보면 Promisethrow된 경우에는 fallback UI를 보여주고 계속 렌더링을 시도 하다 데이터를 다 가져와 화면을 return할 수 있게 되면 for문을 벗어나게 된다.

즉, Suspense는 하위 컴포넌트는 비동기 처리가 끝날때까지 Promise를 throw해 Suspense가 감지해 fallback ui를 그릴수 있도록 해야한다.


function fetchUser(userId) {
  let user = null;
  let isError = false;

  const suspender = fetch(`https://jsonplaceholder.typicode.com/users/${userId}`)
    .then((response) => {
      if (!response.ok) {
        throw new Error("Failed to fetch data");
      }
      return response.json();
    })
    .then((data) => {
        user = data;
    })

  return {
    read() {
			//user가 null이면 Promise를 throw해준다.
      if (user === null) {
        throw suspender;
      }
			// user값을 받은 경우
      return user;
    },
  };
}

//-----------------------------------------

function App() {
  return (
      <Suspense fallback={<Loading></Loading>}>
				<User resource={fetchUser("1")}/>      
		  </Suspense>
  );
}

//-----------------------------------------

function User({resource) {
	console.log("유저 정보 가져오기");
  const user = resource.read();
  console.log("유저 화면 그리기");

  return (
    <div>
      <p>
        {user.name}({user.email}) 님이 작성한 글
      </p>
    </div>
  );
}

위 코드를 실행시킨 화면은 아래와 같다

Promise를 throw하는동안 fallback UI를 보여주고 User컴포넌트는 계속해서 Re-Rendering되다 데이터를 받아 화면을 그릴수 있게 되면 fallback UI에서 User 컴포넌트를 보여준다.


Suspense를 쓰는 이유

1. 선언적 프로그래밍

Suspense 없이 React에서 비동기적으로 데이터를 가져와 화면을 그리는 경우는 상태 값에 따라 조건적으로 렌더링을 하는 명령형 프로그래밍의 코드를 짜게된다.

// 명령형 프로그래밍
export const App = () => {
  const [user, setUser] = useState(null);

  useEffect(() => {
    getUserFetch().then(user => setState(user));
  }, []);
  
  return user === null ? <Loading/> : <User />;
};

이를 Suspense로 이용해 구현하면 선언형 프로그래밍이 가능하고 비동기 로직에 따른 UI로직을 Suspense에 위임할 수 있다.

function App() {
    return (
        <Suspense fallback={<Loading />} />
            <User />
        </Suspense>
    )
}

💡 선언적 프로그래밍이란?
선언형 프로그래밍은 ‘무엇’을 할것인지에 중점을 두고 있다.
이러한 특징떄문에 추상적이고 간결하다는 특징이 있다

const numbers = [1, 2, 3, 4, 5];
const squaredNumbers = numbers.map(num => num * num);
// map이 무엇을 수행할지 선언되어 있지만 구현은 내부에 숨어있다

2. Waterfall 현상 해결


참고

https://www.daleseo.com/react-suspense/
https://react.dev/reference/react/Suspense

profile
프론트엔드

0개의 댓글