주녁, DevNote
article thumbnail

개요

모던 자바스크립트의 구조를 학습하고 컴포넌트 기반의 코드를 작성한다.

목표

순수 Javascript로 작성된 Todo List 어플리케이션을 작성한다.

(이 글은 Next Step에서 진행한 js-todo-list-step을 따라 작성되었습니다.)

 

전체 코드는 여기에서 보실 수 있습니다.


여정

현재 store는 아래와 같은 문제점이 있다.

  • store를 사용하기 위해 컴포넌트들이 알아야할 정보가 너무 많다. (store 변경에 취약하다, 의존적이다)
  • store를 호출하면서 중복되는 코드를 재사용할 수 없다.
  • 어플리케이션을 재시작하면 정보가 초기화된다(휘발성이 있다, 실제 DB를 이용하고 있지 않다.)

하나씩 문제를 해결해보도록 하자.

 

 

Store 의존성 감소

기존 컴포넌트에서는 아래와 같은 형태로 Store를 호출해서 사용했다.

// TodoInput 컴포넌트
createTodoItem(content) {
    const newTodoItem = {
      id: String(Date.now()),
      user: store.getState().selectedUser, // Store를 통한 조회
      content: content,
      completed: false
    };
    store.dispatch(createTodo(newTodoItem)); // store를 통한 요청
}

 

이런 경우에는 store가 어떤 상태를 가지고 있는지,

어떤 연산자를 필요로 하는지, 컴포넌트가 모두 알아야한다.

또한, 연산자가 추가/변경될때마다 같이 변경될 가능성도 포함하고 있다.

 

따라서 필자는 아래와 같이

reducer를 직접 다루는 creator가 store를 직접 사용하도록 했다.

createTodoItem(event) {
    const newTodoItem = {
      id: String(Date.now()),
      user: getSelectedUser(), // creator.js로 코드 응집
      content: content,
      completed: false
    };
    createTodo(newTodoItem); // creator.js로 코드 응집
}
// creator.js(변경 전)
const createTodo = (todo) => {
    return {
        type: CREATE_TODO,
        payload: {
            todo
        },
    }
}

// creator.js(변경 후)
const createTodo = (todo) => {
    store.dispatch({			// store를 직접 다루도록 변경
        type: CREATE_TODO,
        payload: {
            todo
        },
    });
}

 

이렇게 creator가 reducer를 직접 다룸으로써

 

 

컴포넌트는 자신이 필요한 creator만 알고 있으면

store가 누구인지, 몇개인지, 어떤 연산을 하는지 몰라도 된다.

 

사실, 우리는 한가지 문제를 더 해결했다는 사실을 아는가?

 

Store 코드 재사용

creator에서 reducer를 직접 사용함으로써

다양한 상태들을 불러서 의미를 부여하고 재사용할 수 있다.

// user/creator.js
// 유저 목록 조회
const getUserList = () => {
    const { users } = store.getState();
    return users;
}
// 선택한 유저 조회
const getSelectedUser = () => {
    const { selectedUser } = store.getState();
    return selectedUser;
}
// 유저 존재여부 확인
const isUserExist = (userName) => {
    const { users } = store.getState();
    return users.find(user => user.name === userName);
}

 

기존 코드를 보면 더 명확히 알 수 있다.

 

// 기존
const { users, selectedUser } = store.getState();

// 변경
const users = getUserList();
const selectedUser = getSelectedUser();

기존에는 store의 상태에서 어떤 정보를 담고있는지 명확히 알아야했고,

store.getState()의 호출만으로는 어떤 정보가 조회/사용됬는지 알기 어려웠다.

 

이를 재사용하고 명확하게 추적할 수 있게된 것이다.

 

자, 이제 마지막으로 휘발성을 제거해보자!

 

Store 휘발성 제거

(원래대로라면 API를 이용해서 DB에 저장하고 불러와야 하지만

그 부분은 추후 시리즈에서 진행해보도록 하자.)

 

이 시리즈 초창기에 작성했던 Store 코드로 돌아가야 한다.

import { observable } from "./observer.js";

export const createStore = (reducer) => {
    // 스토어를 생성한다.
    // 스토어는 상태를 관리하고, 상태를 변경하는 기능을 가진다.

    // reducer 가 실행될 때
    // 반환하는 객체 State 를 Observable 로 만든다.
    const state = observable(reducer());

    const dispatch = (action) => {
        // 액션을 실행한다.
        // 액션을 실행하면 reducer 가 실행되고,
        // reducer 가 반환하는 새 객체를 state 에 할당된다.
        const newState = reducer(state, action);

        for (const [key, value] of Object.entries(newState)) {
            if(!state[key]) continue;
            state[key] = value;
        }
    }

    // state 를 변경할 수 없도록 한 frozenState 를 만든다.
    const frozenState = {};
    Object.keys(state).forEach(key => {
        Object.defineProperty(frozenState, key, {
            get: () => state[key]
        });
    });

    // frozenState 를 반환하는 getState 함수를 만든다.
    const getState = () => frozenState;

    return { getState, dispatch };
}

 

코드는 길지만 흐름은 단순하다.

다시 되짚어보면 

초기상태 지정 → dispatch(action) / getState() 의 흐름으로 흘러간다.

그렇다면, 초기상태를 불러올 때 localStorage에서 불러오도록,

저장할때 들고있는 state와 함께 localStorage에도 저장하도록 하면 된다!

(코드의 변경사항이 많지 않다)

 

    ...
    /** 변경된 부분 **/
    const initialState = reducer();
    const storedState = JSON.parse(localStorage.getItem("state"));
    const state = observable(storedState || initialState);
	
    const dispatch = (action) => {
        // 액션을 실행한다.
        // 액션을 실행하면 reducer 가 실행되고,
        // reducer 가 반환하는 새 객체를 state 에 할당된다.
        const newState = reducer(state, action);

        for (const [key, value] of Object.entries(newState)) {
            if(!state[key]) continue;
            state[key] = value;
        }
        ...
        /** 변경된 부분 **/
        // 변경된 state를 localStorage에 저장한다.
        localStorage.setItem("state", JSON.stringify(state));
    }

실행한 결과는 아래와 같이

F12 → 응용 프로그램 → 로컬 저장소에서 확인할 수 있다.

이제 새로고침을 하거나 재시작해도 Todo List가 유지되는 것을 확인할 수 있다.


마무리

지금까지 모던 자바스크립트 형태로 작성한 TodoList 어플리케이션을 작성해보았다.

 

고생했다! 이제야 프론트엔드의 첫 시리즈가 끝났다.

이러한 컴포넌트 형태가 발전해서 캐로셀이 되고, 무한 스크롤이 되는 것이다.

이런 형태가 모이고 모여서 발전한 형태가 React이고 Vue인것이다.

 

다음 시리즈에는 더욱 발전한 프로젝트를 진행해보도록 하자!

profile

주녁, DevNote

@junwork

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!