민프
[React] Recoil이란? 본문
Recoil라이브러리의 필요성
https://recoiljs.org/ko/docs/introduction/motivation
공식문서에 의하면 사실 React 자체 기능을 사용하는 것이 좋다고 하지만 다음과 같은 한계가 있다고 하였다.
- 컴포넌트의 상태는 공통된 상위요소까지 끌어올려야만 공유될 수 있으며, 이 과정에서 거대한 트리가 다시 렌더링되는 효과를 야기하기도 한다.
- 이 두 가지 특성이 트리의 최상단(state가 존재하는 곳)부터 트리의 말단(state가 사용되는 곳)까지의 코드 분할을 어렵게 한다.
즉 Recoil은 모든 자식 컴포넌트들이 다시 랜더링 되는 것을 방지하기 위해 나온 상태 관리 라이브러리라고 생각하면 된다.
부모 컴포넌트의 state를 공유하지 않고 Atoms라는 저장소에 상태를 관리하고,
컴포넌트들은 Atoms에 접근하여 상태를 공유하기 때문에 부모 컴포넌트의 state에 의존하지 않아도 된다.
React는 부모 Component -> 자식 Component 바인딩을 하는 단방향 바인딩을 하는 라이브러리 인데
Recoil을 사용하면 양방향 바인딩이 가능해진다.
예를 들어서 ) 인스타그램에서 좋아요를 눌렀을 때 좋아요 개수만 늘어나는게 아닌 좋아요 개수가 늘어나고, 업로드한 유저에게 알림 보내고, 해당 사진에 좋아요를 누른 유저 기록 등 단순하게 하나의 변경으로 끝나지 않게 되는데 이럴 경우를 위해 Redux나 Recoil같은 상태 관리 라이브러리가 등장하게 되었다.
Recoil - 주요 개념 - Atom
Atoms는 상태의 단위이며, 업데이트와 구독이 가능하다. atom이 업데이트되면 각각의 구독된 컴포넌트는 새로운 값을 반영하여 다시 렌더링 된다.
Atoms는 React의 로컬 컴포넌트의 상태 대신 사용할 수 있다. 동일한 atom이 여러 컴포넌트에서 사용되는 경우 모든 컴포넌트는 상태를 공유한다.
Atoms는 atom함수를 사용해 생성한다.
const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
atom을 사용할 땐 특정 고유한 키가 필요하다.
컴포넌트에서 atom을 읽고 쓰려면 useRecoilState라는 훅을 사용한다. React의 useState와 비슷하지만 상태가 컴포넌트 간에 공유될 수 있다는 차이가 있다.
function FontButton() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<button onClick={() => setFontSize((size) => size + 1)} style={{fontSize}}>
Click to Enlarge
</button>
);
}
위 코드에서 fontSize를 1씩 커지게 만들었을 때
function Text() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return <p style={{fontSize}}>This text will increase in size too.</p>;
}
해당 fontSizeState atom을 사용하는 다른 컴포넌트의 글꼴 크기도 같이 변화한다.
전체 코드를 보면
//atom.ts
import { atom } from "recoil"
export const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
//App.tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { useEffect } from "react";
import { useRecoilState } from "recoil";
import { fontSizeState } from "./recoil/atom";
function App() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
return (
<div className="App">
<header className="App-header">
<button
onClick={() => setFontSize((size) => size + 1)}
style={{ fontSize }}
>
Click to Enlarge
</button>
</header>
</div>
);
}
export default App;
위와 같이 useRecoilState 라는 hook 을 recoil 라이브러리에서 가져와, 위에 정의한 atom 을 넣어주면서 값을 사용하고, 변경할 수 있게 된다.
공식문서에서 뽑은
atom과 상호작용하기 위해 자주 사용되는 Hooks는
- useRecoilState(): atom을 읽고 쓰려고 할 때 이 Hook을 사용한다. 이 Hook는 atom에 컴포넌트을 등록하도록 한다.
- useRecoilValue(): atom을 읽기만 할 때 이 Hook를 사용한다. 이 Hook는 atom에 컴포넌트를 등록하도록 한다.
- useSetRecoilState(): atom에 쓰려고만 할 때 이 Hook를 사용한다.
- useResetRecoilState(): atom을 초깃값으로 초기화할 때 이 Hook을 사용한다.
Recoil - 주요 개념 - Selector
Selector라는 개념은 공식문서를 봐도 이해가 쉽게 되진 않았는데
https://recoiljs.org/ko/docs/basic-tutorial/selectors
공식 문서에서는 Selector는 파생된 상태(derived state)의 일부를 나타낸다. 파생된 상태를 어떤 방법으로든 주어진 상태를 수정하는 순수 함수에 전달된 상태의 결과물로 생각할 수 있다.
파생된 상태는 다른 데이터에 의존하는 동적인 데이터를 만들 수 있기 때문에 강력한 개념이다. 우리의 todo 리스트 애플리케이션 맥락에서는 다음과 같은 것들이 파생된 상태로 간주된다.
라고 설명하고 있다.
즉 Selector는 atom을 활용해서 개발자가 원하는 대로 값을 뽑아서 사용할 수 있는 도구라고 생각할 수 있겠다.
예시를 보자
//atom.ts
import { atom, selector } from "recoil"
export const fontSizeState = atom({
key: 'fontSizeState',
default: 14,
});
export const fontSizeLabelState = selector({
key: 'fontSizeLabelState',
get: ({get}) => {
const fontSize = get(fontSizeState);
const unit = 'px';
return `${fontSize}${unit}`;
},
});
//App.tsx
import React from "react";
import logo from "./logo.svg";
import "./App.css";
import { useEffect } from "react";
import { useRecoilState, useRecoilValue } from "recoil";
import { fontSizeState, fontSizeLabelState } from "./recoil/atom";
function App() {
const [fontSize, setFontSize] = useRecoilState(fontSizeState);
const fontSizeLabel = useRecoilValue(fontSizeLabelState);
return (
<div className="App">
<header className="App-header">
<button
onClick={() => setFontSize((size) => size + 1)}
style={{ fontSize }}
>
Click to Enlarge
</button>
<div>Current font size: ${fontSizeLabel}</div>
<div>Current font size: ${fontSize}</div>
</header>
</div>
);
}
export default App;
여기에서 Selectors는 selector함수를 사용해 정의하는데
get 속성은 계산될 함수다. 전달되는 get 인자를 통해 atoms와 다른 selectors에 접근할 수 있다. 다른 atoms나 selectors에 접근하면 자동으로 종속 관계가 생성되므로, 참조했던 다른 atoms나 selectors가 업데이트되면 이 함수도 다시 실행된다.
결과는 아래와 같다
그림에서 보면 selector에서 get한 것을 px를 뒤에 붙여서 return해주는 것을 볼 수 있는데 이렇게 내가 원하는 정보를 가지고 오고 원하는 대로 custom을 진행한 후 return을 해줄 수 있다.
예를 들어서 DB를 생각해보면
여러 테이블들이 있는데 여기에서 여러 테이블은 Atom을 의미하고 selector은 select쿼리문을 보면 된다.
여러 테이블을 참조하여 데이터를 뽑아낼 때 쿼리문에 join을 쓰고 select를해서 원하는 데이터를 조합해서 return해주는 것을 말해주는 것 이다.
공식문서를 보면 get말고도 다른 속성들이 있다.
여기에서 set을 보면 set안에서 get을 쓰게 되면 selector를 주어진 atom이나 selector를 구독하지 않는다고 나와있는데 그냥 get을 할때랑 차이점이 있는것 같다.
단순한 정적인 의존성이 있는 Selector:
const mySelector = selector({
key: 'MySelector',
get: ({get}) => get(myAtom) * 100,
});
동적 의존성인 의존성이 있는 Selector:
아래 예시에서 mySelector는 toggleState atom뿐만 아니라 toggleState에 의존하는 selectorA 또는 selectorB selector도 의존한다.
const toggleState = atom({key: 'Toggle', default: false});
const mySelector = selector({
key: 'MySelector',
get: ({get}) => {
const toggle = get(toggleState);
if (toggle) {
return get(selectorA);
} else {
return get(selectorB);
}
},
});
쓰기 가능한 Selector
이 간단한 selector는 기본적으로 atom을 감싸서 필드를 추가한다. 이것은 단지 설정과 재설정 작업을 업스트림 atom까지 통과한다.
const proxySelector = selector({
key: 'ProxySelector',
get: ({get}) => ({...get(myAtom), extraField: 'hi'}),
set: ({set}, newValue) => set(myAtom, newValue),
});
이 selector는 데이터를 변환하므로 입력 값이 DefaultValue인지 확인해야 한다.
const transformSelector = selector({
key: 'TransformSelector',
get: ({get}) => get(myAtom) * 100,
set: ({set}, newValue) =>
set(myAtom, newValue instanceof DefaultValue ? newValue : newValue / 100),
});
비동기 Selector
api를 사용할 때 비동기 Selector를 사용할 수 있다.
import { selector, useRecoilValue } from "recoil";
import React from "react";
const myQuery = selector({
key: "MyDBQuery",
get: async () => {
const response = await fetch(getMyRequestUrl());
return response.json();
},
});
function QueryResults() {
const queryResults = useRecoilValue(myQuery);
return <div>{queryResults.foo}</div>;
}
function ResultsSection() {
return (
<React.Suspense fallback={<div>Loading...</div>}>
<QueryResults />
</React.Suspense>
);
}
export default ResultsSection;
여기까지 Recoil과 atom, Selector 알아보았는데
아직 Recoil을 사용함으로써 상태들의 대해서 효과적으로 관리를 해줄 수 있을 것 같다.
조금씩 조금씩 적용을 해보자
'[React]' 카테고리의 다른 글
[React][Error] React-modal ariaHideApp이란? (0) | 2023.02.17 |
---|---|
[React][TypeScript] TypeScript에서 JSON 파일 import하기 (0) | 2023.02.09 |
[React][TypeScript] Zendesk -2 봇 로직 추가하기 (0) | 2023.02.07 |
[React][TypeScript] Zendesk -1 챗봇 적용해보기 (0) | 2023.02.07 |
[React][TypeScript] 'IntrinsicAttributes & IntrinsicClassAttributes 에러 해결방법 (0) | 2023.02.04 |