Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

프로그래머스 고양이과제 구현 #26

Open
wants to merge 1 commit into
base: master
Choose a base branch
from

Conversation

passwd10
Copy link
Member

HTML, CSS 관련

  • 현재 HTML 코드가 전체적으로 <div> 로만 이루어져 있습니다. 이 마크업을 시맨틱한 방법으로 변경해야 합니다.
  • 유저가 사용하는 디바이스의 가로 길이에 따라 검색결과의 row 당 column 갯수를 적절히 변경해주어야 합니다.
    • 992px 이하: 3개
    • 768px 이하: 2개
    • 576px 이하: 1개
  • 다크 모드(Dark mode)를 지원하도록 CSS를 수정해야 합니다.
    • CSS 파일 내의 다크 모드 관련 주석을 제거한 뒤 구현합니다.
    • 모든 글자 색상은 #FFFFFF , 배경 색상은 #000000 로 한정합니다.
    • 기본적으로는 OS의 다크모드의 활성화 여부를 기반으로 동작하게 하되, 유저가 테마를 토글링 할 수 있도록 좌측 상단에 해당 기능을 토글하는 체크박스를 만듭니다.

이미지 상세 보기 모달 관련

  • 디바이스 가로 길이가 768px 이하인 경우, 모달의 가로 길이를 디바이스 가로 길이만큼 늘려야 합니다.
  • 필수 이미지를 검색한 후 결과로 주어진 이미지를 클릭하면 모달이 뜨는데, 모달 영역 밖을 누르거나 / 키보드의 ESC 키를 누르거나 / 모달 우측의 닫기(x) 버튼을 누르면 닫히도록 수정해야 합니다.
  • 모달에서 고양이의 성격, 태생 정보를 렌더링합니다. 해당 정보는 /cats/:id 를 통해 불러와야 합니다.
  • 추가 모달 열고 닫기에 fade in/out을 적용해 주세요.

검색 페이지 관련

  • 페이지 진입 시 포커스가 input 에 가도록 처리하고, 키워드를 입력한 상태에서 input 을 클릭할 시에는 기존에 입력되어 있던 키워드가 삭제되도록 만들어야 합니다.
  • 필수 데이터를 불러오는 중일 때, 현재 데이터를 불러오는 중임을 유저에게 알리는 UI를 추가해야 합니다.
  • 필수 검색 결과가 없는 경우, 유저가 불편함을 느끼지 않도록 UI적인 적절한 처리가 필요합니다.
  • 최근 검색한 키워드를 SearchInput 아래에 표시되도록 만들고, 해당 영역에 표시된 특정 키워드를 누르면 그 키워드로 검색이 일어나도록 만듭니다. 단, 가장 최근에 검색한 5개의 키워드만 노출되도록 합니다.
  • 페이지를 새로고침해도 마지막 검색 결과 화면이 유지되도록 처리합니다.
  • 필수 SearchInput 옆에 버튼을 하나 배치하고, 이 버튼을 클릭할 시 /api/cats/random50 을 호출하여 화면에 뿌리는 기능을 추가합니다. 버튼의 이름은 마음대로 정합니다.
  • lazy load 개념을 이용하여, 이미지가 화면에 보여야 할 시점에 load 되도록 처리해야 합니다.
  • 추가 검색 결과 각 아이템에 마우스 오버시 고양이 이름을 노출합니다.

스크롤 페이징 구현

  • 검색 결과 화면에서 유저가 브라우저 스크롤 바를 끝까지 이동시켰을 경우, 그 다음 페이지를 로딩하도록 만들어야 합니다.
  • 랜덤 고양이 배너 섹션 추가
    • 현재 검색 결과 목록 위에 배너 형태의 랜덤 고양이 섹션을 추가합니다.
    • 앱이 구동될 때 /api/cats/random50 api를 요청하여 받는 결과를 별도의 섹션에 노출합니다.
    • 검색 결과가 많더라도 화면에 5개만 노출하며 각 이미지는 좌, 우 슬라이드 이동 버튼을 갖습니다.
    • 좌, 우 버튼을 클릭하면, 현재 노출된 이미지는 사라지고 이전 또는 다음 이미지를 보여줍니다.(트렌지션은 선택)

