-
🍾 React의 상태관리 툴 맛보기! : redux, recoil, react querySOJU 2기 🍾 2023. 7. 3. 16:26
상태관리 툴을 사용하는 이유?
React로 웹 애플리케이션을 만들 때 개발자는 수많은 컴포넌트들을 만들어내고, 컴포넌트들은 부모-자식 관계가 생긴다. 이때 내가 원하는 데이터를 내가 원하는 컴포넌트까지 보내주기 위해서는 부모->자식->자식->자식... 혹은 자식->부모->부모->... 식으로 수~많은 부모 혹은 자식 컴포넌트를 연결시켜주지 않으면 안 되는 상황을 마주하게 된다.
React는 props를 통해 데이터를 연결시켜주는데 앱이 복잡하고 커질수록 이게 도대체 어디서부터 시작된 데이터인지 알기 힘들어진다. 그때 사용하는 게 바로 상태관리 툴이다. 끝없이 이어진 컴포넌트들을 연결, 연결, 연결... 하는 방법이 아니라 내가 원하는 데이터를 내가 원하는 컴포넌트에서 사용할 수 있게 해 준다.
상태관리 툴의 종류
상태관리 툴은 정말~ 많지만 이번 주 스터디 과제로 접하게 된 건 제목에 써있는 세 개의 상태관리 툴이다.
redux, recoil, react query 순으로 공부한 내용 등을 기록해둔다.
✍️ Redux (+react redux)
✨ 설치
npm install redux react-redux
redux, react-redux 를 함께 설치해서 사용한다.
✨ store, reducer, action
import {createStore} from ‘redux’; function reducer(currentState, action) { if(currentState === undefined) { // 찾을 수 없을 때는 기본값을 리턴한다. return { number:1; } } const newState = {…currentState}; // redux는 각각의 state의 변화를 불변하게 유지해야한다 때문에 새로운 스테이트(과거 state 복제본)를 만든다. if(action.type === ‘PLUS’) { newState.number++; } return newState; } const store = createStore(reducer);
- store
Redux store는 앱을 구성하는 상태, 액션, 리듀서를 한데 모아 제공한다.
주의할 점은 한 어플리케이션에는 하나의 store만 존재할 수 있다는 점이다.
- action
store 안에 있는 state (= 즉, currentState)를 어떻게 바꿀 것인가를 정의한다.
예제코드에서 보이는 action.type의 의미는 action의 type이 PLUS 일 때 다음 동작을 실행한다는 의미이다.
type을 설정할 때는 dispatch를 이용한다. 이는 뒤에서 조금 더 살펴보자.
- reducer
이전의 state(=currentState)와 설정한 action을 합쳐서 새로운 state(= newState)를 만들어준다.
예제코드를 보면 더 쉽게 확인할 수 있다. reducer라는 함수에 이전의 state(= currentState)와 action을 함께 인수로 넣어주고 있다.
✨ react redux 4인방!
redux의 주요 3인방 store, action, reducer 가 있다면 react redux에는 주요 4인방이 있다.
Provider, useSelector, useDispatch, connect
이다.
- Provider
state들을 어떤 component들에게 제공할 것인가. 가장 바깥쪽의 울타리를 정의한다.
<Provider store={store}> <Component1 /> <Component2 /> <Component3 /> <Component4 /> ... </Provider>
예제 코드와 같이 컴포넌트들을 감싸준다. 감싸진(?) 컴포넌트들은 store를 사용할 수 있게 된다.
- useSelector
const number = useSelector(state => state.number);
끝없는 부모자식 관계를 거쳐야 하는 게 아니라, 내가 원하는 컴포넌트에 바로 연결할 수 있다. (마치 무선! 블루투스!)
- useDispatch
const dispatch = useDispatch(); onClick = {() => { dispatch({type: ‘PLUS’}) }} // PLUS라는 액션을 전달했다. -> reducer가 호출된다
위에서 잠깐 언급했던 dispatch를 위해 사용한다.
원하는 type명을 설정할 수 있다.
✍️ recoil
✨ 설치
npm install recoil
✨ 대표적 특징?
- React 내부상태만을 이용한다.
- 작은 atom 단위로 관리한다.
- 순수함수인 Selector를 이용하여 데이터를 가공 혹은 다른 상태를 구독한다.
- re-render를 최소화한다.
- 새로운 react 기능과 호환성이 높다.
✨ 사용법
- RecoilRoot
import React from 'react'; import { RecoilRoot, atom, selector, useRecoilState, useRecoilValue, } from 'recoil'; function App() { return ( <RecoilRoot> <CharacterCounter /> </RecoilRoot> ); }
recoil을 사용하는 컴포넌트는 RecilRoot으로 감싸주어야 한다.
- Atom
const textState = atom({ key: 'textState', // unique ID (with respect to other atoms/selectors) default: '', // default value (aka initial value) });
사실 읽어도 들어도 잘 이해가 되지 않았는데 store과 유사한 개념이라고 생각하면 된다고 들으니 그제야 이해가 됐다.
atom 값을 읽는 모든 컴포넌트들은 atom이 변경되었을 때 re-render 된다.
function CharacterCounter() { return ( <div> <TextInput /> <CharacterCount /> </div> ); } function TextInput() { const [text, setText] = useRecoilState(textState); const onChange = (event) => { setText(event.target.value); }; return ( <div> <input type="text" value={text} onChange={onChange} /> <br /> Echo: {text} </div> ); }
atom 을 읽고 쓰게 하기 위해서는 useRecoilState()를 사용한다.
- Selector
Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태는 상태의 변화다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다. - 공식문서
const charCountState = selector({ key: 'charCountState', // unique ID (with respect to other atoms/selectors) get: ({get}) => { const text = get(textState); return text.length; }, }); function CharacterCount() { const count = useRecoilValue(charCountState); return <>Character Count: {count}</>; }
get 프로퍼티를 사용해서 textState를 가공, text.length를 받아올 수 있다.
+) useRecoilState(get/set), useRecoilValue(get)/useSetRecoilValue(set)...
✍️ React Query
왜 TanStack Query!? 하고 당황했지만 이름이 바뀌었다고 한다 🤓 ...
✨ React Query?
서버에서 가져온 데이터를 웹 브라우저 앱에서 사용하기 쉽도록 도와주는 라이브러리이다.
✨ server state / client state
- server state
클라이언트가 요청하면 -> 서버는 데이터베이스의 유저 정보 값을 가져와 server state 값을 만들어낸다.
이때 데이터베이스에 있는 값을 '그대로' 전달하거나 요청에 담긴 특정 값을 이용해 정보를 가공하여 메모리에 들고 있는다 -> 이를 클라이언트에 전달하는 방식이다.
- client state
두 가지로 나눌 수 있다.
- 클라이언트에서 자체적으로 만드는 state (최초발생지 : 클라이언트)
- 서버에서 전달받은 값으로 만드는 state (최초발생지 : 서버)
1의 경우, 대개 UI를 담당하는 부분을 일컫는다. (예: 모달창 오픈/클로즈, 어떤 버튼이 클릭되었는가, 창이 리사이징 되고 있는가 .. 등 UI와 관련된 정보(메타 정보)를 담은 값)
2의 경우, useState를 사용한다는 가정 하에 데이터를 불러와 setState 호출을 통해서 응답 당시의 server state를 component state로 wrapping 한다.
=> 즉, 클라이언트 앱은 server state와 client state에 대한 로직 처리를 나누어서 선언해주어야 한다.
✨ 왜 React Query?
리액트쿼리는 hook기반 로직들로 되어 있어서 해당 훅을 사용하는 컴포넌트에서 상태 값의 변경을 간편하게 파악, 리렌더링을 유발하게 해 준다. 또한 리액트쿼리는 데이터의 캐시 처리를 간편하게 할 수 있는 인터페이스를 제공한다.
- 몇 초 이후에는 데이터가 유효하지 않은 것으로 간주하고 데이터를 다시 불러온다.
- 데이터에 변경점이 있는 경우에만 리렌더링을 유발한다.
- 유저가 탭을 이동했다가 다시 돌아왔을 때 데이터를 다시 불러온다.
- 데이터를 다시 호출할때 응답이 오기 전까지는 이전 데이터를 계속 보여준다. 필요에 따라서는 로딩바와 같은 대안 UI를 보여주기 위해 loading state를 기본적으로 제공한다.
- 무한스크롤!
✨ 설치
npm i @tanstack/react-query
'SOJU 2기 🍾' 카테고리의 다른 글
🍾 React + TS 환경에 Supabase 연결하기 (0) 2023.08.08 🍾 react에서 Tailwind css 사용하기 (0) 2023.07.25 🍾 TypeScript ! 타입스크립트 기초 뿌수기 🕺 (0) 2023.06.20 🍾 React : Router, Navigate, build? (0) 2023.06.10 🍾 React 학습 기록 - 스타일링 ✨ (0) 2023.05.30