sagen - 상태 관리 라이브러리
규모가 큰 앱을 개발할 때 props drilling을 피하기 위한 목적으로, 상태가 변경하는 것을 직접 모니터링하기 위한 목적으로 또는 그 외의 이유로 상태 관리 라이브러리를 사용하곤 합니다.
또한, 상태 관리 라이브러리를 사용하지 않고 ContextAPI를 사용하는 경우도 있습니다(이 경우, ContextAPI는 타 상태 관리 라이브러리와 달리 렌더링 최적화가 이루어지지 않음을 인지하고 설계되어야 합니다).
위와 같이 이미 많은 상태 관리 라이브러리가 존재하며, 새로운 라이브러리가 종종 등장합니다. 본 글에서는 상태 관리 라이브러리 중 하나인 sagen
에 대해서 알아보도록 하겠습니다.
sagen 상태 관리 라이브러리
sagen 라이브러리는 Provider
가 존재하지 않습니다.
Redux는 하나의 store를 제공하며, MobX는 여러 store를 제공합니다.
Redu와 같은 경우 앱의 최상단에 Provider
를 위치시켜 모든 위치에서 참조 가능하게 만들면 되지만, MobX와 같이 여러 store를 제공하는 경우 최적의 Provider 위치를 찾아서 입력해야 합니다(이 위치는 페이지를 감싸는 Wrapper 컴포넌트가 될 수 있고, 일부 컴포넌트를 감싸고 있을 수도 있습니다).
ContextAPI를 사용하면 되지 않을까?
React에서 제공하는 상태를 공유하는 방법 중 하나인 Context API
에도 한계가 존재합니다.
이는 데이터의 변경이 자주 일어나고 데이터를 참조하는 곳이 많아질 수록 업데이트가 비효율적으로 이뤄지게 됩니다.
https://jungpaeng.tistory.com/58
Context API는 이러한 특성을 갖고 있어 낮은 빈도의 업데이트에 최적화되어 있으며, Flux와 같은 상태 관리 시스템을 대체할 수 없습니다.
이러한 이유로 Redux 라이브러리에서는 Context API를 사용하면서도 이를 최적화하기 위한 로직이 포함되어 있습니다.
https://jungpaeng.tistory.com/72
sagen의 특징에는 무엇이 있을까?
- sagen은 바닐라 자바스크립트에서도 원활하게 동작하며, 이를 react에서 사용할 수 있는 라이브러리를 내장하고 있습니다.
- sagen은 배우기 쉽습니다. API가 단순하며, 사용될 수 있는 예제가 ReadMe 파일에 나와 있으며, React Hook 기반으로 사용할 수 있어 익숙하게 다가올 것입니다.
- 여러 store를 제공하고 있으며, Provider의 위치를 고민하지 않아도 되도록 store 내에서 데이터를 관리하고 있습니다.
- selector를 이용해 인자를 보내는 것으로 필요한 데이터만을 가져올 수 있습니다.
- 손쉽게 persist를 설정할 수 있습니다.
sagen 하나씩 살펴보기
Store
Store는 상태 값을 관리하는 '공간'입니다. 이 store에 담겨 있는 값 중 일부가 변경되면 이 store 내의 값을 사용하고 있는 모든 컴포넌트들이 다시 렌더링됩니다.
createStore
store를 생성하기 위한 함수입니다. 인자로는 defaultValue
를 전달받으며, 이 값으로는 숫자, 문자열, 배열, 객체 등의 값이 될 수 있습니다.
useGlobalStore
생성된 store를 인자로 전달받으며, [state, setState]
의 getter, setter를 반환합니다.
이 구조는 useState
hook을 사용해본 경험이 있다면 쉽게 적응할 수 있을 것입니다.
state selector
state 값을 가져올 때 selector
함수를 넘겨 state를 가공할 수 있습니다.
기본적으로 store에 저장되어 있는 값이 변경되면, ===
연산자를 통해 기존 값과 새로운 값을 비교해 컴포넌트 업데이틀 여부를 결정하므로, 아래와 같이 state
에서 필요한 값만을 가져와 사용하는 것이 최적화에 도움이 됩니다.
import React from 'react';
import { createStore, useGlobalStore } from 'sagen';
const globalStore = createStore({ num: 0, str: '' });
const numberSelector = state => state.num;
const stringSelector = state => state.str;
const NumberChild = () => {
const [num, setValue] = useGlobalStore(globalStore, numberSelector);
const handleClickBtn = React.useCallback(() => {
setValue((curr) => ({
...curr,
num: curr.num + 1,
}));
}, []);
return (
<div className="App">
<p>number: {num}</p>
<button onClick={handleClickBtn}>Click</button>
</div>
);
};
const StringChild = () => {
const [str] = useGlobalStore(globalStore, stringSelector);
return (
<div className="App">
<p>string: {str}</p>
</div>
);
};
const App = () => {
return (
<div>
<NumberChild />
<StringChild />
</div>
);
};
위와 같은 예시에서 num
값이 변경된다면 str
값만을 사용하고 있는 StringChild
컴포넌트는 업데이트가 일어나지 않게 됩니다.
customSetStatecreateStore
함수에 (setter: any) => {state: any, customSetState: any}
형태의 함수를 넘겨 반환받는 setter를 커스텀할 수 있습니다.
const testStore = createStore((set) => {
return {
state: {
num: 1,
str: 'test',
},
customSetState: {
setNum: (num: number) => set((prev: any) => ({ ...prev, num })),
},
};
});
const App = () => {
const [state, setState] = useGlobalStore(testStore);
const { num, str } = state;
const { setNum } = setState;
return (
<div className="App">
<p>number state: {num}</p>
<button onClick={() => setNum(100)}>
ClickMe
</button>
</div>
);
};
createStore
에 함수를 전달할 경우, 첫 번째 인자로 전달받는 setter
에서 prev state
값을 가져와 이를 수정할 수 있습니다.
custom setter prev valueuseState
의 setter
에 함수를 전달해 이전 값을 참조할 수 있습니다. 기본적으로 제공해주는 setter
를 사용하면 이와 같은 기능을 사용할 수 있지만, customSetter
를 사용하는 경우 해당 기능이 제외됩니다.
다음과 같이 작성해 custom setter
에 prev 값을 인자로 갖고 있는 형태의 함수를 전달시킬 수 있습니다.
customSetState: {
setNum: (numFunc) => {
if (typeof numFunc === 'function') {
return set((prev: any) => ({ ...prev, num: numFunc(prev.num) }));
} else {
return set((prev: any) => ({ ...prev, numFunc }));
}
}
}
위의 사용 예시는 다음과 같습니다.
setNum(100);
setNum(prev => prev.num + 100);
shallowEqual
selector를 사용해 사용하는 값을 구분하기 어려운 경우, shallowEqual
를 넘겨 비교 연산 자체를 수정할 수 있습니다.
useGlobalStore
의 두 번째 인자로 selector
함수가 전달되는 것을 요구하기 때문에 state => state
와 같은 형태로 상태 값을 그대로 반환해주는 selector 함수를 넘겨 사용할 수 있습니다.
import React from 'react';
import { createStore, useGlobalStore, shallowEqual } from 'sagen';
const globalStore = createStore({ num: 0, str: '' });
const storeSelector = state => state;
const App = () => {
const [state, setState] = useGlobalStore(globalStore, storeSelector, shallowEqual);
return (
<div>
...
</div>
);
};
with vanilla jssagen
의 createStore
는 React에 종속되어 있지 않습니다. createStore
를 호출해 바닐라 자바스크립트에서 사용할 수 있습니다.
redux middlewareredux
미들웨어를 사용할 경우, 리듀서 함수를 전달해 값을 관리할 수 있습니다.
// reduxStore.ts
export function testReducer(state, action) {
switch (action.type) {
case 'INCREMENT':
return state + 1;
case 'DECREMENT':
return state - 1;
default:
return state;
}
};
const reduxStore = createStore(redux(testReducer, 0));
const App = () => {
const [state, dispatch] = useGlobalStore(reduxStore);
return (
<div className="App">
<p>state: {state}</p>
<button
onClick={() => dispatch({ type: 'INCREMENT' })}
>
ClickMe
</button>
</div>
);
}
redux
함수의 첫 번째 함수로 reducer
함수, 두 번째 인자로 defaultValue
값을 전달하면 됩니다.
이 store를 useGlobalStore
에 넘길 경우 [state, dispatch]
와 같은 형태로 반환합니다. useReducer
hook을 사용해본 경험이 있다면 더욱 빠르게 적응할 수 있을 것입니다.
persist middlewarelocalStorage
, sessionStorage
등에 값을 저장하기 위한 persist
미들웨어를 제공합니다.
const globalStore = createStore(
persist(
{
name: 'local-persist-test',
storage: localStorage,
},
redux(testReducer, 0),
),
);
사용해볼만한 라이브러리인가?
React에서 제공하는 Hook에 익숙하다면 손쉽게 사용할 수 있는 라이브러리로, 충분히 사용해볼만한 가치가 있다고 생각합니다.
'Project > JavaScript Project' 카테고리의 다른 글
[React] 전역 상태 관리 라이브러리 제작기 (3) (0) | 2020.12.02 |
---|---|
[React] 전역 상태 관리 라이브러리 제작기 (2) (0) | 2020.12.01 |
[React] 전역 상태 관리 라이브러리 제작기 (1) (0) | 2020.11.29 |
댓글
이 글 공유하기
다른 글
-
[React] 전역 상태 관리 라이브러리 제작기 (3)
[React] 전역 상태 관리 라이브러리 제작기 (3)
2020.12.02 -
[React] 전역 상태 관리 라이브러리 제작기 (2)
[React] 전역 상태 관리 라이브러리 제작기 (2)
2020.12.01 -
[React] 전역 상태 관리 라이브러리 제작기 (1)
[React] 전역 상태 관리 라이브러리 제작기 (1)
2020.11.29