Jobs :: 일자리

프론트엔드 과제 필요기술 8개+ (feat. 프로그래머스)

HJPlumtree 2022. 3. 12. 11:55

프로그래머스 Dev Matching 프론트엔드 과제

알아야 되는 내용 정리

 

'2021 Dev-Matching: 웹 프론트엔드 개발자(상반기)'

=> https://programmers.co.kr/skill_check_assignments/100

 

'하반기'

=> https://programmers.co.kr/skill_check_assignments/199

 

0. 체크사항

Q. 어떤 부분(컴포넌트)로 구성되나

Q. 각 부분은 어떤 관계를 갖나

 

 

1. 컴포넌트화

Q. 컴포넌트 의존성 적도록 / 모듈화

A. script에 type=module을 사용하면, 모듈(파일)을 import/export로 분리가능

또 html 파싱을 멈추지 않고, 페이지 렌더가 다 되면 script를 실행해주는 1석 2조의 기능

<script src="./src/js/App.js" type="module"></script>

모듈 참고 => https://ko.javascript.info/modules-intro

 

Q. module을 사용하면 this는 undefined를 가리키는 문제

A. new 오퍼레이터로 인스턴스를 생성하면된다.

new App()

 

 

2. DOM 그리기 : innerHTML vs createElement

innerHTML 방법이 속도로 보자면 빠르다고 한다

하지만 이벤트를 위해서 다시 불러와야 하는 방법이 뭔가 와닿지가 않는다

 

createElement 사용을 정당화 하려고 찾아봤고

그러다 설득되는 글들을 읽었다

그래서

createElement와 createDocumentFragment로 DOM 그리기 결정

 

검색한 내용 중 일부

항상 DocumentFragments로 DOM 엘리먼트를 추가해야 하는 이유

=> https://coderwall.com/p/o9ws2g/why-you-should-always-append-dom-elements-using-documentfragments

DocumentFragment를 사용해보자 [성능 최적화] 

=> https://7942yongdae.tistory.com/70

 

Q. 반복되는 createElement는 어떻게 할까?

A. 간략하게나마 팩토리 함수를 이용해서 편의성, 가독성 향상

좀 더 편해지면 여러가지 더 추가해서 util 폴더 같은데에 넣어놔도 좋을듯 싶다

function makeEl(element, className, text) {
  if(element === fragment){
    return document.createDocumentFragment()
  }
  const $ele = document.createElement(element)
  $ele.classList.add(className)
  $ele.textContent = text
  return $ele
}

 

dom 팩토리 함수에 대한 생각거리

아무래도 그려질게 많아지면 복잡해지기 마련

  • 방법 1. 그냥 innerHTML와 querySelector 사용
  • 방법 2. 자식 노드도 받아서 append까지 완료 => 복잡함이 속 시원히 해결 안된다
  • 방법 3. 개인적으론 방법3을 찾고싶다. 혹시 알고 계신분 있으면 알려주세요

 

 

3. API 호출

Q. 오류 처리

A. 전체적으로는 try / catch로 잡고,

fetch 오류는 response에 ok가 실려오는지 확인한다

API 호출시에는 항상 try catch에 감싸자

 

 

Q. async/await 추천

A. fetch말고 좀 더 직관적인 문법 async/await로 비동기 처리

다음 async 함수는 'nodeId'가 있을 때와 없을 때 모두 사용 가능하다 

async function getData(nodeId) {
    try {
        const API_END_POINT = 'https://zl3m4qq0l9.execute-api.ap-northeast-2.amazonaws.com/dev'
        const response = await fetch(`${API_END_POINT}/${nodeId ? nodeId : ''}`)
    
        if(!response.ok) {
            throw new Error("서버가 이상해요!")
        }
        const data = await response.json()
        return data
    } catch(e) {
        throw new Error("뭔일이 생겼다!" + e.message)
    }
}

 

Q. API 처리 코드 분리

A. api.js 폴더로 분리해서 사용(feat 로딩까지 적용)

import Loading from './Loading.js'

export default async function fetchData(productId) {
	
    // 로딩 부분
    const $app = document.querySelector('.app')
    const $preload = document.createElement('div')
    $preload.append(Loading())
    $app.append($preload)
    
    // API 콜
    try {
        const BASE_URL = 'https://uikt6pohhh.execute-api.ap-northeast-2.amazonaws.com/dev/products'
        const response = await fetch(`${BASE_URL}${productId ? '/'+productId : ''}`)
        if(!response.ok) {
            throw new Error('뭔가 서버 이상')
        }
        const data = await response.json()
        
        // 로딩 노드 삭제
        $preload.remove()
        return data
    } catch(e) {
    	
        // 로딩 노드 삭제
        $preload.remove()
        throw new Error('뭔가 이상이 생겼다! ' + e.message)
    }
}

 

 

Q. 오류시 사용자에게 알림(toast)

keyframe 0%, 100%에서 투명도를 없애고

toast를 호출할 때

querySelectorAll('.toast')로 잡아서

forEach 돌리고 모든 toast 잡아서 ele.remove() 이런식으로 삭제

코드 추가 

 

 

4. ES6 모듈 형태

위의 1번 '컴포넌트화'에서 처리 완료!

 

 

5. 모달

Q,. 영역 바깥 클릭하거나, esc 누를시 종료

A. 바깥 영역의 display 속성을 보여주거나(block), 없애는(none) 방법으로 구현

 

- state에 따른 block-

$wrapper.style.display = this.state ? 'block' : 'none'

 

- 바깥 영역 클릭시 -

window.addEventListener('click', (e) => {
        if(e.target === $wrapper) {
            this.setState(null)
        }
    })

 

- esc키 누를 때 -

document.addEventListener('keyup', (e) => {
        if(e.key === 'Escape') {
            this.setState(null)
        }
    })

 

 

6. 로딩

Q. 데이터 불러올 때 로딩

A. 로딩 이미지를 리턴하는 Loading 함수를 만들고, api 콜을 할 때 넣었다

- api.js -

export default async function fetchData(nodeId) {
    const $app = document.querySelector('.app')
    const $preload = document.createElement('div')
    $preload.append(Loading())
    $app.append($preload)
    
    try{
        // API 콜 생략 ...
        $preload.remove()
        return data
    } catch(e) {
        $preload.remove()
        throw new Error(`무슨 에러지?? => ${e.message}`) 
    }
}

 

Q. 로딩 중에 다른 액션 불가

Loainng의 css에 전체 화면을 지배하는 모달을 넣어서 다른 것들이 클릭되지 않도록 만들었다 

- Loading.js -

export default function Loading() {
    const $fragment = document.createDocumentFragment()
    const $img = document.createElement('img')
    $img.classList.add('Modal')
    $img.src = './assets/nyan-cat.gif'
    $img.alt = 'Loooooooading...'
    $fragment.append($img)
    return $fragment
}

- Loading 스타일 -

.Modal {
  z-index: 1;
  width: 100%;
  height: 100%;
  position: fixed;
  left: 0;
  top: 0;
  background-color: rgba(0,0,0,0.3);
}

 

 

7. SPA (feat. 라우터)

TL;DR
밑의 내용을 정리해보면
페이지를 바꾼다는 것은
history.pushState()를 이용해서
1. 주소를 바꾸고
2. 라우터 함수를 실행해서
3. 현재 주소에 맞는 것을 보여주면 되는 간단한 이야기

 

주소(Route)를 줘서 SPA처럼 사용할 수 있다

React의 react-router-dom처럼 간단하진 않았다

뭔가 성냥으로 장작에 불을켜서 라면을 끓여먹는 느낌

부싯돌이 아니니 어어엄청 어려운게 아닐 수도 귀찮을 뿐

 

두 단계로 구분했다

 

1. 주소 주기

주소는 이렇게 간단히 줄 수 있다

history.pushState(스테이트, '', 주소)
  • 스테이트
    전해줄 데이터가 객체 형태로 들어간다고 한다
  • 가운데 ''
    여기는 lagacy 같은 느낌 현재는 안쓰이는 곳
    나중 업데이트에서 안전하기 위해 빈 문자열('') 사용을 권한다고 한다 MDN이 그러네
  • 주소
    여기에 넣으면 주소가 현재 주소가 바뀌게 되고
    정확히는 브라우저의 세션 history 스택에 추가하는 느낌

 

사용 예시

전하는 데이터 없고, 클릭된 상품에 따라 주소를 넣어준다

이런식 abc.com/product/3

history.pushState({}, null, '/product/'+ product.id)

이렇게 하면 주소는 바꼈지만(추가 됐지만) 이동이 안된 상태

주소를 컨트롤을 하는 함수를 실행하러 가자

 

 

2. 주소 컨트롤 장치(라우터) 실행

실행이 됐으면 현재 주소가 뭔지 알아봐야겠지

현재 주소 알 수 있는 것

location.pathname

 

콘솔 찍어보면 위에서 주소 바꿨으니 이렇게 찍힌단

console.log(location.pathname)

// 결과값 => /product/3

 

 

이걸 이용해서 원하는 컴포넌트 보여주면 된다

const router = () => {
    const matching = location.pathname.split('/')[1]
    if(matching === 'web') {
        // web이 주소일 때 이걸 보여주고
    } else if(matching === 'product'){
        // product가 주소일 때 여길 보여주고
    }
}

 

 

Q. 이전 버튼 눌렀을 때 작동

A. 이전 버튼을 감지해서 위에서 사용한 라우터를 실행

history에 변화가 생길 때 실행되는 이벤트

정확히는 MDN 참고 

window.onpopstate = () => {
    router()
}

 

 

8. 전역 오염

Q. 전역 오염 최소화 하도록

A. 각 모듈당 명확한 함수 소수만 갖도록

그리고 모든 변수는 함수에 들어가도록 작성

 

 

추후

8. 이벤트 바인딩 최적화

 

 

9. 로컬에 저장

 

 

10. 캐시

불러온 데이터 캐시

중복 http콜 없이 캐시된 데이터 불러오기

 

 

 

 

참고링크

모듈

=> https://ko.javascript.info/modules-intro

 

One-way state management in vanilla JavaScript

=> https://dev.to/antoniovdlc/one-way-state-management-in-vanilla-javascript-444f

 

Dead simple State Management in Vanilla JavaScript

=>https://vijaypushkin.medium.com/dead-simple-state-management-in-vanilla-js-6481c53f7439

 

Why you should always append DOM elements using DocumentFragments

=> https://coderwall.com/p/o9ws2g/why-you-should-always-append-dom-elements-using-documentfragments

 

Javascript - DocumentFragment를 사용해보자 [성능 최적화] 

=> https://7942yongdae.tistory.com/70

 

How to Show a Loader Until the Fetch API Has Finished Loading the Page

=> https://javascript.plainenglish.io/adding-loader-to-your-deployed-projects-d8f389e8c928

 

Close the modal when user clicks outside the modal

=> https://javascript.tutorialink.com/close-the-modal-when-user-clicks-outside-the-modal/

 

history.pushState MDN

=> https://developer.mozilla.org/en-US/docs/Web/API/History/pushState