최근에 다시 프로젝트를 시작하고 있어요!
제가 현재 교육을 듣고 있는 데브코스에서, 어느 정도 요구사항을 요구했었는데, 사실 제멋대로(?!) 프로젝트를 만들고 있답니다. 😅
그래도, 개인 프로젝트란 건 결국 자기가 커스터마이징하는 맛 아니겠어요!
(당연히 팀 프로젝트라면 팀 목적에 맞춰서 진행해야 되겠죠?!😆)
그래도, 프로그래머스 측에서도 역시 저희의 자생력을 길러주기 위해, 요구사항도 최소화해주신 부분이 보였어요. (꽤나, 감동했답니다!😄👍👍)
그렇다면, 이제 제 프로젝트를 하는 과정을, 글과 함께 남기도록 하겠습니다. 결국 남는 건 당시의 생각들을 담은 기록물이라고 생각하니까요.
일단 저는 Vanilla JS
로 컴포넌트의 상태 관리를 통해 포스트를 저장하고, 삭제하는 일종의 노션을 만들고 있어요!
이름까지 지었는데, 이름은
no#tation
으로 지었습니다.
일단 망설이지 말고 글을 쓰고 보자는, 저를 반성하게 하는 의미네요.😂😂😂😂 (변명하자면 최근에 너무 심적인 여유가 없었어요...!)
그래도 막상 짓고나니 애정이 생기네요!
현재 디렉토리 구조는 다음과 같아요.
D:\NO-HESITATION
| .eslintrc
| .gitignore
| .prettierrc
| index.html
| package-lock.json
| package.json
|
\---node_modules
\---src
| App.js
| index.js
|
+---components
| | PostForm.js
| | SideBar.js
| |
| \---common
| Header.js
| Input.js
|
+---pages
\---utils
renderPosts.js
storage.js
예전에 배웠던 대로, 그리고 알고 있던 대로 최대한 SPA
라이브러리와 비슷하게 구현하려 노력해봤습니다.
src
내 주요 폴더를 말씀드리자면
components
: 렌더링 때 생성될 컴포넌트
components\common
: 다른 데서도 공통적으로 쓰일 수 있는 컴포넌트
pages
: 이후 라우팅될 때 렌더링 될 페이지 (아직 미구현했어요!)
utils
: 갖다 쓰기 쉽도록 구현해놓은 함수들
로 일단 간단하게 구성을 해놨어요. 아직은 정~말 초창기라, 이후에 또 계속해서 업데이트될 것 같습니다!
index.js
SPA 애플리케이션처럼, index.html에 직접적으로 호출되는 단 하나의 자바스크립트 파일이에요. 여기서는 App
만 연결시켜줍시다.
import App from './App.js';
new App({
$target: document.querySelector('#app'),
});
app.js
일단 이렇게 Dummy Data들을 직접 붙였어요.
api를 직접 쓰기 전에 이렇게 테스트를 하면 좋다고 해서 해봤는데, 확실히 좀 더 어떻게 처리해야 할지가 잘 그려지는 것 같아요 :)
import PostForm from './components/PostForm.js';
import SideBar from './components/SideBar.js';
export default function App({ $target }) {
new SideBar({
$target,
initialState: {
username: 'jengyoung',
documents: [
{
id: 1, // Document id
title: '노션을 만들자', // Document title
documents: [
{
id: 2,
title: '블라블라',
documents: [
{
id: 3,
title: '함냐함냐',
documents: [],
},
],
},
],
},
{
id: 4,
title: 'hello!',
documents: [],
},
],
},
});
new PostForm({
$target,
initialState: {
title: '테스트합니다',
content: '테스트 중이에요!',
},
});
}
그리고 이번 글은, 불러오기 기능만 구현하는 것이니
SideBar
만 보자구요!
여기서 저는 하위 컴포넌트 Header
를 생각했어요.
sideBar
에서는 일단 유저가 쓴 글을 불러오지만- 맨 위에는 유저의 이름이 나오는
Header
가 있어야 할 것 같다는 생각에서였어요.
import renderPosts from '../utils/renderPosts.js';
import Header from './common/Header.js';
export default function SideBar({ $target, initialState }) {
const $sideBar = document.createElement('nav');
$target.appendChild($sideBar);
this.state = initialState;
this.setState = nextState => {
if (this.state.username !== nextState.username) {
header.setState(nextState);
}
this.state = nextState;
this.render();
};
const header = new Header({
$target: $sideBar,
headerSize: 'h5',
initialState: this.state.username,
});
this.render = () => {
const { documents } = this.state;
renderPosts($sideBar, documents);
};
this.render();
}
이 부분에선 결국 state가 바뀌면 헤더의 상태를 변경시키고, 다시 렌더링을 다시 하게 되는데, 렌더링하는 부분은 바로 Posts
겠네요!
그렇다면 다음과 같은 구조의 데이터를 어떻게 렌더링시킬까 살펴볼까요?!
[
{
id: string,
title: string,
documents: [
{
id: string,
title: string,
documents: [
...
]
},
{
...
}
]
}
]
저는 가장 직관적인 재귀를 사용했어요!
결국 쭉 들어갔을 때, 만약 documents
가 내용물이 없으면 리턴을 시키고, 있으면 계속해서 추가시키는 방식이죠!
여기서 innerHTML
을 쓸까도 싶었는데, 실제로 innerHTML
은 DOM
을 조작하는 데 있어 비효율적이라는 것을 알게 되어, 최대한 createElement
로 썼답니다.
export default function renderPosts($parentNode, nowDocuments) {
if (!nowDocuments.length) return;
nowDocuments.map(doc => {
const $nowNode = document.createElement('h6');
const { id, title, documents: nextDocs } = doc;
$nowNode.dataset.id = id;
$nowNode.textContent = title;
$parentNode.appendChild($nowNode);
renderPosts($nowNode, nextDocs);
});
}
결과적으로 이를 돌려보면, 어떻게 나오는지 볼까요?
아고, 아가들이 잘 딸려 나오네요!
일단 아직 좀 더 생각해둔 구현해야 할 요구사항은 좀 많아서 갈 길은 한~참 멀지만, 그래도 기록하면서 나아가보려 합니다.
누군가에게는 도움이 되는 글이기를.
나아가 저에게는 뿌듯함을 느낄 수 있는 발자취가 되길 바라며, 이상!