만족

[React] 데이터 재사용 및 관리를 위한 hook: SWR 본문

[React] 데이터 재사용 및 관리를 위한 hook: SWR

FrontEnd/React Satisfaction 2022. 3. 30. 17:51

문제

우리가 프로젝트에서 어떤 데이터를 로드하고 사용할 때,

그 데이터가 반복적으로 로딩될 필요가 없다면 어떤 식으로 구현할까?

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const [data, setData]= useState();
   useEffect(()=>{
     loadData().then(setData);
   }, []);
   
   return <h3>{data?.msg}</h3>
}

위 코드에서는 MyComponent가 마운트될때마다 계속해서 loadData를 호출하면서 데이터를 로드한다.

 

갱신될 필요가 없는 데이터인데도 마운트될 때는 데이터가 없으므로 잠시 메시지가 표시되지 않다가

로딩이 완료되고 나면 메시지(hi!)가 표시된다.

 

여기에서 중복 로딩을 제거하려면 다음처럼 구현할 수 있다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

let cache= null;
const MyComponent= ()=>{
   const [data, setData]= useState(cache);
   useEffect(()=>{
     if(!cache)
       loadData().then(setData);
   }, []);
   
   return <h3>{data?.msg}</h3>
}

중복 로딩을 제거하는 것은 간단하다.

(물론 이 코드에서 MyComponent가 동시에 2개 이상 마운트되면 중복 로딩이 발생한다)

 

그러나 주기적으로 갱신을 해주거나, 기존 데이터를 무효화하고 갱신하거나, 오류가 발생했을 때 다시 로드를 시도하는 등

여러 부가 기능들을 붙이다 보면 코드가 늘어나고 다른 컴포넌트와 중복되는 로직이 증가하게 된다.

 

SWR이 이 기능들을 hook으로 제공함으로써 위 요구사항들을 손쉽게 구현할 수 있다.

 

SWR

SWR은 stale-while-revalidate의 약자로,

일단 캐시를 반환하고, revalidate(갱신)하여 최신화된 데이터를 받아오도록 구현되어 있다.

 

기본적으로 캐시 키(key; string), 패쳐(fetcher; promise), 옵션(option; object) 라는 매개변수를 받는다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const {data, error}= useSWR('/greeting', loadData, {});
   
   return <h3>{data?.msg}</h3>
}

 

위 코드에서는 key로 /greeting을 사용하고 있다.

fetcher로 패칭된 데이터는 /greeting이라는 키로 관리될 것이다.

(일반적으로 key는 request url을 사용한다)

 

loadData는 fetcher로, 데이터를 로드하는 데 사용되는 함수이며 promise를 리턴하는 함수이다.

 

{}는 는 option이다.

 

몇 가지 기능들을 간단히 살펴보자.

 

SWR: loading, error, success

 

SWR hook은 object를 리턴한다.

리턴값 중 data, error를 이용해 로딩 상태/결과를 알아낼 수 있다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const {data, error}= useSWR('/greeting', loadData, {});
   
   if(!data && !error)
     return <h3>로딩 중</h3>
     
   if(error)
     return <h3>데이터 로딩 실패</h3>
   
   return <h3>{data.msg}</h3>
}

로딩 중 일 때는 data와 error가 모두 null이고,

둘 중 하나가 null이 아니면 데이터 로딩이 완료되었다는 것이다.

 

SWR: caching data

한 번 데이터를 로딩한 후 데이터를 추가 로드하지 않고 기존 데이터를 사용하려면 어떻게 할까?

 

위 예제에서는 let cache에 데이터를 넣어두고 관리했었다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const {data}= useSWR('/greeting', loadData, {
     revalidateIfStale: false,
     revalidateOnFocus: false,
     revalidateOnReconnect: false
   });
   
   return <h3>{data?.msg}</h3>
}

여기에서는 세 가지 옵션을 추가함으로써 데이터를 한 번 로드하고 추가로 로드하지 않도록 제한한다.

 

이렇게 하면 위에서 let cache 로 구현한 캐시와 동일하게 작동한다.

 

(revalidateIfStale: 항상 갱신; 마운트될 때 마다,

revalidateOnFocus: 브라우저에 포커스가 될 때마다 갱신,

revalidateOnReconnect: 네트워크가 다시 연결될 때마다 갱신)

 

revalidateIfStale: true 를 전달할 경우

컴포넌트가 마운트됐을 때, 캐시가 있으면 일단 캐시를 표시하고 fetcher를 호출해 데이터를 갱신한다.

=> 따라서 첫 로드 후에는 다시 마운트하더라도, 캐시 데이터를 표시함으로써 빈 컴포넌트가 표시되지 않는다.

 

SWR: mutate

정해진 조건이 만족되지 않았더라도 revalidate를 트리거하고 싶을 수도 있다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const {data, mutate}= useSWR('/greeting', loadData, {
     revalidateIfStale: false,
     revalidateOnFocus: false,
     revalidateOnReconnect: false
   });
   
   return <div>
     <h3>{data?.msg}</h3>
     <button onClick={()=> mutate()}>mutate</button>
   </div>
}

 

이렇게 하면 버튼을 눌렀을 때 mutate가 호출되고,

이는 조건에 관계없이 데이터 갱신을 요구한다.

 

즉 다시 fetcher인 loadData가 호출되고 캐시 데이터가 새로운 데이터로 교체된다.

 

반드시 /gretting(key)로 useSWR을 사용하는 컴포넌트에서만 mutate할 수 있는 것은 아니다.

 

전혀 관계없는 컴포넌트에서도 캐시 공급자가 동일하다면,

아래 코드처럼 mutate시킬 수 있다.

 

//return Promise
const loadData= ()=>{
  //load data
  return new Promise((rs)=> rs({msg: 'hi!'}));
};

const MyComponent= ()=>{
   const {data, mutate}= useSWR('/greeting', loadData, {
     revalidateIfStale: false,
     revalidateOnFocus: false,
     revalidateOnReconnect: false
   });
   
   return <div>
     <h3>{data?.msg}</h3>
     <button onClick={()=> mutate()}>mutate</button>
   </div>
}

//다른 컴포넌트
const AnotherComponent= ()=>{
  const {mutate}= useSWRConfig();
  
  const mutateOtherSWR= ()=>{
    mutate('/gretting');
  }
  
  return <div>
    <button onClick={mutateOtherSWR}>
      mutate other component's swr
    </button>
  </div>
}

 

이렇게 하면 AnotherComponent에서도 '/gretting' 데이터를 다시 불러오도록 요구할 수 있다.



Comments