useRef with useEffectとImage lazy loadingの適用

24903 ワード

useref with userEffect問題


以下のコードがある場合、refが参照できるドメインは最初に存在しないためnull値が返されます.
function App() {
  const [isShow, setIsShow] = useState(false);
  const ref = useRef(null);

  console.log('Ref:', ref);

  useEffect(() => {
    console.log('ref change', ref);
  },[ref]);

  return (
    <div>
      {isShow && (
        <div ref={ref}>
          <p>hello</p>
        </div>
      )}
      <button onClick={() => setIsShow(true)}>ref open</button>
    </div>
  );
}

これで、ボタンをクリックすると参照可能なDomが表示され、ref.current propertyにDom要素が含まれ、userEffectの依存配列にrefまたはref.currentが注入されてもuserEffectが実行されないことがわかります.

また、最初はcurrent:nullのように見えましたが、開いてみるとdivが参照され、コンソールが撮影されたときはnullが参照されていましたが、次のjsxコードの実行に伴いdiv要素がレンダリングされ参照されるため、実際には値が参照されていることがわかります.

もしそうなら、refの値が変化するたびに呼び出す方法はありますか?https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780から答えが得られます.
解決策はuserRefではなくcallback refを使用することである.useCallbackを使用すると、refが別のノードに接続されるたびにコールバックが呼び出されるので、callback内部で必要な処理を行えばよい.

テストしたコードは次のとおりです。

function useHookwithRefCallback() {
  const ref = useRef(null)
  const setRef = useCallback(node => {
    console.log('호출', node);

    if (ref.current) {
      // Make sure to cleanup any events/references added to the last instance
    }
    
    if (node) {
      // Check if a node is actually passed. Otherwise node would be null.
      // You can now do what you need to, addEventListeners, measure, etc.
    }
    
    // Save a reference to the node
    ref.current = node
  }, []);
  
  return [setRef];
}

export default useHookwithRefCallback;


function App() {
  const [isShow, setIsShow] = useState(false);
  const [num, setNum] = useState(0);
  const [ref] = useHookwithRefCallback();

  return (
    <div>
      {isShow && (
        <div ref={ref}>
          <p>hello</p>
        </div>
      )}
      <p>{num}</p>
      <button onClick={() => setIsShow((prev) => !prev)}>ref open</button>
      <button onClick={() => setNum(prev => prev+=1)}>+</button>
    </div>
  );
}

refが変更された場合にのみ、以下に示すように内部が呼び出されることがわかります.👍
これは、useCallbackが空の配列に依存し、mountとunmountの場合にのみ内部関数が呼び出され、<div ref={참조할 곳}>などの戻り関数の参照が変更された場合にのみ呼び出されるためです.

Image lazy loading


実際,上記のことを学ぶきっかけはImagelazy loadingを応用するためであり,現在では方法が分かっており,現在は本格的に応用すべきである.

適用前


適用する前に、現在のビューポートに表示されない画像に次のようにロードされるため、保存されている音楽(画像)が多ければ多いほど、初期ロード速度に影響を与える可能性があります.

Custom hookおよびLazyImageコンポーネント


IntersectionObserver APIを使用したいのですが、必ずしもImage lazy loadingのみではなく、custom hookとして個別に削除し、使用したい場所で交差して実行する関数、しきい値のみを渡す方法で実現します.
function useIntersection({ callback, threshold }: Props) {
  const observer = useRef<IntersectionObserver | null>(null);
  
  const setRef = useCallback(
    (node) => {
      // 이전의 관찰자 해제
      if (observer.current) observer.current.disconnect();

      observer.current = new IntersectionObserver(callback, {
        threshold,
      });
      
	  // 관찰 시작 
      if (node) observer.current.observe(node);
    },
    [callback, threshold],
  );

  return setRef;
}
LazyImageは複数の構成部品にも使用可能であるため、構成部品として単独で保持される.
import LazyImage from './LazyImage';

<MusicCardProfileImg
  onClick={() => handleSelectMusic(item)}
  aria-label="music play button"
>
  <LazyImage src={item.url} alt="thumbnail" />
</MusicCardProfileImg>
これで、使いたい場所でクロス時に実行する関数を渡すだけで終わります.
私の場合、画像要素のsrcを変更する関数が渡されます.
function LazyImage({ src, alt }: Props) {
  const ref = useIntersection({
    callback: (
      entries: IntersectionObserverEntry[],
      io: IntersectionObserver,
    ) => {
      entries.forEach((entry) => {
        if (entry.isIntersecting) {
          const lazyImage = entry.target;
          lazyImage.setAttribute('src', src);
          io.unobserve(entry.target);
        }
      });
    },
    threshold: 0.5,
  });

  return <img ref={ref} src={loading} alt={alt} />;
}

適用後


Image lazy loadingを適用した後、まず必要な画像を読み込み、交差する前にスケルトンUIを表示し、その後、元の画像を交差表示します.
(ただしこの機能はNext.jsではImage componentを使うだけ...)

適用される場所はhttps://github.com/Danji-ya/DJ-playlist/commit/fbe9693b0c14c926154f73a0c86f924450bd820aです.

Reference


  • https://medium.com/@teh_builder/ref-objects-inside-useeffect-hooks-eb7c15198780

  • https://reactjs.org/docs/hooks-faq.html#how-can-i-measure-a-dom-node