[TIL] 230904

이세령·2023년 9월 4일
0

TIL

목록 보기
96/118

Nextjs

hydrate 오류 해결

최신 리뷰를 렌더링하여 출력해주는데 서버 컴포넌트로 구성할 시 이전 페이지와 결과 페이지가 다르기 때문에 hydrate오류가 발생하여 임시로 클라이언트 컴포넌트로 구성해두었다.
원래는 api를 query로 불러와 각 데이터마다 유저의 이름을 불러오는 요청을 했었다.

  • 기존 코드
export const getLatestReviews = async () => {
  const fetchData = await supabase
    .from('reviews')
    .select('*')
    .order('date', { ascending: false }) // 날짜 기준으로 내림차순 정렬
    .limit(4); // 가져올 개수 제한

  return fetchData;
};

// userid기반으로 닉네임 찾아야함
export const getUserName = async (userid: string) => {
  const { data: userName } = await supabase.from('users').select('username').eq('id', userid);

  if (userName && userName.length > 0) {
    return userName[0].username;
  }

  return null; // 사용자가 없을 경우 또는 데이터가 올바르지 않을 경우
};

const LatestReviews = () => {
  const { data: latestReviewData } = useQuery(['latest_reviews'], getLatestReviews);

  return (
    <div className="p-5">
      <h2 className="text-2xl">최신 리뷰</h2>
      <div className="flex flex-wrap gap-4">
        {latestReviewData?.data!.map((review) => {
          let username;
          username = getUserName(review.userid);
          return (
            <Link key={review.reviewid} href={`/review/${review.reviewid}`} className="w-full">
              <div className=" p-4 border border-gray-300 rounded">
                <div className="font-bold mb-2">{username}</div>
                <div className="overflow-ellipsis">{review.content}</div>
              </div>
            </Link>
          );
        })}
      </div>
    </div>
  );
};

api를 호출할 때 이름을 찾아 한번에 가져오도록 수정하여 각 데이터마다 따로 요청할 필요 없도록 수정해주었다.

  • 수정된 코드
export const getLatestReviews = async () => {
  const { data: getReviews } = await supabase
    .from('reviews')
    .select('*')
    .order('date', { ascending: false }) // 날짜 기준으로 내림차순 정렬
    .limit(8); // 가져올 개수 제한
  const addUserName = getReviews?.map(async (data) => {
    const { data: userName } = await supabase.from('users').select('username').eq('id', data.userid);

    const usernameData = userName?.map((data) => data.username);

    const filterData = { ...data, username: usernameData! };

    return filterData;
  });
  const allLatestData = await Promise.all(addUserName!);

  return allLatestData;
};

한번에 필요한 데이터를 가져옴으로서 렌더링의 차이를 없애고 서버 컴포넌트로 사용할 수 있었다.

Opacity 예제 분석

const TWEEN_FACTOR = 4.2 // 투명도 

const numberWithinRange = (number: number, min: number, max: number): number =>
  Math.min(Math.max(number, min), max)

type PropType = {
  slides: any[]
  options?: EmblaOptionsType
}

