리덕스는 시시각각 변하는 복잡한 상태를 효과적으로 다루기 위해, 세 가지 기초 원칙에 따라 상태의 변화 시점에 제약을 두어 상태 변화를 예측 가능하게 한다. 리덕스의 세 가지 원칙은 다음과 같다.
모든 상태는 하나의 저장소에 저장된다.
애플리케이션의 모든 상태는 단 하나의 store에 객체 트리 구조로 저장된다.
상태는 읽기 전용이다.
상태를 변화하기 위해서는 반드시 dispatch 함수에 액션 객체를 전달해야 한다.
변화는 순수 함수로 작성되어야 한다.
리듀서는 이전 상태를 직접 변경하지 않고 항상 새로운 객체 상태를 생성해서 반환해야 한다.
먼저 모든 상태들의 저장소인 store를 만드는 createStore()
함수를 작성해보자.
export const createStore = (reducer) => {
let state = reducer(undefined, {type: ''});
let listeners = [];
const subscribe = (listener) => {
listeners.push(()=>listener(getState()))
const unSubscribe = () => {
const index = listeners.indexOf(listener);
listeners.splice(index, 1)
}
return { unSubscribe }
}
const dispatch = (action) => {
state = reducer(state, action)
listeners.forEach(listener => listener());
}
const getState = () => {
deepFreeze(state)
return state
}
return {
getState,
subscribe,
dispatch
}
}
createStore()
함수는 reducer 함수를 인자로 받고, 상태를 다룰 수 있는 세 함수getState()
,subscribe()
,dispatch()
를 갖는 객체를 반환한다.subscribe()
는 리스너 함수를 받아listeners
배열에 넣어 구독한다. 구독된 리스너는 변경 불가능한 state를 받아 사용할 수 있다.listener
의 구독을 취소하기 위해서는 반환 객체의unSubscribe()
함수를 사용한다.dispatch()
는action
객체를 전달 받아 이를 리듀서에 현재 상태와 함께 전달해 상태 변경을 일으킨다. 그리고listeners
배열을 돌며 구독된 모든 함수들을 실행시킨다.getState()
함수는 store의 현재 상태를 반환한다. 단, 상태는 dispatch를 통해서만 변경되어야 하기 때문에deepFreeze()
함수로 상태를 얼린 뒤 반환한다.
deepFreeze()
함수는 다음과 같이 작성 했다.
const deepFreeze = (obj) => {
if(!Object.isFrozen(obj)){
Object.freeze(obj)
}
for(const key in obj) {
if(typeof obj[key] === 'object'){
deepFreeze(obj[key])
}
}
}
변화를 일으키는 함수인 reducer()
는 다음과 같이 작성되어야 한다.
const initState = [{
id: 1,
text: 'hi',
completed: false
},{
id: 2,
text: 'hello',
completed: false
}]
const reducer = (state = initState, action) => {
switch (action.type) {
case 'ADD_TODO':
return [
...state,
{
id: action.id,
text: action.text,
completed: false
}
]
case 'TOGGLE_TODO':
return state.map(todo =>
(todo.id === action.id)
? {...todo, completed: !todo.completed}
: todo
)
default:
return state
}
}
const {getState, subscribe, dispatch} = createStore(reducer)
reducer()
는 현재 상태와 액션 객체를 전달 받아 새로운 상태를 반환한다. reducer()
를 작성할 때 반드시 주의해야할 점은 항상 새로운 상태를 반환해야 한다는 것이다.
reducer()
까지 작성했으면 리덕스의 기본 구조는 다 갖추었다. 이제 완성한 나만의 리덕스로 실제 화면에 그려보자.
TODO를 그려주는 render()
함수를 subscribe()
함수로 구독하면 상태에 변화가 생길 때마다 화면이 다시 그려진다. 하지만 unSubscribe 버튼을 클릭해 render()
함수의 구독을 취소하면 더이상 상태가 변화해도 화면이 다시 그려지지 않을 것이다.
참고자료
- https://github.com/deminoth/redux
- https://ko.redux.js.org/understanding/thinking-in-redux/three-principles
'Javascript' 카테고리의 다른 글
[Javascript] 변수 선언과 호이스팅(feat. TDZ) (2) | 2022.12.27 |
---|---|
[Javascript] this: 이름 값 못하는 자바스크립트의 this (0) | 2022.07.03 |
[Javascript] requestAnimationFrame(애니메이션 최적화하기) (0) | 2022.04.25 |
[Javascript] Closure (0) | 2022.04.14 |
[Javascript] Tagged Templates (styled-components의 이상한 문법) (1) | 2022.02.25 |
댓글