[Redux] createStore - 구현편

relkimm·2022년 10월 14일
1

createStore 란

createStore 는 전역 상태(state) 를 가지고 있는 store 객체를 생성하는 함수이다. 그리고 store 는 getState, subscribe, dispatch API 를 제공한다.

const store = {
	getState,
    subscribe,
    dispatch,
}

createStore 는 함수의 인자로 reducer 를 받는데 이 reducer 는 state 를 변경시키는 역할을 한다. 또한 createStore 는 함수 호출이 끝난 뒤에도 자신의 지역변수 state, subscribers 를 store 객체에서 계속 참조하기 때문에 하나의 클로저라고 할 수 있다.

createStore 구현

그럼 이제 createStore 를 간략하게 구현해 보도록 한다. 앞서 createStore 는 인자로 reducer 를 받고 getState, subscribe, dispatch API 를 제공하는 store 를 반환한다고 했다. 이대로 구현해 보자면 아래와 같다.

function createStore(reducer) {
	function getState() {}

	function subscribe() {}

	function dispatch() {}

	const store = {
		getState,
		subscribe,
		dispatch,
	}

	return store;
}

const store = createStore(reducer)

getState 구현

앞서 store 는 전역 상태(state) 를 가진다고 했다. 그 전역 상태를 조회하는 API 가 getState 라고 보면 된다. 그래서 state 를 지역변수로 가지고 getState 를 통해 전역 상태를 조회할 수 있도록 구현해 보자.

function createStore(reducer) {
	let state = undefined;
	
    function getState() {
		return state;
	}
	...
}

지역 변수 state 는 createStore 가 클로저이기 때문에 함수 호출이 끝나더라도 계속 값을 참조할 수 있다. 또한 클로저이기 때문에 state 에 대한 직접적인 접근은 불가능하다. 좀더 이해하기 쉽도록 store 를 클래스 문법으로 구현해 보자.

class Store {
	constructor(reducer) {
		// _ 는 private 을 표현하기 위함
		this._state = undefined;
		this.reducer = reducer;
	}

	getState() {
		return this.state;
	}
}

const store = new Store(reducer)

아쉽게도 자바스크립트는 private 기능을 완벽히 지원하고 있지 않기 때문에, 멤버 변수에 _ 로 private 임을 표시하거나 폴리필이 필요할 수 있는 # 키워드를 사용해야 한다. 위의 코드는 _state 에 대한 직접적 접근이 가능하다. 따라서 자바스크립트에서 변수에 대한 직접적 접근을 피하고 싶으면 createStore 와 같은 패턴을 사용하면 되고 이러한 패턴을 Module Pattern 이라고도 한다.

subscribe 구현

store 는 자신이 관리하고 있는 전역 상태(state) 를 구독할 수 있는 subscribe API 를 제공한다. 여기서 말하는 구독은 state 가 변경되었을 때, 단순히 state 가 변경되었음을 전달 받는 것이다. 클라이언트는 subscribe 를 통해 state 의 변경 이벤트를 구독할 수 있고 store 는 구독자를 subscribers 배열에 관리하고 있다가 state 에 변경이 발생했을 때, subscribers 를 순회하며 구독자들에게 변경되었음을 알린다. 이 내용을 코드로 옮겨 보자.

function createStore(reducer) {
	let state = undefined;
	const subscribers = [];
	
	function subscribe(subscriber) {	
		subscribers.push(subscriber)
		return function unsubscribe() {
			subscribers.splice(subscribers.indexOf(subscriber), 1)
		}
	}
    ...
}

subscribe 는 단순히 구독자를 구독자 배열에 등록하는 역할만 하고 구독 해제할 수 있는 unsubscribe 함수를 반환한다. subscribe 함수 또한 반환하는 unsubscribe 함수에서 subscriber 를 참조하기 때문에 클로저라고 볼 수 있고, subscribers.splice(subscribers.indexOf(subscriber), 1) 코드는 subscribers 배열에서 자신을 삭제하는 로직이다. 사실 subscribe 는 아래 dispatch 함수와 함께 봐야 전체적인 흐름을 파악할 수 있다.

dispatch 구현

dispatch 는 action 을 인자로 받고 reducer 를 통해 전역 상태(state) 를 변경시키고 나서 변경 사항을 구독자들에게 전달한다. 그리고 다시끔 그 action 을 반환해 다음 dispatch 함수가 action 을 받을 수 있도록 한다. action 을 다시 반환하는 것은 미들웨어와 관련있기 때문에 미들웨어 편에서 좀더 자세히 알아 보도록 하자. 여기서 action 은 state 를 변경시키는 행위 또는 이벤트라고 볼 수도 있다. 예를 들자면, count 라는 state 가 있고 count 를 증가/감소 시키는 버튼이 있다고 하자. 여기서 action 은 증가/감소가 될 수 있고 증가/감소 버튼 클릭 시, action 이 발생한다고 볼 수 있다. 그럼 dispatch 를 구현해 보자.

function createStore() {
	let state = undefined;
	const subscribers = [];

	function dispatch(action) {
		state = reducer(state, action)
		subscribers.forEach(subscriber => subscriber())
		return action;
	}

}

reducer 구현

마지막으로 reducer 는 전역 상태(state) 를 변경시키는 역할을 한다고 보면 된다. 함수의 인자로 현재 state 와 action 을 받고 어떤 action 이 발생했는가에 따라서 state 를 변경시키고 변경된 state 를 반환한다.

function reducer(state, action) {
	switch(action.type) {
		case “increase”: {
			return state + 1;
		}
		case “decrease”: {
			return state - 1;
		}
		default: {
			return state;
		}
	}
}

정리

지금까지 createStore 를 직접 구현해 보면서 Redux 의 구성 요소에 대해 이해해 보는 시간을 가졌다. 아직 설명이 부족한 부분도 많고 combineReducers, applyMiddleware, enhancer, preloadedState 등 구현 못한 기능들도 많지만 다음 편에서는 지금 구현한 createStore 를 통해 Counter UI 를 그려보고자 한다. 그럼 다음 편에서 실제 Redux store 가 어떻게 활용되는지 확인해 보자.

0개의 댓글