- 상태 관리 기술이란 앱 상에서의 데이터를 메모리 등에 저장하고 하나 이상의 컴포넌트에서 데이터를 공유하는 것을 의미한다.
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>
)
}