1. 메모장 프로젝트의 소개 및 설명
1.1. 설계 및 기본 컴포넌트 구현
- 왼쪽 사이드바에서는 현재 등록된 메모의 목록이, 오른쪽 메모 편집에서는 제목과 내용을 수정할 수 있는 입력 폼.
- 사이드바 구조: SideBarHeader, MemoList(MemoItem), SideBarFooter
- 메모 편집 구조: MemoContainer(MemoTitle + MemoContent)
- 메모 리스트 = [메모1, 메모2, 메모3, 메모4]
1.2. 메모 수정 및 선택 기능 구현
- 사이드바에서 선택한 메모를 오른쪽 편집 영역에서 확인하고 수정.
- 수정된 내용은 다른 메모로 이동하더라도 보존.
1.3. 메모 추가 기능 구현
- 사이드바 최하단에는 새로운 메모를 추가할 수 있는 버튼 존재
- 새로운 메모 추가 시 초기 제목은 ‘untitled’로 설정되며, 내용은 비어있음
- 새로운 메모 추가 시 해당 메모가 자동으로 선택됨
1.4. 메모 삭제 기능 구현
- 사이드바의 각 메모 오른쪽에는 메모를 삭제하는 x 버튼 존재
- x버튼 클릭 시 해당 메모는 삭제, 해당 메모를 선택한 상태라면 가장 첫 번째 메모로 선택이 변경됨
- 모든 메모 삭제 시, 오른쪽 편집 영역에는 “메모가 없습니다. 새로운 메모를 생성해주세요” 라는 문구가 출력됨
1.5. 메모 저장 기능 구현
- 브라우저를 닫아도 편집한 메모가 유지됨 (localStroage 사용)
2. 기본 컴포넌트 구현하기
2.1. App.js
function App (){
const [memos, setMemos] = useState([
{
title: 'Memo 1',
content: 'This is memo1',
createdAt: 1719983657532,
updatedAt: 1719983657532,
},
{
title: 'Memo 2',
content: 'This is memo2',
createdAt: 1719983697309,
updatedAt: 1719983697309,
}
]);
return (
<div className="App">
<SideBar memos={memos}/>
<MemoContainer/>
</div>
);
}
export default App;
2.2. MemoContainer.js
function MemoContainer(){
return(
<div className="MemoContainer">
<div>
<input type="text" className="MemoContainer_title"/>
</div>
<div>
<textarea className="MemoContainer_content"/>
</div>
</div>
)
}
export default MemoContainer;
import MemoList from "./MemoList";
import SideBarHeader from "./SideBarHeader";
import SideBarFooter from "./SideBarFooter";
function SideBar({memos}){
return(
<div className="SideBar">
<SideBarHeader/>
<MemoList memos={memos}/>
<SideBarFooter/>
</div>
)
}
export default SideBar;
2.4. MemoList.js
function MemoList({memos}){
return(
<div>
{
memos.map((memo, index)=>(
<div key={index}>{memo.title}</div>
))
}
</div>
);
}
export default MemoList;
function SideBarHeader({memos}){
return(
<div className="SideBar">
SideBarHeader
</div>
)
}
export default SideBarHeader;
function SideBarFooter({memos}){
return(
<div className="SideBar">
SideBarFooter
</div>
)
}
export default SideBarFooter;
3. 메모 수정 기능 및 메모 선택 기능 구현
3.1. MemoContainer.js에 메모 제목과 내용을 수정해주기
function MemoContainer({memo, setMemo}){
return(
<div className="MemoContainer">
<div>
<input
type="text"
className="MemoContainer_title"
value={memo.title}
onChange={(e)=>{
setMemo({
...memo,
title: e.target.value,
updatedAt: new Date().getTime(),
});
}}
/>
</div>
<div>
<textarea
className="MemoContainer_content"
value={memo.content}
onChange={(e)=>{
setMemo({
...memo,
content: e.target.value,
updatedAt: new Date().getTime(),
});
}}
/>
</div>
</div>
)
}
export default MemoContainer;
3.2. App.js에서 setMemo 변수를 이용해 내용을 ReRendering해주기
const setMemo = (newMemo) => {
const newMemos = [...memos];
newMemos[selectedMemoIndex] = newMemo;
setMemos(newMemos);
}
3.3. 메모를 선택하기 위해 새롭게 메모 내용을 불러와주는 컴포넌트인 MemoItem.js를 만들어주고, MemoList.js에 onClick을 넣어주기
function MemoItem({children, onClick, isSelected}){
return(
<div className={"MemoItem" + (isSelected ? ' selected' : '')}
onClick={onClick}
>
{children}
</div>
);
}
export default MemoItem;
import MemoItem from "./MemoItem";
function MemoList({memos, setSelectedMemoIndex, selectedMemoIndex}){
return(
<div>
{
memos.map((memo, index)=>(
<MemoItem
key = {index}
onClick={()=>{
setSelectedMemoIndex(index);
}}
isSelected={index === selectedMemoIndex}
index = {index}
setSelectedMemoIndex={setSelectedMemoIndex}
>{memo.title}</MemoItem>
))
}
</div>
);
}
export default MemoList;
3.4. App.js에 selectedMemoIndex와 setSelectedMemoIndex를 선언해주기
function App (){
return (
<div className="App">
<SideBar
memos={memos}
selectedMemoIndex={selectedMemoIndex}
setSelectedMemoIndex={setSelectedMemoIndex}/>
<MemoContainer memo={memos[selectedMemoIndex]}
setMemo={setMemo}/>
</div>
);
}
export default App;
3.5. 시각적인 변화를 보여주기 위해 App.css 구현하기.
.App {
text-align: center;
display: flex;
height: 100vh;
}
.SideBar{
background-color: aquamarine;
width: 200px;
}
.MemoContainer{
background-color: beige;
flex: 1;
}
.MemoContainer_title{
width: 100%;
box-sizing: border-box;
font-size: 2rem;
}
.MemoContainer_content{
width: 100%;
font-size: 1.1rem;
}
.MemoItem{
width: 100%;
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
}
.MemoItem.selected{
background-color: rgba(100, 100, 100, 0.5);
font-weight: 500;
}
3.6. npm start시 화면

