이틀 전에 intersection observer를 이용해서 무한 스크롤을 구현하고자 하였지만 실패로 끝나 다른 기능을 먼저 구현하면서 머리를 식히고 오늘 다시 시도해보려고 하였다. 현재 나는 무한 스크롤 기능을 미루고,, 다른 기능들은 거의 다 구현하였다.(디자인과 리팩토링은...많이 해야할듯 하다...😂) 아무래도 전에 프로젝트를 진행하면서 react-beautiful-dnd를 이용해 드래그 앤 드롭을 했던 것이 많은 도움이 되었다.
현재 문제는 무한 스크롤이 되긴하는데 아.. 무한 스크롤만 되고 같은 페이지만 계속 불러온다.
const target = useRef(null);
useEffect(() => {
let observer
if (target.current) {
observer = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return
setisLoading(true)
observer.unobserve(entry.target)
setPage(prevState => prevState + 1)
await callMovieApi(page)
setisLoading(false)
observer.observe(target.current)
}, {
threshold: 1,
})
observer.observe(target.current)
}
return () => observer && observer.disconnect()
}, [target.current])
다음과 같이 intersection observer가 target을 감지하여 entry.intersecting이 true가 될 때 기존 page 보다 1을 더한 다음 페이지를 axios api 함수에서 불러와야 하는데 이 setState이 비동기이다 보니까 적용이 안 된 채로 계속 같은 페이지를 불러오고 있는 것이다. 그리고 또 다른 문제는 useEffect의 dependency 배열 안에 들어가는 인수에 target.current 를 넣었는데 vscode가 경고해댄다. Vscode에서 말하는 인수를 넣으면 entry.intersecting이 true가 되는 순간 api를 무한,,,불러와 스크롤이 점(.)만해진다. 아무래도 코드를 잘못 짰다는 것이겠지... 이 문제를 해결하기 위해 예전에 배웠지만 100퍼센트 깊에 이해는 하지 못했던 것으로 보이는 비동기
개념을 오늘 하루종일 파보았다.
movieApi를 너무 빨리 불러오는 문제를 확인하기 위해서 먼저 가짜 fetch함수
를 넣어주었다.
const testFetch = () =>
new Promise((res) => setTimeout(res, 4000))
...
useEffect(() => {
...
setisLoading(true)
await testFetch()
observer.unobserve(entry.target)
setPage(prevState => prevState + 1)
console.log(page)
// await callMovieApi(page)
console.log('api done')
await testFetch()
setisLoading(false)
...
}, [page, target])
페이지를 어떻게 불러오고 있는지 확인하고 싶어서 다음과 같이 콘솔창에 api 함수 호출 전후로 불러오는 page와 'api done'을 띄워주도록 했다.
그런데 결과 너무나 충격...
난 분명히 api 함수 전후로(주석처리 해줌) 콘솔에 띄워주도록 했는데 일단 한 번에 4~5개의 api done을 띄워주고 있었고, page는 분명 1씩 증가하도록 하였는데 그냥 맘대로 보여주고 있었다.
const [target, setTarget] = useState<HTMLElement | null | undefined>(null)
알고보니 감지할 대상 객체는 계속해서 바뀌는데, useRef는 참조값의 변경사항을 알리지 않아 useEffect가 트리거(발생)되지 않는다. 그래서 다음과 같이 useState의 setState를 이용해서 target을 지정해주었다.
setState({ number: number + 1 }); //2
setState({ nextNumber: number + 1 });//2
setState({ nextNextNumber: nextNumber + 1 });//2
useState 는 다음과 같이 비동기적으로 작동한다. useState를 동기적으로 만들어주기 위해서는 콜백함수
를 사용하는 방법과 updater인 prevState
를 사용하는 방법이 있다.
하지만 알다시피 콜백함수로 처리해주게 되면
this.setState({ number: this.state.number + 1 }, () => {
this.setState({ nextNumber: this.state.number + 1 }, () => {
this.setState({ nextNextNumber: this.state.nextNumber + 1 }, () => {
console.log(
this.state.number,
this.state.nextNumber,
this.state.nextNextNumber
);
});
});
});
다음과 같이 콜백지옥..에 빠지게 된다.
setState({ number: number + 1 }); //2
setState(prevState => ({ nextNumber: prevState.number + 1 })); //3
setState(prevState => ({ nextNextNumber: prevState.nextNumber + 1 })); //4
이렇게 prevState를 사용해주면 쉽게 비동기처리가 가능하다.
밑에서 useState 비동기처리에 관해 포스팅 했듯이 useState는 제대로 비동기처리가 된 채 작동이 되고 있었다!
다시 위의 콘솔창이 보여준 기괴한 결과에 집중해보자. 먼저 api done을 한 번에 여러 개씩 띄워주는 건 IntersectionObserver가 target을 계속 감지하게 된다는 것 같았다. 이에 loading을 추가하고 스타일을 입혔다.
<div className="lastMovie" ref={setTarget}>{isLoading && <div className={styles.loading}>loading</div>}</div>
로딩될 때 loading으로 가려 target과 루트 영역의 교차상태를 false로 만들어 주었다. 이렇게 하니 api done이 4초에 하나씩 콘솔에 찍히게 되었다.
하지만 다시 api함수 주석을 취소하고 시행해보니 한 번에 여러 번의 api 함수를 호출하며 페이지를 계속해서 띄웠다.
useEffect(() => {
let observer: IntersectionObserver
if (target) {
observer = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return
setisLoading(true)
await testFetch()
observer.unobserve(entry.target)
console.log(page)
setPage(prevState => prevState + 1)
MovieCallApi(page)
console.log('api done')
setisLoading(false)
observer.observe(target)
}, {
threshold: 1,
})
observer.observe(target)
}
return () => observer && observer.disconnect()
}, [MovieCallApi, page, target])
useEffect의 dependency를 살펴 보니(vscode가 안내해주는 dependency 값으로 update한 결과) page가 바뀔 때마다 IntersectionObserver가 target을 재감지 하도록 되어있었다. 이는 api함수를 다시 중복해서 불러오게 한다.
useEffect(() => {
let observer: IntersectionObserver
if (target) {
observer = new IntersectionObserver(async ([entry]) => {
if (!entry.isIntersecting) return
setisLoading(true)
await testFetch()
observer.unobserve(entry.target)
setPage(prevState => prevState + 1)
setisLoading(false)
observer.observe(target)
}, {
threshold: 1,
})
observer.observe(target)
이를 해결해주기 위해 IntersectionObserver 내에서 api함수를 호출해주지 않고 api함수를 useEffect 안에 넣어 page값이 변경될 때마다 호출되도록 하였다. 이렇게 하였더니 무한 스크롤 기능을 제대로 볼 수 있었다.