개요
모던 자바스크립트의 구조를 학습하고 컴포넌트 기반의 코드를 작성한다.
목표
순수 Javascript로 작성된 Todo List 어플리케이션을 작성한다.
(이 글은 Next Step에서 진행한 js-todo-list-step을 따라 작성되었습니다.)
전체 코드는 여기에서 보실 수 있습니다.
여정
유저를 담당하는 컴포넌트 작성
유저를 생성/삭제/조회하는 컴포넌트 UserList를 작성할 것이다.
저번 포스팅에서 작성했던 Component를 바탕으로
작성해야할 메소드만 나열하면 아래와 같아진다.
// UserList.js
import Component from "../core/Component";
export default class UserList extends Component {
initState () { return {}; }
mounted () {
// 컴포넌트가 마운트된 후에 동작한다.
}
template () {
// 컴포넌트의 내용을 반환
return '';
}
setEvent () {
}
}
우선 최종적으로 표현할 모양(template)을 먼저 잡아보도록 하자
조영호님의 오브젝트를 읽어보면
객체간의 상호협력은 책임과 메시지로 정의된다.
즉, 데이터 중심의 설계보다는 어떤 책임을 수행할지가 중요하다.
필요한 행동을 먼저 정의하고 template() 메서드에 작성하면 아래와 같아진다.
// UserList.js 중..
template () {
const { users } = [] // 유저를 가져오는 법은 나중에 생각해보자
return `
<div id="user-list">
${ users && users.map( (user) =>
UserListItem(...)
)}
<button class="ripple user-create-button" data-action="createUser">
+ 유저 생성
</button>
<button class="ripple user-delete-button" data-action="deleteUser">
삭제 -
</button>
</div>
`
}
여기서 필요한 데이터 객체는 제쳐두고
필요한 행동 3가지를 먼저 정의해보자
- UserListItem : 저장된 유저 정보를 불러와 선택 여부에 따라 User 버튼을 생성해야 한다.
- createUser : 팝업으로 유저 이름을 입력하면, 유저를 저장해야 하고, User 버튼을 생성해야 한다.
- deleteUser : 현재 선택되어 있는 유저를 저장소에서 삭제한다. 삭제하면 User 버튼도 삭제되어야 한다.
일단 저장소 역할을 추가해야 한다.
하지만 Component가 저장의 역할까지 모두 맡는다면 책임이 무거워진다.
새로운 저장소 역할을 하는 객체를 만들자.
여기서부터는 코드 진행이 혼잡해지므로
전체 코드는 저장소에서 보는 것을 추천합니다!
저장소 객체 Store
아쉽지만 저장소 객체 역시 간단하지만은 않다.
하지만 저번 시간에 작성한 observable을 사용할 기회가 왔다!
저장소를 생성해주는 함수를 작성한다.
// createStore.js
import { observable } from "./observer.js";
export const createStore = (reducer) => {
// 스토어를 생성한다.
// 스토어는 상태를 관리하고, 상태를 변경하는 기능을 가진다.
// reducer 가 실행될 때
// 반환하는 객체 State 를 Observable 로 만든다.
const state = observable(reducer());
매개변수로 reducer라는 함수를 전달받아
구독가능한 observable로 생성하여 state에 저장한다.
reducer는 (state, action) => state로 반환되는 형태의 함수이다.
형태를 보면 아래와 같이 액션 타입과 그 액션에 대한 실제 행동의 모음집이다.
// reducer.js
const initialState = {
users: [],
}
const reducer = (state = initialState, { type, payload }) => {
switch (type) {
// case GET_USERS:
// ...state,
// ...action.payload,
default:
return state;
}
}
export default reducer
현재로 유저 목록 조회는 store의 getState()로 충분하니 비워두고 넘어가자
(다음 포스팅에서 실제 액션을 같이 작성할 것이다.)
자 reducer까지 알았으니 저장소에 저장하는 메서드를 store에 마저 작성해보자
// createStore.js 중
...
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;
}
}
그리고 이 값을 조회할때 반환하는 객체도
실제 객체가 아니라 복사한 객체로 전달하여 값이 변경되지 않도록 해야한다.
// createStore.js 중
// state 를 변경할 수 없도록 한 frozenState 를 만든다.
const frozenState = {};
Object.keys(state).forEach(key => {
Object.defineProperty(frozenState, key, {
get: () => state[key]
});
});
// frozenState 를 반환하는 getState 함수를 만든다.
const getState = () => frozenState;
return { getState, dispatch };
}
지금까지 작성한 store를 활용할 수 있도록 아래와 같이 내보낸다.
// store index.js
import reducer from "./reducer.js";
import {createStore} from "../core/createStore.js";
const store = createStore(reducer);
export { store };
자 이제 모든 준비가 끝났다!
동작 확인! 원래 코드로
원래 UserList 컴포넌트로 돌아와서
이제 아래와 같은 방식으로 저장소에 특정 행동을 하도록 요청을 보낼 수 있다.
// UserList.js 중..
template () {
const { users } = userStore.getState(); // 아까 막혔던 부분
return `
<div id="user-list">
${ users && users.map( (user) =>
UserListItem(user)
)}
...
이제 각 유저마다 버튼을 만들어 줘야한다.
자주 사용할법한 함수는 모조리 구현해두자.
// UserList.js 중..
const UserListItem = (user) => {
return `
<button class='ripple'>
${user.name}
</button>
`
}
자 지금까지 만든 UserList.js를 App에서 사용해보자.
물론 index.html에서 관련된 부분은 비워둬야 확인할 수 있다.
// index.htm
...
<div id="user-list">
</div>
// TodoApp.js
import { $ } from "./utils/selector.js";
import { UserList } from "./component/index.js";
export default class TodoApp {
constructor($target) {
this.$target = $target;
this.setup();
}
setup () {
const userListTarget =$('#user-list');
new UserList(userListTarget);
}
}
기본 템플릿에 작성한대로 출력된다.
당연히 아무것도 추가하지 않았으니 유저 버튼을 확인할 수 없다! ㅠㅠ...
다음 시간에는 유저 생성과 삭제를 이어서 진행해보자
짜잘한 팁
- 백틱(`)으로 문자열을 생성하는 방법은 템플릿 리터럴이라고 하며, 표현식과 개행문자를 지원한다 (ES6부터 사용가능)
- HTMLElement.dataset은 읽기 전용 속성으로 data-*에 대해 접근할 수 있도록 해준다.
<div id="newspaper" data-parent="social" data-type="article" data-index="123456" />
---
const newspaper = document.querySelect("#newspaper);
newspaper.dataset.parent // "social"
newspaper.dataset.type // "article"
newspaper.dataset.index // "123456"
마무리
이번 시간에는 Store 객체를 통해 상태를 저장하고
이를 컴포넌트에서 불러와 사용하는 방법을 학습했다.
다음 시간에는 각 액션에 따라 동작을 정의하여 실제 결과화면을 볼 수 있도록 한다.
참고자료
'Frontend' 카테고리의 다른 글
모던 자바스크립트 TodoList - Todo Component (3) (0) | 2023.05.08 |
---|---|
모던 자바스크립트 TodoList - Todo Component (2) (0) | 2023.05.06 |
모던 자바스크립트 TodoList - Todo Component (1) (0) | 2023.05.02 |
모던 자바스크립트 TodoList - User Component (0) | 2023.04.21 |
모던 자바스크립트 TodoList - Component & Observer (0) | 2023.04.16 |