아고라스테이츠
javascript const data = {id: { id, cretaedAt, title, url, author, answer: { id, createdAt, url, author, body, avatarUrl }, body, avatarUrl, tag } } 데이...
솔로 프로젝트
데모 이미지

데이터 형태
const data = {id: {
id,
cretaedAt,
title,
url,
author,
answer: {
id,
createdAt,
url,
author,
body,
avatarUrl
},
body,
avatarUrl,
tag
}
}
데이터 형태는 위와 같은 형태로 만들었다. 처음 설계할 당시에는 데이터를 {discussion, answer, user, tag}로 나누어 inner join을 통해 핸들링하려 했지만, 웹에서 sql을 사용하려면 web SQL을 이용해야 하는데, 이 기능은 2011년을 기점으로 더 이상 기능 개선이 없어 스토리지 api를 이용하라고 권고하고 있기 때문에 sql을 사용하지 않았다.
데이터 저장을 로컬 스토리지에서 하는데 배열 형태로 저장하지 않은 이유는 리스트에서 디스커션 아이디를 이용해 배열 내의 해당 디스커션을 참조하는 경우가 많을 것이라 생각해 해쉬로 접근할 수 있는 객체형태로 저장하게 되었다.
만약 배열로 저장하게 된다면, 삭제를 하거나 해당 디스커션의 내용을 찾을 때 순회하는 것이 불가피 하지만(순서가 보장되어 있더라도 인덱스를 저장하지 않는 삭제하고 추가하는 것이 빈번하기 때문에 한 번에 접근이 어렵다), 객체를 이용하게 되면 해당 디스커션 아이디를 얻을 수 있는 경우 쉽게 접근이 가능하고 삭제 역시 가능하다. 배열에서 delete를 이용해 중간에 있는 요소를 삭제할 경우 배열의 요소가 연속된 배열이 아닌 희소 배열의 형태로 변할 것이라 생각했기 때문도 있다.
데이터 정규화
위 데이터의 경우 로컬 스토리지에서 한 번에 모든 데이터를 불러올 수 있기 때문에 저장과 불러오기가 편하지만, 필요한 데이터만 가져올 수 없다는 단점이 있다. 이를 위해선 데이터 정규화가 필요한데, 정규화를 해서 로컬 스토리지에 저장하는 것을 기획했지만, 하다보니 편한 것을 찾게 되어 하지 않았다. 그냥 귀찮았다. 추후에 데이터 형태를 바꾸고 discussion.js나 storageAPI.js에서 데이터를 합치는 기능을 넣어 구현이 가능할 것이라고 생각한다.
옵저버 패턴
페이지 구현 중 현재 로그인 된 유저나 디스커션 추가 삭제 등으로 인해 다른 컴포넌트에 변화가 필요한 경우 보통 변화가 있는 컴포넌트의 상위 컴포넌트에서 이벤트 핸들링 함수를 만들겠지만, 설계를 잘못해서 상위 컴포넌트에 두 컴포넌트가 없는 경우가 생기게 되었다.
Header.js와 Aside-l, Aside-m은 현재 로그인 된 유저에 맞게 렌더링에 변화가 있지만, 어사이드 컴포넌트는 메인 컴포넌트 안에 있기 때문에 위의 방법으로 처리하는 데 어려움이 있을 것 같았다.
이 문제를 해결하기 위해 옵저버 패턴을 사용했는데
import { filter, go, map } from "../utils/functional.js";
export const user = (() => {
let currentUser = ""; // 현재 로그인된 유저
let observers = []; // 옵저버 구독 배열
return {
getSubscribers() { // 구독한 객체들을 반환하는 메소드
return observers;
},
subscribe(component) { // 구독하는 메소드
observers.push(component);
},
unSubscribe(component) { // 구독을 끊는 메소드
observers = filter((a) => a !== component, observers);
},
notifyAll() { // 구독한 모든 객체에 대해 현재 로그인 된 유저가 바뀌었다는 것을 알리는 메소드
go(
observers,
map((a) => a.setState({ userId: currentUser }))
);
},
getCurrentUser() {
return currentUser;
},
setCurrentUser(userId) {
currentUser = userId;
},
};
})();
위 코드는 즉시 실행 함수를 이용하여 클로저 모듈 패턴을 사용했다. 사실 자바스크립트에서는 모듈을 이용하게 되면 각 파일마다 모듈 스코프를 생성하기 때문에 위와 같은 방법을 사용하지 않고 해당 모듈의 지역 변수로 선언하기만 해도 되지만, 혹시 모르기 때문에 (모듈 파일 내에서 추가적인 코드를 작성할 수 있을 것 같아) 이런 방식을 선택했다.
위 코드를 보게 되면 현재 로그인 된 유저 정보를 상태로 가지고 있는 모든 객체들이 observer배열에 들어갈 것이라 예상했고, 현재 로그인 된 유저가 변하게 되면 notifyAll을 통해 observer배열 안에 있는 모든 객체에 대해 상태 변화를 알렸다.
각 객체에서는 상태가 변할 때 render함수가 다시 실행되기 때문에 변경된 상태에 맞는 dom트리를 다시 구성하게 된다.
export const userAPI = {
signUp(userId, userPw, url = "./asset/user.png") {
const userData = storage.getData("user");
if (userData[userId]) {
alert("아이디가 존재합니다.");
return false;
}
userData[userId] = { id: userId, pw: SHA256(userPw), url: url };
storage.setData("user", userData);
user.setCurrentUser(userId);
user.notifyAll(); // 회원가입을 하게 되면, 현재 로그인 된 유저를 회원가입한 아이디로 바꾸고 이를 알린다.
return true;
},
signIn(userId, userPw) {
const userData = storage.getData("user");
if (userData[userId] && userData[userId].pw === SHA256(userPw)) {
user.setCurrentUser(userId);
user.notifyAll(); // 로그인을 하면 현재 유저를 바꾸고 이를 알린다.
return true;
} else if (!userData[userId]) alert("아이디가 존재하지 않습니다.");
else if (!passwordMatch(userData[userId], SHA256(userPw)))
alert("비밀번호가 틀렸습니다.");
return false;
},
logOut() {
user.setCurrentUser("");
user.notifyAll(); // 로그아웃을 하면 현재 유저를 ''로 바꾸고 이를 알린다.
},
};
function passwordMatch(pw1, pw2) {
return pw1 === pw2;
}
이 같은 방식은 디스커션 데이터의 변경에도 사용했다. 디스커션 데이터를 추적하고 있는 컴포넌트는 어사이드와 디스커션 컨텐트 컴포넌트가 있지만, 추가나 삭제를 하는 것은 모달창에서 이루어 지기 때문에 이 역시 같은 상위 컴포넌트가 아니어서 옵저버 패턴을 사용하게 되었다.
회고
함수형 프로그래밍을 적용하고 싶었다. 하지만, 실제 서비스에서 어떤 형태로 함수형 프로그래밍을 적용시켜야할 지 아직 완벽하게 이해하고 있지 못하다는 생각이 들었다. 또한, 시간이 없었다는 핑계로 명령형 식의 코딩을 좀 많이 작성한 것 같은 느낌이다. 수정을 할 수 있는 부분은 수정할 예정이다.
자바스크립트 만으로 컴포넌트 식으로 서비스를 만든 경험이 많이 없기 때문에 파일 구조나 컴포넌트를 어떤 식으로 나눌 지 생각을 많이 해야 했고, 실제로 구현된 코드들에서 컴포넌트를 더 빼낼 수 있을 것 같은 부분이 많아 제대로 설계를 하지 못하면 어떤 비효율적인 코드가 나올 수 있는지 경험할 수 있었던 것 같다.
이러한 작은 서비스 형태의 프로젝트를 꾸준히 하면서 어떤 방식으로 폴더 구조를 잡고, 컴포넌트를 어떻게 나눠야 하는지 설계 하는 연습을 해야할 것 같고, 그 안에서 함수형 프로그래밍 방법을 적용하여 실제 현업 들어갔을 때도 자연스럽게 함수형을 녹아낼 수 있게 노력해야 할 것 같다.