const Carousel = (props: PropType) => {
  const { slides, options } = props
  const [emblaRef, emblaApi] = useEmblaCarousel(options)
  const [tweenValues, setTweenValues] = useState<number[]>([])

  const onScroll = useCallback(() => {
    if (!emblaApi) return

    const engine = emblaApi.internalEngine()
    const scrollProgress = emblaApi.scrollProgress()

    const styles = emblaApi.scrollSnapList().map((scrollSnap, index) => {
      let diffToTarget = scrollSnap - scrollProgress

      if (engine.options.loop) {
        engine.slideLooper.loopPoints.forEach((loopItem) => {
          const target = loopItem.target()
          if (index === loopItem.index && target !== 0) {
            const sign = Math.sign(target)
            if (sign === -1) diffToTarget = scrollSnap - (1 + scrollProgress)
            if (sign === 1) diffToTarget = scrollSnap + (1 - scrollProgress)
          }
        })
      }
      const tweenValue = 1 - Math.abs(diffToTarget * TWEEN_FACTOR)
      return numberWithinRange(tweenValue, 0, 1)
    })
    setTweenValues(styles)
  }, [emblaApi, setTweenValues])

  useEffect(() => {
    if (!emblaApi) return

    onScroll()
    emblaApi.on('scroll', () => {
      flushSync(() => onScroll())
    })
    emblaApi.on('reInit', onScroll)
  }, [emblaApi, onScroll])

  return (
    <div className="embla">
      <div className="embla__viewport" ref={emblaRef}>
        <div className="embla__container">
          {slides.map((index) => (
            <div
              className="embla__slide"
              key={index}
              style={{
                ...(tweenValues.length && { opacity: tweenValues[index] })
              }}
            >
              <div className="embla__slide__number">
                <span>{index + 1}</span>
              </div>
              <img
                className="embla__slide__img"
                src={imageByIndex(index)}
                alt="Your alt text"
              />
            </div>
          ))}
        </div>
      </div>
    </div>
  )
  );
};
  • TWEEN_FACTOR: 스크롤 위치에 다른 이미지의 투명도를 조절하기 위한 상수
  • numberWithinRange: 주어진 최솟값과 최대값을 벗어나지 않도록 보정
  • const [emblaRef, emblaApi] = useEmblaCarousel(options): embla carousel 라이브러리의 참조와 API저장 -> 공식문서 참조
  • tweenValues: 투명도 값 저장
  • onScroll: 스크롤 이벤트 처리 함수, 스크롤 위치와 슬라이드의 투명도를 계산한다.
    emblaApi가 존재하지 않으면 실행하지 않는다.
    emblaApi가 있다면 내부 엔진인 internalEngine을 가져오는데 해당 엔진을 통해 스크롤 및 애니메이션을 처리한다.
    scrollProgress()를 emblaApi에서 가져오는데 현재 스크롤 위치의 진행률을 가져오는 api이다.
if (engine.options.loop) {
        engine.slideLooper.loopPoints.forEach((loopItem) => {
          const target = loopItem.target()
          if (index === loopItem.index && target !== 0) {
            const sign = Math.sign(target)
            if (sign === -1) diffToTarget = scrollSnap - (1 + scrollProgress)
            if (sign === 1) diffToTarget = scrollSnap + (1 - scrollProgress)
          }
        })
      }

무한 루프 모드가 활성화 되어있는지 확인하고 engine.slideLooper.loopPoints.forEach 루프 정보를 가져오는데 각 루프 슬라이드의 target위치가 포함되어 있고 각 루프 슬라이드의 목표 위치의 부호를 계산하여 스크롤 방향을 파악한다. 목표 위치가 0이 아닌 경우에만 따로 처리를 해준다.

  • useEffect ~
useEffect(() => {
    if (!emblaApi) return

    onScroll()
    emblaApi.on('scroll', () => {
      flushSync(() => onScroll())
    })
    emblaApi.on('reInit', onScroll)
  }, [emblaApi, onScroll])

emblaApi가 없으면 종료하고(초기화 전 useEffect 호출 방지) onScroll 함수를 초기 호출하여 초기 스크롤 위치와 트윈값을 설정한다. 그리고 스크롤 이벤트가 발생할 때마다 onScroll함수를 호출하는데 flushSync함수를 사용하여 onScroll이 동기적으로 호출되도록 한다.
슬라이드가 재설정(reInit)될때 마다 onScroll함수를 호출하여 슬라이드가 추가되거나 제거되는 경우에 트윈 효과를 적용한다.

Embla Carousel의 스크롤 이벤트와 슬라이드 재설정 이벤트를 구독하여 스크롤 위치와 트윈 값을 업데이트하고, 스크롤 이벤트가 발생할 때마다 이를 동기적으로 처리하여 부드러운 효과를 제공한다.

profile
https://github.com/Hediar?tab=repositories

0개의 댓글