[프로그래머스] 고양이 사진첩 해설 - 2

지은·2023년 3월 12일
0

고양이 사진첩 해설 - 프로그래머스 블로그

fetch 함수로 데이터 불러오기

fetch 함수는 기본적으로 url을 파라미터로 받고 Promise 객체를 반환한다.

fetch('http://example.com/movies.json')
  .then((response) => {
    return response.json();
  })
  .then((myJson) => {
    console.log(JSON.stringify(myJson));
  })

여기서 중요한 부분은, 이때 반환되는 Promise 객체는 HTTP error 상태를 reject하지 않는다. 그래서 HTTP 요청 중 에러가 발생했더라도 Promise의 catch로 떨어지지 않는다.
➡️ 그래서 요청이 정말로 성공했는지 체크하려면 response의 ok를 체크해야 한다.

fetch('http://example.com/movies.json')
  .then((response) => {
    if(!response.ok) { // response.ok가 없다면 에러를 내보낸다.
      throw new Error('http 오류');
    }
    return response.json();
  })
  .then((myJson) => {
    console.log(JSON.stringify(myJson));
  })
  .catch((err) => { // catch문 추가
    alert(err.message);
  })

api 호출하는 부분을 별도로 분리하기

실제로 fetch 함수로 호출하는 부분을 컴포넌트 내에 코딩하지 않고, 별도의 유틸리티 함수로 분리하는 것이 좋다.
각 컴포넌트가 데이터를 어떤 방식으로 불러오는지는 해당 컴포넌트의 관심사가 아니기 때문이다. (관심사 분리)

api.js

const API_END_POINT = '...';

const request = (nodeId) => {
  // nodeId 유무에 따라 root directory를 조회할지, 특정 directory를 조회할지 처리한다.
  fetch(`${API_END_POINT}/${nodeId ? nodeId : ''}`)
    .then((response) => {
      if (!response.ok) {
        throw new Error('서버의 상태가 이상합니다!');
      }
      return response.json();
    })
    .catch((err) => {
      throw new Error(`무언가 잘못되었습니다! ${err.message}`);
    })
}

async, await 사용하기

async 문법은 Promise 형태의 응답을 반환하는 함수를 동기식 문법으로 작성할 수 있도록 도와주는 문법이다.
위의 request 함수를 async, await로 바꿔보면...

const request = async (nodeId) => {
  try {
    const response = await fetch(`${API_END_POINT}/${nodeId ? nodeId : ''}`); // <-- await
    if (!response.ok) {
        throw new Error('서버의 상태가 이상합니다!');
    }
    return await response.json(); // <-- await
  } catch(err) {
    throw new Error(`무언가 잘못되었습니다! ${err.message}`);
  }
}

App에서 데이터 불러오도록 처리하기

이제 fetch 함수를 넣었으니 App 컴포넌트에서 초기 데이터를 불러오게 하고, 이를 Nodes 컴포넌트에 적용하도록 코드를 수정해보자.

App.js

function App($app) {
  this.state = {
    isRoot: false,
    nodes: [],
    depth: []
  }
  
  // 파라미터 코드 생략
  const breadcrumb = new Breadcrumb({...});
  const nodes = new Nodes({...});
  
  // App 컴포넌트에도 setState 함수 정의하기
  this.setState = (nextState) => {
	this.state = nextState;
    breadcrumb.setState(this.state.depth);
    nodes.setState({
      isRoot: this.state.isRoot,
      nodes: this.state.nodes
    })
  }
  
  const init = async () => {
    try {
      const rootNodes = await request();
      this.setState({
        ...this.state,
        isRoot: true,
        nodes: rootNodes
      });
    } catch (err) {
      // 에러 처리하기
    }
  }
  
  init();
}

이렇게 하면 최초 앱 실행 시, root directory의 데이터를 불러오는 부분이 완성된다.


Nodes 컴포넌트 완성하기

