관리 메뉴

bright jazz music

[프레임워크 없는 프론트엔드 개발] 1.2. 뷰 함수 분리 본문

Books/프레임워크 없는 프론트엔드 개발

[프레임워크 없는 프론트엔드 개발] 1.2. 뷰 함수 분리

bright jazz music 2025. 5. 25. 22:30

https://catnails.tistory.com/577

 

[프레임워크 없는 프론트엔드 개발] 1.1. 순수함수를 이용한 렌더링 구현

https://github.com/hojuncha997/frameworkless-front-end-dev GitHub - hojuncha997/frameworkless-front-end-dev: 프레임워크 없는 프론트엔드 서적 학습 리포지토리프레임워크 없는 프론트엔드 서적 학습 리포지토리. Contribut

catnails.tistory.com

 

이전 포스팅에서 다뤘던 view.js의 함수는 DOM 을 조작하는 함수가 하나뿐이었다. 이번에는 해당 함수를 기능별로 나누었다.

프로젝트 디렉토리의 루트 경로에 view 디렉토리를 만들고 아래의 파일을 만들어 주었다.

 

1. /view 디렉토리 내부의 파일 설명

 

- app.js (작은 뷰 함수들로 이루어진 뷰 함수)

: 컨트롤러 역할을 하는 index.js로부터 변경해야 할 HTML 요소와 상태(배열과 필터 텍스트)를 넘겨 받는다. 그리고 아래 파일(함수)들에 분배한다. 함수들로부터의 반환값은, 깊은 복사를 한 HTML 요소에 적용하고 이 복제된 요소를 다시 반환한다.

 

- todo.js (할일 목록을 렌더링하는 뷰 함수)

: app.js로부터 HTML 요소와 상태를 인자로 받는다. 상태를 사용해 <li> 태그로 이루어진 텍스트를 만들고 그것을 다시 HTML 요소에 적용하여 변경된 HTML 요소를 반환한다.

 

- counter.js (할 일 개수를 렌더링 하는 뷰 함수)
: app.js로부터 HTML 요소와 상태를 인자로 받는다. 상태 객체 내부의 배열의 길이를 텍스트로 만들고, 인자로 받은 HTML요소의 textContent 메서드를 사용하여 텍스트 값을 적용한 뒤 반환한다.

 

- filter.js ( 필터링을 이후 결과를 렌더링하는 뷰 함수)

: app.js로부터 HTML 요소와 상태를 인자로 받는다. 건네 받은 요소의 하위 요소 가운데 textContent값이 인자로 받은 상태 객체의 currentFilter 속성 값과 같으면 selected라는 css class를 해당 요소에 추가한 뒤 반환한다.

 

2. 파일 내용

 

2.1. /index.js

:기존 view.js 대신 app.js를 임포트한다.

// /index.js
// 컨트롤러 역할을 하는 파일

import getTodos from './getTodos.js'
// import view from './view.js'
import view from './view/app.js'  // 기존 view.js 대신 view/app.js 사용. app.js에서는 분리된 뷰 함수들을 사용한다.

const state = {
  todos: getTodos(),
  currentFilter: 'All'
}

const main = document.querySelector('.todoapp')

window.requestAnimationFrame(() => {
    // 뷰 함수를 호출하여 투두 앱 요소를 반환한다.
  const newMain = view(main, state)
  // DOM에서 기존 요소를 제거하고 새 요소를 삽입한다.
  main.replaceWith(newMain)
})

 

2.2. /view/app.js

// /view/app.js
import todosView from './todos.js'
import counterView from './counter.js'
import filtersView from './filters.js'

export default (targetElement, state) => {
  const element = targetElement.cloneNode(true)

  const list = element
    .querySelector('.todo-list')
  const counter = element
    .querySelector('.todo-count')
  const filters = element
    .querySelector('.filters')

  list.replaceWith(todosView(list, state))  // 상태(투두 배열과 필터)를 인자로 넘기고, 반환값으로 HTML 요소를 받아서 교체한다.
  counter.replaceWith(counterView(counter, state))  // 상태(투두 배열)를 인자로 넘기고, 반환값으로 HTML 요소를 받아서 교체한다.
  filters.replaceWith(filtersView(filters, state))  // 상태(필터)를 인자로 넘기고, 반환값으로 HTML 요소를 받아서 교체한다.

  return element
}

 

2.3. /view/counter.js

// /view/counter.js
const getTodoCount = todos => {
    const notCompleted = todos
      .filter(todo => !todo.completed)
  
    const { length } = notCompleted
    if (length === 1) {
      return '1 Item left'
    }
  
    return `${length} Items left`
  }
  
  export default (targetElement, { todos }) => {
    const newCounter = targetElement.cloneNode(true)
    newCounter.textContent = getTodoCount(todos)
    // 새로운 카운터 HTML 요소를 반환한다.
    return newCounter
  }

 

2.4. /view/filters.js

// /view/filters.js

export default (targetElement, { currentFilter }) => {
    const newCounter = targetElement.cloneNode(true)
    Array
      .from(newCounter.querySelectorAll('li a'))
      .forEach(a => {
        if (a.textContent === currentFilter) {
          a.classList.add('selected') // 현재 필터와 같은 것에만 css 클래스를 추가한다.
        } else {
          a.classList.remove('selected') // 현재 필터와 다른 것에는 css 클래스를 제거한다.
        }
      })
    // 새로운 필터 HTML 요소를 반환한다.
    return newCounter
  }

 

2.5. todos.js

// /view/todos.js   : 할일을 표시하는 렌더링 함수

const getTodoElement = todo => {
    const {
      text,
      completed
    } = todo
  
    return `
        <li ${completed ? 'class="completed"' : ''}>
          <div class="view">
            <input 
              ${completed ? 'checked' : ''}
              class="toggle" 
              type="checkbox">
            <label>${text}</label>
            <button class="destroy"></button>
          </div>
          <input class="edit" value="${text}">
        </li>`
  }
  
  export default (targetElement, { todos }) => {    
    const newTodoList = targetElement.cloneNode(true)
    const todosElements = todos
      .map(getTodoElement)  // 상태로 넘어온 배열을 순회하며 각 투두 아이템 요소를 생성하고 이를 문자열로 변환하여 결합한다. <li>...</li> 여러 개가 한 줄로 만들어진 문자열이다.
      .join('')
    newTodoList.innerHTML = todosElements // 새로운 투두 아이템 HTML 요소를 반환한다.
    return newTodoList
  }
Comments