ํ ์ ํฌ๊ธฐ๋ก ์๋ผ ๋จน๋ ๋ฆฌ์กํธ ์น์ 12 - Context
์ปดํฌ๋ํธ๊ฐ์ ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌํ๋ ๋ ๋ค๋ฅธ ๋ฐฉ๋ฒ์ด๋ค.
๊ธฐ์กด์ props๊ฐ ๊ฐ์ง๊ณ ์๋ ๋จ์ (์ค์ง ์ง๊ณ ๋ถ๋ชจ์์ ์์์ผ๋ก๋ง ๋ฐ์ดํฐ๋ฅผ ์ ๋ฌ)์ ํด๊ฒฐํ ์ ์๋ค. (๋น์ : props๋ ํ ์๋ฒ์ง๊ฐ ์์์๊ฒ ์ ๋ฌ ๋ชปํ๊ณ ์๋ฒ์ง๋ฅผ ํตํด ์ ๋ฌ๋ง ๊ฐ๋ฅํ๋ค.)
context๋ ๋ฐ์ดํฐ ๋ณด๊ด์์ด๋ค. ์ด ๊ณณ์ ๋ณด๊ดํ์ฌ ์ ๋ฌํ๋ ๋ฐฉ๋ฒ์ด๋ค.

์ด๋ฐ์์ผ๋ก ๊ตฌ์กฐ๋ฅผ ๋ง๋ค์ด์ ์ฝ๋๋ฅผ ์์ฑํด์ค๋ค.
App.jsx
import "./App.css";
import {
useState,
useRef,
useReducer,
useCallback,
createContext,
} from "react";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";
const mockData = [
{
id: 0,
isDone: false,
content: "React ๊ณต๋ถํ๊ธฐ",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "์ฝ์ดํ์ ๊ฐ๊ธฐ",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "์ผ์ฐ ์ผ์ด๋๊ธฐ",
date: new Date().getTime(),
},
];
function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
case "UPDATE":
return state.map((item) =>
item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
);
case "DELETE":
return state.filter((item) => item.id !== action.targetId);
default:
return state;
}
}
export const TodoContext = createContext();
function App() {
const [todos, dispatch] = useReducer(reducer, mockData);
const idRef = useRef(3);
const onCreate = useCallback((content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
}, []);
const onUpdate = useCallback((targetId) => {
dispatch({
type: "UPDATE",
targetId: targetId,
});
}, []);
const onDelete = useCallback((targetId) => {
dispatch({
type: "DELETE",
targetId: targetId,
});
}, []);
return (
<div className="App">
<Header />
<TodoContext.Provider value={{ todos, onCreate, onUpdate, onDelete }}>
<Editor onCreate={onCreate} />
<List todos={todos} onUpdate={onUpdate} onDelete={onDelete} />
</TodoContext.Provider>
</div>
);
}
export default App;

