React Use 라이브러리 2편

이수빈·2024년 4월 16일
0

React

목록 보기
19/20
post-thumbnail

State

useRafState

  • requestAnimationFrame의 callback을 update하는 hook이다.

  • Raf => RequestAnimationFrame의 약자

function Demo() {
  const [state, setState] = useRafState({ x: 0, y: 0 });

  useMount(() => {
    const onMouseMove = (event: MouseEvent) => {
      setState({ x: event.clientX, y: event.clientY });
    };
    const onTouchMove = (event: TouchEvent) => {
      setState({
        x: event.changedTouches[0].clientX,
        y: event.changedTouches[0].clientY,
      });
    };

    document.addEventListener("mousemove", onMouseMove);
    document.addEventListener("touchmove", onTouchMove);

    return () => {
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("touchmove", onTouchMove);
    };
  });

  return <pre>{JSON.stringify(state, null, 2)}</pre>;
};
  • 구현 코드는 다음과 같다
import type { Dispatch, SetStateAction } from "react";
import { useCallback, useRef, useState } from "react";
import { useUnmount } from "../useUnmount";

export const useRafState = <S>(
  initialState: S | (() => S),
): readonly [S, Dispatch<SetStateAction<S>>] => {
  const frame = useRef(0);
  const [state, setState] = useState(initialState);

  const setRafState = useCallback((value: S | ((prevState: S) => S)) => {
    cancelAnimationFrame(frame.current);

    frame.current = requestAnimationFrame(() => {
      setState(value);
    });
  }, []);

  useUnmount(() => {
    cancelAnimationFrame(frame.current);
  });

  return [state, setRafState] as const;
};

requestAnimationFrame이란?

  • 여기서 requestAnimationFrame이란, window 인터페이스에 있는 JS내장함수이다. 보통 애니메이션을 구현 할 때 css의 animation, transition, transform 속성을 통해 구현 할 수도 있지만, 복잡한 상호작용을 구현하기 위해 JS를 사용할 때는 해당 함수를 사용함

  • 간단하고 규칙적인 애니메이션은 CSS로만 요소의 좌표값이나 스타일 크기를 변화시키고, 세밀한 조작이 필요한 애니메이션은 자바스크립트로 스타일 속성을 변경 시키는 편.

  • 하지만 => JS로 스타일 속성을 변화시키는 방법은 CSS보다 성능적으로 좋지 않다. => 이를 최적화하는 기법이 존재함.

  • 애니메이션이란게 결국 => 여러 FRAME들이 빠른시간안에 화면이 전환되는 ㅁ형태

  • FPS(FRAME PER SECOND)를 보통 단위로 사용하는데 => 보통 인간의 눈은 1초에 60번 장면이 넘어가야 부드럽다고 느낀다고 함

  • 현대 기기들은 시각적인 효과를 위해 초당 60번 화면을 다시 그리도록 기본적으로 설계된다. 이를 60fps 혹은 60hz 라고 불린다.

  • 브라우저 렌더링은 여러 단계에 걸쳐서 이뤄진다. (HTML => DOM, CSS => CSSOM , => LAYOUT 계산, 이를 화면에 그려내는 PAINT 작업까지 )..

  • 웹 페이지에서 부드러운 애니메이션 효과를 주기 위해서는 인간에 눈에 최적화된 60FPS단위로 설계해야함(즉 초당 60개의 FRAME을 렌더링해야함.) => 16.666ms 간격으로 하나의 프레임을 생성해야한다.

  • setInterval, setTimeout 함수로도 구현 할 수 있지만, 이는 시간내에 동작을 할뿐, 프레임을 신경쓰지 않는 단점이 있다.

const performAnimation = () => {
  /* 스타일 조정 스크립트 */
}

// 1초에 60번 무한 반복
setInterval(performAnimation, 1000 / 60)
  • 만일 아래 그림처럼 약 16ms 간격으로 프레임 단위가 진행되어야 하는데, 브라우저가 다른 작업 수행으로 인해 지연되어 자바스크립트의 콜백 코드 부분이 단위 중간에서 호출되었다고 가정

  • 자바스크립트 실행에 의해 리플로우가 일어나 위에서 본 브라우저 렌더링 단계인 레이아웃 - 페인트 - 합성 과정이 다시 일어나게 되는데, 그러면 프레임이 생성되지 못하고 누락되어 버려 1 프레임이 깎여버리는 현상이 나타나게 됨.

  • JS는 하나의 콜스택을 가지기 때문에, callback이 호출되는 시점에서 task queue에 진행중인 작업이 존재하게 된다면 => queue가 비어질때까지 기다린 후 setTimeout의 callback을 호출하는 dealy현상이 발생한다, 즉 애니메이션이 버벅거리게 되는 것!!

  • requestAnimationFrame 함수는 시스템이 프레임을 그릴 준비가 되면 애니메이션 프레임을 호출하여 애니메이션 웹페이지를 보다 원활하고 효율적으로 생성할 수 있도록 해줌

  • 실제 화면이 갱신되어 표시되는 주기에 따라 함수를 호출해주기 때문에 자바스크립트가 프레임 시작 시 실행되도록 보장해주어 위와 같은 밀림 현상을 방지한다.

