티스토리 뷰
개요
- 지금의 상태관리 라이브러리가 생겨난 이유에 대해 알아보자
- 지난 면접에서 물어본 mvc, mvvm 패턴에 대해 이해하고 현재의 라이브러리들이 상태관리를 위해 어떠한 패턴을 도입했는지 알아보자
MVC 패턴 도입
mvc 패턴이란?
Model, View, Controller의 줄임말로 패턴을 이용해서 구조화 된 코드를 작성하는 방식이다.
일반적으로 백엔드에서 주로 사용되었으며 각각 해당하는 부분은 다음과 같다.
Model: 데이터를 수집 가공 한다 (데이터 베이스 내용 조회, 생성)
Controller: Router (Request, Response)
View: 뷰 생성
이전에 수업에서 JSP를 사용했을 때 주로 사용하던 패턴이였다. View 부터 데이터베이스 까지 강하게 의존성이 엮어서 위의 다이어그램 처럼 작동했었다.
하지만 현재는 프론트엔드와 백엔드 둘로 나뉘어서 따로 관리되고 있으며 React, Vue 같은 프론트엔드 프레임워크(라이브러리)는 View를 담당하고 있다.
프론트엔드에서의 MVC 패턴
원리
Controller 부분을 프론트엔드에 적용하게 될 경우 URL에 따라 Model로 부터 데이터를 가져와 DOM을 생성하게 된다면 MVC 패턴을 이용할 수 있을 것이라 생각하게 된다.
- 유저가 browser를 통해 URL에 따라 접속
- Controller에 의해 View를 어떻게 보여줄지 선택
- 이벤트가 발생하면 Controller에 의해 Model을 갱신하고 View를 업데이트
- 새로운 View를 Controller에 보내주고 Browser에 새로운 View를 보여준다.
문제점
하지만 현재의 View는 단순히 페이지를 보여주는 것이 아니라 사용자와의 다양한 인터렉티브가 일어나고 보여줘야 할 View는 굉장히 많고 계층적 트리 구조를 갖게 된다.
- view → model: view에서 보여지는 사용자의 입력값에 따라 Model 변경
- model → view: model로부터 데이터를 불러와서 view rendering
mvc패턴으로 구성할 경우 코드는 class 코드는 점점 많아지고 어디서 어디를 호출해야 할지도 점점 복잡해지게 될 것이다.
<!DOCTYPE html>
<html>
<head>
<title>MVC Example</title>
</head>
<body>
<input id="numberInput" type="number" />
<button id="doubleButton">Double</button>
<p id="result"></p>
<script>
// 모델
class Model {
constructor() {
this.number = 0;
}
setNumber(num) {
this.number = num;
}
getNumber() {
return this.number;
}
}
// 뷰
class View {
constructor() {
this.numberInput = document.getElementById('numberInput');
this.doubleButton = document.getElementById('doubleButton');
this.result = document.getElementById('result');
}
getNumber() {
return this.numberInput.value;
}
displayResult(result) {
this.result.innerText = `Result: ${result}`;
}
bindDoubleButton(handler) {
this.doubleButton.addEventListener('click', handler);
}
}
// 컨트롤러
class Controller {
constructor(model, view) {
this.model = model;
this.view = view;
this.view.bindDoubleButton(this.handleDouble.bind(this));
this.updateView();
}
updateView() {
this.view.displayResult(this.model.getNumber());
}
handleDouble() {
const number = this.view.getNumber();
this.model.setNumber(number * 2);
this.updateView();
}
}
const model = new Model();
const view = new View();
const controller = new Controller(model, view);
</script>
</body>
</html>
그렇다면 이런 복잡성을 제거하기 위해서는 어떻게 해야할까?
해결 방법
- 복잡한 view, model 관계를 단순화 필요성
- 효율적인 DOM 처리 방법이 필요하다.
문제점을 해결하기 위해 현재 FE에서 사용되는 기술들을 알아보자
- MVVM
- flux 아키텍처
원리
- view가 main activity가 되어 user의 이벤트를 받아처리
- view model로 부터 요청을 받아 model을 조회 및 갱신
- view model은 model로 부터 응답받아 데이터를 가공
- view model과 view는 바인딩 되어 view 갱신
정리
view model은 view에 관련 된 model을 갖고 있는 것이다.
그래서 view에 이벤트에 따라 model을 변경하고 또 view model이 변경되면 view도 변경이 된다.
이 말이 처음에는 와 닿지가 않았는데 생각해보면 검색을 예로 들어보면 input 이벤트를 받게 되면 view model로 전달되어 이에 따라 model로 부터 데이터를 받아 화면을 갱신하게 된다.
또 처음 url주소에 접근했을 때 model로 부터 view model을 업데이트 하게되면 이에 따라 view도 변경됨을 알 수 있다.
현재의 vue나 react가 생기게 된 이유를 생각해보면 controller를 대체하기 위해 view model을 지원하는 툴을 만들고 view를 main activity로 변경했다.
라고 생각하면 좋을 것 같다.
mvvm은 view와 view model 간의 데이터바인딩으로 양방향 바인딩을 제공했다면 flux 패턴은 단방향 바인딩으로 view를 갱신하기 위한 패턴이다.
react를 주로 사용한다면 redux, redux-saga 또는 useReducer를 쓰는 사람들에게 익숙할 것이라 생각한다.
원리
- 사용자로 부터 이벤트 발생, Action 함수호출
- 리듀서에서 Dispatcher Action에 따른 동작을 수행하여 store 변경
- store에서 갱신된 값을 view에 반영한다.
참고로 왼쪽의 Action은 사용자의 이벤트 없이 발생하는 Action들로 setInterval이나 URL에 처음 접속할 때 서버로 부터 데이터를 받아오는 과정으로 생각하면 된다.
import React, { useReducer } from 'react';
// 액션 타입 정의
const INCREMENT = 'INCREMENT';
const DECREMENT = 'DECREMENT';
// 액션 생성자 함수
2. 액션 함수 실행
const incrementAction = () => ({ type: INCREMENT });
const decrementAction = () => ({ type: DECREMENT });
// 리듀서 함수
3. 리듀서에서 해당 Action에 해당하는 부분을 수행
const reducer = (state, action) => {
switch (action.type) {
case INCREMENT:
return { count: state.count + 1 };
case DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
// 초기 상태
const initialState = { count: 0 };
// 카운터 컴포넌트
const Counter = () => {
// 4. 업데이트가 state에 반영되어 view를 업데이트
const [state, dispatch] = useReducer(reducer, initialState);
return (
<div>
<h1>Count: {state.count}</h1>
// 1. 사용자로 부터 버튼 이벤트 발생 -> Action 함수 호출
<button onClick={() => dispatch(incrementAction())}>Increment</button>
<button onClick={() => dispatch(decrementAction())}>Decrement</button>
</div>
);
};
export default Counter;
'프론트엔드' 카테고리의 다른 글
[React] suspense를 써야하는 이유 (1) | 2023.12.30 |
---|---|
useCallback과 useMemo (2) | 2023.11.20 |
[css] float (2) | 2023.11.01 |
- Total
- Today
- Yesterday
- 가장 긴 짝수 연속한 부분 수열
- react suspense
- React useCallback
- 서버사이드 error handling
- CSS
- 자바스크립트
- 1600 파이썬
- storybook scss import
- node 버전 마이그레이션
- 서비스 디자인 패턴
- React useMemo
- 백준 22862
- storybook scss이슈
- 관심사 분리하기
- 선언적 UI
- javascript
- suspense 장점
- 백준 1600번
- 불량 사용자 자바스크립트
- nextjs 에러핸들링
- node version yarn berry
- nextjs errorboundary
- 에러핸들링
- 표현 가능한 이진트리
- 미로탈출 명령어
- nestjs 배포하기
- useCallback과 useMemo 사용
- storybook react is not defiend 해결
- serverless nestjs
- serverless 배포
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |