8. news viewer

  1. 전체적인 구조를 설명하면, Nav, NewsList는 컴포넌트로 관리하여 독립적인 요소로 관리하고 root 요소에 appendChild 해주었다.
  2. Nav 요소가 클릭되면 state 상태 데이터를 클릭한 요소의 id와 변경해준다. ⇒ Nav 요소는 상태를 변경 시키지만 리렌더링은 일어나지 않는다.
  3. state 상태 데이터가 변경이 일어나면 NewsList가 리렌더링이 발생한다.

Proxy

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let state = new Proxy(
{ category: "all" }, // state 객체에 key,value로 들어가게된다.
{
set: (obj, key, value) => {
obj[key] = value;
window.dispatchEvent(
new CustomEvent("statechange", {
detail: obj,
})
);
return true; // setter 는 성공했음을 나타내기 위해 true를 반환해줘야 한다.
},
}
);

state[category] = "business"; // handler 객체의 setter 접근자 프로퍼티가 실행된다.
  • 프록시 객체를 생성하여 state(상태)가 변경, 할당이 발생하면 proxy의 handler에서 먼저 로직을 실행
  • setter 접근자 프로퍼티가 실행된다는 의미는 state(상태)에 변경이 일어난 경우이므로 이 때, window에게 custom event의 이벤트 객체를 보낸다.

Observer(Intersection Observer API)

옵저버 패턴은 이벤트를 발생 시킨 객체(subject)에 옵저버나 리스너를 등록하여서 이들이 subject를 관찰하면서 구독 혹은 구독 취소 같은 행위를 할 수 있도록 하는 패턴을 말한다.

  • 상태가 변경되면 옵저버에게 notify() 메서드로 알림을 보낸다
  • 옵저버(handler)는 subject가 변경될 때, 호출할 함수들이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let observer = new IntersectionObserver(
entries => {
entries.forEach(entry => {
if (entry.isIntersecting) {
currentNews = [...currentNews, ...getCategoryData(currentCategory)];
render(currentNews);
}
});
},
{
threshold: 1,
}
);

observer.observe($newsList.querySelector('.scroll-observer'));
};
  • spinner(’.scroll-obeserver’) 가 viewport에 지정한 임계점(threshold) 이상 나타났을 때, if문 안의 코드를 실행시킨다.
  • entries forEach로 조건식 entry.isIntersecting이 true 이면 처음 스크롤을 내려서 보였을 때 실행하고 false면 다시 스크롤을 올렸을 때는 코드를 실행하지 말라는 의미이다. (양방향으로 일어나기 때문에 관리해줘야한다.)
1
2
3
4
5
6
window.addEventListener("DOMContentLoaded", async () => {
observer.subscribe(render);
intersectionObserver = createObserver();
await render();
intersectionObserver.observe($container.querySelector(".scroll-observer"));
});
  • observe() 메서드를 사용하여 옵저버 타겟을 설정할 수 있다.
    • 이 코드는 DOMContentLoaded 이벤트가 발생했을 때, 한번만 발생하면 된다. 리팩터링 이전 코드는 render 할 때마다 spinner 요소를 재생성하여 위 메서드를 사용하여 target으로 지정하기 위해서 꼭 render 함수 내부에 있어야 했는데, 이는 불필요한 함수 호출과 리렌더링이므로 이를 해결하기 위해 렌더링이 불필요한 템플릿들은 App.js에서 $container innerHTML로 미리 할당해준다.
    • 위와 같이 await로 렌더가 될 때 까지 기다렸다가 ‘.scroll-observer’ 요소가 생성된게 보장이 되면 옵저버 타겟을 등록한다.
1
2
3
4
5
6
7
8
9
10
const addNews = (category, nextNews) => {
news = {
...news,
[category]: {
...news[category],
articles: [...news[category].articles, ...nextNews],
page: news[category].page + 1,
},
};
};
  • 기존에 있던 news 객체에 새로운 news 객체를 스프레드 문법으로 추가해줘야하는데, 추가해줄 때, 기존의 값을 스프레드 문법으로 풀어준 뒤 새로운 객체를 추가해줘야지 아니면 새로운 값만 할당된다.

9. CBD

1
2
3
4
5
6
class Component {
constructor($container) {
this.$container = $container;
observer.subscribe(this.render.bind(this));
}
}
  • this.render는 handler 내부에 있는 함수이름으로 호출하면 이는 일반함수로 호출되므로 this가 전역객체에 바인딩된다. 그래서 this가 불일치하는 현상이 발생한다. 이 때 bind 메서드를 사용하여 Component로 this를 바인딩한 새로운 함수를 전달해주므로 this를 일치시켜준다.

소감

  • 객체의 깊이가 길어지고 대괄호 표기법과 마침표 표기법 사용시점을 확실히 알지 못한다.
  • 구조분해 해줄 수 있는 것들은 구조분해 해줘서 간단하게 작성하자.
  • 클래스 new 연산자로 생성했을 때, 인스턴스의 구조가 어떤지 모른다. + super()
  • 8번 옵저버 패턴 구조 설명을 제대로 못한다.
  • $container를 잘못이해하고있다. 클래스 구조에서 받은 $container 매개변수를 컴포넌트로 단위로 구성할 것이다. InnerHTML과 혼동하고 있다. 컴포넌트로 사용할 컨테이너를 매개변수로 받는다.