MVVM ์ํคํ ์ณ ๋์์ธ ํจํด
MVVM์ Model - View - View Model์ ์ฝ์๋ก ์ ํ๋ฆฌ์ผ์ด์ ์ ๋น์ฆ๋์ค ๋ก์ง๊ณผ ์ฌ์ฉ์์๊ฒ ๋ณด์ฌ์ฃผ๋ presentation UI๋ฅผ ๋ถ๋ฆฌํ๋ ํจํด์ด๋ค.
- View
- ์ฑ์ ๋ณด์ฌ์ง๋ UI
- View Model
- ์ฑ์ ๋น์ฆ๋์ค ๋ก์ง์ ๋ด๊ณ ์์
- ๋ทฐ์ ๋ชจ๋ธ ์ฌ์ด์์ ๋ทฐ์ ์์ฒญ์ ๋ฐ๋ผ ๋ก์ง์ ์คํ
- ๋ชจ๋ธ ๋ฐ์ดํฐ์ ๋ณํ์ ๋ฐ๋ผ ๋ทฐ๋ฅผ ๊ฐฑ์
- ๋ชจ๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์์ฒญ, ๋ฐ์ดํฐ๋ฅผ ๊ฐ๊ณต
- Model
- ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌ
- ๋ทฐ๋ชจ๋ธ์๊ฒ ๋ฐ์ดํฐ๋ฅผ ์๋ต
๋ทฐ
ui ์ปดํฌ๋ํธ์ด๋ค. ์์ ์ปดํฌ๋ํธ์์ props๋ก ์ ๋ฌ๋ฐ์ ์ํ์ ๋ฐ๋ผ์ ํ๋ฉด์ด ๋์ํ๋ค.
์ฌ์ฉ์๊ฐ ์ด๋ฒคํธ๋ฅผ ๋ฐ์์ํค๋ฉด ์ด๋ฒคํธ์ ๋ฐ๋ฅธ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ ๋ทฐ์์ ๋ฐ๋ก ์ฒ๋ฆฌํ์ง์๊ณ , ์์ ์ปจํ ์ด๋์ธ View Model ์ปจํ ์ด๋์์ ์ฒ๋ฆฌํ๋๋ก ํ๋ค.
interface Props {
data: Todo;
deleteTodoItem: (id: string) => void;
updateCheckBox: (id: string, isChecked: boolean) => void;
}
export const TodoBox = ({
data,
deleteTodoItem,
updateCheckBox,
}: Props) => {
const [checked, setChecked] = useState(data.isDone);
const [isOnHover, setIsOnHover] = useState(false);
const handleChangeCheckBox = (e: React.ChangeEvent<HTMLInputElement>) => {
setChecked(e.target.checked);
updateCheckBox(data.id, e.target.checked);
};
const handleDelete = () => {
deleteTodoItem(data.id);
};
const showDeleteIconBox = () => {
if (isOnHover)
return (
<Box>
<IconButton onClick={handleDelete}>
<DeleteIcon />
</IconButton>
</Box>
);
};
return (
<Paper
elevation={3}
sx={{ m: 2 }}
onMouseEnter={() => setIsOnHover(true)}
onMouseLeave={() => setIsOnHover(false)}>
<Box
sx={{
display: 'flex',
justifyContent: 'space-between',
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Checkbox
checked={data.isDone}
onChange={handleChangeCheckBox}
inputProps={{ 'aria-label': 'controlled' }}
/>
{data.content}
</Box>
{showDeleteIconBox()}
</Box>
</Paper>
);
};
๋ทฐ๋ชจ๋ธ
View Model์ ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๊ณ ์๋ Model์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฐ์ดํธํ๋ ์ญํ ์ ํ๋ค.
๋ํ ์ค์ ๋ก ๋ฐ์ดํฐ ์ํ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ก์ง์ ๋ด๊ณ ์์ด์ ์ค์ ๋ฉ์๋๋ฅผ ํธ์ถํ๊ณ Viewํํ ๊ทธ ๋ฉ์๋๋ฅผ props๋ก ๋ด๋ ค์ค๋ค.
export const TodoContainer = () => {
const dispatch = useDispatch();
const todolist = useSelector((state: RootState) => state.todo);
// console.log(todolist, 'todolist ์
๋ ํฐ');
const updateCheckBox = (id: string, isChecked: boolean) => {
dispatch(updateTodoCheck({ id: id, isDone: isChecked }));
};
const deleteTodoItem = (id: string) => {
dispatch(deleteTodo({ id: id }));
};
return (
<>
{todolist.map((el: Todo) =>
<TodoBox
key={el.id}
data={el}
updateCheckBox={updateCheckBox}
deleteTodoItem={deleteTodoItem}
designateEditItem={designateEditItem}></TodoBox>
)}
</>
);
};
๋ชจ๋ธ
์ค์ ๋ฐ์ดํฐ๋ฅผ ๋ด๊ณ ๋ฐ์ดํฐ๋ฅผ ์ฒ๋ฆฌํ๋ ๋ถ๋ถ์ด๋ค. ์ํ ๊ด๋ฆฌ๋ฅผ ์ํด redux toolkit์ ํ์ฉํด์ ์์ฑํ๋ค.
View Model์์ ๋ฐ์ดํฐ ๋ณ๊ฒฝ์ rtk dispatch ํจ์๋ฅผ ํตํด Modelํํ ์ ๋ฐ์ดํธํ๊ณ , ๋ชจ๋ธ ์ฆ, ์ฌ๋ผ์ด์ค์ ์ ์ญ ์ํ๊ฐ ๋ณํํ๋ค.
View Model์ ์ฌ๋ผ์ด์ค์ ๋ณ๊ฒฝ๋ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์งํ๊ฒ ๋๊ณ ์ฌ๋ ๋๋ง์ด ๋๋ค. View ๋ํ View Model์ ๋ฐ๋ผ๋ณด๊ณ ์๊ธฐ์ View Model์์ ๋ณ๊ฒฝ๋ ์ํ๊ฐ์ ๋ฐ์ View๋ ๋ณ๊ฒฝ์ ๊ฐ์งํ๊ฒ ๋๊ณ ์๋ก์ด ๋ฐ์ดํฐ๋ฅผ ๋์ฐ๊ธฐ ์ํด ์ฌ๋ ๋๋ง ๋๋ค.
export interface Todo {
id: string;
content: string;
createdAt: string;
isDone: boolean;
completedAt: string | null;
}
export const todoSlice = createSlice({
name: 'todo',
initialState: [
{
id: 'bzte_tfG3k2mh3o-Vf-zX',
content: '๊ฐ๋',
createdAt: '6/19/2023, 8:20:02 PM',
isDone: false,
completedAt: null,
},
{
id: 'rAFHp_GrutBJzt_5r1Gej',
content: '๋ค๋ผ',
createdAt: '6/19/2023, 8:20:02 PM',
isDone: false,
completedAt: null,
},
] as Todo[],
reducers: {
updateTodoCheck: (
state,
action: PayloadAction<{ id: string; isDone: boolean }>,
) => {
// ์ํ๋ฅผ immutableํ๊ฒ ๋ฐ๊พธ๊ธฐ
const findItem = state.find((it) => it.id === action.payload.id);
return state.map((el) => {
if (el === findItem) {
if (action.payload.isDone) {
return {
...findItem,
isDone: action.payload.isDone,
// content: action.payload.content,
completedAt: koreanTimeFormat(),
};
} else if (!action.payload.isDone) {
return {
...findItem,
isDone: action.payload.isDone,
completedAt: null,
};
}
}
return el;
});
},
deleteTodo: (state, action: PayloadAction<{ id: string }>) => {
return state.filter((el: Todo) => el.id !== action.payload.id);
},
},
});
๋ด ํฌ๋ ์ฑ์ ๊ฒฝ์ฐ ๊ฐ ํ์ผ์ MVVM ์ค์์ ๋ณธ์ธ์๊ฒ ํด๋นํ๋ ์ญํ ์ ํ๊ณ ์๋ค. ํ์ผ๋ช ์ ๋ง๋ถ์ฌ๋ณด์๋ฉด ์ด๋ฐ ์์ด๋ค.
Reference
https://velog.io/@ellyheetov/MVVM%EC%9D%B4%EB%9E%80