App.js
const cache = {};
onSubmit: async (e) => {
e.preventDefault();
const {
target: {
firstChild: { value: food },
},
} = e;
// input value값을 food변수에 담아 가져온다.
// 해당 food 변수 깂( 음식 명 )으로 데이터를 펫치한적이 있는지를 체크한다. ( 캐싱 되어있는지를 체크한다 )
if (cache[food]) {
// 캐시되어있다면
// 비동기요청 하지않고 캐시스토어에서 데이터를 가져온다.
this.setState({
...this.state,
meals: cache[food],
currentFoodKeyword: food,
});
} else {
// 캐시되어있지않다면
// 비동기 요청을 하여 데이터를 fetch 해온다.
const newMeals = await fetcher(SEARCH_KEY, food);
// 캐시를 업데이트해준다.
cache[food] = newMeals.meals;
this.setState({
...this.state,
meals: newMeals.meals,
currentFoodKeyword: food,
});
}
e.target.firstChild.value = "";
// 비동기요청 하고 셋스테이트
},
Form
컴포넌트로 전달되는 onSubmit
함수에
입력된 음식명으로 저장된 캐시데이터가 있는지를 점검하는 라인을 추가한다.
있으면 캐시데이터를 가져오고
없으면 비동기요청을 하여 새롭게 fetch해온다. (이 때 온전히 가져왔다면 캐싱한다)
Meals
, ResultHeading
컴포넌트가 다시 렌더링 될 필요가 없음에도 렌더링됨.두 컴포넌트다 제출 시점의 음식명이 달라지는 경우에만 렌더링을 다시하도록 함. ( 제출 시점의 음식명이 같다면 데이터도 같으므로 재 렌더링할 필요없음 )
ex ) useEffect(()=>{},[currentFoodName])
shouldComponentUpdate.js
callback
함수를 실행하는 함수이다.export default function shouldComponentUpdate(
callback,
{ prevState, nextState }
) {
if (prevState !== nextState) {
callback();
}
}
this.setState = (nextState) => {
const prevState = this.state;
this.state = nextState;
shouldComponentUpdate(
() => {
this.render();
},
{
prevState: prevState.currentFoodKeyword,
nextState: nextState.currentFoodKeyword,
}
);
};
ResultHeading.js
shouldComponentUpdate(
() => {
this.render();
},
{
prevState: prevState.currentFoodKeyword,
nextState: nextState.currentFoodKeyword,
}
);
SingleMeal.js
shouldComponentUpdate(
() => {
this.render();
},
{
prevState: prevState.singleFood ? prevState.singleFood.idMeal : null,
nextState: nextState.singleFood.idMeal,
}
);
Loading.js
export default function Loading({ $app, initialState }) {
this.state = initialState;
this.$target = document.createElement("div");
this.$target.setAttribute("class", "Loading");
this.$target.innerHTML = `<i class="fas fa-random"></i>`;
// this.$target.textContent = "loading!!";
$app.appendChild(this.$target);
this.setState = (nextState) => {
this.state = nextState;
this.render();
};
this.render = () => {
// 상태값으로 로딩중이면 보여주게하고, 로딩중이 아니면 안보이게한다.
this.$target.style.display = this.state.isLoading ? "block" : "none";
};
this.render();
}
App.js
const loading = new Loading({
$app,
initialState: { isLoading: this.state.isLoading },
});
onSubmit Method
// 비동기 요청 전 로딩을 걸어준다.
this.setState({ ...this.state, isLoading: true });
// 비동기 요청 수행
const newMeals = await fetcher(SEARCH_KEY, food);
cache[food] = newMeals.meals;
// 비동기 요청 끝났으므로 로딩 풀어준다.
this.setState({
...this.state,
meals: newMeals.meals,
currentFoodKeyword: food,
singleFood: null,
isLoading: false,
});
제출을 하면 로딩컴포넌트가 잠시 노출되고 없어진 후 비동기 요청한 데이터가 노출된다.
다음은 랜덤 버튼을 누르면 수행되는 핸들러함수이다. (RandomButton
컴포넌트로 보내준다)
App.js
onRandomButtonHandler: async (e) => {
// 비동기 요청전 로딩을 걸어준다.
this.setState({ ...this.state, isLoading: true });
// 비동기 요청을 하여 랜덤 밀을 받는다.
const {
meals: [randomMeal],
} = await fetcher(RANDOM_SINGLE_KEY);
// 로딩을 풀어주고 기존의 meals, currentFoodKeyword 를 초기화, 방금 받은 데이터를 상탯값으로 전달한다.
this.setState({
...this.state,
isLoading: false,
singleFood: randomMeal,
meals: null,
currentFoodKeyword: null,
});
},
검색음식 데이터가 노출되면 랜덤 음식 데이터는 초기화되어야하고
랜덤음식 데이터가 노출되면 검색 음식 데이터가 초기화되어야함.
다음은 Form
과 RandomButton
에 붙일 핸들러 함수 일부이다.
const newMeals = await fetcher(SEARCH_KEY, food);
cache[food] = newMeals.meals;
this.setState({
...this.state,
// 방금 얻은 데이터를 state에 넣어준다.
meals: newMeals.meals,
// 제출 시 입력되어있는 키워드 값을 넣어준다.
currentFoodKeyword: food,
// single Food 값은 지워준다
singleFood: null,
// 데이터가 왔으므로 로딩을 지워준다.
isLoading: false,
});
const {
meals: [randomMeal],
} = await fetcher(RANDOM_SINGLE_KEY);
this.setState({
...this.state,
// 로딩을 지워준다.
isLoading: false,
// 방금 얻은 randomMeal을 singleFood 속성으로
singleFood: randomMeal,
// 기존의 meals 상태를 비워준다.
meals: null,
// meals를 지웟으므로 키워드 값도 지워준다.
currentFoodKeyword: null,
});