4. 메모 추가 기능 구현
function SideBarFooter({onClick}){
return(
<div className="SideBar">
<button
className="SideBarFooter_add-Button"
onClick={onClick}
>
+
</button>
</div>
)
}
export default SideBarFooter;
4.2. App.js에 addMemo 함수를 생성하여 버튼 클릭 시 변화를 정의하기
const addMemo = () => {
const now = new Date().getTime();
setMemos([...memos, {
title: 'Untitled',
content: '',
createdAt: new Date().getTime(),
updatedAt: new Date().getTime()},
]);
setSelectedMemoIndex(memos.length);
};
return (
<div className="App">
<SideBar
memos={memos}
addMemo={addMemo}
selectedMemoIndex={selectedMemoIndex}
setSelectedMemoIndex={setSelectedMemoIndex}/>
<MemoContainer memo={memos[selectedMemoIndex]}
setMemo={setMemo}/>
</div>
);
}
export default App;
import MemoList from "./MemoList";
import SideBarHeader from "./SideBarHeader";
import SideBarFooter from "./SideBarFooter";
function SideBar({memos, addMemo, setSelectedMemoIndex, selectedMemoIndex}){
return(
<div className="SideBar">
<SideBarHeader/>
<MemoList
memos={memos}
selectedMemoIndex={selectedMemoIndex}
setSelectedMemoIndex={setSelectedMemoIndex}/>
<SideBarFooter onClick={addMemo}/>
{}
</div>
)
}
export default SideBar;
5. 메모 삭제 기능 구현
function MemoItem({children, onClickItem, onClickDelete, isSelected}){
return(
<div className={"MemoItem" + (isSelected ? ' selected' : '')}
onClick={onClickItem}
>
{children}
<button className="MemoItem_delete_button"
onClick={onClickDelete}>X</button>
</div>
);
}
export default MemoItem;
5.2. MemoList.js에 onClickDelete라는 함수를 정의해주기
import MemoItem from "./MemoItem";
function MemoList({memos, setSelectedMemoIndex, selectedMemoIndex, deleteMemo}){
return(
<div>
{
memos.map((memo, index)=>(
<MemoItem
key = {index}
onClickItem={()=>{
setSelectedMemoIndex(index);
}}
onClickDelete={(e)=>{
deleteMemo(index);
e.preventDefault();
e.stopPropagation();
}}
isSelected={index === selectedMemoIndex}
>{memo.title}</MemoItem>
))
}
</div>
);
}
export default MemoList;
5.3. App.js에 deleteMemo 함수의 정의 적용하기
function App (){
const deleteMemo = (index) => {
const newMemos = [...memos];
newMemos.splice(index, 1);
setMemos(newMemos);
if(index === selectedMemoIndex){
setSelectedMemoIndex(0);
}
}
return (
<div className="App">
<SideBar
memos={memos}
addMemo={addMemo}
selectedMemoIndex={selectedMemoIndex}
setSelectedMemoIndex={setSelectedMemoIndex}
deleteMemo={deleteMemo}/>
<MemoContainer memo={memos[selectedMemoIndex]}
setMemo={setMemo}/>
</div>
);
}
export default App;
import MemoList from "./MemoList";
import SideBarHeader from "./SideBarHeader";
import SideBarFooter from "./SideBarFooter";
function SideBar({memos, addMemo, setSelectedMemoIndex, selectedMemoIndex, deleteMemo}){
return(
<div className="SideBar">
<SideBarHeader/>
<MemoList
memos={memos}
selectedMemoIndex={selectedMemoIndex}
setSelectedMemoIndex={setSelectedMemoIndex}
deleteMemo={deleteMemo}
/>
<SideBarFooter onClick={addMemo}/>
{}
</div>
)
}
export default SideBar;
6. LocalStorage를 이용한 데이터 저장
6.1. Cookie, LocalStorage와 Session Storage의 비교
- Cookie: 네트워크 요청 시, 서버로 함께 저장되는 문자열 데이터.
- Local Storage: key-value 쌍으로 데이터를 저장/조회할 수 있는 데이터 저장소. 서버로 전달되지 않고, 지우지 않는 이상 브라우저나 탭을 닫았다 열어도 유지됨
- Session Storage: Local Storage와 같으나, 같은 탭 안에서만 데이터가 유지되고, 탭이나 브라우저를 닫으면 데이터가 지워짐.