개요
모던 자바스크립트의 구조를 학습하고 컴포넌트 기반의 코드를 작성한다.
목표
순수 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인것이다.
다음 시리즈에는 더욱 발전한 프로젝트를 진행해보도록 하자!
'Frontend' 카테고리의 다른 글
모던 자바스크립트 Youtube Playlist - 베이스 코드 작성하기 (0) | 2023.07.05 |
---|---|
모던 자바스크립트 TodoList - User별 TodoList (0) | 2023.05.21 |
모던 자바스크립트 TodoList - Todo Component (3) (0) | 2023.05.08 |
모던 자바스크립트 TodoList - Todo Component (2) (0) | 2023.05.06 |
모던 자바스크립트 TodoList - Todo Component (1) (0) | 2023.05.02 |