Editor.jsx ....
import "./Editor.css";
import { useState, useRef, useContext } from "react";
import { TodoContext } from ".../App";
const Editor = () => {
const { onCreate } = useContext(TodoContext);
const [content, setContent] = useState("");
const inputRef = useRef();
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onKeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
const onSubmit = () => {
if (content === "") {
inputRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
return (
<div className="Editor">
<input
ref={inputRef}
value={content}
onKeyDown={onKeydown}
onChange={onChangeContent}
placeholder="์๋ก์ด Todo..."
/>
<button onClick={onSubmit}>์ถ๊ฐ</button>
</div>
);
};
export default Editor;
์ด๋ฐ ์์ผ๋ก ๋๋จธ์ง ๊ธฐ๋ฅ๋ค๋ ์์ฑํด์ค๋ค.

context ๋ฆฌ๋ ๋๋ง์ด ๋ฐ์ํ๋ค. ์ต์ ํ๊ฐ ์ ์ฉ๋์ง ์์๋ค! ๋ฐ๋ผ์ context ๋ถ๋ฆฌ ํ์!

๋ถ๋ณ๊ฐ๊ณผ ๊ฐ๋ณ๊ฐ์ ๋ถ๋ฆฌํ๋ค.

์ต์ข ์ ์ผ๋ก ์ด๋ ๊ฒ ์๋ํ๋๋ก ๋ง๋ค์ด์ผ ํ๋ค.
Editor.jsx
import "./Editor.css";
import { useState, useRef, useContext } from "react";
import { TodoDispatchContext } from "../App";
const Editor = () => {
const { onCreate } = useContext(TodoDispatchContext);
const [content, setContent] = useState("");
const inputRef = useRef();
const onChangeContent = (e) => {
setContent(e.target.value);
};
const onKeydown = (e) => {
if (e.keyCode === 13) {
onSubmit();
}
};
const onSubmit = () => {
if (content === "") {
inputRef.current.focus();
return;
}
onCreate(content);
setContent("");
};
return (
<div className="Editor">
<input
ref={inputRef}
value={content}
onKeyDown={onKeydown}
onChange={onChangeContent}
placeholder="์๋ก์ด Todo..."
/>
<button onClick={onSubmit}>์ถ๊ฐ</button>
</div>
);
};
export default Editor;
Exam.jsx
import { useReducer } from "react";
function reducer(state, action) {
switch (action.tye) {
case "INCREASE":
return state + action.data;
case "Decrease":
return state - action.data;
default:
return state;
}
}
const Exam = () => {
const [state, dispatch] = useReducer(reducer, 0);
const onClickPlus = () => {
dispatch({
type: "INCREASE",
data: 1,
});
};
const onClickMinus = () => {
dispatch({
type: "DECREASE",
data: 1,
});
};
return (
<div>
<h1>{state}</h1>
<button>onClick={onClickPlus}</button>
<button onClick={onClickMinus}>-</button>
</div>
);
};
export default Exam;
Header.jsx
import "./Header.css";
import { memo } from "react";
const Header = () => {
return (
<div className="Header">
<h3>์ค๋์ ๐ </h3>
<h1>{new Date().toDateString()}</h1>
</div>
);
};
const memoizedHeader = memo(Header);
export default memoizedHeader; //window + . ๋ก ์ด๋ชจ์ง ์ถ๊ฐ ๊ฐ๋ฅํ๋ค.
List.jsx
import "./List.css";
import TodoItem from "./TodoItem";
import { useState, useMemo, useContext } from "react";
import { TodoStateContext } from "../App";
const List = () => {
const todos = useContext(TodoStateContext);
const [search, setSearch] = useState("");
const onChangeSearch = (e) => {
setSearch(e.target.value);
};
const getFilteredData = () => {
if (search === "") {
return todos;
}
return todos.filter((todo) =>
todo.content.toLowerCase().includes(search.toLowerCase())
);
};
const filteredTodos = getFilteredData();
const { totalCount, doneCount, notDoneCount } = useMemo(() => {
const totalCount = todos.length;
const doneCount = todos.filter((todo) => todo.isDone).length;
const notDoneCount = totalCount - doneCount;
return { totalCount, doneCount, notDoneCount };
}, [todos]);
return (
<div className="List">
<h4>Todo List ๐ฑ</h4>
<div>
<div>total: {totalCount}</div>
<div>done: {doneCount}</div>
<div>notDone: {notDoneCount}</div>
</div>
<input
value={search}
onChange={onChangeSearch}
placeholder="๊ฒ์์ด๋ฅผ ์
๋ ฅํ์ธ์"
/>
<div className="todos_wrapper">
{filteredTodos.map((todo) => (
<TodoItem key={todo.id} {...todo} />
))}
</div>
</div>
);
};
export default List;
TodoItem.jsx
import "./TodoItem.css";
import { memo, useContext } from "react";
import { TodoDispatchContext } from "../App";
const TodoItem = ({ id, isDone, content, date }) => {
const { onUpdate, onDelete } = useContext(TodoDispatchContext);
const onChangeCheckbox = () => {
onUpdate(id);
};
const onClickDeleteButton = () => {
onDelete(id);
};
return (
<div className="TodoItem">
<input
onChange={onChangeCheckbox}
readOnly
checked={isDone}
type="checkbox"
/>
<div className="content">{content}</div>
<div className="date">{new Date(date).toLocaleDateString()}</div>
<button onClick={onClickDeleteButton}>์ญ์ </button>
</div>
);
};
export default memo(TodoItem);
App.jsx
import "./App.css";
import {
useState,
useRef,
useReducer,
useCallback,
createContext,
useMemo
} from "react";
import Header from "./components/Header";
import Editor from "./components/Editor";
import List from "./components/List";
const mockData = [
{
id: 0,
isDone: false,
content: "React ๊ณต๋ถํ๊ธฐ",
date: new Date().getTime(),
},
{
id: 1,
isDone: false,
content: "์ฝ์ดํ์ ๊ฐ๊ธฐ",
date: new Date().getTime(),
},
{
id: 2,
isDone: false,
content: "์ผ์ฐ ์ผ์ด๋๊ธฐ",
date: new Date().getTime(),
},
];
function reducer(state, action) {
switch (action.type) {
case "CREATE":
return [action.data, ...state];
case "UPDATE":
return state.map((item) =>
item.id === action.targetId ? { ...item, isDone: !item.isDone } : item
);
case "DELETE":
return state.filter((item) => item.id !== action.targetId); // โ
์ด ๋ถ๋ถ ์์
default:
return state;
}
}
export const TodoStateContext = createContext();
export const TodoDispatchContext = createContext();
function App() {
const [todos, dispatch] = useReducer(reducer, mockData);
const idRef = useRef(3);
const onCreate = useCallback((content) => {
dispatch({
type: "CREATE",
data: {
id: idRef.current++,
isDone: false,
content: content,
date: new Date().getTime(),
},
});
}, []);
const onUpdate = useCallback((targetId) => {
dispatch({
type: "UPDATE",
targetId: targetId,
});
}, []);
const onDelete = useCallback((targetId) => {
dispatch({
type: "DELETE",
targetId: targetId,
});
}, []);
const memoizedDispatch = useMemo(()=> {
return {onCreate, onUpdate, onDelete};
}, []);
return (
<div className="App">
<Header />
<TodoStateContext.Provider value={ todos}>
<TodoDispatchContext.Provider
value={memoizedDispatch}
>
<Editor/>
<List/>
</TodoDispatchContext.Provider>
</TodoStateContext.Provider>
</div>
);
}
export default App;

