React에서 사용되는 상태관리 패턴이다. ( Redux, Context API )
핵심은 동작이 단방향으로 흐르다.
Action
상태를 변경하는 메소드. ( action key + payload )
Dispatcher
action을 store에게 전달하는 역할.
Model(Store)
상태을 관리하는 역할.
View
화면을 렌더링하는 역할.
export class Store {
#state = {};
#listeners = [];
#reducer;
/**
* 액션을 수행하고 새로운 state를 반환한다. dispatch를 통해 원하는 액션을 수행할 수 있다.
* @param { {} } state
* @param {{ (state, actionKey: String, payload: {}): {} }} reducer
*/
constructor(state, reducer) {
this.#state = state;
this.#reducer = reducer;
}
getState() {
return { ...this.#state };
}
subscribe(func) {
this.#listeners.push(func);
}
publish() {
this.#listeners.forEach((func) => func());
}
/**
* @param {string} actionKey
*/
async dispatch(actionKey, { ...payload } = {}) {
this.#state = await this.#reducer(this.#state, actionKey, { ...payload });
this.publish();
}
}
reducer
Store을 생성할 때 정의한다.
해당 Store에서 어떻게 state을 변경할지(액션)를 미리 정하고 이후 dispatch
할 때 해당 액션을 수행한다.
async
비동기 action을 할 수 있기 때문에 붙인다.
#
private 변수를 의미하는 것으로 안붙여도 무방하다.
payload
액션을 수행할 메소드의 인자이다. json타입을 사용한다.
/stores/RecentlyViewedStore.js
import { Store } from "../core";
const initState = { recentlyViewedList: [] };
const reducer = (state, actionKey, { text }) => {
const { recentlyViewedList } = state;
switch (actionKey) {
case "ADD_ITEM":
const newList = [text, ...recentlyViewedList.filter((v) => v !== text)];
return { ...state, recentlyViewedList: newList };
default:
return { ...state };
}
};
/**
* @actionKey `ADD_ITEM`
* @state { recentlyViewedList: string[] }
*/
export const RecentlyViewedStore = new Store(initState, reducer);
/** */
JSDoc으로 타입을 명시해주면 actionKey
, state 타입
작성할 때 실수를 줄일 수 있다.
// getState을 통해서 화면을 렌더링한다.
const renderRecentItems = () => {
const { recentlyViewedList } = RecentlyViewedStore.getState();
root.innerHTML = recentlyViewedList.join(", ");
};
// Store 상태가 변경되면 리렌더링을 한다.
RecentlyViewedStore.subscribe(renderRecentItems)
// Store 상태 변경하는 action을 호출한다.
RecentlyViewedStore.dispatch("ADD_ITEM", { text: e.target.innerText });