과제가 또 추가됐다. 이왕 과제가 추가된거 후딱 끝내고 쉬면서 코드리뷰를 봐야지...
이번에 배운건 크게 다른 것 없고 컴포넌트 분할, 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);
}
길이없으면 예외처리 해주고...끝!
정합성이란 무엇이냐? 감각적으론 알겠으나 정확한 단어의 뜻을 모르니 뜻부터 찾아봤다.
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);
};
객체값이 왔다면 첫번째 깊이까지만 타입구별을 한다. 나머지값들은 껍데기만..ㅎㅎㅋㅋ
일단 이렇게 완성해두고 차차 깊이까지 조절하면 될것 같은데...
여기선 끝까지 다 비교해야한다.
따라서 비교 알고리즘을 하나 짜두고...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;
};
이미 만들어놓은 유틸리티 함수와 재귀호출을 이용하여 만들었다.
썩 그럴듯 한데? 엣지케이스는 내일 생각해보자...생각보다 빨리 제작했다!
다만 null과 undefined를 같은 값 취급해야하는지 고민이다...
나날이 발전하는 게 몸소 느껴지니 너무 좋다.
또한 WIL회고를 할땐 KPT방식을 사용해보겠다. TIL마다 KPT방식을 사용하기엔 너무 번잡스러운 거같기도하고...
과제가 계속 비슷한 양상을 띄는데, 아예 보일러 플레이트를 만들어놓을까 싶다!