본문 바로가기
Javascript

[Javascript] 리덕스 만들어보기 - (1)

by 슥짱 2022. 7. 24.

리덕스는 시시각각 변하는 복잡한 상태를 효과적으로 다루기 위해, 세 가지 기초 원칙에 따라 상태의 변화 시점에 제약을 두어 상태 변화를 예측 가능하게 한다. 리덕스의 세 가지 원칙은 다음과 같다.

모든 상태는 하나의 저장소에 저장된다.

애플리케이션의 모든 상태는 단 하나의 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://junilhwang.github.io/TIL/Javascript/Design/Vanilla-JS-Store/#_1-%E1%84%8C%E1%85%AE%E1%86%BC%E1%84%8B%E1%85%A1%E1%86%BC-%E1%84%8C%E1%85%B5%E1%86%B8%E1%84%8C%E1%85%AE%E1%86%BC%E1%84%89%E1%85%B5%E1%86%A8-%E1%84%89%E1%85%A1%E1%86%BC%E1%84%90%E1%85%A2%E1%84%80%E1%85%AA%E1%86%AB%E1%84%85%E1%85%B5\

 

Vanilla Javascript로 상태관리 시스템 만들기 | 개발자 황준일

Vanilla Javascript로 상태관리 시스템 만들기 본 포스트는 Vuex나 Redux 같은 상태관리 프레임워크를 직접 만들어보는 내용이다. 그리고 이 포스트를 읽기 전에 Vanilla Javascript로 웹 컴포넌트 만들기open

junilhwang.github.io

- https://github.com/deminoth/redux

 

GitHub - deminoth/redux: 자바스크립트 앱을 위한 예측 가능한 상태 컨테이너

자바스크립트 앱을 위한 예측 가능한 상태 컨테이너. Contribute to deminoth/redux development by creating an account on GitHub.

github.com

- https://ko.redux.js.org/understanding/thinking-in-redux/three-principles

 

3가지 원칙 | Redux

소개 > 3가지 원칙: Redux 사용의 3가지 중요 원칙

ko.redux.js.org

 

댓글