코드 구조 관련

  • ES6 module 형태로 코드를 변경합니다.
    • webpack , parcel 과 같은 번들러를 사용하지 말아주세요.
    • 해당 코드 실행을 위해서는 http-server 모듈을(로컬 서버를 띄우는 다른 모듈도 사용 가능) 통해 index.html 을 띄워야 합니다.
  • API fetch 코드를 async , await 문을 이용하여 수정해주세요. 해당 코드들은 에러가 났을 경우를 대비해서 적절히 처리가 되어있어야 합니다.
  • 필수 API 의 status code 에 따라 에러 메시지를 분리하여 작성해야 합니다.
  • SearchResult 에 각 아이템을 클릭하는 이벤트를 Event Delegation 기법을 이용해 수정해주세요.
  • 컴포넌트 내부의 함수들이나 Util 함수들을 작게 잘 나누어주세요.

@passwd10 passwd10 self-assigned this Jan 28, 2021
Copy link
Member

@Woomin-Jeon Woomin-Jeon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

전체적으로 관심사가 잘 나뉘어져 있던 것 같아서 좋았습니다.
수고하셨습니다~ 👍

export const fetchCats = async (keyword) => {
try {
const response = await fetch(`${API_ENDPOINT}/api/cats/search?q=${keyword}`);
if (response.status === SERVER_ERROR) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오우 이런 것까지 상수화 해주시니 좋네여

}
return response.json();
} catch (error) {
alert(error);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이렇게하면 error.message가 '서버에러'이고, error는 Object일텐데 [object Object]라고 출력되지 않나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error: 서버에러 라고 뜹니다!

this.$button.className = 'Button';
this.text = text;

$target.appendChild(this.$button);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

appendChild는 모던자바스크립트 튜토리얼에서 레거시라고 하더라구요. append를 사용해보심이 어떠신가요? append가 지원하는 기능이 더 많습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 append 찾아봐야겠네요 감사합니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines +9 to +11
this.$button.addEventListener('click', (e) => {
onClick();
})
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 코드는 이렇게 줄여볼 수도 있을 것 같네요.

this.$button.addEventListener('click', onClick);

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

그러네요 굳이 감쌀필요가 없군요

@@ -0,0 +1,20 @@
export default class Button{
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 작은 것도 컴포넌트화하니까 좋네요 👍


$target.appendChild(this.$loading);

this.isFetching && this.render();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

constructor는 최초 클래스 생성시에 호출되고, App.js에서 isFetching을 false로 내려주고 있어서 9번째줄의 this.render()는 호출될 일이 없어보이는데 9번째 줄의 코드가 하는 역할이 따로 있나요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

말씀하신대로 쓸모없는 코드인데 넣어놨네요 감사합니다!

Comment on lines +12 to +21
startFetching() {
this.isFetching = true;
this.render();
this.$loading.style.display = 'inline-block';
}

finishFetching() {
this.isFetching = false;
this.$loading.style.display = 'none';
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

위 리뷰의 질문과 같은 맥락인 것 같습니다. 13, 19번째줄의 this.isFetching을 조작하는 코드는 아무런 역할을 하지 않는 것 같아요.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isFetching 굳이 필요가없는데 넣어놨군요...ㅜㅜ 감사합니다!

constructor({ $target, onSearch, loading, setInput }) {
this.$recentKeywords = document.createElement('div');
this.$recentKeywords.className = 'RecentKeywords';
this.keywords = JSON.parse(localStorage.getItem('keywords'));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

보니까 캐싱하는 작업이 keywords를 캐싱해서 다시 데이터를 불러오는 게 아니라, 그냥 배열안에 담긴 고양이 객체들 자체를 JSON.stringify로 캐싱하는 것 같아요.

지금 과정이,
새로고침 - 키워드를 바탕으로 서버로부터 데이터 받음 - 데이터에 있는 image.url을 통해 다시 서버로부터 이미지 받아옴 - 렌더링
이렇게 되는데, 그냥 이미 받았던 객체배열 자체를 캐싱하면,
새로고침 - 데이터에 있는 image.url을 통해 다시 서버로부터 이미지 받아옴 - 렌더링
이렇게 한단게 줄일 수 있습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LocalStorage엔 고양이 데이터가 들어가는게아니라 검색키워드만 들어갑니다.

따라서 절차가 새로고침 - localStorage에서 맨 앞의 키워드 뽑음 - 키워드를가지고 서버에요청 - 렌더링 순으로 진행됩니다.

localstorage에 키워드 배열들이 문자열로 저장이 되기때문에 parse해서 사용하는것입니다!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아아 제가 말을 이상하게 했네요 ㅋㅋㅋㅋ 인서님은 그렇게 구현하신 게 맞는데,
정답은 고양이 객체들이 담긴 배열 그 자체를 캐싱하는 게 정답이지 않나 한 것이었습니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"마지막 검색 결과 화면이 유지되도록 처리합니다" 이니깐
서버쪽에서 업데이트 하면 마지막 검색 결과가 달라 질 수 있으니 기존 데이터를 저장 후 쓰는게 맞는것 같아요!

Comment on lines +21 to +25
focusInput() {
setTimeout(()=> {
this.$searchInput.focus();
}, 0)
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오잉 이거 setTimeout 줘야해요? 안줘도 될 것 같은데...
아니면,

constructor에 this.$searchInput.setAttribute('autofocus', true);

줘보시는 건 어떤가요?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

스택오버플로우에서 저렇게 한다고 나와있길래 한번 해봤습니다! 말씀하신대로 했는데 적용이 안되네요~ 원인을 찾아봐야겠어요

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

앗... 저는 저렇게 해서 잘 되던데... 뭐가 문제일까요 궁금하네요 ㅠ

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

적용이 안되는 이유가 새로고침할때마다 localStorage에서 keyword를 뽑아서 자동으로 서버에 데이터들을 요청하게됩니다. 그러면 이전에 focus했던것들이 풀리게됩니다. setTimeout으로 하게되면 eventLoop가 콜스택이 다 실행되고 마지막에 콜백을 넣어주기때문에 저렇게 setTimeout을 이용해야 Focus가 정상적으로 동작하는 것 같습니다.

Comment on lines +4 to +10
if (htmlElement.dataset.theme === 'dark') {
htmlElement.setAttribute('data-theme', 'light');
localStorage.setItem('theme', 'light');
return;
}
htmlElement.setAttribute('data-theme', 'dark');
localStorage.setItem('theme', 'dark');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

const themeMode = htmlElement.dataset.theme === 'dark' ? 'light' : 'dark';

htmlElement.setAttribute('data-theme', themeMode);
localStorage.setItem('theme', themeMode);

이렇게 리팩터링 해볼 수도 있을 것 같습니다!

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

훨씬 깔끔하네요 감사합니다!

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

theme 자체를 os에서 받아오시면 localstorage 쓸 필요 없을것 같아요!


constructor($target) {
this.$target = $target;
this.theme = localStorage.getItem('theme');
Copy link
Collaborator

@gyim1345 gyim1345 Jan 30, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 os 기준으로 받아온 다음에 유저가 조작 할 수 있게 만드는걸로 알고 있어요!
window.matchMedia('(prefers-color-scheme: dark)').matches
https://stackoverflow.com/questions/50840168/how-to-detect-if-the-os-is-in-dark-mode-in-browsers

`<div class="item">
${index < INIT_IMAGE_CNT ?
`<img src=${cat.url} alt=${cat.name} data-index=${index} />` :
`<img class="lazy" src='' data-src=${cat.url} data-index=${index} alt=${cat.name} />`}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

여기에 title=${cat.name} 하면
"추가 검색 결과 각 아이템에 마우스 오버시 고양이 이름을 노출합니다."
바로 해결 됩니다

Copy link
Collaborator

@gyim1345 gyim1345 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lazy loading 할때 12개는 무조건 이미지 렌더링 하는 것 같은데 굳이 그럴 필요 없을 것 같아요 ㅋㅋㅋ 고생하셨습니다!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants