javascript | 인스타그램 클론 프로젝트 리뷰 ②

sunny·2021년 3월 27일
0
post-thumbnail

인스타그램 로그인 페이지와 메인 페이지를 클론 코딩해보았다.
아래는 나의 며칠간의 삽질기록이다..💥


댓글 좋아요, 삭제 기능

예전에 티스토리 스킨 만들 때 비밀댓글 체크 좋아요 댓글 구현한거!!

<input type="checkbox" id="like-heart">
<label for="like-heart"></label>

input[id="like-heart"] {
    display: none;
}

input[id="like-heart"] + label {
    display: inline-block;
    font-size: 11px;
    color: var(--black);
}

input[id="like-heart"]:checked + label:before {
    content: "\f023";
    font-size: 11px;
    font-family: "FontAwesome";
}

input[id="like-heart"] + label:before {
    content: "\f09c";
    font-size: 11px;
    font-family: "FontAwesome";
}

이것처럼 input checkbox로 checked될때는 꽉찬 하트로 하려고 했는데 fontAwesome cheatsheet에 빈하트가 없어서 일단 이 방법은 패스.. 🙃
체크박스와 라벨로 해보는건 언젠간 해볼 수 있는 날이 있길!!

어떻게 할까 하다가 클래스네임 토글방식으로 구현했다!

// 좋아요
const likeBtn = document.querySelectorAll('.like-heart');
const likeReply = (e) => {
    e.classList.toggle('reply-like');
    if (e.classList.contains('reply-like')) {
        e.classList.remove('xi-heart-o');
        e.classList.add('xi-heart');
        e.style.color = '#ed4956';
        alert('Like!!!');
    } else {
        e.classList.add('xi-heart-o');
        e.classList.remove('xi-heart');
        e.style.color = '#000';
        alert('unLike!!!');
    }
}

// 삭제
const deleteBtn = document.querySelectorAll('.delete-reply');
const deleteReply = (e) => {
    if (confirm('댓글을 삭제하시겠습니까?')) {
        e.parentNode.remove();
        alert('댓글이 삭제되었습니다.');
    }
}

querySelectorAll로 노드 찾아서 배열로 받은 다음에 로그인에서 구현했던거랑 똑같이 for of 문 사용해서 이벤트리스너 부여해줬당

그런데..!
login.js 랑 main.js 에 querySelectorAll배열로 받아서 for문 돌려서 이벤트리스너 등록해주는 코드가 3번이나 중복돼서 common.js 를 따로 빼서 공통함수를 만들어주었다.
elements(배열)이랑 함수이름, 이벤트이름을 인자로 받는다.

// 공통함수
const repeatFunction = (elements, functionName, eventName) => {
    for (let element of elements) {
        element.addEventListener(eventName, (e) => {
            functionName(e.target);
        })
    };
}

// 함수 호출
repeatFunction(likeBtn, likeReply, 'click');

파일 하나 더 만드는게 메모리상 부담?이 될 수도 있으니까 멘토님께 여쭤봤는데 다행히 너무 좋다고 해주셨다 🤩
코드 리팩토링해서 중복을 제거하는건 정말 짜릿하다


그런데 또 문제 발생 🥲🥲🥲🥲
좋아요, 삭제 기능이 하드코딩한 부분에만 추가되어있고 동적으로 새로 추가한 코드에는 적용이 안되어있었다... 왜지?
처음엔 내가 createElement로 요소를 생성하지 않고 cloneNode를 써서 그런가 해서 createElement로 하나하나 생성해주는 방법으로 바꿔보았는데 그것도 아니였다.

도움 요청하니까 댓글을 배열 객체로 관리하라고 말씀해주셔서

//댓글배열
const replyArr = [];
//댓글 객체 만들기 위한 클래스
class Reply {
    constructor(id, content, isLike) {
        this.id = id;
        this.content = content;
        this.isLike = isLike;
    }
}

const addComment = () => {
  
  ...
  
  //댓글객체 생성하고 배열에 푸쉬
 replyArr.push(new Reply('ididididid', commentInput.value, false));
 
  ...
    }
}

이런식으로 바꾼후에 배열을 for문 돌려서 해보려고 했는데 내가 클릭한 것의 배열 인덱스를 어떻게 알고 이벤트리스너를 걸어줘야할지 감이 안왔다.
js에서는 어렵지만 리액트에서는 쉽게(🤔) 구현할 수 있는 방법이라고 하셔서 리액트가 두려운 마음도 있지만 설레기도 한다..!

며칠째 해결하지 못해서 반쯤 포기하고 있었던 기능인데 멘토님과 이야기하다가 갑자기 아이디어가 생각나서 객체로 만들지 않고 그냥 ChildNodes로 직접 노드 선택해준 후에 이벤트 리스너 추가함!!!!!!!!!!!!

//이벤트 추가
newComment.childNodes[5].addEventListener('click', (e) => {
  deleteReply(e.target);
});

newComment.childNodes[7].addEventListener('click', (e) => {
  likeReply(e.target);
});

사실 약간의 하드?코딩이라 좀 효율적인 방법은 아닌 것 같지만 그래도 이정도에서 만족하려고 한다ㅎㅎ
며칠동안 못하던거 하니까 기분이 너무 좋았다!!

완성된 댓글기능!!


아이디 검색기능

  • 구현조건
  1. 아이디 데이터를 담고 있는 배열을 선언한다.
  2. 검색 창에 텍스트 입력 시 배열의 요소 중 해당 텍스트에 일치하는 아이디만 보일 수 있도록 구현한다.
  3. for 문이 아닌 다른 array method를 사용한다.

const id = [
    {
        id: 'instaquokka',
        profile: 'url',
        desc: '호주 로트네스트에 사는 쿼카',
    }
];

배열 객체 만들어서 가짜데이터를 생성하고 keyup으로 검색기능을 구현했다.
includes를 썼고 id가 검색값에 포함될때 li태그를 생성해서 보여주었다.

id.filter((obj) => {
  const noResult = document.querySelector('.noResult');
  
  if (obj.id.includes(searchInput.value.toLowerCase())) {
    createLi(obj.id, obj.profile, obj.desc);
  } else {
    searchList.innerHTML = '<span class="noResult light">검색결과가 없습니다.</span>'
  }
});

if문 안에 li태그 생성 코드를 넣기엔 너무 길어져서 li태그를 만드는 createLi 함수를 따로 빼서 호출해주었다!

근데 동적으로 태그 생성 후 appendchild로 붙여주는건 이런식으로 코드가 길어질 수 밖에 없는것인지..ㅠ 뭔가 다른 방법을 찾아보고싶다.

const createLi = (id, imgSrc, desc) => {
    const ul = document.querySelector('.search-list');
    const li = document.createElement('li');
    const div = document.createElement('div');

    const img = document.createElement('img');
    img.src = imgSrc;

    const profileId = document.createElement('a');
    profileId.className = 'black';
    profileId.innerHTML = id;
    
    const profileDesc = document.createElement('span');
    profileDesc.innerHTML = desc;

    div.appendChild(profileId);
    div.appendChild(profileDesc);
    li.appendChild(img);
    li.appendChild(div);
    ul.appendChild(li);
}

암튼 그렇게 하니까 검색은 되는데 keyup을 할때마다 검색결과가 붙어져서 나오게되었다💦

innerHTML을 초기화해주는 코드를 if문안에 추가하니까 간단하게 해결 완료.

searchList.innerHTML = '';

이제 검색결과가 없을때 검색결과가 없습니다. 가 나오게 구현해보자.
처음엔 else 문이 검색결과가 포함하지 않은 경우니까 그 부분에 코드를 넣어주면 된다고 생각했는데 검색결과가 없다가 검색결과가 있는 키워드를 쳤을때도 검색결과가 없습니다가 없어지지 않았다..... 😔

왜그런가 생각해보니까 내가 쓴 메소드는 filter였다.. 그리고 keyup할때마다 배열의 모든 데이터에 접근해서 비교해주기때문에 그런거였다..

해결법은 hasResult라는 변수를 하나 생성해서 키워드가 포함될때마다 ++를 해주었다. 그리고 hasResult===0 인 경우에만 innerHTML에 검색결과가 없습니다 span태그를 넣어주었다!

