티스토리 뷰
Frontend/react.js
[SWR | 서버데이터관리] SWR 사용해보기, optimistic vs pessimistic UI, cookie 공유 tip
blueprint-12 2023. 5. 24. 15:03SWR는 요청 보낸 데이터를 저장해준다. (보통은 GET요청의 데이터를 저장, POST요청을 저장못한다는 것은 아님)
Next 팀에서 만들었으며 공식 홈페이지에서는 `데이터를 가져오기 위한 React Hooks`라고 설명하고 있다.
- react-query의 light version이라고 생각하면 될 거 같다. 상태관리가 주 목적이기보단 서버데이터패칭이 주 목적같다. 물론 둘 다 가능하다고 한다.
SWR를 사용하면 컴포넌트는 지속적이며 자동으로 데이터 업데이트 스트림을 받게된다. 또한, UI는 항상 빠르고 반응적이다.
- 이름의 유래는 HTTP RFC 5861에 의해 알려진 HTTP 캐시 무효 전략인 `stale-while-revalidate`에서 유래됐다.
- SWR은 먼저 캐시(스테일)로부터 데이터를 반환한 후, fetch 요청(재검증)을 하고, 최종적으로 최신화된 데이터를 가져오는 전략이다. (react-query 처럼 다른 창에서 현재 창으로 돌아올 때에도 refetch 기능을 제공한다. 즉, 최신 데이터를 UI에 유지하는게 가능해진다.)
- 2023년 기준 2버전이 나왔다. 눈여겨볼 기능으로는 옵티미스틱 UI*를 제공한다. (실패했을 때 이전 값으로 돌아갈수있는 rollback기능도 당연히 제공된다.) 옵티미스틱 ui 개념에 대한 내용은 본문에 정리해놓았다.
사용 TIP
- fetcher 는 get요청을 주로 많이쓰지만 get에만 국한된 것이 아니라 여러 fetcher를 만들어서 활용할 수 있다. 예를 들어, fetcherPost 라는 함수를 만들어서 axios.post로 활용해도 된다.
- SWR은 비동기 요청을 할 때만 사용하는 것이 아니다. key 에 들어가는 값이 항상 api 주소일 필요 없다는 소리이다. 예시로 localStorage set, get 을 활용해서 useSWR훅을 만들면 이 훅을 사용하는 컴포넌트 끼리 공유하게 되면서 리덕스를 대체하여 사용할 수 있다.
- 같은 api 엔드포인트에 fetcher만 다르게해서 사용하는 경우, SWR는 이 API 키로 들어가는 값이 하나라도 다르면 다른 data로 본다. 그렇기 때문에 url의 뒤에 쿼리스트링을 붙여서 다른 http 요청(get, post 등)을 보낼 수 있다.
- e.g) http://github.com/something? 와 http://github.com/something 은 다른 api로 SWR은 인식한다.
- e.g.) http://github.com/something#123 과 http://github.com/something도 마찬가지이다.
- 하지만, 서버의 요청의 경우 뒤에 쿼리스트링은 무시되기 때문에 같은 api로 요청을 보낼 수 있다.
사용 예시
import useSWR from 'swr'
function Profile() {
const { data, error, isLoading } = useSWR('/api/user', fetcher)
if (error) return <div>failed to load</div>
if (isLoading) return <div>loading...</div>
return <div>hello {data.name}!</div>
}
- useSWR hook은 key 문자열과 fetcher 함수를 받는다.key는 데이터의 고유한 식별자이며(일반적으로 API URL) fetcher 로 전달될 것이다. (즉, fetcher의 매개변수로 key인 url이 넘어가게 된다.)
- fetcher는 데이터를 반환하는 어떠한 비동기 함수도 될 수 있다. 네이티브 fetch 또는 Axios와 같은 도구를 사용할 수 있다.
- 이 훅의 반환값은 data와 error가 있다(배열이 아니라 객체로 리턴함). 로딩 상태도 isLoading으로 알 수 있는데 데이터가 존재하지 않으면 loading 상태로 처리한다.
useSWR 사용해보기
1. 노드 패키지 매니저(저는 npm 대신 yarn 사용)로 swr 을 설치해준다.
yarn add swr
2. 해당 훅을 사용할 컴포넌트에서 useSWR을 임포트해주고 인자값으로 url 과 fetcher함수를 전달해준다.
- 참고로 import할 때, 모양을 보면 알다시피 `useSWR`은 구조분해로 임포트해올 필요 없다. 에러가 나면 어차피 IDE에서 알려주니 걱정x
import { useNavigate, Link } from 'react-router-dom';
import useSWR from 'swr';
const LogIn = () => {
const navigate = useNavigate();
const { data, error, isLoading } = useSWR('/api/users/login', fetcher);
2-1. fetcher 함수를 만들어준다. (폴더 경로 utils>fetcher.ts)
- TS를 사용중이므로 확장자명은 .ts이다.
- fetcher 함수는 위에 설명에서 봤듯이 fetch 혹은 axios와 같이 사용할 수 있다. (저는 axios 선택)
import axios from 'axios';
const fetcher = (url: string) => axios.get(url).then((res) => res.data);
// ? 성공 시, res.data가 useSWR의 첫번째인자인 data로 들어간다.
export default fetcher;
- 여기서 만약에 성공시 res.data가 아니라 res만을 넘게게 되면 그게 useSWR의 data가 된다.
SWR 설정(config)
- 위에서 `react-query`처럼 화면에 다시 돌아올 때마다 api콜을 보내는 기능을 제공한다고 했는데 이렇게 주기적으로 api요청을 보내는 것보다 내가 원하는 때 혹은 어떤 주기마다 api호출을 할 수 있다.
- 원할 때 호출하기 위해서는 useSWR의 `mutate` 함수를 활용하면 된다.
- 이전의 revalidate가 deprecated 됐기 때문에 mutate를 사용하면 된다.
- mutate() : update the local data immediately and revalidate (refetch)
- dedupingInterval = 2000(default): 이 시간 범위(밀리초)에서 동일한 키를 사용하여 요청을 중복 제거한다. 이 밀리초 범위가 짧으면 서버에 무리가 많이 가기 때문에 너무 짧지않게 잡아준다.
- 관련 설정들은 useSWR의 3번째 인자인 options 객체로 넘겨주면 된다. 이 기능들은 docs 에 자세히 나와있으니 필요에 따라서 사용하면 된다. SWR API docs
사용 예시
const { data, error, isLoading, mutate } = useSWR('http://localhost:3095/api/users/login', fetcher, {
// 이 시간 범위(밀리초)에서 동일한 키를 사용하여 요청을 중복 제거합니다.
// 즉 이 기간 내에 요청을 보내게 되면 캐시에서 가져온다.
dedupingInterval: 20000,
});
mutate
- mutate 는 swr 2.0 에서 data값을 api 콜이 아니라 직접 새로운 data를 넣어 갱신시켜줄 수 있다. 이때, 다시 재검증을 하지 않으려면 options객체로 revalidate: false를 넣어주면 된다.
- mutate로 optimistic update를 하는 방법은 docs를 참고하면 된다.
- 주의할 점은 이 mutate는 useSWR에서 가져온 mutate라는 점이다. 그렇기 때문에 api 엔드포인트(swr에서 key라고 불리우는 것)를 mutate에 넣어서 알려줄 필요가 없다.
- 동일한 기능으로 사용하고 싶다면 `import {mutate} from "swr"`을 해서 mutate(api, data, options)형태로 넣어주면 된다.
Q. 왜 전역 mutate가 따로 있을까?
A. useSWR의 mutate를 사용하면 key를 작성하지않아도 되는 장점이 있지만 컴포넌트가 로드될 때, 무조건 한번은 useSWR 훅이 실행되면서 api콜이 최소 한번은 발생하게 된다. 하지만, 다른 컴포넌트에서 mutate를 통해서 로컬데이터만 변경하고싶을 수도 있다.(최소 한번의 api콜도 낭비하기 싫다!) 이럴때,
`import {mutate} from "swr" 를 통해서 mutate()함수를 써주면 된다.
옵티미스틱 UI(Optimistic UI) vs 파시미스틱 UI(Pessimistic UI)
- 말 그대로 낙관적 ui, 비관적 ui
Pessimistic UI😈(안정성, 보편성)
- 대부분 파시미스틱 UI 패턴을 사용한다. 사용자의 작업을 보다 안전하게 처리하기 위해 사용하는 패턴이다.
- 사용자가 작업을 요청했을 때, 시스템은 해당 작업이 완료되기를 기다리는 동안 loading상태를 보여준다.
- 이로인해서 해당 작업이 처리되고 결과를 받아올 때까지 사용자는 기다려야 한다는 단점이 있다. 대신, 오류가 발생했을 때 사용자에게 해당 오류에 대한 정보를 제고앟고, 작업의 완료를 더욱 명확하게 알려준다.
- 즉, 비관적 UI 패턴은 클라이언트의 요청이 실패할 것으로 간주하고 먼저 서버에 요청을 보내 확인후, 성공했을 시 클라이언트에 반영하는 것을 말한다.
Optimistic UI🥳(사용성, 반응성)
- 사용자의 작업을 즉시 처리하고, 작업의 결과를 나중에 업데이트하는 패턴이다. 사용자가 어떤 작업을 요청했을 때, 시스템은 즉시 작업을 수행하고 성공적으로 완료되었다고 가정한다. 즉, 화면에 먼저 성공했을 때를 반영한다.
- 로딩상태를 기다리지않고도 다음 단계를 진행할 수 있어 사용자 입장에선 좋다. 그러나, 작업이 실패하거나 오류가 발생했을 때 사용자에게 해당 정보를 제공하는 것이 어렵고 작업의 성공 여부를 나중에 확인해야 한다.
- 간단히 말하자면, 먼저 클라이언트 요청이 성공했을 것으로 간주하고 화면을 업데이트하고 이후 서버에서 실제 요청의 성공여부를 따지는 것이다.
- 확실히 uxui적으로 좋지만, 서버에서 오류가 발생했을 때 사용자가 다음 단계로 진행함에 있어서 더 큰 불편함을 야기하거나 위험요소가 큰 작업(결제라든가..)은 optimistic ui 패턴을 적용하면 안될 거 같다. 좋아요 기능등에 적용하면 좋을 거 같은 패턴
프론트와 백엔드 쿠키 공유하기
우선 프론트 도메인 주소가 백엔드 주소와 다를 때 http 요청을 보내면 1번만 요청이 가는 게 아니라 2번 요청이 간다. 이 내용은 네트워크 탭을 보면 확인해 볼 수 있다. 하나는 내가 보내는 요청이고 하나는 OPTIONS 요청이다.
OPTIONS요청이란?
- preflight인 OPTIONS 요청은 서버와 브라우저가 통신하기 위한 통신 옵션을 확인하기 위해 사용한다.
- 서버가 어떤 method, header, content type을 지원하는지를 알 수 있다.
- 브라우저가 요청할 메서드와 헤더를 허용하는지 미리 확인한 후, 서버가 지원할 경우에 통신한다. 좀 더 효율적으로 통신할 수 있다.
cookie 생성 관련 내용
CORS에러가 발생했을 때 백엔드에서 프론트 주소를 허용하거나 조치를 통해 해결했다한들, 둘의 도메인 주소(ex. front:3090, back:3095)가 다르면 백엔드가 `쿠키`를 생성해서 보내줄 수도 없고 프론트도 `쿠키`를 받아서 돌려줄 수 없다.
이럴 땐, `withCredentials: true` 속성을 활용하면 된다. (프론트에서 http요청을 보낼 때, config의 값으로 이 속성을 세팅한다)
axios.get()의 경우 2번째 인자로 넘겨주면 되고 post의 경우는 3번째 인자로 넘겨주면 된다(해당 위치가 config 이기 때문임).
세팅된 쿠키의 명은 언어의 프레임워크 마다 다른데 node(express)의 경우 `connect.sid` 이다.
- 크롬 개발자도구(F12)의 Application 탭 => Cookies에 가면 확인해볼 수 있다.
계속 CORS에러가 나길래 front 코드와 back 코드를 수정하면서 확인해봤는데 아무리 봐도 잘못된 점을 못 찾았다.
문득 나는 프론트서버는 매번 재실행했지만 백엔드 서버는 재실행하지 않고도 http 요청을 보냈던 것을 알게됐다.
즉, 이전에 켜둔 서버 포트가 종료되지 않고 계속해서 이전 버전으로 돌아가고 있다는 느낌..?
우선 실행중인 back서버를 종료하기 위해서 CMD(운영체제: window) 에서 `netstate -ano` 명령어를 통해 사용중인 프로세스 pid를 찾아내고 작업관리자(=> 세부정보 탭으로 이동하면 pid를 볼 수 있음)에서 해당 pid의 프로세스를 kill 해주었다. 보니까 거의 4개의 node.js가 돌아가고 있어서 죄다 찾아서 죽인다음 재실행하였더니 정상작동했다.
참고로 CORS문제는 서버문제가 아니라 브라우저에서 발생하는 문제이다.
'Frontend > react.js' 카테고리의 다른 글
[React | socket.io] Websockek의 개념과 리액트에서 소켓 통신하기 (0) | 2023.06.25 |
---|---|
[React] Suspense 와 React.lazy (0) | 2023.05.28 |
[RTK Query | Redux] RTK Query 사용하기 (0) | 2023.05.12 |
[RTK Query | Redux] RTK Query란? (redux, redux-toolkit 비교) (0) | 2023.05.11 |
[React | vite] 번들 사이즈 비주얼라이저(애널라이저) (0) | 2023.04.20 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- text input pattern
- 원티드 프리온보딩 프론트엔드 챌린지 3일차
- nvm 설치순서
- 형제 요소 선택자
- && 셸 명령어
- nvm경로 오류
- 부트캠프항해
- 프리렌더링확인법
- 타입스크립트 DT
- 타입스크립트 장점
- ~ ^
- 원티드 3월 프론트엔드 챌린지
- fs모듈 넥스트
- grid flex
- 항해99프론트
- 프리온보딩 프론트엔드 챌린지 3월
- tilde caret
- 원티드 프리온보딩 FE 챌린지
- getServerSideProps
- getStaticPaths
- 항해99추천비추천
- D 플래그
- 틸드와 캐럿
- is()
- 원티드 FE 프리온보딩 챌린지
- aspect-ratio
- Prittier
- 항해99프론트후기
- reactAPI
- float 레이아웃
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
글 보관함