- ์ํ ๊ด๋ฆฌ ๊ธฐ์ ์ด๋ ์ฑ ์์์์ ๋ฐ์ดํฐ๋ฅผ ๋ฉ๋ชจ๋ฆฌ ๋ฑ์ ์ ์ฅํ๊ณ ํ๋ ์ด์์ ์ปดํฌ๋ํธ์์ ๋ฐ์ดํฐ๋ฅผ ๊ณต์ ํ๋ ๊ฒ์ ์๋ฏธํ๋ค.
Store(Data Source)๋ฅผ ์ ๋ฐ์ดํธํ๋ฉด View๊ฐ ์ ๋ฐ์ดํธ ๋๋ ๋ฑ ํ ๋ฐฉํฅ์ผ๋ก ๋ฐ์ดํฐ๊ฐ ํ๋ฅด๋ ๊ฐ๋ ์ด๋ค.
MVC ํจํด์ Model์ ๋ฐ์ดํฐ ์์ค๋ก ๋ณผ ์ ์๊ณ , View๋ ์ฌ์ฉ์๊ฐ ๋ณด๋ ํ๋ฉด๋จ์ด๋ผ๊ณ ๋ณผ ์ ์๋ค. ํ๋์ Model์์ ๋ค์์ View๋ค์ด ๋ฐ์ดํฐ๋ฅผ ๋ถ๋ฌ์ค๋ ํํ์ด๋ค. ์ฌ๊ธฐ์์ MVC ํจํด์ Bidirectional(์๋ฐฉํฅ) ๋ฐ์ดํฐ ํ๋ฆ์ ๊ฐ๋ ๊ตฌ์กฐ๋ฅผ ํ๊ณ ์๋ค. ์ฆ, ์๋ก ์ฐ๊ฒฐ๋ ์ํ์ธ ๊ฒฝ์ฐ View๊ฐ ์ ๋ฐ์ดํธ๋๋ฉด Model ๋ํ ์ ๋ฐ์ดํธ ๋๋ค.
Store(๋ฐ์ดํฐ ์์ค) -> ๋ค์์ View. View๊ฐ ์ ๋ฐ์ดํธ ๋์ด๋ Store๋ ์ ๋ฐ์ดํธ ๋์ง ์๋ unidirectional data flow
Action: View๊ฐ Store๋ฅผ ์ ๋ฐ์ดํธ์ํค๊ธฐ ์ํด ์์ฑํ ๊ฒ. Reducer์ ๋๊ฒจ์ง ํ, Store๋ฅผ ์ ๋ฐ์ดํธํ๋ค.
callback: ์ด๋ฒคํธ์ ๋ฐ๋ผ ์คํ๋ ํจ์
setTimout์ timerId๋ฅผ ์ ์ฅํ๋ ๊ฒ ๋ฑ์ด ํด๋น
Dynamic Form์ ์์๋ฅผ ๋ค ์ ์๋ค.
function TodoApp() {
const [todos, setTodos] = useState([]);
const [filter, setFilter] = useState('all');
const [globalId, setGlobalId] = useState(3000);
const toggleTodo = (id) => {
setTodos((todos)=>{
todos.map((todo)=>{
todo.id === id ? {...todo, completed: !todo.completed}
}: todo)
})
}
const deleteTodo = (id) => {
setTodos((todos)=> todos.filter((todo)=> todo.id !== id));
};
const addTodo = (title => {
setTodos((todos)=> [{title, id: globalId + 1}, ... todos]);
setGlobalId((id)=> id+1);
return <TodosPage
state ={{todos, filter}}
toggleTodo={toggleTodo}
addTodo={addTodo}
deleleTodo={deleteTodo}
changeFilter={setFilter}
/>
})
}
function TodosPage({state, addTodo, deleteTodo, toggleTodo, changeFilter}){
const filterTodos = state.todos.filter((todo)=>{
const {filter} = state;
return (
filter === 'all' ||
(filter === 'completed' && todo.completed) ||
(filter === 'todo' && !todo.completed)
);
})
return (
<div>
<h3>TodosPage</h3>
<TodoForm onSubmit={addTodo}/>
<TodoFilter filter={state.filter}
changeFilter={changeFilter}/>
<ToDoList
todos={filteredTodos}
toggleTodo={toggleTodo}
deleteTodo={deleteTodo}
/>
</div>
);
}
function TodoForm({onSubmit}) {
const [title, setTitle] = useState('');
return (
<form
onSubmit={(e) =>{
e.preventDefault();
onSubmit(title);
setTitle('');
}}
>
<label htmlFor ='todo-title'>Title</label>
<input id='todo-title' type='text'
name='todo-title' onchange={(e)=>
setTitle(e.target.value)} value={title} />
<button type='submit'>Make</button>
</form>
);
}
function TodoList({todos, toggleTodo, deleteTodo}) {
return (
<ul>
{todos.map(({title, completed, id}) => (
<li onClick{()=> toggleTodo(id)}>
<h5>{title}</h5>
<div>
{completed ? ์ฒดํฌ์์ด์ฝ: ์ฐ๊ธฐ ์์ด์ฝ}
<button onClick={()=>
deleteTodo(id)}>Delete</button>
</div>
</li>
))}
</ul>
);
}
function TodoFilter({filter, changeFilter}) {
return (
<div>
<label htmlFor='filter'>Filter</label>
<select
onChange{(e)=> changeFilter(e.target.value)}
id='filter'
name='filter'
>
{filterList.map((filterText)=> (
<option selected={filter === filterText} value={filterText}>
</option>
))};
</select>
</div>
);
}
TodoContext
const TodoContext = createContext(null);
const initialState ={
todos: [],
filter: 'all',
globalId: 3000,
};
function useTodoContext(){
const context = useContext(TodoContext);
if (!context){
throw new Error('Use TodoContext inside Provider.');
}
return context;
}
function TodoContextProvider({children}){
const values = useTodoState();
return <TodoContext.Provider
value={values}>{children}</TodoContext.Provider>;
}
function reducer(state, action) {
switch(action.type) {
case 'change.filter':
return {...state, filter:
action.payload.filter};
case 'init.todos':
return {...state, todos:
action.payload.todos};
case 'add.todo': {
return {...state, todos:
[{title: action.payload.title,
id: state.globalId + 1}],
globalId: state.globalId + 1};
}
case 'delete.todo': {
return {...state, todo:
state.todos.filter((todo)=> todo.id !==
action.payload.id)};
}
case 'toggle.todo': {
return {...state, todos: state.todos.map((t)
=> t.id === action.payload.id ?
{...t, completed: !t.compeleted}: t)};
}
deafault: return state;
}
}
function useTodoState() {
const [state, dispatch] = useReducer(reducer, initialState);
const toggleTodo = useCallback((id)=> dispatch({type: 'toggle.todo', payload: {id} }, []);
const deleteTodo = useCallback((id)=> dispatch({type: 'delete.todo', payload: {id} }, []);
const addTodo = useCallback((title)=> dispatch({type: 'add.todo', payload: {title} }, []);
const changeFilter = useCallback((filter)=> dispatch({type: 'change.filter', payload: {title} }, []);
const initializeTodos = useCallback((todos)=> dispatch({type: 'init.todos', payload: {todos} }, []);
return {state, toggleTodo, deleteTodo, addTodo, changeFilter, initializeTodos};
}
todoApp
function TodoApp() {
return (
<TodoContextProvider>
<TodosPage/>
</TodoContextProvider>
);
}
TodosPage
function TodosPage() {
const {initializeTodos} = useTodoContext();
// ์ฒ์ ๋ง์ดํธ ๋์์ ๋, todo๋ฅผ ๋ฐ์์ ์
๋ฐ์ดํธํ๋ค.
useEffect(()=>{
console.log('useEffect');
fetchTodos().then(initializeTodos);
}, [initializeTodos]);
return (
<div>
<TodoForm/>
<TodoFilter/>
<TodoList/>
</div>
);
}
TodoForm
function TodoForm(){
const {addTodo} = useContext();
const [title, setTitle] = useState('');
return (
<form
onSubmit={(e)=>{
e.preventDefault();
addTodo(title);
setTitle('');
}}
>
<label htmlFor='todo-title>Title</label>
<input
id='todo-title'
type='text'
name='todo-title'
onChange={(e)=> setTitle(e.target.value)}
value={title}/>
<button type='submit'>Make</button>
</form>
);
}
TodoList
function TodoList() {
const {state, toggleTodo, deleleTodo} = useTodoContext();
const { todos, filter } = state;
const filteredTodos = todos.filter((todo)=>{
return (
filter === 'all' ||
(filter === 'completed' && todo.completed) ||
(filter === 'todo' && !todo.completed)
);
})
return (
<ul>
{filteredTodos.map(({title, completed, id}) => (
<li key={id} onClick={()=> toggleTodo(id)}>
<h5>{title}</h5>
<div>
{completed ? ์ฒดํฌ๋ฐ์ค์์ด์ฝ :ํ ์์ด์ฝ}
<button onClick={()=> deleteTodo(id)}>Delete</button>
</div>
))}
</ul>
)
}