Constate
Context를 사용하고 싶은데 다루어야 하는 상태가 많아진 상황에서 성능도 챙기고 개발도 편리하게 하고 싶을 때
Context는 리덕스보다 쓰기 쉽다는 장점이 있지만 Context 상태 중 의존하지 않는 다른 값이 바뀌게 될 때에도 컴포넌트가 렌더링 된다는 단점이 있었음
그렇기 떄문에 ContextAPI를 이용할 때 Context 를 구독하는 값마다 적절히 쪼개줘야하는 작업이 필요했다.
constate 함수에 넣는 selector들을 기반으로 Context들이 자동으로 만들어지고 Context를 사용하는 Hook도 자동으로 만들어진다.
useState, useReducer를 그냥 사용하면 되기 때문에 추가적으로 배울 것이 없다.
사용법
- npm install constate
- constate import후에
const [Provider, selector1이름, selector2이름...] = constate(()⇒ provider, value들의 셀렉터들...)
- value와 리턴해주는 함수를 첫 번째 파라미터로 ( Provider ), value를 셀렉해 파라미터로 넣어줄 수도 있다 (selector hook)
예시
import React, { useState } from "react";
import constate from "constate";
// 1️⃣ Create a custom hook as usual
const useCounter = () => {
const [count, setCount] = useState(0);
const increment = () => setCount((prevCount) => prevCount + 1);
return { count, increment };
};
//1번
const [CounterProvider, useCounterContext] = constate(useCounter);
//2번
const [CounterProvider, useCount, useIncrement] = useState(
useCounter,
(value) => value.count,
(value) => value.increment
);
/**
* 파라미터로는 useValue과 selector함수가 들어간다.
*
* useValue : value와 value의 setter-> ex) count, increment
* 뒤에 value로 만들어준 값의 셀렉터(hook)를 구체적으로 구조분해 할당을 통해 가져옴
*
*/
const Button = () => {
const { increment } = useCounterContext();
return <button onClick={increment}>+</button>;
};
const Count = () => {
// 4️⃣ Use context in other components
const { count } = useCounterContext();
return <span>{count}</span>;
};
export default () => {
// 5️⃣ Wrap your components with Provider
return (
<CounterProvider>
<Count />
<Button />
</CounterProvider>
);
};
Recoil
Context 만을 사용하여 글로벌 상태 관리를 하는 것은 오래전부터 성능이슈가 컸다.
- 상태 업데이트를 하기 위해서는 공통 조상 컴포넌트로 상태를 끌어올려야 하는데, 이 과정에서 너무 큰 트리가 리렌더링 될 수 있다.(구독하고 있는 친구들이 불필요한 값 변경시에도 렌더링 되는 이슈)
- Context 를 사용할 때 Consumer는 다양하게 사용되는데 Context에는 하나의 값만 담을 수 있다.
- 상태가 만들어지는 곳과 사용되는 곳의 코드 분리가 어려워짐
이를 위해서 많은 노력으로 Redux나 Mobx가 생겼다.
하지만 이들을 사용하려면 추가적인 기능을 위해 Redux-saga와 같은 라이브러리를 추가해 주어야 한다.
그리고 React 자체 라이브러리가 아니기에 상태 저장소가 외부에서 처리되며, 이때문에 React내부의 스케쥴러에 대한 접근이 불가하다고 한다.
이 Redux나 MobX같은 라이브러리들 보다 좀 더 가볍고 리액트 스럽게 사용할 수 있게 페이스북에서 공개한 라이브러리 이다.
단순한 getter, setter를 이용할 수 있으며, 코트 스플리팅 등을 이용할 수 있다는 점에서 장점이 크다.
Recoil의 용어들
- RecoilRoot : 상태를 사용하는 컴포넌트를 감싸줘야할 컴포넌트. 말 그대로 루트
- atom : 상태의 일부 상태, 값들을 의미
- useRecoilValue() : 구독하고 있는 상태값 가져오기 파라미터로 key넣어주기
- useRecoilState() : atom을 읽고 쓰게 할 수 있다. (getter, setter)
- useSetRecoilState() : atom의 setter만 가져다 쓰기
- useResetRecoilState: 값을 기본값으로 reset 시키는 함수
- selector : derived state의 변경사항을 나타낸다. (derived state : 주어진 state값을 주어진 function으로 수행한 결과값)
이 밖에도 많은 API들이 많다.
Atom (구독할 값 설정)
const todoListState = atom({
key: "todoListState",
default: [],
});
key 값으로 어떻게 이 값을 설정할 지에 대해 할당해줄 수 있다.
default에는 initial Value
Atom을 변경해보기
- useRecoilState를 이용여기서 좀 더 확장하여 이런 함수를 만들어 줄 수도 있다.
function TextInput() { const [text, setText] = useRecoilState(textState);/ 앞에서 textState로 만들어준 값 // 전역 상태를 useState처럼 만들어 준다. const onChange = (event) => { setText(event.target.value); }; return ( <div> <input type="text" value={text} onChange={onChange} /> <br /> Echo: {text} </div> ); }
const [text, setText] = useRecoilState(textState);
- useSetRecoilState
setter만 뽑아냄. 하단의 예제처럼 쓰임
기존의 값을 기반으로 새로운 값을 만들 수 있도록 updater형식을 사용해야 함.
const setTodoList = useSetRecoilState(todoListState);
const addItem = () => {
setTodoList((oldTodoList) => [
...oldTodoList,
{
id: getId(),
text: inputValue,
isComplete: false,
},
]);
setInputValue("");
};
Atom값을 가져와보기 (getter)
useRecoilValue() 을 이용하여 구독중인 값을 가지고 올 수 있다. 파라미터로 atom의 key값을 받는다.
function CharacterCount() {
const count = useRecoilValue(charCountState);
return <>Character Count: {count}</>;
}
selector
다른 atom에 의존하는 동적인 데이터를 만들 수 있게 해준다.
- key : atom을 식별하는 데에 사용되는 고유한 문자열 전역적으로 고유해야 함
- get : 파생된 상태의 값을 가지고 옴
다른 atom이나 selector를 리턴할 수 있다.
const certainState = selector ({
key : "해당 함수의 리턴 값의 키값"
get : ({get}) => {
const statename = get(가져오고 싶은 state키)
// ====== 내부적으로 get으로 가져온 값의 의존하고 있으며 둘 중 하나라도 변하면 값이 재실행된다.
return 계산된 결과값
}
});
예시
ToDoList를 필터링 하기 위해서 atom에 저장될 수 있는 필터의 기준을 만들어줄 것 인데 이 기준을 todoListFilterState라고 명명
const todoListFilterState = atom({
key: "todoListFilterState",
default: "Show All",
});
기준이 되는 값의 변경이 일어날 경우 필터링된 리스트를 만들어주는 selector를 만들어 줍니다.
하단의 함수에서 filteredToDoListState는 todoListFilterState와 todoListState의 변경을 감지한다.
const filteredTodoListState = selector({
key: '**filteredTodoListState'**, // 요 이름으로 받아줌
get: ({get}) => {
const filter = get(todoListFilterState); // 기준이 될 값을 만들어 준다.
const list = get(todoListState);
switch (filter) { // 해당값의 변경에 맞추어 기존 todoList값을 변경해줌
case 'Show Completed':
return list.filter((item) => item.isComplete);
case 'Show Uncompleted':
return list.filter((item) => !item.isComplete);
default:
return list;
}
},
});
해당하는 filteredToDoListState의 값의 변경은 이런 식으로 할 수 있다.
function TodoListFilters() {
// todoListFilterState를 변경할 수 있도록 useRecoilState를 만들어 준다.
const [filter, setFilter] = useRecoilState(todoListFilterState);
const updateFilter = ({ target: { value } }) => {
setFilter(value);
};
return (
<>
Filter:
<select value={filter} onChange={updateFilter}>
<option value="Show All">All</option>
<option value="Show Completed">Completed</option>
<option value="Show Uncompleted">Uncompleted</option>
</select>
</>
);
}
또한 Setter도 넣을 수 있다. set으로 구독하는값을 세팅해주는데, useRecoilState로 값을 가져올 때 setState값으로 가져올 수 있다.
비동기 함수의 지원
데이터를 읽어오는 것 같은 비동기 함수도 지원한다.
많은 옵션이 있다...
관련 링크 : 비동기 데이터 쿼리 | Recoil
예를 들면, selector 함수에 특정 값의 변경사항이 있을 시 데이터를 받아오는 쿼리문을 작성할 수 있다.
const currentUserNameQuery = selector({
key: "CurrentUserName",
get: async ({ get }) => {
const response = await myDBQuery({
userID: get(currentUserIDState),
});
return response.name;
},
});
function CurrentUserInfo() {
const userName = useRecoilValue(currentUserNameQuery);
return <div>{userName}</div>;
}
비동기 함수에서 에러처리
React.Suspense를 이용하여 에러 발생시에 잡아줄 수 있다.
<React.Suspense fallback={<div>Loading...</div>}>
<CurrentUserInfo />
</React.Suspense>
비동기 함수요청시 매개변수가 있는 쿼리
이럴 땐 selectorFamily 를 이용한다.
const userNameQuery = selectorFamily({
key: "UserName",
get: (userID) => async () => {
const response = await myDBQuery({ userID });
if (response.error) {
throw response.error;
}
return response.name;
},
});
function UserInfo({ userID }) {
// 불러올 땐 이렇게~
const userName = useRecoilValue(userNameQuery(userID));
}
이 밖에도 많은 함수를 지원하지만, 기본 문법 만으로도 구현시 크게 어렵지 않아보이는 게 큰 장점인 듯 합니다.
'Front-end > React 이론 스터디' 카테고리의 다른 글
Redux Middleware (0) | 2021.06.17 |
---|---|
API Fetching을 Context와 함께 써보자. (0) | 2021.06.04 |
CSS Module (0) | 2021.05.31 |
Context API (0) | 2021.05.29 |
useCallback (0) | 2021.05.26 |