일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 크롬 익스텐션
- discord.js
- supabase
- 2156
- 동적계획법
- webpack
- 프로그래머스 #정수삼각형 #동적계획법
- Message Passing
- 자료구조
- 백준 #7568번 #파이썬 #동적계획법
- X
- 공부시간측정어플
- react
- 갓생
- 포도주시식
- 디스코드 봇
- Chrome Extension
- C언어로 쉽게 풀어쓴 자료구조
- content script
- 크롬 확장자
- 파이썬
- popup
- TypeScript
- 백준
- background script
- 캠스터디
- nodejs
- 백준 7579
- Today
- Total
히치키치
Recoil : selector에서 비동기 로직 처리 본문
다음의 글을 참고하며 공부한 내용
다음의 api 사용함
https://www.themoviedb.org/documentation/api
💙 비동기 로직에서 주목해야 할 Selector의 특성
1. selector는 순수함수이다
순수함수
: same Input -> same Output
: give Function
as Output according to Input
2. selector는 RecoilValueReadOnly 객체임
RecoilValueReadOnly 객체
: readOnly 하여 return 값만 가짐
: 값을 set 할 수 없음
3. selector의 Type 둘러보기
key 프로퍼티
: string
: selector를 구분하는 Id
get 프로퍼티
인자 : {get
}
get
: GetRecoilValue
반환
T
Promise<T>
RecoilValue<T>
비동기적 역할
- api call로 받아온 data 반환
set 프로퍼티 (optional)
첫번째 인자 : {get
| set
| reset
}
get
: GetRecoilValueset
: SetRecoilStatereset
: ResetRecoilState
두번째 인자 : newValue
newValue
: T 또는Default Value
반환
void
비동기적 역할
- wrtiable한 state 값을 변경(set)할 수 있는 함수 반환
- 자기 자신 selector를 set하지 않음 : 무한 루프 발생
- 다른 writable한 atom을 set함 : 에러 발생 없음
- 애초에 selector는 read-only한 RecoilValue만 가짐
dangerouslyAllowMutability 프로퍼티 (optional)
: boolean
정리하기 : 예제 코드 분석
/state.js
import { atom, selector } from "recoil";
import { serverAxios } from "./apis";
export const movieState = atom({
key: "movieState",
default: [],
});
export const getTrending = selector({
key: "trending/get",
get: async ({ get }) => {
try {
const {
data: { results },
} = await serverAxios.get(
`/trending/all/week?api_key=${process.env.REACT_APP_API_KEY}&language=en-US`
);
return results;
} catch (err) {
throw err;
}
},
set: ({ set }, newValue) => {
set(movieState, newValue);
},
});
movieState
: atom
- writable한 state 가짐
getTrending
: selector
- set은 selector의 값 수정하는 함수 정의 X, 다른 atom의 writable한 state (movieState) 수정하는 함수 정의 O
/movie.js
import { getTrending } from "./state";
import { useRecoilState } from "recoil";
const Movie = () => {
const [movies, setMovies] = useRecoilState(getTrending);
console.log(movies);
return (
<>
{movies.map((movie, idx) => (
<div key={idx}>
<h1>{movie.original_title}</h1>
<p>{movie.overview}</p>
</div>
))}
</>
);
};
export default Movie;
useRecoilState(getTrending)
- get 프로퍼티로 api call의 반환 결과가
movies
에 대입됨 - set 프로퍼티인
set(movieState, newValue)
로movieState
atom의 state가 get 프로퍼티로 받아온 새로운 값으로 write됨
정리
selector의 역할
get : 다른 selector/atom 값 가져오기
set : get한 값을 바탕으로 다른 atom의 state를 modify함
💙 비동기 상태 (load, success, fail)에 대한 처리
그러나 막상 비동기 api로 받아온 값을 콘솔에 출력하면 다음의 에러가 발생함. 비동기 작업은 컴포넌트가 unmount되어도 실행됨.즉 컴포넌트가 unmount되면 setState 함수가 실행되지 않도록 해야함. 이를 비동기 처리 중 (= 컴포넌트 unmount됨 / 랜더링 할 데이터 도착 전 / Loading ) 보여줄 UI를 따로 보여주며 에러 해결하도록 하자
1. React의 Suspense의 fallback props 이용
Suspense
컴포넌트로 랜더링할 컴포넌트인 <Movies/>
를 감싸고 fallback
이란 props에 loading 시 보여줄 UI를 부여함
/App.js
import React, { Suspense } from "react";
import { RecoilRoot } from "recoil";
import Movie from "./movie";
function App() {
return (
<RecoilRoot>
<Suspense fallback={<div>Loading...</div>}>
<Movie />
</Suspense>
</RecoilRoot>
);
}
export default App;
2. Recoil의 Loadable 이용
Recoil의 Loadable인 useRecoilValueLoadable
사용
Loadable 이란?
- atom/selector의 현재 상태를 나타내는 객체로
state
와contents
프로퍼티를 가짐 state
:hasValue
,hasError
,loading
상태 가짐contents
: state 값에 따라 각각value
,Error
객체,Promise
가짐
/movie.js
import { getTrending } from "./state";
import { useRecoilValueLoadable } from "recoil";
const Movie = () => {
const movieLoadble = useRecoilValueLoadable(getTrending);
console.log(movieLoadble);
return <></>;
};
export default Movie;
따라서 useRecoilValueLoadable 객체의 state 상태에 따라 비동기 처리하는 UI 코드를 추가해보자
/movie.js
import { getTrending } from "./state";
import { useRecoilValueLoadable } from "recoil";
const Movie = () => {
const movieLoadble = useRecoilValueLoadable(getTrending);
//console.log(movieLoadble);
switch (movieLoadble.state) {
case "hasValue":
return (
<>
{movieLoadble.contents.map((movie, idx) => (
<div key={idx}>
<h1>{movie.name}</h1>
<p>{movie.overview}</p>
</div>
))}
</>
);
case "Loading":
return <div>Loading...</div>;
case "hasError":
throw movieLoadble.contents;
}
};
export default Movie;
💙 selectorFamily : 동적인 라우터 URL Query 따른 api 호출
영화 목록을 보일 Movie
컴포넌트와 id에 따른 특정 영화 세부 정보를 보일 MovieDetail
컴포넌트를 연결하는 라우터 작성
브라우저 라우터 설정하기
/index.js
import React from "react";
import ReactDOM from "react-dom/client";
import App from "./App";
import { BrowserRouter } from "react-router-dom";
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<BrowserRouter>
<App />
</BrowserRouter>
);
라우터 작성
/App.js
import React from "react";
import { RecoilRoot } from "recoil";
import Movie from "./movie";
import MovieDetail from "./movieDetail";
import { Routes, Route } from "react-router-dom";
const App = () => {
return (
<RecoilRoot>
<Routes>
<Route exact path="/" element={<Movie />} />
<Route path="/:id" element={<MovieDetail />} />
</Routes>
</RecoilRoot>
);
};
export default App;
영화 목록에서 특정 영화 클릭 해당 영화 id와 함께 세부 정보 페이지로 넘어갈 Link 설정
/movie.js
import { getTrending } from "./state";
import { useRecoilValueLoadable } from "recoil";
import { Link } from "react-router-dom";
const Movie = () => {
const movieLoadble = useRecoilValueLoadable(getTrending);
console.log(movieLoadble);
switch (movieLoadble.state) {
case "hasValue":
return (
<>
{movieLoadble.contents.map((movie, idx) => (
<Link to={`/${movie.id}`}>
<div key={idx}>
<h1>{movie.original_title}</h1>
<p>{movie.overview}</p>
</div>
</Link>
))}
</>
);
case "Loading":
return <div>Loading...</div>;
case "hasError":
throw movieLoadble.contents;
}
};
export default Movie;
동적으로 바뀌는 영화 id로 api를 호출해 영화 세부정보 받아오는 SelectorFamily 생성
- key 프로퍼티 : string, selecotrFamily를 구분하는 unique Id
- get 프로퍼티 : 인자로 받아온 값(id)을 기반으로 api를 호출하고 그 결과를 반환함
/state.js
import { atom, selector, selectorFamily } from "recoil";
import { serverAxios } from "./apis";
(..생략..)
export const getMovieDetail = selectorFamily({
key: "movieDetail/get",
get: (movieId) => async () => {
if (!movieId) return "";
const { data } = await serverAxios.get(
`/movie/${movieId}?api_key=${process.env.REACT_APP_API_KEY}`
);
console.log(data);
return data;
},
});
SelectorFamily의 Value를 get하여 특정 영화 세부 정보 페이지 생성
/movieDetail.js
import React from "react";
import { useRecoilValue } from "recoil";
import { useParams, useNavigate } from "react-router-dom";
import { getMovieDetail } from "./state";
const MovieDetail = () => {
const { id } = useParams();
const navigate = useNavigate();
const movie = useRecoilValue(getMovieDetail(id));
//console.log(movie);
const _handleBackBtn = () => {
navigate("/");
};
return (
<div>
<h1>{movie.original_title}</h1>
<p>{movie.release_date}</p>
<p>run time : {movie.runtime}</p>
<p>{movie.overview}</p>
<button onClick={_handleBackBtn}>back</button>
</div>
);
};
export default MovieDetail;
'FE' 카테고리의 다른 글
Javascript 세미나 (0) | 2023.02.23 |
---|---|
[타입스크립트 핸드북] : 기본 타입 & 함수 (1) | 2022.09.21 |
[타입스크립트 핸드북] : 기본 타입 & 함수 (0) | 2022.09.20 |
Recoil : 시작하기 (0) | 2022.07.07 |
Typescript : 객체 내 특정 string인 key값에 해당하는 value 가져오기 (0) | 2022.07.04 |