Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Chapter 2 / 순수함수 렌더링 #6

Merged
merged 11 commits into from
Jan 19, 2024
Merged

Chapter 2 / 순수함수 렌더링 #6

merged 11 commits into from
Jan 19, 2024

Conversation

fecapark
Copy link
Contributor

PR 설명

Chapter2 의 순수함수 렌더링 섹션의
테스트 코드들을 통과하는 코드들을 구현했습니다!

  • todos.js, counter.js, filters.js 구현
  • 유틸리티 함수들을 담는 utils.js와 테스트 코드 작성

체크리스트

  • 로컬에서 변경 사항을 테스트해 본 결과 예상대로 작동했습니다.
  • 필요한 경우 적절한 문서나 설명을 추가했습니다.
  • 프로젝트의 코딩 스타일과 규칙을 따랐습니다.
  • 리뷰어를 올바르게 지정했습니다.

추가 설명

utils.js 생성

유틸리티 함수를 담기 위해 따로 utils.js 파일을 생성하고,
파일 안에 유틸리티 함수와 그 함수의 테스트 코드를 작성했습니다.

작성한 함수는 createInnerHTML() 인데,
생성할 태그의 이름과 속성 값들, innerHTML(자식)을 넘겨주면,
html 문자열을 생성해주는 함수입니다!

불가피한 eslint 규칙 추가

불가피하게 eslint에 규칙을 하나 추가했습니다..

"import/extensions": "off" 규칙을 하나 추가했는데,
.js 확장자를 붙이지 않고 모듈을 불러왔더니
localhost 상에서 파일을 찾지 못하는 이슈가 있었기 때문에...

문제 된다면 다시 수정하고 커밋하겠습니다!

배운 것

1. 더 나은 CSR 구현 방식

이번 스프린트의 코드는 CSR 방식의 코드로 작성되었다고 보는데,
제가 기존에 알고있는 방식과는 다른 방식으로 구성되어 있었습니다.

제가 기존에 CSR를 구현할 때는,

...
<body>
    <div id="root"></div>
</body>
...

와 같이 아예 빈 껍데기를 만들어놓고,
div#root 엘리먼트에 직접 모든 뷰를 때려넣는 방식으로 구현했었는데,

이번 스프린트에서는

...
<body>
    <div id="root">
        <div>....</div>
        <ul id="list"></ul>
    </div>
</body>
...

처럼 뷰가 들어갈 컨테이너 수준까지 작성을하고,

const container = document.getElementById("list")  // 1. 수정할 타겟을 가져옴
const view = createView(container, state)  // 2. 가상 돔을 렌더링 함
container.replaceWith(view)  // 3. 가상 돔으로 교체 함

가상 돔(뷰)을 만들어서 컨테이너와 교체하는 식으로 구현하게 되었습니다.

굉장히 신선하네요...!
당연히 이러다보니 실제로 짜야할 HTML 코드도 줄어들고,
상태를 다루기도 쉬워지고,
뷰 단위로 쪼개기도 더 수월한데다,
테스트 코드 작성도 더 쉬워졌습니다...

2. 가상 DOM

책 p.54 에서 말하는 '가상' 이라는 단어를 보고 의문이 들었습니다.
아직 코드에서는 실제 DOM에 조작을 취하고 있다고 생각했습니다.

그래서 가상 DOM에 대해 찾아보게 되었습니다.

보통 말하는 가상 DOM의 핵심은,

  1. 실제로 렌더링 되지 않는 DOM 객체(가상 DOM)를 만든다.
  2. 여러 변경 사항을 실제 DOM 대신 가상 DOM에 적용한다.
  3. 실제 DOM과 가상 DOM에 대한 변경 사항을 비교한다.
  4. 변경 사항을 한 번에 모아서 한 번에 리렌더링을 시켜버린다. (Batch)

3~4번 과정을 '조화(Reconciliation)' 과정이라고 하는데, (실제 작동 원리)
실제 코드에는 이 과정이 없고, 실제 DOM 객체에 직접적으로 조작을 하고 있었습니다.

또한 전반적으로 가상 DOM을 말하면 1~4번 과정을 통틀어서 말하지만,
'가상' 이라는 의미는 1번에서의 '실제로 렌더링 되지 않는 DOM 객체' 를 말하고 있었습니다.

이와 관련해서 더 자세한 내용은
이번 장 후반부에 더 작성해보겠습니다!

@fecapark fecapark self-assigned this Jan 14, 2024
@2wndrhs 2wndrhs linked an issue Jan 14, 2024 that may be closed by this pull request
@2wndrhs 2wndrhs added this to the 02. 렌더링 milestone Jan 14, 2024
Copy link
Member

@2wndrhs 2wndrhs left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

대상혁 수고 많으셨습니다..

@@ -0,0 +1,13 @@
export const createInnerHTML = (tagName, { attributes = {}, innerHTML = '' } = {}) => {
const arrtibuteString = Object.entries(attributes)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

혹시 R키와 T키를 바꾸셨나요🤔

.join(' ');
const closeTagString = innerHTML !== '' ? `${innerHTML}</${tagName}>` : '';

return `<${tagName} ${arrtibuteString}>${closeTagString}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return `<${tagName} ${arrtibuteString}>${closeTagString}`;
return `<${tagName} ${arrtibuteString}>${innerHTML}</${tagName}>`;

innerHTML 매개변수 기본값으로 빈 문자열 넣어주니까 closeTagString 변수 따로 만들 필요 없이 이렇게 작성해도 되지 않을까요?

@@ -0,0 +1,20 @@
const getAllListAnchorsFrom = (element) => [...element.querySelectorAll('li a')];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

책에서는 Array.from()으로 nodeList를 배열로 바꿔서 spread 구문이랑 뭔 차이가 있을까 궁금해서 검색했는데

array-from-vs-spread-syntax

spread 구문은 iterable 객체만 배열로 바꿀 수 있고 Array.from()은 유사 배열 객체까지 배열로 바꿀 수 있어서 Array.from()이 더 범용성이 좋다고 하네요

@@ -0,0 +1,16 @@
const getNotCompleteCount = (todos) => todos.filter((todo) => !todo.completed).length;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

함수는 모조리 분리하시네요 역시 대상혁

Comment on lines 3 to 33
const getTodoItemElement = ({ text, completed }) => {
const toggle = createInnerHTML('input', {
attributes: {
class: 'toggle',
type: 'checkbox',
checked: completed,
},
});
const label = createInnerHTML('label', {
innerHTML: text,
});
const edit = createInnerHTML('input', {
attributes: {
class: 'edit',
value: text,
},
});
const viewBox = createInnerHTML('div', {
attributes: {
class: 'view',
},
innerHTML: toggle + label,
});

return createInnerHTML('li', {
attributes: {
class: completed ? 'completed' : '',
},
innerHTML: `${viewBox}${edit}`,
});
};
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

가독성 측면에서는 템플릿 스트링 사용하는게 더 좋아보이는데 어떻게 생각하시나요?

@2wndrhs 2wndrhs mentioned this pull request Jan 14, 2024
4 tasks
@2wndrhs 2wndrhs merged commit ec10308 into main Jan 19, 2024
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

02. 렌더링 - 렌더링 함수 - 순수 함수 렌더링
2 participants