requestAnimationFrame의 장점

  • 백그라운드 동작 중지 : setTimeout 같은 경우 다른 탭화면을 보거나 브라우저가 최소화 되어있을때에도 계속해서 타이머가 돌아서 콜백을 호출하기 때문에 리소스가 낭비됨, requestAnimationFrame는 페이지가 비활성화된 상태라면 화면을 그리는 것을 중단함.

  • 디스플레이 주사율에 맞게 호출 횟수 결정 : 웹브라우저는 디스플레이 주사율을 따름 => 만일 144hz 주사율 고사양 모니터일 경우 rAF 역시 1초에 144번 호출되게 됨.

  • task queue가 아닌 animation frame이라는 별도의 queue에서 처리하게 됨, task queue보다 우선순위가 높다

  • 이벤트 루프가 비동기 작업을 처리하는 우선순위는 Microtask Queue -> Animation Frames -> Task Queue 순이다.

  • 이벤트 루프는 Microtask Queue나 Animation Frames를 방문할 때는, 큐 안에 있는 모든 작업들을 수행하지만, Task Queue를 방문할 때는 한 번에 하나의 작업만 call stack으로 전달하고 다른 Queue를 순회한다. (이렇기 때문에 일정주기마다 동일하게 FRAME을 그려내는게 가능한듯)

  • cancelAnimationFrame => requestAnimationFrame을 중지하는 함수 (clearInterval처럼..)

const rocket = document.querySelector('.rocket');
const value = document.querySelector('.value');
let yPos = 0;
let rafId;

// 콜백 함수
const render = () => {
  yPos += 2; // y 좌표 증가

  rocket.style.transform = `translateY(${-yPos}px)`; // 로켓을 위로 올리기
  value.innerHTML = yPos; // 카운터 표시

  // 만약 로켓 위치가 일정 y좌표값일 경우 requestAnimationFrame 종료
  if (yPos >= 500) {
    cancelAnimationFrame(rafId);
    return;
  }

  rafId = requestAnimationFrame(render); // rAF 반복 호출
}

requestAnimationFrame(render); // 애니메이션 시작

useSupported

  • 브라우저가 특정기능을 제공하는지 확인가능함
function Demo() {
  const isSupported = useSupported(() => "EyeDropper" in window);
  return (
    <div>
      <p>
        window.EyeDropper is {isSupported ? "supported" : "unsupported"} in your
        browser
      </p>
    </div>
  );
};

result : window.EyeDropper is supported in your browser

useTextSelection

  • document.getSelection 을 바탕으로 user의 text Selection을 tracking 하는 hook

  • Selection는 사용자, 캐럿의 현재 위치에 의해 선택된 텍스트의 범위를 나타냄.

  • 사용자가 마우스를 통해 드래그하거나, 키보드를 통해 선택한 텍스트의 범위를 나타냄

  • 브라우저에서는 사용자가 선택한 텍스트에 대한 처리를 지원하기 위해 Selection API를 지원

  • selection검사 또는 조작을 위해 객체를 얻으려면 document.getSelection()을 호출합니다.

  • 사용자는 왼쪽에서 오른쪽 또는 오른쪽에서 왼쪽을 선택 할 수 있습니다.(방향성 존재)

  • 데스크탑 마우스로 선택하면 마우스 버튼을 누른 위치에 앵커가 배치되고 마우스 버튼을 놓은 위치에 포커스가 배치됩니다.

function Demo() {
  const selection = useTextSelection();

  return (
    <div style={{ padding: 40 }}>
      <p>
        Select some text here or anywhere on the page and it will be displayed
        below
      </p>

      <div>Selected text: {selection?.toString()}</div>
    </div>
  );
};

useThrottle

  • value를 throttle하는 hook
function Demo() {
  const [value, setValue] = useState<string>();
  const throttledValue = useThrottle(value, 500);

  return (
    <div>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
        placeholder="Typed value"
        style={{ width: 280 }}
      />
      <p style={{ marginTop: 16 }}>throttledValue: {throttledValue}</p>
    </div>
  );
};

render(<Demo/>)
  • lodash throttle => 내부적으로 debounce를 이용하고 있네

  • option을 바꿔서 사용하는듯.

function throttle(func, wait, options) {
    let leading = true;
    let trailing = true;

    if (typeof func !== 'function') {
        throw new TypeError('Expected a function');
    }
    if (isObject(options)) {
        leading = 'leading' in options ? !!options.leading : leading;
        trailing = 'trailing' in options ? !!options.trailing : trailing;
    }
    return debounce(func, wait, {
        leading,
        trailing,
        maxWait: wait,
    });
}

export default throttle;

ref)
https://developer.mozilla.org/ko/docs/Web/API/window/requestAnimationFrame
requestAnimationFrame : https://inpa.tistory.com/entry/%F0%9F%8C%90-requestAnimationFrame-%EA%B0%80%EC%9D%B4%EB%93%9C

JS 호출스택, 이벤트루프 : https://iamsjy17.github.io/javascript/2019/07/20/how-to-works-js.html

getSelection : https://developer.mozilla.org/en-US/docs/Web/API/Document/getSelection

profile
응애 나 애기 개발자

0개의 댓글