๐ย ๋ชฉ์ฐจ
1. ๋น์ฆ๋์ค ๋ก์ง๊ณผ ํ๋ ์ ํ ์ด์ ๋ก์ง
2. MVVM์ด๋?
3. Recoil์ ์ ๋ชฉ์ํจ MVVM ํจํด
์ด๋ฒ์ collusic-new, about ํ๋ก์ ํธ์ mvvm ํจํด์ ์ ์ฉํ๋ฉด์ ๋์์ธ ํจํด์ ๋ํด ์ฒ์ ๊ณต๋ถํ๊ฒ ๋์๋ค. model, view, viewmodel์ด๋ ๊ฐ๊ฐ์ ์ฉ์ด๋ ์์ํ์ง๋ง, ๋ฌด์๋ณด๋ค mvvm์ react์ ์ ์ฉ์ํค๋ ๋ฐฉ์์ด ์์ ๋ง๋ค ๋๋ฌด ๋ฌ๋ผ์ ํผ๋์ค๋ฌ์ ๋ค. ๋ํ, ๋ทฐ๋ก์ง๊ณผ ๋น์ฆ๋์ค ๋ก์ง์ด ์ ํํ ์ด๋ค ์ฐจ์ด๊ฐ ์๋์ง ์๋ฟ์ง ์์ ํ๋ก์ ํธ๋ฅผ ํ๋ ๋์ค, ์ฝ๋๋ฅผ ๋ถ๋ฆฌํ๋ฉด์๋ ๊ฐ์ด์น๊ฐ ๋ชปํ๋ค. ๋ฐ๋ผ์ ์ค๋ ๋ด์ฉ์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ํ๋ ์ ํ ์ด์ ๋ก์ง(๋ทฐ ๋ก์ง๊ณผ ๋น์ทํ ๊ฐ๋ ์ธ ๊ฒ ๊ฐ๋ค...)๋ถํฐ ์๊ธฐํด๋ณด๋ ค๊ณ ํ๋ค.
์ํคํผ๋์์์ ์ ์ํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
๋น์ง๋์ค ๋ก์ง(Business Logic) ๋๋ ๋๋ฉ์ธ ๋ก์ง(Domain Logic)์ํ์ค ์ธ๊ณ์์ ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ๋ง๋ค๊ณ ์ ์ฅํ๊ณ ๋ฐ๊ฟ ๊ฒ์ธ์ง์ ๋ํ ๋น์ง๋์ค ๊ท์น(Business Rules)์ ์ธ์ฝ๋(Encodes) ํ,์ํํธ ์จ์ด ์์ ํ๋ก๊ทธ๋จ์ ํ ๋ถ๋ถ์ด๋ค.
โ์ํคํผ๋์(์๋ฌธํ),ย Business Logic
๋น์ฆ๋์ค ๋ก์ง์ ์ฌ์ฉ์๊ฐ ์ํ๋ ์์ฒญ์ ์ํด ์ด๋ ํ ๋ฐ์ดํฐ๋ฅผ ์ด๋ป๊ฒ CRUD ํ๊ฑฐ๋, ๊ฐ๊ณตํ ๊ฒ์ธ์ง๋ฅผ ๋งํ๋ค.
ํน์ ์ฑ
์ ๋ํ ์์ธ์ ๋ณด๋ฅผ Readํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๊ฐ์ง detailBook(bookId)
๋ง๋ค์ด๋ณด์.
async detailBook(bookId: number) {
const { data } = await axios.get(`api/books/${bookId}`);
return data;
}
์์ ๊ฐ์ด ๊ฐ๋จํ api์์ฒญ์ ํตํด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ๋น์ฆ๋์ค ๋ก์ง๋ ์์ง๋ง, ๋ฐ์ ๋ฐ์ดํฐ๋ค์ ์ฌ์ฉ์์ ์ ๋ง์ ๋ง๊ฒ ๊ฐ๊ณตํ๋ ๋น์ฆ๋์ค ๋ก์ง๋ ์กด์ฌํ๋ค.
์ฌ์ฉ์๊ฐ ํน์ ์ฑ ์ ํํธ๋ฅผ ๋๋ ์ ๋, ์ฌ์ฉ์ ์ข์ํ๋ ์ฑ ๋ชฉ๋ก ๋ฐ์ดํฐ์ ํน์ ์ฑ ์ ์ถ๊ฐํ๋ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์ฑ ๋ชฉ๋ก์ ๋ฐํํ๋ ๋น์ฆ๋์ค ๋ก์ง์ ๋ง๋ค์ด๋ณด์.
public addFavoriteList(bookId: number, bookTitle: string) {
this.favoriteList.push({ bookId, bookTitle });
}
public getFavoriteList() {
return this.favoriteList;
}
์ํคํผ๋์์์ ์ ์ํ๋ ํ๋ ์ ํ ์ด์ ๋ก์ง์ ๋ค์๊ณผ ๊ฐ๋ค.
ํ๋ ์ ํ ์ด์ ๋ก์ง์ ๋น์ง๋์ค ์ค๋ธ์ ํธ(๋น์ง๋์ค ๋ก์ง)๊ฐ ํ์ ํ๋ฉด๊ณผ ๋๋กญ ๋ค์ด ๋งค๋ด ์ค์ด๋ค ๊ฒ์ ์ ํํ ๊ฒ์ธ์ง์ ๊ฐ์ด์ํํธ์จ์ด ์ฌ์ฉ์์๊ฒ ํ์๋๋ ๋ก์ง์ ๋งํ๋ค. ํ๋ ์ ํ ์ด์ ๋ก์ง์์ ๋น์ง๋์ค ๋ก์ง์ ๋ถ๋ฆฌํ๋ ๊ฒ์์ํํธ์จ์ด ๊ฐ๋ฐ ๋ฐ ํ๋ ์ ํ ์ด์ ๊ณผ ์ปจํ ์ธ ๋ฅผ ๋ถ๋ฆฌํ๋ คํ๋ ์ฌ๋ก์(an instance of) ์ค์ํ ๊ด์ฌ์ฌ์ด๋ค.
โ์ํคํผ๋์(์๋ฌธํ),ย Presentation Logic
ํ๋ ์ ํ ์ด์ ๋ก์ง(๋ทฐ ๋ก์ง)์ ์๋ง์ ๋น์ฆ๋์ค ๋ก์ง๋ค์ ์ด๋ป๊ฒ ๋ณด์ฌ์ค ๊ฒ์ธ์ง์ ๋ํ ๋ก์ง์ด๋ค. ์ฆ, ์ฌ์ฉ์ ์ธํฐํ์ด์ค(UI)๋ฅผ ์ด๋ป๊ฒ ํ์ํ ๊น์ ๋ํ ๋ก์ง์ด๋ผ๊ณ ํ ์ ์๋ค.
๊ธฐ์ฌ์ ๋ชฉ๋ก์ ๋ณด์ฌ์ค ๋ ์ฌ์ฉ์์ ํ๋กํ ์ฌ์ง์ด ์์ผ๋ฉด ๊ทธ ์ฌ์ง์ ํ๋กํ๋ก, ์๋๋ฉด ๋ฏธ๋ฆฌ ์ ์ฅํด๋์ defaultProfile๋ก ๋ณด์ฌ์ฃผ๋ ๋ทฐ ๋ก์ง์ ๋ง๋ค์ด๋ณด์.
{contributeList.map((project, idx) => (
<img
src={
project.userProfile !== undefined
? project.userProfile
: `../../assets/defaultProfile/defaultProfile.png`
}
alt={project.userEmail}
className="profile"
/>
))}
๋ณธ๊ฒฉ์ ์ผ๋ก MVVM ํจํด์ ๋ํ ์ค๋ช ์ ํ๋ ค๊ณ ํ๋ค. ๋จผ์ MVVM ํจํด์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ํ๋ ์ ํ ์ด์ ๋ก์ง์ UI๋ก ๋ถํฐ ๋ถ๋ฆฌํ๊ธฐ ์ํด ๋ง๋ค์ด์ก๋ค. ๋น์ฆ๋์ค ๋ก์ง๊ณผ ํ๋ ์ ํ ์ด์ ๋ก์ง์ UI๋ก๋ถํฐ ๋ถ๋ฆฌํ๊ฒ ๋๋ฉด ์ ์ง๋ณด์, ์ฌ์ฌ์ฉ, ํ ์คํธ๊ฐ ์ฌ์์ง๋ค.
MVVM์ Model, View, ViewModel์ ์ฝ์์ด๋ค. ๊ฐ๊ฐ์ ์ญํ ์ ๋ํด ์์๋ณด๋๋ก ํ์.
class BookModel {
constructor() {
books = [
{id: 'RCB-123',name: "React Cook Book", isFavorite: false},
{id: 'VCB-123',name: "Vue Cook Book", isFavorite: false},
{id: 'ACB-123',name: "Angular Cook Book", isFavorite: false}
];
}
getBooks() {
return this.books
}
toggleFavorite(bookId) {
const target = this.books.filter(item => item.id === bookId)[0];
target.isFavorite = !target.isFavorite
}
}
ViewCotroller์ ๋ง๋ค์ด View์์ ๋ทฐ ๋ก์ง์ ๋ถ๋ฆฌ์ํค๋ ๋ฐฉ๋ฒ๋ ์๋๋ฐ, ๋ ํท๊ฐ๋ฆด ์ ์์ผ๋ฏ๋ก ์๋ ์์ ์์๋ ๋ถ๋ฆฌ์ํค์ง ์๋๋ก ํ๊ฒ ๋ค.
const BookView = ({bookViewModel}) => {
const [isNeverView, setIsNeverView] = useState(false);
const handleToggleFavorite = useCallback((bookId) => {
bookViewModel.toggleFavorite(bookId)
}, [viewModel]);
const handleClickNeverView = useCallback(() => {
setIsNeverView(!isNeverView);
}, [isNeverView]);
return (
<BookList
books={bookViewModel.getBooks()}
handleToggleFavorite={handleToggleFavorite}
handleClickNeverView={handleClickNeverView}
/>
)
}
class BookViewModel {
constructor(bookStore) {
this.store = bookStore
}
getBooks() {
return this.store.getBooks()
}
toggleFavorite(bookId) {
this.store.toggleFavorite(bookId)
}
}
Provider๋ฅผ ๋ง๋ค์ด ์๋ก๋ฅผ ์ด์ด์ฃผ๋ ๊ตฌ์ฑ์ฒด๋ฅผ ํ์ฑํ ์ ์๋๋ฐ ์๋ ์์ ์์๋ ๋ถ๋ฆฌ์ํค์ง ์๋๋ก ํ๊ฒ ๋ค.
import React from 'react';
import BookViewModel from './viewModel/BookViewModel'
import BookModel from './model/BookModel'
import BookView from './view/BookView'
function App() {
const bookModel = new BookModel()
const bookViewModel = new BookViewModel(bookModel)
return (
<>
<BookView
bookViewModel={bookViewModel}
/>
</>
)
}
export default App;
recoil์ state๋ฅผ ๊ตฌ๋ ํ๋ ์ปดํฌ๋ํธ์ ํํ์ฌ state๊ฐ ์๋ก์ด ๊ฐ์ผ๋ก ๋ฆฌ๋ ๋๋ง์ด ๋๋ ์ํ๊ด๋ฆฌ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ด๋ค. recoil์ ๊ดํด์๋ ๋ค์์ ์์ธํ ์ค๋ช ํด๋ณด๋๋ก ํ๊ฒ ๋ค.
Recoil๋ก MVVM์ ์ฌ์ฉํ๋ ์์ ๋ ๋ง์ง๊ฐ ์๋ค. ์์ ๋ฅผ ๋ฐ๋ผํ๋ฉด์ ViewModel์์ ๋ทฐ ๋ก์ง์ด ๋ค์ด๊ฐ๋ฉด์ View์ ViewModel์ ๊ตฌ๋ถ์ด ํ์ค์น ์๋ค๋ ๋๋์ ๋ฐ์๋ค. ์๋์์๋ Recoil์ด ์ ์ฉ๋ Model๊ณผ ๋ด๊ฐ ์์ฐ์น ์๊ฒ ์๊ฐํ๋ ๋ถ๋ถ์ ๋ํด ์๊ธฐํด๋ณด๋ ค ํ๋ค.
recoil์์๋ ๋ฐ์ดํฐ์ ์ํ๋ฅผ atom์ผ๋ก ๊ด๋ฆฌํ๊ณ , atom์ ์ํ๊ฐ์ ์ด์ฉํด ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณตํ ์ํ๋ฅผ selector๋ก ๊ด๋ฆฌํ๋ค. CRUD์ ๋ฐ์ดํฐ ๊ฐ๊ณต์ ๋ํ ๋น์ฆ๋์ค ๋ก์ง ๋ชจ๋ recoil์ atom, selector๋ฅผ ํตํด ์ด๋ฃจ์ด์ง๋ค.
export const AllCheckProductItemsSelector = selector({
key: "ProductList/Item/AllCheck",
get: ({ get }) => {
const productList = get(ProductListAtom)
if (productList) return false;
return get(ProductListAtom).every(item => item.checked)
},
set: ({ set }) => {
set(ProductListAtom, prevState => prevState.map(item => {
const currentItem = { ...item };
currentItem.checked = !currentItem.checked;
return currentItem;
})
)
}
})
export const CheckProductItemSelector = selector({
key: "ProductList/Item/Check",
get: ({ get }) => { },
set: ({ get, set }, product_id) => {
set(ProductListAtom, prevState => {
return prevState.map(item => {
const currentItem = {...item}
if (item.id === product_id) {
currentItem.checked = !currentItem.checked
}
return currentItem;
})
})
}
})
MVVM์์ ์์ ViewModel์ ์์กด์ฑ ์ฃผ์ ์ ํตํด View์ Model์ ์ฐ๊ฒฐํด์ฃผ๋ ์ญํ ์ ๋ด๋นํ๋ค. ์ด ๊ตฌ์ฑ์ฒด์์๋ ๋ทฐ๋ก์ง๋, ๋น์ฆ๋์ค ๋ก์ง๋ ๋ค์ด๊ฐ์ง ์๋๋ค. ํ์ง๋ง ํ ๋ธ๋ก๊ทธ์์์ ViewModel์์ ๋ฅผ ๋ณด๋ฉด ๋ทฐ ๋ก์ง์ด ๋ค์ด๊ฐ ์๋ ๊ฒ์ ์ ์ ์๋ค.
const [deliveryMethodList] = useRecoilState(DeliveryMethodAtom);
const [selectedMethod] = useRecoilState(SelectedMethodAtom);
const [productList] = useRecoilState(ProductListAtom);
const price = useRecoilValue(PriceStatsSelector);
const [, toggleChecked] = useRecoilState(CheckProductItemSelector);
const [allChecked, allCheckedProductItem] = useRecoilState(
AllCheckProductItemsSelector
);
const [, increaseAmount] = useRecoilState(
IncreaseProductItemAmount
);
const [, decreaseAmount] = useRecoilState(
DecreaseProductItemAmount
);
const [, handleDeleteProductItem] =
useRecoilState(DeleteProductItem);
const [, changeDeliveryMethod] = useRecoilState(
ChangeDeliveryMethod
);
const { isDropdown, toggleDropdown } = useDropdown();
const toggleAllChecked = () => {
allCheckedProductItem(allChecked);
};
const handlechangeDeliveryMethod = (method_id: number) => {
changeDeliveryMethod(method_id);
toggleDropdown();
};
const handleOrder = () => {
console.log("์ฃผ๋ฌธ ์๋ฃ ๋ก์ง");
};
์์ผ๋ก collusic-new ํ๋ก์ ํธ ๋ฆฌํฉํ ๋ง์ ๊ณผ์ ์์ recoil์ ์ฌ์ฉํ ViewModel๊ณผ View๋ฅผ ๋ง๋ค์ด๋ณด๋ ค๊ณ ํ๋ค.
์ฐธ์กฐ
[ Eassy - Technology, IT, Web ] ๋น์ง๋์ค ๋ก์ง(๊ท์น, ์ธต) ๊ณผ ํ๋ ์ ํ ์ด์ ๋ก์ง(๊ท์น, ์ธต) ์ด๋ ๋ฌด์์ธ๊ฐ?
React์์ MVVM ํจํด ์์๋ณด๊ธฐ
๋ก์ง์ UI๋ก๋ถํฐ ๋ถ๋ฆฌํ๋ MVVM Architecture Pattern
MVVM ๋์์ธํจํด
[TIL]React์ MVVMํจํด
Recoil๋ก MVVM ์ฌ์ฉํ๊ธฐ - (1) MVVM