| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
- 이터레이터
- GIT
- Kernighan의 C언어 프로그래밍
- 자바편
- 자료구조와함께배우는알고리즘입문
- 스프링 시큐리티
- 데비안
- 처음 만나는 AI수학 with Python
- iterator
- resttemplate
- d
- /etc/network/interfaces
- 스프링부트핵심가이드
- ㅒ
- 서버설정
- 알파회계
- 친절한SQL튜닝
- 코드로배우는스프링웹프로젝트
- 선형대수
- 티스토리 쿠키 삭제
- 네트워크 설정
- 처음 만나는 AI 수학 with Python
- 코드로배우는스프링부트웹프로젝트
- 리눅스
- 페이징
- 자료구조와 함께 배우는 알고리즘 입문
- baeldung
- network configuration
- 구멍가게코딩단
- 목록처리
- Today
- Total
bright jazz music
[프레임워크 없는 프론트엔드 개발] 1.1. 순수함수를 이용한 렌더링 구현 본문
[프레임워크 없는 프론트엔드 개발] 1.1. 순수함수를 이용한 렌더링 구현
bright jazz music 2025. 5. 24. 00:04https://github.com/hojuncha997/frameworkless-front-end-dev
GitHub - hojuncha997/frameworkless-front-end-dev: 프레임워크 없는 프론트엔드 서적 학습 리포지토리
프레임워크 없는 프론트엔드 서적 학습 리포지토리. Contribute to hojuncha997/frameworkless-front-end-dev development by creating an account on GitHub.
github.com
* css, 라이브러리 등의 파일들은 여기에 기록하지 않았다. 위 리포지토리에는 남아 있으므로 참고할 것
* 브라우저에서 file://경로/index.html 로 접근하면 CORS 정책 오류 때문에 자바스크립트 파일을 가져오지 못한다.
따라서 npx http-server를 사용하여 노드 서버를 구동하거나 VS Code 의 Live Server등의 확장프로그램을 사용할 것.
1. 개요
리액트와 같은, 프레임워크에 가까운 라이브러리의 내부 방식에 대한 이해가 부족하여 학습을 시작하였다. 이 책에서는 프레임워크를 사용하지 않으면서 프레임워크에 사용되는 방식을 구현하는 내용이 있다.
2. 파일 설명
이 포스팅에서는 2장 '렌더링'의 일부를 기록하였다.
- index.html은 화면에 직접적으로 보여지는 파일이다. css 파일이 임포트 돼 있으며, index.js파일을 임포트하고 있다.
- index.js 파일은 컨트롤러 역할을 하는 파일이다. getTodos.js와 view.js 파일을 임포트하고 있다.
- getTodos.js은 fake.js 라이브러리를 사용하여 임의의 todo 리스트 배열을 만들어 반환하는 모듈이다.
- view.js은 HTML 요소와 상태를 인자로 받는다. 인자로 받은 HTML 요소를 복제한 요소를 반환한다. 이 때 복제된 요소를 가공하기 위해 상태값을 사용한다.
즉, index.js에서 getTodos.js 파일를 사용해 임의의 todos 배열을 만들어내고, 해당 배열을 상태 역할을 하는 객체에 포함시켜 view.js 파일 의 view 함수에 인자로 넘긴다. view 함수는 상태 객체 뿐만 아니라 HTML 요소 또한 인자로 받는데, 이는 view 함수가 인자로 받은 요소의 복사된 HTML 요소를 반환하기 때문이다. 상태값은 이 복사된 요소를 가공하는 데 사용된다.
3. 스키마
위에서 열거한 파일들로 생성한 스키마는 아래와 같다.
[브라우저 렌더링] -> [다음 렌더링 대기] -> [새 가상 노드] -> [DOM 조작] -> [브라우저 렌더링]
4. 코드
4.1. /index.html

<html>
<head>
<link rel="shortcut icon" href="../favicon.ico" />
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-common@1.0.5/base.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/todomvc-app-css@2.1.2/index.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/Faker/3.1.0/faker.js"></script>
<!-- <link rel="stylesheet" href="./base.css">
<link rel="stylesheet" href="./index.css">
<script src="./faker.js"></script> -->
<title>
Frameworkless Frontend Development: Rendering
</title>
</head>
<body>
<section class="todoapp">
<header class="header">
<h1>todos</h1>
<input class="new-todo" placeholder="What needs to be done?" autofocus>
</header>
<section class="main">
<input id="toggle-all" class="toggle-all" type="checkbox">
<label for="toggle-all">Mark all as complete</label>
<ul class="todo-list">
</ul>
</section>
<footer class="footer">
<span class="todo-count">1 Item Left</span>
<ul class="filters">
<li>
<a href="#/">All</a>
</li>
<li>
<a href="#/active">Active</a>
</li>
<li>
<a href="#/completed">Completed</a>
</li>
</ul>
<button class="clear-completed">Clear completed</button>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="http://twitter.com/thestrazz86">Francesco Strazzullo</a></p>
<p>Thanks to <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
<script type="module" src="index.js"></script>
</body>
</html>
4.2. /index.js
// 컨트롤러 역할을 하는 파일
import getTodos from './getTodos.js'
import view from './view.js'
const state = {
todos: getTodos(),
currentFilter: 'All'
}
const main = document.querySelector('.todoapp')
window.requestAnimationFrame(() => {
// 뷰 함수를 호출하여 투두 앱 요소를 반환한다.
const newMain = view(main, state)
// DOM에서 기존 요소를 제거하고 새 요소를 삽입한다.
main.replaceWith(newMain)
})
/*
# innerHTML 대신 replaceWith()를 사용하는 주요 이유:
1. 성능 이점: innerHTML은 문자열을 HTML로 파싱해야 하므로 더 많은 리소스를 사용한다. replaceWith()는 이미 생성된 DOM 노드를 직접 교체하므로 더 효율적이다.
2. 이벤트 리스너 보존: innerHTML을 사용하면 모든 이벤트 리스너가 제거된다. replaceWith()는 새로운 요소에 등록된 이벤트 리스너를 그대로 유지한다.
3. 깔끔한 코드 구조: 복잡한 요소 구조를 문자열로 조합하지 않고 DOM API를 일관되게 사용할 수 있다.
4. 참조 관리: 노드 참조를 직접 다루기 때문에 코드가 더 명확하고 관리하기 쉽다.
# main = view(main, state) 처럼 할당하지 않는 이유:
1. JavaScript 변수 vs DOM: 변수 main에 새 값을 할당하는 것은 JavaScript 메모리 내의 참조만 변경하고, 실제 DOM에는 아무 영향이 없다.
2. 실제 DOM 조작 필요: DOM 요소를 실제로 교체하려면 DOM API 메서드를 사용해야 한다.
3. 불변성 원칙: 이 코드는 원본 요소를 직접 수정하지 않고 새 요소를 만들어 교체하는 패턴을 사용한다. 이는 프레임워크 없는 환경에서도 리액트와 같은 최신 프레임워크의 작동 방식을 모방하고 있다.
4. 명확한 의도: 새 변수를 생성하고 교체하는 방식은 코드의 의도를 더 명확하게 보여준다.
# window.requestAnimationFrame
1. 기본 기능
- 브라우저의 Window 인터페이스에서 제공하는 메서드이다.
- 다음 리페인트(화면 갱신) 직전에 지정된 콜백 함수를 실행. 즉, 화면 갱신 직전에 실행된다.
- 보통 초당 60회(60fps) 실행되지만, 기기 성능과 화면 주사율에 따라 달라질 수 있다.
2. 장점
- 성능 최적화: 브라우저가 적절한 타이밍에 실행하도록 스케줄링
- 배터리 효율: 백그라운드 탭이나 화면 밖의 요소는 실행 빈도가 줄어든다.
- 부드러운 애니메이션: 모니터 주사율에 맞춰 실행되어 부드러운 시각 효과를 제공한다.
3. 구분
- 자바스크립트 엔진의 메서드가 아니다.
- 브라우저가 제공하는 Web API의 일부이다.
- 브라우저의 렌더링 엔진과 연결되어 있다.
4. 작동 과정
- 1. 콜백이 특별한 큐에 등록된다.
- 2. 브라우저는 다음 리페인트 직전에 이 콜백을 실행하도록 스케줄링한다.
- 3. 자바스크립트 메인 스레드의 다른 작업이 완료된 후 실행된다.
- 4. 브라우저의 화면 주사율에 맞춰 실행된다.
5. 일반적인 사용 사례
- 애니메이션 구현
- DOM 업데이트 최적화
- 복잡한 렌더링 작업의 효율적 처리
*/
4.3. /getTodos.js
const { faker } = window
const createElement = () => ({
text: faker.random.words(2), // 2개의 랜덤 단어를 반환한다.
completed: faker.random.boolean() // 랜덤 불리언 값을 반환한다.
})
// 함수와 숫자를 받아서 해당 함수를 숫자만큼 반복하여 반환된 값을 배열에 추가하고 배열을 반환
const repeat = (elementFactory, number) => {
const array = []
for (let index = 0; index < number; index++) {
array.push(elementFactory())
}
return array
}
// 익명 함수로 export. index.js 에서 임의의 이름으로 import하여 사용할 수 있다.
export default () => {
const howMany = faker.random.number(10) // 10개 이하의 랜덤 숫자를 반환한다.
return repeat(createElement, howMany) // 결국 {text: '랜덤 단어', completed: 랜덤 불리언} 형태의 객체를 10개 이하로 생성하여 배열로 반환한다.
}
4.4. view.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>`
}
// 투두 아이템 개수를 반환하는 함수. 투두 아이템 배열을 받아서 투두 아이템 개수를 반환한다.
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 되기 때문에 사용하는 곳에서 임의의 이름으로 사용될 수 있다. e.g. import view from './view.js'
export default (targetElement, state) => {
console.log('targetElement: ', targetElement)
console.log('state:', state)
const {
currentFilter,
todos // 투두 아이템 배열
} = state
// 타겟 DOM 요소를 복사한다. JS의 DOM API가 가진 메서드이다. true 인자를 넘기면 깊은 복사로, 자식까지 전부 복제한다. false는 자식 없이 해당 요소만 복제한다.
const element = targetElement.cloneNode(true)
console.log('cloned element: ', element)
// 복제된 DOM 요소의 하위 요소 중에서 클래스 이름으로 조회하여 가져온다. 텍스트만 가져오는 것이 아니라 요소 자체를 가져온다.
const list = element.querySelector('.todo-list')
const counter = element.querySelector('.todo-count')
const filters = element.querySelector('.filters')
//
list.innerHTML = todos.map(getTodoElement).join('') // 투두 아이템 배열을 순회하며 각 투두 아이템 요소를 생성하고 이를 문자열로 변환하여 결합한다. <li>...</li> 여러 개가 한 줄로 만들어진 문자열이다.
counter.textContent = getTodoCount(todos) // (예: "1 Item left" 또는 "5 Items left"). 이 생성된 문자열이 counter 요소의 textContent 속성에 할당된다. 태그로 감싸진 내부의 문자열에만 적용된다.
Array
.from(filters.querySelectorAll('li a')) // 타겟 DOM 요소의 하위 요소 중에서 li 요소의 하위 요소 중에서 a 요소를 모두 선택한다.
.forEach(a => { // 그리고 해당 요소이 가진 텍스트가 현재 인자로 넘어온 필터와 같은 것들에만 css 클래스를 추가한다.
if (a.textContent === currentFilter) {
a.classList.add('selected') // add 역시 DOM API가 가진 메서드이다. HTML요소에 css 클래스를 추가한다. 결과적으로 HTML에서 <a class="selected"> 형태로 변경된다.
} else {
a.classList.remove('selected') // HTML에서 <a> 형태로 변경된다.
}
})
return element
}
/*
여기서 export 하는 view 함수는 기본으로 사용되는 타겟 DOM 요소를 받는다. state의 형태는
1. 파라미터:
- targetElement: 타겟 DOM 요소
- state: 상태 객체. 컨트롤러 역할을 하는 index.js 파일에서 전달받은 상태 객체이며 형태는 아래와 같다.
const state = {
todos: getTodos(),
currentFilter: 'All'
}
다시 todos속성을 확인해보면, 이는 {text: '랜덤 단어', completed: 랜덤 불리언}의 객체형태의 요소가 10개 이하로 채워진 배열이다.
- 이 함수에서는 인자로 받은 타겟 DOM 요소를 복사하고,
- 인자로 넘어온 배열을 <li> 문자열로 만들어서 복사한 요소의 하위 요소에 입력한다.
- 그리고 현재 필터 상태에 따라서 필터 요소에 css 클래스를 추가하거나 제거한다.
즉, 이 함수는 실제 HTML요소와 JS로 만들어진 객체인 상태를 인자로 받아서,
기존 요소를 복제하고, 상태 값을 가공하여 HTML 요소에 입력하고 css 스타일링까지 완료하여
실제 HTML 요소를 반환한다.
그리고 이 함수를 호출한 곳에서 replaceWith 메서드를 사용하여 기존 요소를 반환된 요소로 교체한다.
**** 주의. ****
반환 값은 '<section class="todoapp">...</section>' 이런 형태의 문자열이 아니다.
아래와 같은 DOM 객체이다.
// 콘솔에 출력했을 때의 표현
const 실제반환값 = {
tagName: "SECTION",
className: "todoapp",
childNodes: [...], // DOM 노드 배열
innerHTML: "...",
outerHTML: "<section class=\"todoapp\">...</section>",
querySelector: function() { ... },
replaceWith: function() { ... },
// 기타 수많은 DOM 메서드와 속성들...
}
*/

* 저자가 관리하는 리포지토리
https://github.com/Apress/frameworkless-front-end-development/tree/master
GitHub - Apress/frameworkless-front-end-development: Source code for 'Frameworkless Front-End Development' by Francesco Strazzul
Source code for 'Frameworkless Front-End Development' by Francesco Strazzullo - Apress/frameworkless-front-end-development
github.com
'Books > 프레임워크 없는 프론트엔드 개발' 카테고리의 다른 글
| [프레임워크 없는 프론트엔드 개발] 1.2. 뷰 함수 분리 (0) | 2025.05.25 |
|---|