Directory 클릭시 데이터 불러와서 렌더링하도록 처리하기

Nodes 컴포넌트의 onClick 이벤트 핸들러를 아래와 같이 구현한다.

App.js

// 이전 코드 생략

const nodes = new Nodes({
  $app,
  initialState: [],
  onClick: async (node) => {
    try {
      if (node.type === 'DIRECTORY') {
        const nextNodes = await request(node.id);
        this.setState({
          ...this.state,
          depth: [...this.state.depth, node],
          nodes: nextNodes
        });
      } else if (node.type === 'FILE') {
        // 이미지 보기 처리하기
      } catch (err) {
        // 에러 처리하기
      }
    }
  }
})

File 이미지 보는 처리하기

문제지에 보면, 아래의 마크업을 이용해서 ImageView를 처리하라고 안내되어 있다.

<div class="ImageViewer">
  <div class="content">
    <img src="https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public/images/a2i.jpg">
  </div>
</div>

이 부분도 아래와 같이 컴포넌트로 만들어보자.

// 상수 처리
const IMAGE_PATH_PREFIX = 'https://fe-dev-matching-2021-03-serverlessdeploymentbuck-t3kpj3way537.s3.ap-northeast-2.amazonaws.com/public'

function ImageView({$app, initialState}) {
  // image url
  this.state = initialState;
  this.$target = document.createElement('div');
  this.$target.className = 'Modal ImageView';
  
  $app.appendChild(this.$target);
  
  this.setState = (nextState) => {
    this.state = nextState;
    this.render();
  }
  
  this.render = () => {
    this.$target.innerHTML = `<div class="content">${this.state ? `<img src="${IMAGE_PATH_PREFIX}${this.state}" />` : ''}</div>`;

    this.$target.style.display = this.state ? 'block' : 'none';
  }
  
  this.render();
}

App 컴포넌트 내에서 ImageView를 생성하고, Nodes에서 File을 클릭 시 ImageView에 따라 image url을 넘기는 방식으로 처리하면 된다.

App.js

this.state = {
  isRoot: true,
  nodes: [],
  depth: [],
  selectedFilePath: null
}

const imageView = new ImageView({
  $app,
  initialState: this.state.selectedNodeImage
})

this.setState = (nextState) => {
  this.state = nextState;
  breadcrumb.setState(this.state.depth);
  nodes.setState({
    isRoot: this.state.isRoot,
    nodes: this.state.nodes
  });
  imageView.setState(this.state.selectedFilePath)
}

// 이전 코드 생략

const nodes = new Nodes({
  $app,
  initialState: [],
  onClick: async (node) => {
    try {
      if (node.type === 'DIRECTORY') {
        // Directory 처리 코드 생략
      } else if (node.type === 'FILE') {
        this.setState({
          ...this.state,
          selectedFilePath: node.filePath
        });
      } catch (err) {
        // 에러 처리하기
      }
    }
  }
})
profile
개발 공부 기록 블로그

6개의 댓글

comment-user-thumbnail
2023년 3월 14일

response.ok 하는 이유를 명확히 알았습니다 바닐라 js 언제 봐도 생소한거 같습니다 ㅎㅎ

답글 달기
comment-user-thumbnail
2023년 3월 19일

오랜만에 들고 오셨군요! 그때는 차이점을 크게 봤는데 이번엔 명령형으로 프로그래밍하셨네요! 명령형의 다양한 예시 잘봤습니다!

답글 달기
comment-user-thumbnail
2023년 3월 19일

설명이 너무 좋아요!

답글 달기
comment-user-thumbnail
2023년 3월 19일

정확하게 체크하려면 response.ok를 확인해야겠네요 !

답글 달기
comment-user-thumbnail
2023년 3월 19일

자바스크립트는 참 해줄 게 많네욥 ㅠㅠ

답글 달기
comment-user-thumbnail
2023년 3월 19일

문제 풀이 레벨이 다르시네요 역시.

답글 달기