프론트엔드 데브코스 5기 TIL 25 - 고양이 사진첩 과제

김영현·2023년 10월 31일
1

TIL

목록 보기
29/129

어제의 코드리뷰를 보고 수정하려했는데..

과제가 또 추가됐다. 이왕 과제가 추가된거 후딱 끝내고 쉬면서 코드리뷰를 봐야지...

이번에 배운건 크게 다른 것 없고 컴포넌트 분할, api호출, 로딩시 로딩용 컴포넌트 보여주기 등임.
부모에게 몰아놓고 자식에게 분배하는 방식이다.

과제는 이렇게 만든 고양이 사진첩을 조금 손봐주는 일이다.


요구사항

  • 지금 구현된 코드에서는 state에 대한 정합성 체크를 전혀 하지 않는데, 이 부분을 보충해주세요.
    컴포넌트별로 올바르지 않은 state를 넣으면 오류가 발생하도록 해주세요.
  • 각 컴포넌트의 setState를 최적화하여 이전 상태와 비교해서 변경사항이 있을 때만 render 함수를 호출하도록 최적화를 해봅니다.
  • 루트 탐색 중이 아닌 경우, 백스페이스 키를 눌렀을 때 이전 경로로 이동하도록 만들어봅니다.

대충 하면 쉽고 어렵게하면 어려울 것 같다
일단 쉬운 것 부터 차근차근 드가자.

백스페이스 눌렀을때 이전 경로

if (e.key === "Backspace" && this.state.paths.length) {
 	const nextPaths = [...this.state.paths];
    nextPaths.pop();
    this.setState({
        ...this.state,
        paths: nextPaths,
    });
    const id = nextPaths.length ? nextPaths[nextPaths.length - 1].id : null;
    this.fetchNodes(id);
 }

keyup이벤트로 백스페이스를 체크하고, 뒤로 갈수 있을때만 넣어준다.
단, 배열 길이가 1일때 pop()을한다면 배열의 마지막이 존재할수없으니...하고 쓰다보니까
옵셔널 체이닝으로 하는게 낫겠다 싶다. 어차피fetchNodes에서 받아올때 falsy한값이면 루트로 판단하니까.

const id = nextPaths[nextPaths.length - 1]?.id;

이렇게 고쳐주었다.

이렇게 또 하고보니onPrevClick과 기능이 겹친다.

      onPrevClick: async () => {
        const nextPaths = [...this.state.paths];
        nextPaths.pop();
        this.setState({
          ...this.state,
          paths: nextPaths,
        });
        if (nextPaths.length === 0) {
          await this.fetchNodes();
        } else {
          await this.fetchNodes(nextPaths[nextPaths.length - 1].id);
        }
      },

둘을 하나의 함수로 뽑아줘야겠다..

  async goBack() {
    if (!this.state.paths.length) {
      return;
    }
    const nextPaths = [...this.state.paths];
    nextPaths.pop();
    this.setState({
      ...this.state,
      paths: nextPaths,
    });
    const id = nextPaths[nextPaths.length - 1]?.id;
    await this.fetchNodes(id);
  }

길이없으면 예외처리 해주고...끝!

state 정합성 체크

정합성이란 무엇이냐? 감각적으론 알겠으나 정확한 단어의 뜻을 모르니 뜻부터 찾아봤다.

consistency: 일관성

생각한 뜻이 맞다. prevState와 nextState의 타입같은것이 일치하는지 확인하는 과정...
어떻게 할지 고민됐다. 저번처럼 껍데기 타입만 추론해볼까? 하다가도, 1depth 내부 키 타입까지라도 추론해보자고 생각함.

import { getTag } from "./getTag.js";
import { isEqaulType } from "./isEqualType.js";

export const validateState = (initialState, nextState) => {
  const initialStateType = getTag(initialState);
  //객체만 하위 상태들 체크
  if (initialStateType === "Object") {
    for (const state in initialState) {
      const initialStateKeyType = getTag(initialState[state]);
      const nextStateKeyTyep = getTag(nextState[state]);
      //초기 상태(객체)들의 키타입과 다음 상태들의 키타입 비교
      if (initialStateKeyType !== nextStateKeyTyep) return false;
    }
    return true;
  }
  //배열...은 어떻게 처리하지?
  return isEqaulType(initialState, nextState);
};

//getTag.js
export const getTag = (value) => {
  if (value == null) {
    return value === undefined ? "Undefined" : "Null";
  }
  return Object.prototype.toString.call(value).slice(8, -1);
};

//isEqulType.js
import { getTag } from "./getTag.js";

export const isEqaulType = (value1, value2) => {
  return getTag(value1) === getTag(value2);
};

객체값이 왔다면 첫번째 깊이까지만 타입구별을 한다. 나머지값들은 껍데기만..ㅎㅎㅋㅋ
일단 이렇게 완성해두고 차차 깊이까지 조절하면 될것 같은데...

setState에서 전-후상태 비교

여기선 끝까지 다 비교해야한다.
따라서 비교 알고리즘을 하나 짜두고...validateState에서도 써먹으면 될 것 같다.
값의 비교는 어떻게 할것인가?
순회 가능한 오브젝트, 객체는 순회하며 재귀적으로 다시 비교알고리즘에 넣고
원시 값들은 서로 비교하면 될터이다.
천천히 생각해보자...

import { getTag } from "./getTag.js";

export const isEqaul = (value1, value2) => {
  //두 값이 같은지 끝까지 재귀적으로 내려가며 처리해야할듯...
  /* 
    1. 두 값의 타입 비교
    2. 배열, 객체가 아니면 값 자체 비교
    3. 배열, 객체라면 순회하면서 다시 함수에 집어넣기
  */
  const OBJ_TAG = "Object";
  const ARR_TAG = "Array";
  const value1Type = getTag(value1);
  const value2Type = getTag(value2);
  if (value1Type !== value2Type) {
    return false;
  }
  if (value1Type !== OBJ_TAG && value1Type !== ARR_TAG) {
    return value1 === value2;
  }
  if (value1Type === OBJ_TAG) {
    for (const key in value1) {
      if (!isEqaul(value1[key], value2[key])) {
        return false;
      }
    }
  } else if (value1Type === ARR_TAG) {
    if (value1.length !== value2.length) {
      return false;
    }
    for (let i = 0; i < value1.length; i++) {
      if (!isEqaul(value1[i], value2[i])) {
        return false;
      }
    }
  }
  return true;
};

이미 만들어놓은 유틸리티 함수와 재귀호출을 이용하여 만들었다.
썩 그럴듯 한데? 엣지케이스는 내일 생각해보자...생각보다 빨리 제작했다!

다만 nullundefined를 같은 값 취급해야하는지 고민이다...


느낀점

나날이 발전하는 게 몸소 느껴지니 너무 좋다.
또한 WIL회고를 할땐 KPT방식을 사용해보겠다. TIL마다 KPT방식을 사용하기엔 너무 번잡스러운 거같기도하고...
과제가 계속 비슷한 양상을 띄는데, 아예 보일러 플레이트를 만들어놓을까 싶다!

profile
모르는 것을 모른다고 하기

0개의 댓글