주녁, DevNote
article thumbnail

개요

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

목표

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

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

 

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


여정

버튼을 누르면 이벤트 호출

버튼을 누르면 이벤트가 발생해야 한다.

잊고 있었겠지만 Component.js에는 이벤트 관련 함수가 두개나 작성되어 있다.

이벤트를 추가하는 addEvent()와 모든 이벤트를 등록하는 setEvent()다.

// Component.js 중
addEvent (eventType, selector, callback) {
    // 컴포넌트의 이벤트를 추가한다.
    this.$target.addEventListener(eventType, event => {
        if(!event.target.closest(selector)) return false;
        callback(event);
    });
}

setEvent () {
    // 컴포넌트의 모든 이벤트를 등록한다.
    // 모든 이벤트를 this.$target 에 등록하여 사용하면 된다. (이벤트 버블링)
    /* ex)
        const { deleteItem, toggleItem } = this.$props;

        this.addEvent('click', '.deleteBtn', ({target}) => {
          deleteItem(Number(target.closest('[data-seq]').dataset.seq));
        });

        this.addEvent('click', '.toggleBtn', ({target}) => {
          toggleItem(Number(target.closest('[data-seq]').dataset.seq));
        });
    */
}

 

이를 활용해서 UserList에 이벤트를 등록하면 아래와 같다.

// UserList.js
    setEvent () {
        this.addEvent('click', '.user-create-button', (event) => {
        	// 유저 생성 버튼 클릭 시 발생하는 일
        });
        this.addEvent('click', '.user-delete-button', (event) => {
        	// 유저 삭제 버튼 클릭 시 발생하는 일
        });
    }
    createUser(...) { // 유저 생성 로직 }
    deleteUser(...) { // 유저 삭제 로직 }

 

굳이 이벤트 발생 함수와 실제 처리 함수를 분리한 것은 

검증 로직과 비즈니스 로직(유저 생성, 삭제)은 분리되어야 하기 때문이다.

 

이제 유저 생성/삭제 로직을 작성해보자.


유저 생성

우선 이벤트 발생 시 기본적인 검증로직을 작성해준 뒤

비즈니스 로직을 호출해준다.

// UserList.js 중..
setEvent () {
    this.addEvent('click', '.user-create-button', (event) => {
        const userName = prompt('추가하고 싶은 이름을 입력해주세요')

        if (!userName) {
            alert('이름을 반드시 입력해야 합니다.');
            return false;
        }
        if (userName.length < 2) { // 제시된 제약조건
            alert('사용자 이름은 2글자 이상이어야 합니다.');
            return false;
        }
        if(this.isUserExist(userName)){
            alert('이미 존재하는 이름입니다.');
            return false;
        }
        
        this.createUser(userName);
    });
    ...
}

getUsers() { // 전체 유저 조회
    return store.getState();
}

isUserExist(userName) { // 유저가 이미 존재하는지 확인
    const { users } = this.getUsers();
    return users.find(user => user.name === userName);
}

createUser(userName) { // 유저 생성
    store.dispatch(createUser(userName));
}

 

그 다음은 비즈니스 로직에서 store에 요청을 보내는 dispatch() 함수를 호출한다.

여기서 createUser()는 reducer의 action이다.

현재 우리는 유저 이름만 알고있으니 이름을 매개변수로 전달한다.

 

 

reducer의 형식은 { type, payload } = action 형태이다.

전달하는 내용에 맞춰 함수를 작성한다.

// actions.js
// 타입을 정의하는 변수이므로 추후에는 코드 소개는 생략한다
const GET_USERS = 'GET_USERS'
const CREATE_USER = 'CREATE_USER'

export {
    GET_USERS,
    CREATE_USER,
}
// creator.js
import {
    GET_USERS,
    CREATE_USER,
    DELETE_USER,
} from './actions.js'

const getUsers = (users) => {
    return {
        type: GET_USERS,
        payload: {
            users,
        },
    }
}

const createUser = (name) => {
    return {
        type: CREATE_USER,
        payload: {
            name,
        },
    }
}

export { getUsers, createUser }

 

그리고 이를 reducer에서는 어떻게 처리할 것인지 정의해준다.

// reducer.js
import {
    GET_USERS,
    CREATE_USER,
    DELETE_USER,
} from "./user/actions.js";


let initialState = {
    users: [],
}

const reducer = (state = initialState, action = {}) => {
    switch (action.type) {
        case GET_USERS:
            return {
                ...state,
                ...action.payload,
            }
        case CREATE_USER:
            return {
                ...state,
                users: [...state.users, action.payload],
            }
        default:
            return state;
    }
}

중요한 점은 전달된 객체의 상태를 변경해서는 안되며,

상태가 바뀐다면 새로운 객체를 반환해야 한다는 점이다.

 

자 이제 결과를 확인해보자

유저가 정상적으로 추가된다.

 

하지만 여러번 눌러본 사람이라면 알겠지만

이벤트 콜백이 1, 2, 3개 점점 증가한다.

 

이 때 이벤트 전파 방지함수를 호출하면 이를 해결할 수 있다.

물론 Component의 addEvent 구현 안에 집어넣어도 된다. (필자는 집어넣기로 했다)

// UserList.js
setEvent () {
    this.addEvent('click', '.user-create-button', (event) => {
        ...
        event.stopImmediatePropagation();
    });
    ...
}

유저 삭제

유저 삭제도 생성과 동일한 과정을 거쳐 작성한다.

// UserList.js
setEvent () {
	...유저 생성...
    
    this.addEvent('click', '.user-delete-button', (event) => {
        const userName = prompt('삭제하고 싶은 이름을 입력해주세요')

        if (!userName) {
            alert('이름을 반드시 입력해야 합니다.');
            return false;
        }
        if (userName.length < 2) { // 제시된 제약조건
            alert('사용자 이름은 2글자 이상이어야 합니다.');
            return false;
        }
        if(this.isUserNotExist(userName)){
            alert('존재하지 않는 이름입니다.');
            return false;
        }
        
        this.deleteUser(userName);
    });
}

isUserNotExist(userName) {
    return !this.isUserExist(userName);
}

deleteUser(userName) {
    if(this.isUserNotExist(userName)){ return false; }
    store.dispatch(deleteUser(userName));
}
// creator.js
...
const deleteUser = (name) => {
    return {
        type: DELETE_USER,
        payload: {
            name,
        },
    }
}
export { getUsers, createUser, deleteUser }
// reducer.js

const reducer = (state = initialState, action = {}) => {
    switch (action.type) {
        ...
        case DELETE_USER:
            return {
                ...state,
                // 유저이름이 동일하면 필터링하여 반환
                users: state.users.filter(user => user.name !== action.payload.name),
            }
        ...
    }
}

삭제 메시지 출력화면


 


마무리

자 드디어 Component를 하나 완성했다.

그러나, 아직 기뻐하긴 이르다.

Todo리스트 작성, 필터링 등 다양한 Component가 남았다.

다음 시간에는 TodoList를 생성/삭제하는 로직을 작성해보도록 하자.

 

 

profile

주녁, DevNote

@junwork

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