1. Controller 에 너무 많은 제어권이 존재
2. 그 모든 제어를 개발자가 직접 주관적인 판단하에 진행
-> 시스템이 아닌 개인에 의존하는 이러한 방식은 예측 가능성을 깨지기 쉬운것으로 만듬!
1. 그렇기 때문에 Data, View 에 시스템을 도입하는 것이 "예측 가능성"을 지켜주리라 생각함.
-> 예측 가능한 '데이터' 를 위해 'Flux' 라는 아키텍처를
-> 예측 가능한 'UI' 를 위해 'React' 라는 시스템을 도입.
모든 어플리케이션은 'Data', 'UI' 를 어떻게 제어할 것인지에 대한것으로 구성된다.
MVC 는 Controller 가 Model, View의 제어권을 갖는 것을 의한다.
“우리의 지적 능력은 정적인 관계를 정복하는데에는 준비가 되어 있으나, 시간에 따라 진화하는 프로세스를 시각화 할 수 있는 힘은 상대적으로 뛰어나지 않다. 이러한 이유로 정적인 프로그램과 동적인 프로세스 사이의 개념적인 격차를 단축하여 프로그램과 프로세스 사이의 대응 및 차이를 가능한 만큼 사소한 것으로 만들기 위해 최선을 다해야 한다.”
위 프로세스에서 더 복잡해질수록 결과를 예측하는 것은 어려울 것이다.
-> 이와 같은 상황을 '동적인 프로세스'
현대 웹 프레임워크로 개발 시 위와 같은 프로세스에 대해서 고민을 하나?
-> 현재 상태에 대한 Render 를 데이터에 맞춰서 할 뿐.
-> '어떠한 프로세스'를 거쳤는지 중요하지 않다.
-> 현재 데이터만 중요할 뿐.
정적인 프로그램이 가지는 하나의 상태에만 집중하는 것.
-> 이는 너무나도 당연해 보이지만 리액트가 탄생하기 전 동적인 프로세스와 정적인 어플리케이션의 시각화는 갭 차이가 컸다. -> 예측 불가능한 UI를 만든다고 생각.
리액트 팀은 멱등성을 유지하는 것만이 예측 가능성을 지키는 일이라 생각했다.
허나,
데이터가 바뀔 때마다 DOM 요소를 수정하는 것은 멱등성을 해친다. 하나의 데이터에 여러 수정을 통해서 변경하는 점은 옳지 않다.
-> 대신 데이터가 바뀔 때마다 완전히 새로운 DOM 요소를 그리는 방법을 택한다.
1. 변경점이 발생
2. 매번 새로운 데이터를 요청받고
3. 매번 새로운 UI를 그림.
-> 데이터마다 결과 스냅샷을 가지고 있는 것 처럼
이러한 것들이
1. 설명 가능한 순간적인 UI
2. 주어진 입출력에 관해 결과를 예측
3. 쉬운 테스트
와 같은 특징을 만들어낸다고 믿음.
buttonAClick() {
const buttonA = document.querySelector(".buttonA");
if(buttonA.isOff) {
buttonA.style.backgroundColor = "red";
buttonA.style.innerText = "on";
}
else {
buttonA.style.backgroundColor = "gray";
buttonA.style.innerText = "off";
}
}
// 이를 매번 스크린 샷을 찍듯 매번 새로운 화면을 만들기 위해선 다음과 같이 변경, stateless 렌더링 방식
buttonAClick(color, text) {
const buttonParent = document.querySelector(".buttonParent");
buttonParent.innerHTML = `
<button style="color:${color};">${text}</button>
`
}
더 이상 이전 버튼이 상태가 무엇인지에 따라 화면이 달라지는 것이 아닌, 순수하게 현재 데이터를 기반으로 작성.
-> 이전 상태가 무엇이었는지, 어떤 과정을 거쳤는지 더 이상 알 필요가 없기 때문에 예측 가능성을 지킴!
2가지 문제점
1. 모든 것을 매번 새로 그리는 것은 성능에 대한 효율이 매우 떨어진다.
2. DOM을 새로 그리기 위해서는 전부 지워야 한다. 그려야 할 DOM이 복잡할 수록 유저들은 흰 화면을 오래 보게 됨.
Virtual DOM 불리우는 추상화는 DOOM 3와 매우 유사하다.
우리는 '서버의 이벤트' 혹은 '브라우저의 이벤트' 에 의해 동작하는 '어플리케이션'을 가짐.
그리고 '비지니스 로직' 을 설명하기 위한 React Component 를 선언적으로 작성한다.
이러한 컴포넌트에 담긴 정보를 취합하여 논리적으로 표현가능한 가상의 DOM을 생성한다.
물리적인 렌더링 대신 논리적인 렌더링 계층을 추가 -> 개념적인 갭의 격차를 줄일 수 있는 아이디어
-> 실제로 그리지 않아도 Front End 에서는 마치 새로 그리듯이 Component를 만들면 된다.
-> 나머지는 Virtual DOM 과 Back End 에서 물리적인 변경을 시도할 것.
Virtual DOM 은 태생 자체가 퍼포먼스 개선이 목표가 아닌 개념적으로 멱등성을 가지는 View를 만들어나가는 과정에서 생긴 문제를 해결하기 위해 나온 아이디어.
"안읽은 메시지" 기능의 대한 처리에 대해서 작업을 해놔도 이후 이미 메시지를 다 읽었는데도 여전히 1 메시지가 사라지지 않은 문제가 발생함.
function newMessageHandler(message) {
var chatTab = ChatTabs.getChatTab(message.threadID);
chatTab.appendMessage(message);
}
function newMessageHandler(message) {
UnseenCount.incrementUnSeen(); // 읽지 않은 메시지를 가진 스레드를 증가 시킨다.
var chatTab = ChatTabs.getChatTab(message.threadID);
chatTab.appendMessage(message);
// 만약 Tabs이 Focus 되어 있는 중이라면 읽지 않은 스레드를 감소 시킨다.
if(chatTab.hasFocus()) {
UnseenCount.decrementUnseen();
}
}
function newMessageHandler(message) {
UnseenCount.incrementUnSeen(); // 읽지 않은 메시지를 가진 스레드를 증가 시킨다.
var chatTab = ChatTabs.getChatTab(message.threadID);
chatTab.appendMessage(message);
// 만약 메인 채팅에 현재 메시지에 해당하는 스레드가 열려있다면 해당 스레드에 메시지를 추가한다.
var messageView = Messages.getOpenView();
var threadID = MessagesView.getThreadID();
if ( threadID == message.threadID) {
messagesView.appendMessage(message);
}
// 만약 Tabs이 Focus 되어 있는 중이라면 읽지 않은 스레드를 감소 시킨다.
// 만약 새 메시지가 메인 채팅의 스레드라면 읽지 않은 스레드를 감소 시킨다.
if(chatTab.hasFocus() || threadID == message.threadID) {
UnseenCount.decrementUnseen();
}
}
이러한 과정을 추적하며 구조적인 결함을 발견할 수 있다.
위 어플리케이션은 깨지기 쉬운 구조처럼 보임.
2번 같은 경우에는 Flux 혹은 Redux 에서 항상 나오는 MVC 혹은 양방향 바인딩의 문제점을 설명하는 내용과 동일하다.
구체적으로
1. 외부에 제어권(External Control)이 있는 상황을 문제라고 여김.
2. 각각의 데이터들이 일관적으로 데이터를 유지해야 하는데 외부(Handler)에 제어권이 있다면
3. 데이터들은 스스로 일관성을 유지할 방법이 없다.
-> Model의 대한 제어권이 외부에 있어서 해당 Model 이 원치 않게 바뀔 수 있고 이는 다른 Model과도 연관될 수 있다?
왜 기존에 이런 변화를 주지 않았나
Controller는 왜 필요하지?
모든 도메인은 서로 간의 의존성
읽지 않은 메시지 -> Chat Tab, Main Message 의 읽음 여부에 의존적이다.
위 구조에서 확실한 점
1. Unseen Cotuner 는 정보가 불충분하다
2. 의존성이 있는 도메인으로부터 Unseen Counter 만을 위한 더 명확한 데이터 (more explicit data) 가 필요하다.
이를 해결하기 위해서
1. Chat Tab 과 같은 도메인의 View 에서 이미 읽었음(Mark Seen)과 같은 추가적인 트리거를 실행
2. 새로운 사이클을 실행하여 문제를 해결.
-> 이미 읽었음 이라는 추가적인 트리거를 추가하고, 읽지 않은 메시지 와 채팅창, 메인 채팅창과, 이미 읽었음 의 대한 도메인 간 관계도를 더 명확하게 만듦?
하나의 Controller 에서 처리해야하는 비지니스를
1. 도메인 별 분리와
2. 더 구체적이고 많은 액션으로 대체하면서
3. 내부의 일관성과 도메인 간의 정보 부족 이라는 문제를 해결.
기존에 파생된 데이터를 사용했던 방식에서 -> 더 명시적인 데이터를 사용해야 했음.
파생된 데이터는 존재하는 또 다른 데이터과 합성되었거나 추정된 데이터
명시적인 데이터는 오로지 해당 도메인만을 위한 다른 데이터와 독립적인 성질을 지닌 데이터
해당 시스템에서 중요한 핵심 전략
기존의 MVC 와 달리 한 방향으로 순서를 지키며 진행한다.
이러한 단순한 시스템과 하나의 방향에만 집중
하나의 방향에만 집중하기 때문에 다양한 모델과 뷰를 압축할 수 있고
-> "간단한 멘탈 모델" 을 형성할 수 있음.
그리고 이것은 중첩된 업데이트를 방지하는 것으로 계단식 or 전파 효과 방지 를 도움.
FLUX 라는 시스템은 다음과 같은 변화를 만들어 냄.
1. 향샹된 데이터 일관성
2. 버그의 근원을 발견하는 것이 쉬워짐
3. 더욱 의미 있는 유닛 테스트
데이터가 일관성을 유지 -> 결과를 예측하는 것이 수월해짐 -> 상태와 상태를 업데이트하는 로직이 한 곳에 생겼고 -> 그 뜻은 의존성이 줄어든다는 의미 -> 유닛 테스트 작성에 도움.
일관성을 유지하기 위해 내부로 제어권을 이동 -> 이를 통해 저장소 사이에 의존성이 사라짐.
= 이는 꽤 큰 변경점을 시사 -> 코드의 작성 방법.
의존성이 생긴다는 것은 기능이 변경 할 시에 여파 범위가 넓어진다는 것을 의미함.
MVC는 여파를 해결하기 위해서 OOP를 기본적인 전략으로 사용함.
-> 모든 값들을 상대적으로 만들어서 기능 간의 의존성을 약하게 만드는 전략
function printHelloMessage(name) {
const defaultMessage = "Hello!"; // 데이터
console.log(defaultMessage + name); // 뷰
}
기본 메시지가 변경된다면 또는 더 이상 서버에서 터미널에 출력하는 것이 아니라면
-> printHelloMessage 의 값들을 상대적인 참조 값으로
printHelloMessage(name) {
const result = this.Message.buildMessage(name);
this.View.print(result);
}
printHelloMessage 는 메시지와 뷰에 변경 사항이 있더라도 buildMessage 만 실행하고 print 만 실행하면 된다. 혹은 메시지나 뷰가 아니여도 상관없다. 그저 필요한 메서드를 가진 무언가가 존재하기만 하면 됨.
FLUX 시스템 내에서는 이러한 의존성은 없으며, 위와 같이 작성하지 않아도 된다.
-> 각각의 데이터끼리의 의존성이 아닌. 데이터 자체가 자신을 관리하며, 전달만?
printHelloMessage 액션(name) -> dispatcher -> message -> view
printHelloMessage -> Message.buildMessage(name) -> printHelloMessage -> View.print(name)