const search = () => {
    if (searchInput.value.length > 0) {
        keywordDelBtn.classList.remove('hidden');
        searchResult.classList.remove('hidden');
        searchList.innerHTML = '';
        let hasResult = 0;

        id.filter((obj) => {
            const noResult = document.querySelector('.noResult');
            if (noResult) {
                noResult.remove();
            }
            if (obj.id.includes(searchInput.value.toLowerCase())) {
                hasResult++;
                createLi(obj.id, obj.profile, obj.desc);
            } else {
                hasResult === 0
                    ? searchList.innerHTML = '<span class="noResult light">검색결과가 없습니다.</span>'
                    : '';
            }
        });
    } else {
        searchResult.classList.add('hidden');
    }
}

완성된 검색기능!


프로필 사진 클릭 시 메뉴 박스 생성

  • 구현조건
  1. 프로필 사진 클릭 시 메뉴 박스가 생성될 수 있도록 구현한다.
  2. 메뉴 박스 바깥 영역 클릭 시에는 다시 닫히도록 구현한다.

박스 밖을 클릭하면 어떻게 메뉴박스를 꺼지게 할지 고민하고 구글링했는데도 안나와서
(jquery의 has가 javascript에 있는지 계속 구글링했다..ㅎㅎ)
멘토님께서 알려주신 검색 키워드로 검색해보니까 한번에 나왔다..
이래서 구글링도 개발자의 실력이라고 하는거구나.. 😲

암튼 document에 클릭이벤트 걸어서 contains로 해결완료!
하고보니까 검색창에도 똑같이 적용할 수 있을 것 같아서 역시 공통함수로 빼버렸다.

//메뉴박스 밖 클릭 시 프로필 메뉴 닫기, 검색창 밖 클릭 시 검색결과창 닫기
document.addEventListener('click', (e) => {
    closeMenu(profileMenu, profile, e);
    closeMenu(searchResult, searchInput, e);
});

const closeMenu = (menubox, includedClass, e) => {
    if (!menubox.contains(e.target) && !includedClass.contains(e.target)) {
        menubox.classList.add('hidden');
    }
}


peer review

중간에 다른 분들과 서로의 코드 리뷰를 해주는 peer review를 했다.
반응형이 추가기능 구현으로 되어있어서 처음부터 반응형 생각도 못하고 있던 나에게 다른 분들과 진행한 peer review는 많은걸 알게 해주었다...

우선

  1. 반응형을 위해 px보다는 vw, vh, rem을 애용하자.. 🥲

모든 크기를 px로 지정한 것을 반성하면서 하나하나 rem으로 바꿔주었다.. 다음엔 처음부터 반응형으로 구현할 것을 염두해두고 레이아웃을 짜야겠다는 생각이 들었다.

  1. script 태그를 밑에 쓸게아니라 defer속성을 쓰자!

defer속성이란❓
<script>태그의 defer 속성은 페이지가 모두 로드된 후에 외부 스크립트를 실행하게 해주는 속성!!

난 맨날 바디 맨 밑에 스크립트태그 넣어줬는데 저런 속성도 있었다니..!

아무튼 유익했던 peer review!!


반응형

처음부터 레이아웃을 반응형 신경쓰지 않고 했더니.. 이미 반응형 적용하기에는 너무 멀리와 있었다..ㅎㅎ
그래서 로그인페이지는 450px이하 일때 border값이 없어지고,
메인페이지는 1078px 이하 (태블릿) 일때는 회원님을 위한 추천 부분이 사라지게 하고 768px 이하 (모바일) 일때는 검색창이 사라지는 간단한 반응형만 구현하기로 했다.

🔽 로그인 페이지

🔽 메인 페이지 1078px 이하 (main-right 사라짐)

🔽 메인 페이지 768px 이하 (nav의 검색창 사라짐)

다음부터는 반응형 구현할 것 까지 생각해서 처음부터 레이아웃과 css를 잘 짜야겠다는 생각이 들었다..! 👊🏻


완성🔥🔥🔥


만들고나니 뿌듯하다!!!

profile
blog 👉🏻 https://kimnamsun.github.io/

0개의 댓글